Skip to content

Commit 8b9c58d

Browse files
FIX: restrict OPM orientation grouping to triaxial overlaps
1 parent 676c4c5 commit 8b9c58d

2 files changed

Lines changed: 54 additions & 10 deletions

File tree

mne/viz/tests/test_topomap.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -851,6 +851,32 @@ def test_split_opm_overlaps(triaxial_evoked):
851851
assert tangential == ["OPM002", "OPM003", "OPM005", "OPM006"]
852852

853853

854+
def test_should_use_opm_orientation_groups_only_for_triaxial():
855+
"""Test that OPM orientation grouping is restricted to triaxial overlaps."""
856+
ch_names = [f"OPM{k:03}" for k in range(1, 7)]
857+
info = create_info(ch_names, 1000.0, ch_types="mag")
858+
with info._unlock():
859+
for ch in info["chs"]:
860+
ch["coil_type"] = FIFF.FIFFV_COIL_FIELDLINE_OPM_MAG_GEN1
861+
862+
picks = np.arange(len(ch_names))
863+
pair_overlaps = [
864+
np.array(["OPM001", "OPM002"]),
865+
np.array(["OPM003", "OPM004"]),
866+
]
867+
triax_overlaps = [
868+
np.array(["OPM001", "OPM002", "OPM003"]),
869+
np.array(["OPM004", "OPM005", "OPM006"]),
870+
]
871+
872+
assert not topomap._should_use_opm_orientation_groups(
873+
info, picks, pair_overlaps, "mag"
874+
)
875+
assert topomap._should_use_opm_orientation_groups(
876+
info, picks, triax_overlaps, "mag"
877+
)
878+
879+
854880
def test_plot_evoked_topomap_opm_triaxial_groups(triaxial_evoked):
855881
"""Test grouped radial/tangential topomap rendering for triaxial OPM."""
856882
fig = triaxial_evoked.plot_topomap(

mne/viz/topomap.py

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,22 @@ def _compute_opm_orientation_topomap_data(data, ch_names, pos, overlapping_chann
359359
]
360360

361361

362+
def _should_use_opm_orientation_groups(info, picks, merge_channels, ch_type):
363+
"""Return whether OPM orientation grouping should be enabled.
364+
365+
Grouping is only used for OPM magnetometer channels with overlap sets that
366+
include at least 3 colocated channels (triaxial-style sensors).
367+
"""
368+
if ch_type != "mag" or not merge_channels:
369+
return False
370+
371+
pick_chs = [info["chs"][pick] for pick in picks]
372+
if not pick_chs or not all(ch["coil_type"] in _opm_coils for ch in pick_chs):
373+
return False
374+
375+
return any(len(overlap_set) >= 3 for overlap_set in merge_channels)
376+
377+
362378
def _plot_update_evoked_topomap(params, bools):
363379
"""Update topomaps."""
364380
from ..channels.layout import _merge_ch_data
@@ -1744,9 +1760,9 @@ def plot_ica_components(
17441760

17451761
axes = axes.flatten() if isinstance(axes, np.ndarray) else axes
17461762
for k, picks in enumerate(pick_groups):
1747-
try: # either an iterable, 1D numpy array or others
1748-
_axes = axes[k * max_subplots : (k + 1) * max_subplots]
1749-
except TypeError: # None or Axes
1763+
if axes is None:
1764+
_axes = None
1765+
else:
17501766
_axes = axes
17511767

17521768
(
@@ -1767,10 +1783,8 @@ def plot_ica_components(
17671783
data = np.atleast_2d(data)
17681784
data = data[:, data_picks]
17691785

1770-
use_opm_orientation_groups = (
1771-
ch_type == "mag"
1772-
and any(ch["coil_type"] in _opm_coils for ch in ica.info["chs"])
1773-
and bool(merge_channels)
1786+
use_opm_orientation_groups = _should_use_opm_orientation_groups(
1787+
ica.info, data_picks, merge_channels, ch_type
17741788
)
17751789
n_group_axes = 2 if use_opm_orientation_groups else 1
17761790

@@ -2320,8 +2334,9 @@ def plot_evoked_topomap(
23202334
clip_origin,
23212335
) = _prepare_topomap_plot(evoked, ch_type, sphere=sphere)
23222336
outlines = _make_head_outlines(sphere, pos, outlines, clip_origin)
2323-
is_opm = any(ch["coil_type"] in _opm_coils for ch in evoked.info["chs"])
2324-
use_opm_orientation_groups = ch_type == "mag" and is_opm and bool(merge_channels)
2337+
use_opm_orientation_groups = _should_use_opm_orientation_groups(
2338+
evoked.info, picks, merge_channels, ch_type
2339+
)
23252340
# check interactive
23262341
axes_given = axes is not None
23272342
interactive = isinstance(times, str) and times == "interactive"
@@ -2456,7 +2471,10 @@ def plot_evoked_topomap(
24562471
grouped_data = None
24572472
if merge_channels:
24582473
# check modality
2459-
if is_opm:
2474+
is_opm_picks = len(picks) > 0 and all(
2475+
evoked.info["chs"][pick]["coil_type"] in _opm_coils for pick in picks
2476+
)
2477+
if is_opm_picks:
24602478
modality = "opm"
24612479
elif ch_type in _fnirs_types:
24622480
modality = "fnirs"

0 commit comments

Comments
 (0)