From 99d8081f1aa61005e9dc6eeaa0e5fcecd4f89523 Mon Sep 17 00:00:00 2001 From: Aditya Singh Date: Wed, 5 Aug 2026 06:05:51 -0700 Subject: [PATCH] Rescale the waveform view when the waveforms type changes The waveform view computed its y axis bounds on the first plot and kept them for the lifetime of the view, so switching between raw waveforms, mean waveforms and templates reused the scale of whichever type happened to be shown first. Templates then appeared as flat lines, and raw waveforms overflowed their box. Discard the cached bounds whenever the waveforms type actually changes, through the property setter, the next and previous actions, and the mean waveforms toggle. Bounds are still shared across cluster selections of the same type, so the scale stays comparable while curating. Fixes #1045 --- docs/changelog.md | 4 ++ phy/cluster/views/tests/test_waveform.py | 56 ++++++++++++++++++++++++ phy/cluster/views/waveform.py | 22 +++++++++- 3 files changed, 80 insertions(+), 2 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 4ca30298..43acba56 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -50,6 +50,10 @@ behavior they verify rather than listed separately. Escape, and outside clicks release filter focus so global shortcuts resume. - Display metadata columns containing multiple values in the Cluster and Similarity Views instead of leaving their cells blank. +- Rescale the Waveform View y axis when the waveforms type changes. Templates + and mean waveforms are no longer drawn as flat lines after switching away + from raw waveforms, and raw waveforms no longer overflow their box after + switching back. ### Changed diff --git a/phy/cluster/views/tests/test_waveform.py b/phy/cluster/views/tests/test_waveform.py index 90335fc6..9250a1e6 100644 --- a/phy/cluster/views/tests/test_waveform.py +++ b/phy/cluster/views/tests/test_waveform.py @@ -113,3 +113,59 @@ def on_select_channel(sender, channel_id=None, button=None, key=None): v.set_state(v.state) _stop_and_close(qtbot, v) + + +def test_waveform_view_rescale_on_waveforms_type(qtbot, gui): + nc = 5 + ns = 10 + + raw = 100 * artificial_waveforms(ns, 20, nc) + # Mean waveforms are a thousand times smaller here, the way templates typically + # are next to raw traces. + mean = raw / 1000.0 + + def _waveforms(data): + def get_waveforms(cluster_id): + return Bunch( + data=data, + channel_ids=np.arange(nc), + channel_positions=staggered_positions(nc), + ) + + return get_waveforms + + v = WaveformView( + waveforms={'waveforms': _waveforms(raw), 'mean_waveforms': _waveforms(mean)}, + sample_rate=10000.0, + ) + with qtbot.waitExposed(v.canvas): + v.show() + v.attach(gui) + + v.on_select(cluster_ids=[0]) + raw_max = v.data_bounds[3] + assert raw_max > 0 + + # The y axis follows the displayed waveforms type, otherwise the smaller + # waveforms are drawn as flat lines. + v.next_waveforms_type() + assert v.waveforms_type == 'mean_waveforms' + ac(v.data_bounds[3], raw_max / 1000.0, rtol=1e-5) + + v.previous_waveforms_type() + assert v.waveforms_type == 'waveforms' + ac(v.data_bounds[3], raw_max, rtol=1e-5) + + v.toggle_mean_waveforms(True) + assert v.waveforms_type == 'mean_waveforms' + ac(v.data_bounds[3], raw_max / 1000.0, rtol=1e-5) + + v.toggle_mean_waveforms(False) + assert v.waveforms_type == 'waveforms' + ac(v.data_bounds[3], raw_max, rtol=1e-5) + + v.waveforms_type = 'mean_waveforms' + v.plot() + ac(v.data_bounds[3], raw_max / 1000.0, rtol=1e-5) + + _stop_and_close(qtbot, v) diff --git a/phy/cluster/views/waveform.py b/phy/cluster/views/waveform.py index c1257f75..2c08d6cc 100644 --- a/phy/cluster/views/waveform.py +++ b/phy/cluster/views/waveform.py @@ -511,27 +511,45 @@ def waveforms_type(self): @waveforms_type.setter def waveforms_type(self, value): + previous_type = self.waveforms_type self.waveforms_types.set(value) + self._rescale_if_waveforms_type_changed(previous_type) + + def _rescale_if_waveforms_type_changed(self, previous_type): + """Discard the cached y axis bounds when the waveforms type has changed. + + Raw waveforms, mean waveforms and templates have very different amplitude + scales, so bounds computed for one type make the others unreadable, either as + flat lines or as traces overflowing their box. Dropping the bounds makes the + next plot recompute them for the type now displayed. + + """ + if self.waveforms_type != previous_type: + self.data_bounds = None def next_waveforms_type(self): """Switch to the next waveforms type.""" + previous_type = self.waveforms_type self.waveforms_types.next() + self._rescale_if_waveforms_type_changed(previous_type) logger.debug('Switch to waveforms type %s.', self.waveforms_type) self.plot() def previous_waveforms_type(self): """Switch to the previous waveforms type.""" + previous_type = self.waveforms_type self.waveforms_types.previous() + self._rescale_if_waveforms_type_changed(previous_type) logger.debug('Switch to waveforms type %s.', self.waveforms_type) self.plot() def toggle_mean_waveforms(self, checked): """Switch to the `mean_waveforms` type, if it is available.""" if self.waveforms_type == 'mean_waveforms' and 'waveforms' in self.waveforms: - self.waveforms_types.set('waveforms') + self.waveforms_type = 'waveforms' logger.debug('Switch to raw waveforms.') self.plot() elif 'mean_waveforms' in self.waveforms: - self.waveforms_types.set('mean_waveforms') + self.waveforms_type = 'mean_waveforms' logger.debug('Switch to mean waveforms.') self.plot()