Skip to content

Commit cfa0f60

Browse files
authored
Bug channel annot merge (mne-tools#275)
1 parent 519c6e9 commit cfa0f60

File tree

2 files changed

+69
-7
lines changed

2 files changed

+69
-7
lines changed

mne_qt_browser/_pg_figure.py

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2359,7 +2359,7 @@ def __init__(self, mne, weakmain, annot, ch_name):
23592359

23602360
self.mne.plt.addItem(self, ignoreBounds=True)
23612361

2362-
self.annot.removeRequested.connect(self.remove)
2362+
self.annot.removeSingleChannelAnnots.connect(self.remove)
23632363
self.annot.sigRegionChangeFinished.connect(self.update_plot_curves)
23642364
self.annot.sigRegionChanged.connect(self.update_plot_curves)
23652365
self.annot.sigToggleVisibility.connect(self.update_visible)
@@ -2399,6 +2399,7 @@ class AnnotRegion(LinearRegionItem):
23992399
regionChangeFinished = Signal(object)
24002400
gotSelected = Signal(object)
24012401
removeRequested = Signal(object)
2402+
removeSingleChannelAnnots = Signal(object)
24022403
sigToggleVisibility = Signal(bool)
24032404
sigUpdateColor = Signal(str)
24042405

@@ -2435,29 +2436,54 @@ def __init__(self, mne, description, values, weakmain, ch_names=None):
24352436
self.mne.plt.addItem(self.label_item, ignoreBounds=True)
24362437

24372438
def _region_changed(self):
2438-
self.regionChangeFinished.emit(self)
2439-
self.old_onset = self.getRegion()[0]
2440-
# remove merged regions
2439+
# Check for overlapping regions
2440+
overlap_has_sca = []
24412441
overlapping_regions = list()
24422442
for region in self.mne.regions:
24432443
if region.description != self.description or id(self) == id(region):
24442444
continue
24452445
values = region.getRegion()
2446-
if any(self.getRegion()[0] <= val <= self.getRegion()[1] for val in values):
2446+
if (
2447+
any(self.getRegion()[0] <= val <= self.getRegion()[1] for val in values)
2448+
or (values[0] <= self.getRegion()[0] <= values[1])
2449+
and (values[0] <= self.getRegion()[1] <= values[1])
2450+
):
24472451
overlapping_regions.append(region)
2452+
overlap_has_sca.append(len(region.single_channel_annots) > 0)
2453+
2454+
# If this region or an overlapping region have
2455+
# channel specific annotations then terminate
2456+
if (len(self.single_channel_annots) > 0 or any(overlap_has_sca)) and len(
2457+
overlapping_regions
2458+
) > 0:
2459+
dur = self.getRegion()[1] - self.getRegion()[0]
2460+
self.setRegion((self.old_onset, self.old_onset + dur))
2461+
warn(
2462+
"Can not combine channel-based annotations with "
2463+
"any other annotation."
2464+
)
2465+
return
2466+
24482467
# figure out new boundaries
24492468
regions_ = np.array(
24502469
[region.getRegion() for region in overlapping_regions] + [self.getRegion()]
24512470
)
2471+
2472+
self.regionChangeFinished.emit(self)
2473+
24522474
onset = np.min(regions_[:, 0])
24532475
offset = np.max(regions_[:, 1])
2476+
2477+
self.old_onset = onset
2478+
24542479
logger.debug(f"New {self.description} region: {onset:.2f} - {offset:.2f}")
24552480
# remove overlapping regions
24562481
for region in overlapping_regions:
24572482
self.weakmain()._remove_region(region, from_annot=False)
24582483
# re-set while blocking the signal to avoid re-running this function
24592484
with SignalBlocker(self):
24602485
self.setRegion((onset, offset))
2486+
24612487
self.update_label_pos()
24622488

24632489
def _add_single_channel_annot(self, ch_name):
@@ -2469,7 +2495,7 @@ def _remove_single_channel_annot(self, ch_name):
24692495
self.single_channel_annots[ch_name].remove()
24702496
self.single_channel_annots.pop(ch_name)
24712497

2472-
def _toggle_single_channel_annot(self, ch_name):
2498+
def _toggle_single_channel_annot(self, ch_name, update_color=True):
24732499
"""Add or remove single channel annotations."""
24742500
# Exit if mne-python not updated to support shift-click
24752501
if not hasattr(self.weakmain(), "_toggle_single_channel_annotation"):
@@ -2486,7 +2512,10 @@ def _toggle_single_channel_annot(self, ch_name):
24862512
else:
24872513
self._remove_single_channel_annot(ch_name)
24882514

2489-
self.update_color(all_channels=(not list(self.single_channel_annots.keys())))
2515+
if update_color:
2516+
self.update_color(
2517+
all_channels=(not list(self.single_channel_annots.keys()))
2518+
)
24902519

24912520
def update_color(self, all_channels=True):
24922521
"""Update color of annotation-region.
@@ -2539,6 +2568,7 @@ def update_visible(self, visible):
25392568

25402569
def remove(self):
25412570
"""Remove annotation-region."""
2571+
self.removeSingleChannelAnnots.emit(self)
25422572
self.removeRequested.emit(self)
25432573
vb = self.mne.viewbox
25442574
if vb and self.label_item in vb.addedItems:

mne_qt_browser/tests/test_pg_specific.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,28 @@ def test_annotations_interactions(raw_orig, pg_backend):
101101
assert fig.msg_box.informativeText() == "Start can't be bigger or " "equal to Stop!"
102102
fig.msg_box.close()
103103

104+
# Test that dragging annotation onto the tail of another works
105+
annot_dock._remove_description("E")
106+
annot_dock._remove_description("C")
107+
fig._fake_click(
108+
(4.0, 1.0), add_points=[(6.0, 1.0)], xform="data", button=1, kind="drag"
109+
)
110+
fig._fake_click(
111+
(4.0, 1.0), add_points=[(3.0, 1.0)], xform="data", button=1, kind="drag"
112+
)
113+
assert len(raw_orig.annotations.onset) == 1
114+
assert len(fig.mne.regions) == 1
115+
116+
# Make a smaller annotation and put it into the larger one
117+
fig._fake_click(
118+
(8.0, 1.0), add_points=[(8.1, 1.0)], xform="data", button=1, kind="drag"
119+
)
120+
fig._fake_click(
121+
(8.0, 1.0), add_points=[(4.0, 1.0)], xform="data", button=1, kind="drag"
122+
)
123+
assert len(raw_orig.annotations.onset) == 1
124+
assert len(fig.mne.regions) == 1
125+
104126

105127
def test_ch_specific_annot(raw_orig, pg_backend):
106128
"""Test plotting channel specific annotations."""
@@ -167,6 +189,16 @@ def test_ch_specific_annot(raw_orig, pg_backend):
167189
modifier=Qt.ShiftModifier,
168190
)
169191
assert "MEG 0133" in annot.single_channel_annots.keys()
192+
193+
# Check that channel specific annotations do not merge
194+
fig._fake_click(
195+
(2.0, 1.0), add_points=[(3.0, 1.0)], xform="data", button=1, kind="drag"
196+
)
197+
with pytest.warns(RuntimeWarning, match="combine channel-based"):
198+
fig._fake_click(
199+
(2.1, 1.0), add_points=[(5.0, 1.0)], xform="data", button=1, kind="drag"
200+
)
201+
170202
else:
171203
# emit a warning if the user tries to test single channel annots
172204
with pytest.warns(RuntimeWarning, match="updated"):

0 commit comments

Comments
 (0)