Skip to content

Commit 845d7a7

Browse files
Fix annotation of last sample (mne-tools#308)
1 parent 902726b commit 845d7a7

File tree

2 files changed

+101
-12
lines changed

2 files changed

+101
-12
lines changed

mne_qt_browser/_pg_figure.py

+10-11
Original file line numberDiff line numberDiff line change
@@ -1437,9 +1437,8 @@ def mouseDragEvent(self, event, axis=None):
14371437
elif event.isFinish():
14381438
drag_stop = self.mapSceneToView(event.scenePos()).x()
14391439
drag_stop = 0 if drag_stop < 0 else drag_stop
1440-
drag_stop = (
1441-
self.mne.xmax if self.mne.xmax < drag_stop else drag_stop
1442-
)
1440+
xmax = self.mne.xmax + 1 / self.mne.info["sfreq"]
1441+
drag_stop = xmax if xmax < drag_stop else drag_stop
14431442
self._drag_region.setRegion((self._drag_start, drag_stop))
14441443
plot_onset = min(self._drag_start, drag_stop)
14451444
plot_offset = max(self._drag_start, drag_stop)
@@ -2659,7 +2658,7 @@ def __init__(self, mne, description, values, weakmain, ch_names=None):
26592658
orientation="vertical",
26602659
movable=True,
26612660
swapMode="sort",
2662-
bounds=(0, mne.xmax),
2661+
bounds=(0, mne.xmax + 1 / mne.info["sfreq"]),
26632662
)
26642663
# Set default z-value to 0 to be behind other items in scene
26652664
self.setZValue(0)
@@ -3035,16 +3034,16 @@ def _init_ui(self):
30353034
self.start_bx = QDoubleSpinBox()
30363035
self.start_bx.setDecimals(time_decimals)
30373036
self.start_bx.setMinimum(0)
3038-
self.start_bx.setMaximum(self.mne.xmax - 1 / self.mne.info["sfreq"])
3037+
self.start_bx.setMaximum(self.mne.xmax)
30393038
self.start_bx.setSingleStep(0.05)
30403039
self.start_bx.valueChanged.connect(self._start_changed)
30413040
layout.addWidget(self.start_bx)
30423041

30433042
layout.addWidget(QLabel("Stop:"))
30443043
self.stop_bx = QDoubleSpinBox()
30453044
self.stop_bx.setDecimals(time_decimals)
3046-
self.stop_bx.setMinimum(1 / self.mne.info["sfreq"])
3047-
self.stop_bx.setMaximum(self.mne.xmax)
3045+
self.stop_bx.setMinimum(0)
3046+
self.stop_bx.setMaximum(self.mne.xmax + 1 / self.mne.info["sfreq"])
30483047
self.stop_bx.setSingleStep(0.05)
30493048
self.stop_bx.valueChanged.connect(self._stop_changed)
30503049
layout.addWidget(self.stop_bx)
@@ -3262,15 +3261,15 @@ def _start_changed(self):
32623261
start = self.start_bx.value()
32633262
sel_region = self.mne.selected_region
32643263
stop = sel_region.getRegion()[1]
3265-
if start < stop:
3264+
if start <= stop:
32663265
self.mne.selected_region.setRegion((start, stop))
32673266
# Make channel specific fillBetweens stay in sync with annot region
32683267
# if len(sel_region.single_channel_annots.keys()) > 0:
32693268
# sel_region.single_channel_annots(start, stop)
32703269
else:
32713270
self.weakmain().message_box(
32723271
text="Invalid value!",
3273-
info_text="Start can't be bigger or equal to Stop!",
3272+
info_text="Start can't be bigger than Stop!",
32743273
icon=QMessageBox.Critical,
32753274
modal=False,
32763275
)
@@ -3280,12 +3279,12 @@ def _stop_changed(self):
32803279
stop = self.stop_bx.value()
32813280
sel_region = self.mne.selected_region
32823281
start = sel_region.getRegion()[0]
3283-
if start < stop:
3282+
if start <= stop:
32843283
sel_region.setRegion((start, stop))
32853284
else:
32863285
self.weakmain().message_box(
32873286
text="Invalid value!",
3288-
info_text="Stop can't be smaller or equal to Start!",
3287+
info_text="Stop can't be smaller than Start!",
32893288
icon=QMessageBox.Critical,
32903289
)
32913290
self.stop_bx.setValue(sel_region.getRegion()[1])

mne_qt_browser/tests/test_pg_specific.py

+91-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,96 @@
2323
SHOW_PROJECTORS = "Show projectors"
2424

2525

26+
def test_annotations_single_sample(raw_orig, pg_backend):
27+
"""Test anotations with duration of 0 s."""
28+
# Crop and resample to avoid failing tests due to rounding in browser
29+
# Resampling also significantly speeds up the tests
30+
raw_orig = raw_orig.copy().crop(tmax=20.0).resample(100)
31+
# Add first annotation to initialize the description "A"
32+
onset = 2
33+
duration = 1
34+
description = "A"
35+
first_time = raw_orig.first_time
36+
raw_orig.annotations.append(onset + first_time, duration, description)
37+
fig = raw_orig.plot(duration=raw_orig.duration)
38+
fig.test_mode = True
39+
# Activate annotation_mode
40+
fig._fake_keypress("a")
41+
42+
# Select Annotation
43+
fig._fake_click((2.5, 1.0), xform="data")
44+
# Assert that annotation was selected
45+
annot_dock = fig.mne.fig_annotation
46+
assert annot_dock.start_bx.value() == 2
47+
assert annot_dock.stop_bx.value() == 3
48+
49+
# Test by setting values with Spinboxes
50+
# First, test zero duration annotation at recording start.
51+
annot_dock.start_bx.setValue(0)
52+
annot_dock.start_bx.editingFinished.emit()
53+
annot_dock.stop_bx.setValue(0)
54+
annot_dock.stop_bx.editingFinished.emit()
55+
# Assert that annotation starts and ends at 0 and duration is 0
56+
assert_allclose(raw_orig.annotations.onset[0], 0 + first_time, atol=1e-4)
57+
assert_allclose(raw_orig.annotations.duration[0], 0, atol=1e-4)
58+
59+
# Now test zero duration annotation at arbitrary time.
60+
sample_time = raw_orig.times[10]
61+
annot_dock.stop_bx.setValue(sample_time)
62+
annot_dock.stop_bx.editingFinished.emit()
63+
annot_dock.start_bx.setValue(sample_time)
64+
annot_dock.start_bx.editingFinished.emit()
65+
# Assert that annotation starts and ends at selected time and duration is 0
66+
assert_allclose(raw_orig.annotations.onset[0], sample_time + first_time, atol=1e-4)
67+
assert_allclose(raw_orig.annotations.duration[0], 0, atol=1e-4)
68+
69+
# Finally, test zero duration annotation at recording end.
70+
last_time = raw_orig.times[-1]
71+
annot_dock.stop_bx.setValue(last_time)
72+
annot_dock.stop_bx.editingFinished.emit()
73+
annot_dock.start_bx.setValue(last_time)
74+
annot_dock.start_bx.editingFinished.emit()
75+
# Assert that annotation starts and ends at last sample and duration is 0
76+
assert_allclose(raw_orig.annotations.onset[0], last_time + first_time, atol=1e-4)
77+
assert_allclose(raw_orig.annotations.duration[0], 0, atol=1e-4)
78+
79+
80+
def test_annotations_recording_end(raw_orig, pg_backend):
81+
"""Test anotations at the end of recording."""
82+
# Crop and resample to avoid failing tests due to rounding in browser
83+
# Resampling also significantly speeds up the tests
84+
raw_orig = raw_orig.copy().crop(tmax=20.0).resample(100)
85+
# Add first annotation to initialize the description "A"
86+
onset = 2
87+
duration = 1
88+
description = "A"
89+
first_time = raw_orig.first_time
90+
raw_orig.annotations.append(onset + first_time, duration, description)
91+
n_anns = len(raw_orig.annotations)
92+
fig = raw_orig.plot(duration=raw_orig.duration)
93+
fig.test_mode = True
94+
# Activate annotation_mode
95+
fig._fake_keypress("a")
96+
97+
# Draw additional annotation that extends to the end of the current view
98+
fig._fake_click(
99+
(0.0, 1.0),
100+
add_points=[(1.0, 1.0)],
101+
xform="ax",
102+
button=1,
103+
kind="drag",
104+
)
105+
# Assert number of annotations did not change
106+
assert len(raw_orig.annotations) == n_anns
107+
new_annot_end = raw_orig.annotations.onset[0] + raw_orig.annotations.duration[0]
108+
# Assert that the annotation end extends 1 sample above the recording
109+
assert_allclose(
110+
new_annot_end,
111+
raw_orig.times[-1] + first_time + 1 / raw_orig.info["sfreq"],
112+
atol=1e-4,
113+
)
114+
115+
26116
def test_annotations_interactions(raw_orig, pg_backend):
27117
"""Test interactions specific to pyqtgraph-backend."""
28118
# Add test-annotations
@@ -97,7 +187,7 @@ def test_annotations_interactions(raw_orig, pg_backend):
97187
annot_dock.start_bx.setValue(6)
98188
annot_dock.start_bx.editingFinished.emit()
99189
assert fig.msg_box.isVisible()
100-
assert fig.msg_box.informativeText() == "Start can't be bigger or equal to Stop!"
190+
assert fig.msg_box.informativeText() == "Start can't be bigger than Stop!"
101191
fig.msg_box.close()
102192

103193
# Test that dragging annotation onto the tail of another works

0 commit comments

Comments
 (0)