diff --git a/docs/changelog.md b/docs/changelog.md index f981abcc..3729006c 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. - Sort text columns holding numbers, such as the channel column `ch`, by numeric value in the Cluster and Similarity Views. Channel 2 no longer appears after channel 10. 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()