Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix annotation of last sample #308

Merged
merged 4 commits into from
Jan 27, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 10 additions & 11 deletions mne_qt_browser/_pg_figure.py
Original file line number Diff line number Diff line change
@@ -1437,9 +1437,8 @@ def mouseDragEvent(self, event, axis=None):
elif event.isFinish():
drag_stop = self.mapSceneToView(event.scenePos()).x()
drag_stop = 0 if drag_stop < 0 else drag_stop
drag_stop = (
self.mne.xmax if self.mne.xmax < drag_stop else drag_stop
)
xmax = self.mne.xmax + 1 / self.mne.info["sfreq"]
drag_stop = xmax if xmax < drag_stop else drag_stop
self._drag_region.setRegion((self._drag_start, drag_stop))
plot_onset = min(self._drag_start, drag_stop)
plot_offset = max(self._drag_start, drag_stop)
@@ -2659,7 +2658,7 @@ def __init__(self, mne, description, values, weakmain, ch_names=None):
orientation="vertical",
movable=True,
swapMode="sort",
bounds=(0, mne.xmax),
bounds=(0, mne.xmax + 1 / mne.info["sfreq"]),
)
# Set default z-value to 0 to be behind other items in scene
self.setZValue(0)
@@ -3035,16 +3034,16 @@ def _init_ui(self):
self.start_bx = QDoubleSpinBox()
self.start_bx.setDecimals(time_decimals)
self.start_bx.setMinimum(0)
self.start_bx.setMaximum(self.mne.xmax - 1 / self.mne.info["sfreq"])
self.start_bx.setMaximum(self.mne.xmax)
self.start_bx.setSingleStep(0.05)
self.start_bx.valueChanged.connect(self._start_changed)
layout.addWidget(self.start_bx)

layout.addWidget(QLabel("Stop:"))
self.stop_bx = QDoubleSpinBox()
self.stop_bx.setDecimals(time_decimals)
self.stop_bx.setMinimum(1 / self.mne.info["sfreq"])
self.stop_bx.setMaximum(self.mne.xmax)
self.stop_bx.setMinimum(0)
self.stop_bx.setMaximum(self.mne.xmax + 1 / self.mne.info["sfreq"])
self.stop_bx.setSingleStep(0.05)
self.stop_bx.valueChanged.connect(self._stop_changed)
layout.addWidget(self.stop_bx)
@@ -3262,15 +3261,15 @@ def _start_changed(self):
start = self.start_bx.value()
sel_region = self.mne.selected_region
stop = sel_region.getRegion()[1]
if start < stop:
if start <= stop:
self.mne.selected_region.setRegion((start, stop))
# Make channel specific fillBetweens stay in sync with annot region
# if len(sel_region.single_channel_annots.keys()) > 0:
# sel_region.single_channel_annots(start, stop)
else:
self.weakmain().message_box(
text="Invalid value!",
info_text="Start can't be bigger or equal to Stop!",
info_text="Start can't be bigger than Stop!",
icon=QMessageBox.Critical,
modal=False,
)
@@ -3280,12 +3279,12 @@ def _stop_changed(self):
stop = self.stop_bx.value()
sel_region = self.mne.selected_region
start = sel_region.getRegion()[0]
if start < stop:
if start <= stop:
sel_region.setRegion((start, stop))
else:
self.weakmain().message_box(
text="Invalid value!",
info_text="Stop can't be smaller or equal to Start!",
info_text="Stop can't be smaller than Start!",
icon=QMessageBox.Critical,
)
self.stop_bx.setValue(sel_region.getRegion()[1])
92 changes: 91 additions & 1 deletion mne_qt_browser/tests/test_pg_specific.py
Original file line number Diff line number Diff line change
@@ -23,6 +23,96 @@
SHOW_PROJECTORS = "Show projectors"


def test_annotations_single_sample(raw_orig, pg_backend):
"""Test anotations with duration of 0 s."""
# Crop and resample to avoid failing tests due to rounding in browser
# Resampling also significantly speeds up the tests
raw_orig = raw_orig.copy().crop(tmax=20.0).resample(100)
# Add first annotation to initialize the description "A"
onset = 2
duration = 1
description = "A"
first_time = raw_orig.first_time
raw_orig.annotations.append(onset + first_time, duration, description)
fig = raw_orig.plot(duration=raw_orig.duration)
fig.test_mode = True
# Activate annotation_mode
fig._fake_keypress("a")

# Select Annotation
fig._fake_click((2.5, 1.0), xform="data")
# Assert that annotation was selected
annot_dock = fig.mne.fig_annotation
assert annot_dock.start_bx.value() == 2
assert annot_dock.stop_bx.value() == 3

# Test by setting values with Spinboxes
# First, test zero duration annotation at recording start.
annot_dock.start_bx.setValue(0)
annot_dock.start_bx.editingFinished.emit()
annot_dock.stop_bx.setValue(0)
annot_dock.stop_bx.editingFinished.emit()
# Assert that annotation starts and ends at 0 and duration is 0
assert_allclose(raw_orig.annotations.onset[0], 0 + first_time, atol=1e-4)
assert_allclose(raw_orig.annotations.duration[0], 0, atol=1e-4)

# Now test zero duration annotation at arbitrary time.
sample_time = raw_orig.times[10]
annot_dock.stop_bx.setValue(sample_time)
annot_dock.stop_bx.editingFinished.emit()
annot_dock.start_bx.setValue(sample_time)
annot_dock.start_bx.editingFinished.emit()
# Assert that annotation starts and ends at selected time and duration is 0
assert_allclose(raw_orig.annotations.onset[0], sample_time + first_time, atol=1e-4)
assert_allclose(raw_orig.annotations.duration[0], 0, atol=1e-4)

# Finally, test zero duration annotation at recording end.
last_time = raw_orig.times[-1]
annot_dock.stop_bx.setValue(last_time)
annot_dock.stop_bx.editingFinished.emit()
annot_dock.start_bx.setValue(last_time)
annot_dock.start_bx.editingFinished.emit()
# Assert that annotation starts and ends at last sample and duration is 0
assert_allclose(raw_orig.annotations.onset[0], last_time + first_time, atol=1e-4)
assert_allclose(raw_orig.annotations.duration[0], 0, atol=1e-4)


def test_annotations_recording_end(raw_orig, pg_backend):
"""Test anotations at the end of recording."""
# Crop and resample to avoid failing tests due to rounding in browser
# Resampling also significantly speeds up the tests
raw_orig = raw_orig.copy().crop(tmax=20.0).resample(100)
# Add first annotation to initialize the description "A"
onset = 2
duration = 1
description = "A"
first_time = raw_orig.first_time
raw_orig.annotations.append(onset + first_time, duration, description)
n_anns = len(raw_orig.annotations)
fig = raw_orig.plot(duration=raw_orig.duration)
fig.test_mode = True
# Activate annotation_mode
fig._fake_keypress("a")

# Draw additional annotation that extends to the end of the current view
fig._fake_click(
(0.0, 1.0),
add_points=[(1.0, 1.0)],
xform="ax",
button=1,
kind="drag",
)
# Assert number of annotations did not change
assert len(raw_orig.annotations) == n_anns
new_annot_end = raw_orig.annotations.onset[0] + raw_orig.annotations.duration[0]
# Assert that the annotation end extends 1 sample above the recording
assert_allclose(
new_annot_end,
raw_orig.times[-1] + first_time + 1 / raw_orig.info["sfreq"],
atol=1e-4,
)


def test_annotations_interactions(raw_orig, pg_backend):
"""Test interactions specific to pyqtgraph-backend."""
# Add test-annotations
@@ -97,7 +187,7 @@ def test_annotations_interactions(raw_orig, pg_backend):
annot_dock.start_bx.setValue(6)
annot_dock.start_bx.editingFinished.emit()
assert fig.msg_box.isVisible()
assert fig.msg_box.informativeText() == "Start can't be bigger or equal to Stop!"
assert fig.msg_box.informativeText() == "Start can't be bigger than Stop!"
fig.msg_box.close()

# Test that dragging annotation onto the tail of another works