Skip to content

Commit 1555a3c

Browse files
Gnefilwmvanvlietlarsoner
authored
Fix channel ordering (#13943)
Co-authored-by: Marijn van Vliet <w.m.vanvliet@gmail.com> Co-authored-by: Eric Larson <larson.eric.d@gmail.com>
1 parent 13e5e15 commit 1555a3c

4 files changed

Lines changed: 32 additions & 7 deletions

File tree

doc/changes/dev/13943.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix channel ordering bug in :func:`mne.viz.plot_compare_evokeds` when ``axes="topo"``, by `Lifeng Qiu Lin`_.

mne/evoked.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1609,7 +1609,7 @@ def _get_evoked_node(fname):
16091609
return evoked_node
16101610

16111611

1612-
def _check_evokeds_ch_names_times(all_evoked):
1612+
def _check_evokeds_ch_names_times(all_evoked, inplace=False):
16131613
evoked = all_evoked[0]
16141614
ch_names = evoked.ch_names
16151615
for ii, ev in enumerate(all_evoked[1:]):
@@ -1618,7 +1618,8 @@ def _check_evokeds_ch_names_times(all_evoked):
16181618
raise ValueError(f"{evoked} and {ev} do not contain the same channels.")
16191619
else:
16201620
warn("Order of channels differs, reordering channels ...")
1621-
ev = ev.copy()
1621+
if not inplace:
1622+
ev = ev.copy()
16221623
ev.reorder_channels(ch_names)
16231624
all_evoked[ii + 1] = ev
16241625
if not np.max(np.abs(ev.times - evoked.times)) < 1e-7:

mne/viz/evoked.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2858,9 +2858,7 @@ def plot_compare_evokeds(
28582858
for evk in evokeds[cond]:
28592859
_validate_type(evk, Evoked, "All evokeds entries ", "Evoked")
28602860
# ensure same channels and times across all evokeds
2861-
all_evoked = sum(evokeds.values(), [])
2862-
_check_evokeds_ch_names_times(all_evoked)
2863-
del all_evoked
2861+
_check_evokeds_ch_names_times(sum(evokeds.values(), []), inplace=True)
28642862

28652863
# get some representative info
28662864
conditions = list(evokeds)
@@ -3019,6 +3017,7 @@ def plot_compare_evokeds(
30193017
if not do_topo:
30203018
# add vacuous "index" (needed for topo) so same code works for both
30213019
axes = [(ax, 0) for ax in axes]
3020+
assert len(axes) == 1
30223021
if np.array(picks).ndim < 2:
30233022
picks = [picks] # enables zipping w/ axes
30243023
else:
@@ -3130,7 +3129,8 @@ def click_func(
31303129
c_func = None if do_topo else combine_func
31313130
all_data = list()
31323131
all_cis = list()
3133-
for _picks, (ax, idx) in zip(picks, axes):
3132+
# We need to truncate axes because of a possible additional ax for the legend
3133+
for ax, idx in axes[: len(picks)]:
31343134
data_dict = dict()
31353135
ci_dict = dict()
31363136
for cond in conditions:
@@ -3145,7 +3145,7 @@ def click_func(
31453145
combine,
31463146
c_func,
31473147
ch_type=ch_type,
3148-
picks=_picks,
3148+
picks=picks[idx],
31493149
scaling=scalings,
31503150
ci_fun=ci_fun,
31513151
)

mne/viz/tests/test_evoked.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,16 @@ def test_plot_compare_evokeds(evoked):
442442
# test defaults
443443
figs = plot_compare_evokeds(evoked)
444444
assert len(figs) == 3
445+
# test arbitrary ordering of channels is handled correctly, in topo mode
446+
evoked_subset = evoked.copy().pick(["MEG 0113", "MEG 0112"])
447+
fig_ordered = plot_compare_evokeds(evoked_subset, axes="topo")
448+
evoked_reordered = evoked_subset.copy()
449+
evoked_reordered.reorder_channels(["MEG 0112", "MEG 0113"])
450+
figs_reordered = plot_compare_evokeds(evoked_reordered, axes="topo")
451+
assert_allclose(
452+
fig_ordered[0].axes[0].lines[0].get_ydata(),
453+
figs_reordered[0].axes[0].lines[0].get_ydata(),
454+
)
445455
# test passing more than one evoked
446456
red, blue = evoked.copy(), evoked.copy()
447457
red.comment = red.comment + "*" * 100
@@ -463,6 +473,19 @@ def test_plot_compare_evokeds(evoked):
463473
yvals = line.get_ydata()
464474
assert (yvals < ylim[1]).all()
465475
assert (yvals > ylim[0]).all()
476+
# test that the channels are aligned when many evoked
477+
# are passed in different orders, in topo mode
478+
evoked_subset = evoked.copy().pick(["MEG 0113", "MEG 0112"])
479+
evoked_reordered = evoked_subset.copy()
480+
evoked_reordered.reorder_channels(["MEG 0112", "MEG 0113"])
481+
# catch warnings when testing misalignment on purpose
482+
with pytest.warns(RuntimeWarning, match="Order of channels differs"):
483+
figs = plot_compare_evokeds(
484+
dict(orig=evoked_subset, reordered=evoked_reordered), axes="topo"
485+
)
486+
assert_allclose(
487+
figs[0].axes[0].lines[0].get_ydata(), figs[0].axes[0].lines[1].get_ydata()
488+
)
466489
# test plotting eyetracking data
467490
plt.close("all") # close the previous figures as to avoid a too many figs warning
468491
info_tmp = mne.create_info(["pupil_left"], evoked.info["sfreq"], ["pupil"])

0 commit comments

Comments
 (0)