From 0bdad9be0782e1ef2377aa3c186251f9e11ea2f9 Mon Sep 17 00:00:00 2001 From: Paul McCarthy <pauld.mccarthy@gmail.com> Date: Mon, 29 Jun 2015 14:05:11 +0100 Subject: [PATCH] Plot refresh on property change is no longer performed by PlotPanel - it is done by subclasses. This is due to HistogramPanel.autoBin complicatedness - the data series need to be refreshed before the plot is refreshed. --- fsl/data/strings.py | 22 ++--- fsl/fslview/controls/__init__.py | 2 +- fsl/fslview/controls/histogramcontrolpanel.py | 73 ++++++++++++++ fsl/fslview/controls/plotcontrolpanel.py | 3 - .../controls/timeseriescontrolpanel.py | 27 ++--- fsl/fslview/controls/timeserieslistpanel.py | 1 + fsl/fslview/views/histogrampanel.py | 99 +++++++++++++------ fsl/fslview/views/plotpanel.py | 18 ++-- fsl/fslview/views/timeseriespanel.py | 15 +-- 9 files changed, 183 insertions(+), 77 deletions(-) create mode 100644 fsl/fslview/controls/histogramcontrolpanel.py diff --git a/fsl/data/strings.py b/fsl/data/strings.py index cb066725d..b814daa28 100644 --- a/fsl/data/strings.py +++ b/fsl/data/strings.py @@ -58,13 +58,7 @@ messages = TypeDict({ 'calling render directly with ' 'this command: \n{}', - 'HistogramPanel.noData' : 'Selected overlay has no data', - 'TimeSeriesPanel.noData' : 'Selected overlay has no data', - 'TimeSeriesPanel.not4D' : 'Selected overlay is ' - 'not four dimensional', - 'TimeSeriesPanel.outOfBounds' : 'Selected overlay has no data ' - 'at the current coordinates', - 'TimeSeriesPanel.screenshot' : 'Save screenshot', + 'PlotPanel.screenshot' : 'Save screenshot', 'SpacePanel.nonVolumetric' : 'Non-volumetric overlays ' 'are not supported', @@ -118,6 +112,7 @@ titles = TypeDict({ 'TimeSeriesListPanel' : 'Time series list', 'TimeSeriesControlPanel' : 'Time series control', 'HistogramListPanel' : 'Histogram list', + 'HistogramControlPanel' : 'Histogram control', 'LookupTablePanel.loadLut' : 'Select a lookup table file', 'LookupTablePanel.labelExists' : 'Label already exists', @@ -153,6 +148,7 @@ actions = TypeDict({ 'TimeSeriesPanel.toggleTimeSeriesList' : 'Time series list', 'TimeSeriesPanel.toggleTimeSeriesControl' : 'Time series control', 'HistogramPanel.toggleHistogramList' : 'Histogram list', + 'HistogramPanel.toggleHistogramControl' : 'Histogram control', 'OrthoViewProfile.centreCursor' : 'Centre cursor', 'OrthoViewProfile.resetZoom' : 'Reset zoom', @@ -246,12 +242,14 @@ properties = TypeDict({ 'PlotPanel.xlabel' : 'X label', 'PlotPanel.ylabel' : 'Y label', - 'TimeSeriesPanel.demean' : 'Demean', - 'TimeSeriesPanel.usePixdim' : 'Use pixdims', + 'TimeSeriesPanel.demean' : 'Demean', + 'TimeSeriesPanel.usePixdim' : 'Use pixdims', + 'TimeSeriesPanel.showCurrent' : 'Plot time series for current voxel', - 'HistogramPanel.dataRange' : 'Data range', - 'HistogramPanel.autoHist' : 'Automatic histogram binning', - 'HistogramPanel.nbins' : 'Number of bins', + 'HistogramPanel.histType' : 'Histogram type', + 'HistogramPanel.autoBin' : 'Automatic histogram binning', + 'HistogramPanel.showCurrent' : 'Plot histogram for current overlay', + 'HistogramPanel.enableOverlay' : 'Enable 3D histogram overlay', 'OrthoEditProfile.selectionSize' : 'Selection size', 'OrthoEditProfile.selectionIs3D' : '3D selection', diff --git a/fsl/fslview/controls/__init__.py b/fsl/fslview/controls/__init__.py index 6c2ab0df6..4577323c8 100644 --- a/fsl/fslview/controls/__init__.py +++ b/fsl/fslview/controls/__init__.py @@ -17,9 +17,9 @@ from plotcontrolpanel import PlotControlPanel from timeserieslistpanel import TimeSeriesListPanel from timeseriescontrolpanel import TimeSeriesControlPanel from histogramlistpanel import HistogramListPanel +from histogramcontrolpanel import HistogramControlPanel from orthotoolbar import OrthoToolBar from orthoprofiletoolbar import OrthoProfileToolBar from lightboxtoolbar import LightBoxToolBar from overlaydisplaytoolbar import OverlayDisplayToolBar -from histogramtoolbar import HistogramToolBar diff --git a/fsl/fslview/controls/histogramcontrolpanel.py b/fsl/fslview/controls/histogramcontrolpanel.py new file mode 100644 index 000000000..9ac6261bc --- /dev/null +++ b/fsl/fslview/controls/histogramcontrolpanel.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python +# +# histogramcontrolpanel.py - +# +# Author: Paul McCarthy <pauldmccarthy@gmail.com> +# + +import wx + +import props + +import fsl.fslview.panel as fslpanel +import fsl.data.strings as strings +import plotcontrolpanel + + +class OverlayHistOptionsPanel(fslpanel.FSLViewPanel): + def __init__(self, parent, overlayList, displayCtx, hsPanel): + pass + + +class HistogramControlPanel(fslpanel.FSLViewPanel): + + + def __init__(self, parent, overlayList, displayCtx, hsPanel): + + fslpanel.FSLViewPanel.__init__(self, parent, overlayList, displayCtx) + + self.__plotControl = plotcontrolpanel.PlotControlPanel( + self, overlayList, displayCtx, hsPanel) + self.__plotControl.SetWindowStyleFlag(wx.SUNKEN_BORDER) + + self.__autoBin = props.makeWidget(self, hsPanel, 'autoBin') + self.__showCurrent = props.makeWidget(self, hsPanel, 'showCurrent') + self.__histType = props.makeWidget(self, hsPanel, 'histType') + self.__enableOverlay = props.makeWidget(self, hsPanel, 'enableOverlay') + + self.__histTypeLabel = wx.StaticText(self) + + self.__autoBin .SetLabel(strings.properties[hsPanel, + 'autoBin']) + self.__showCurrent .SetLabel(strings.properties[hsPanel, + 'showCurrent']) + self.__enableOverlay.SetLabel(strings.properties[hsPanel, + 'enableOverlay']) + self.__histTypeLabel.SetLabel(strings.properties[hsPanel, + 'histType']) + + self.__htSizer = wx.BoxSizer(wx.HORIZONTAL) + self.__htSizer.Add(self.__histTypeLabel, flag=wx.EXPAND) + self.__htSizer.Add(self.__histType, flag=wx.EXPAND, proportion=1) + + self.__optSizer = wx.GridSizer(2, 2) + + self.__optSizer.Add(self.__autoBin, flag=wx.EXPAND) + self.__optSizer.Add(self.__showCurrent, flag=wx.EXPAND) + self.__optSizer.Add(self.__htSizer, flag=wx.EXPAND) + self.__optSizer.Add(self.__enableOverlay, flag=wx.EXPAND) + + self.__sizer = wx.BoxSizer(wx.VERTICAL) + self.SetSizer(self.__sizer) + + self.__sizer.Add(self.__plotControl, + flag=wx.EXPAND | wx.ALL, + border=5, + proportion=1) + self.__sizer.Add(self.__optSizer, + flag=wx.EXPAND) + + self.Layout() + + self.SetMinSize(self.__sizer.GetMinSize()) + self.SetMaxSize(self.__sizer.GetMinSize()) diff --git a/fsl/fslview/controls/plotcontrolpanel.py b/fsl/fslview/controls/plotcontrolpanel.py index 1b4aef81b..ba589032a 100644 --- a/fsl/fslview/controls/plotcontrolpanel.py +++ b/fsl/fslview/controls/plotcontrolpanel.py @@ -93,9 +93,6 @@ class PlotControlPanel(fslpanel.FSLViewPanel): self.Layout() - self.SetMinSize(self.__sizer.GetMinSize()) - self.SetMaxSize(self.__sizer.GetMinSize()) - plotPanel.addListener('autoScale', self._name, self.__autoScaleChanged) self.__autoScaleChanged() diff --git a/fsl/fslview/controls/timeseriescontrolpanel.py b/fsl/fslview/controls/timeseriescontrolpanel.py index b06cc80c0..ca63fc013 100644 --- a/fsl/fslview/controls/timeseriescontrolpanel.py +++ b/fsl/fslview/controls/timeseriescontrolpanel.py @@ -22,30 +22,33 @@ class TimeSeriesControlPanel(fslpanel.FSLViewPanel): self.__tsPanel = tsPanel - self.__tsControl = plotcontrolpanel.PlotControlPanel( + self.__plotControl = plotcontrolpanel.PlotControlPanel( self, overlayList, displayCtx, tsPanel) - self.__tsControl.SetWindowStyleFlag(wx.SUNKEN_BORDER) + self.__plotControl.SetWindowStyleFlag(wx.SUNKEN_BORDER) - self.__demean = props.makeWidget(self, tsPanel, 'demean') - self.__usePixdim = props.makeWidget(self, tsPanel, 'usePixdim') + self.__demean = props.makeWidget(self, tsPanel, 'demean') + self.__usePixdim = props.makeWidget(self, tsPanel, 'usePixdim') + self.__showCurrent = props.makeWidget(self, tsPanel, 'showCurrent') - self.__demean .SetLabel(strings.properties[tsPanel, 'demean']) - self.__usePixdim.SetLabel(strings.properties[tsPanel, 'usePixdim']) + self.__demean .SetLabel(strings.properties[tsPanel, 'demean']) + self.__usePixdim .SetLabel(strings.properties[tsPanel, 'usePixdim']) + self.__showCurrent.SetLabel(strings.properties[tsPanel, 'showCurrent']) - self.__optSizer = wx.BoxSizer(wx.HORIZONTAL) - self.__optSizer.Add(self.__demean, flag=wx.EXPAND) - self.__optSizer.Add(self.__usePixdim, flag=wx.EXPAND) + self.__optSizer = wx.GridSizer(1, 3) + self.__optSizer.Add(self.__demean, flag=wx.EXPAND) + self.__optSizer.Add(self.__usePixdim, flag=wx.EXPAND) + self.__optSizer.Add(self.__showCurrent, flag=wx.EXPAND) self.__sizer = wx.BoxSizer(wx.VERTICAL) self.SetSizer(self.__sizer) - self.__sizer.Add(self.__optSizer, - flag=wx.EXPAND) - self.__sizer.Add(self.__tsControl, + self.__sizer.Add(self.__plotControl, flag=wx.EXPAND | wx.ALL, border=5, proportion=1) + self.__sizer.Add(self.__optSizer, + flag=wx.EXPAND) self.Layout() diff --git a/fsl/fslview/controls/timeserieslistpanel.py b/fsl/fslview/controls/timeserieslistpanel.py index ec5f23596..a0ae885c0 100644 --- a/fsl/fslview/controls/timeserieslistpanel.py +++ b/fsl/fslview/controls/timeserieslistpanel.py @@ -92,6 +92,7 @@ class TimeSeriesListPanel(fslpanel.FSLViewPanel): def destroy(self): fslpanel.FSLViewPanel.destroy(self) self._displayCtx .removeListener('selectedOverlay', self._name) + self._displayCtx .removeListener('location', self._name) self._overlayList.removeListener('overlays', self._name) self.__tsPanel .removeListener('dataSeries', self._name) diff --git a/fsl/fslview/views/histogrampanel.py b/fsl/fslview/views/histogrampanel.py index 44323757e..b1953bdc5 100644 --- a/fsl/fslview/views/histogrampanel.py +++ b/fsl/fslview/views/histogrampanel.py @@ -75,9 +75,11 @@ class HistogramSeries(plotpanel.DataSeries): self.__calcInitDataRange() self.histPropsChanged() - hsPanel.addListener('autoBin', self.name, self.histPropsChanged) - self .addListener('nbins', self.name, self.histPropsChanged) - self .addListener('dataRange', self.name, self.histPropsChanged) + self.addListener('nbins', self.name, self.histPropsChanged) + self.addListener('ignoreZeros', self.name, self.histPropsChanged) + self.addListener('volume', self.name, self.histPropsChanged) + self.addListener('allVolumes', self.name, self.histPropsChanged) + self.addListener('dataRange', self.name, self.histPropsChanged) def __del__(self): @@ -164,8 +166,11 @@ class HistogramPanel(plotpanel.PlotPanel): def __init__(self, parent, overlayList, displayCtx): - actionz = {'toggleHistogramList' : lambda *a: self.togglePanel( - fslcontrols.HistogramListPanel, False, self) + actionz = { + 'toggleHistogramList' : lambda *a: self.togglePanel( + fslcontrols.HistogramListPanel, False, self), + 'toggleHistogramControl' : lambda *a: self.togglePanel( + fslcontrols.HistogramControlPanel, False, self) } plotpanel.PlotPanel.__init__( @@ -178,21 +183,32 @@ class HistogramPanel(plotpanel.PlotPanel): figure.patch.set_visible(False) - self._overlayList.addListener( - 'overlays', - self._name, - self.__updateCurrent) - self._displayCtx.addListener( - 'selectedOverlay', - self._name, - self.__updateCurrent) - self.addGlobalListener(self._name, self.__updateCurrent) + self._overlayList.addListener('overlays', + self._name, + self.__updateCurrent) + self._displayCtx .addListener('selectedOverlay', + self._name, + self.__updateCurrent) + + # Re draw whenever any PlotPanel or + # HistogramPanel property changes. + self.addGlobalListener(self._name, self.draw) + + # But a separate listener for autoBin - + # this overwrites the one added by the + # addGlobalListener method above. See + # the __autoBinChanged method. + self.addListener('autoBin', + self._name, + self.__autoBinChanged, + overwrite=True) + self.__current = None self.__updateCurrent() self.Layout() - + def destroy(self): """De-registers property listeners. """ plotpanel.PlotPanel.destroy(self) @@ -200,30 +216,48 @@ class HistogramPanel(plotpanel.PlotPanel): self._overlayList.removeListener('overlays', self._name) self._displayCtx .removeListener('selectedOverlay', self._name) + + def __autoBinChanged(self, *a): + """Called when the :attr:`autoBin` property changes. Makes sure that + all existing :class:`HistogramSeries` instances are updated before + the plot is refreshed. + """ - def __updateCurrent(self, *a): + for ds in self.dataSeries: + ds.histPropsChanged() - self.__current = None + if self.__current is not None: + self.__current.histPropsChanged() - if self._overlayList == 0: - return + self.draw() - overlay = self._displayCtx.getSelectedOverlay() + - if not isinstance(overlay, fslimage.Image): - return + def __updateCurrent(self, *a): - if overlay in [hs.overlay for hs in self.dataSeries]: - return + overlay = self._displayCtx.getSelectedOverlay() + currentHs = self.__current + self.__current = None - hs = HistogramSeries(self, overlay) - hs.colour = [0.2, 0.2, 0.2] - hs.alpha = 1 - hs.lineWidth = 0.5 - hs.lineStyle = ':' - hs.label = None + if len(self._overlayList) == 0: + pass + elif not isinstance(overlay, fslimage.Image): + pass + elif overlay in [hs.overlay for hs in self.dataSeries]: + pass + elif currentHs is not None and overlay == currentHs.overlay: + self.__current = currentHs + else: + hs = HistogramSeries(self, overlay) + hs.colour = [0, 0, 0] + hs.alpha = 1 + hs.lineWidth = 2 + hs.lineStyle = ':' + hs.label = None - self.__current = hs + self.__current = hs + + self.draw() def getCurrent(self): @@ -234,5 +268,6 @@ class HistogramPanel(plotpanel.PlotPanel): current = self.getCurrent() - if current is not None: self.drawDataSeries([current]) + if self.showCurrent and \ + current is not None: self.drawDataSeries([current]) else: self.drawDataSeries() diff --git a/fsl/fslview/views/plotpanel.py b/fsl/fslview/views/plotpanel.py index 20eb9f3e7..32cbebaea 100644 --- a/fsl/fslview/views/plotpanel.py +++ b/fsl/fslview/views/plotpanel.py @@ -113,23 +113,19 @@ class PlotPanel(viewpanel.ViewPanel): canvas.mpl_connect('motion_notify_event', self.__onMouseMove) self.__name = '{}_{}'.format(type(self).__name__, self._name) - self.addGlobalListener(self.__name, self.__propChanged) + self.addListener('dataSeries', self.__name, self.__dataSeriesChanged) + self.Bind(wx.EVT_SIZE, lambda *a: self.draw()) - def draw(self): + def draw(self, *a): raise NotImplementedError('The draw method must be ' 'implemented by PlotPanel subclasses') - def __propChanged(self, value, valid, ctx, name): - - draw = lambda *a: self.draw() - - if name == 'dataSeries': - for ds in self.dataSeries: - ds.addGlobalListener(self.__name, draw, overwrite=True) - self.draw() + def __dataSeriesChanged(self, *a): + for ds in self.dataSeries: + ds.addGlobalListener(self.__name, self.draw, overwrite=True) def destroy(self): @@ -269,6 +265,8 @@ class PlotPanel(viewpanel.ViewPanel): if ds.alpha == 0: return (0, 0), (0, 0) + log.debug('Drawing plot for {}'.format(ds.overlay)) + xdata, ydata = ds.getData() if self.smooth: diff --git a/fsl/fslview/views/timeseriespanel.py b/fsl/fslview/views/timeseriespanel.py index f4f82452e..74ca5fcd9 100644 --- a/fsl/fslview/views/timeseriespanel.py +++ b/fsl/fslview/views/timeseriespanel.py @@ -60,10 +60,8 @@ class TimeSeriesPanel(plotpanel.PlotPanel): """ - demean = props.Boolean(default=True) - usePixdim = props.Boolean(default=False) - - # TODO make this setting functional + demean = props.Boolean(default=True) + usePixdim = props.Boolean(default=False) showCurrent = props.Boolean(default=True) @@ -92,6 +90,7 @@ class TimeSeriesPanel(plotpanel.PlotPanel): displayCtx .addListener('selectedOverlay', self._name, self.draw) displayCtx .addListener('location', self._name, self.draw) + self.addGlobalListener(self._name, self.draw) self.Layout() self.draw() @@ -102,6 +101,7 @@ class TimeSeriesPanel(plotpanel.PlotPanel): self._overlayList.removeListener('overlays', self._name) self._displayCtx .removeListener('selectedOverlay', self._name) self._displayCtx .removeListener('location', self._name) + self.removeGlobalListener(self._name) def __overlaysChanged(self, *a): @@ -144,9 +144,9 @@ class TimeSeriesPanel(plotpanel.PlotPanel): return ts = TimeSeries(self, overlay, vox) - ts.colour = [0.2, 0.2, 0.2] + ts.colour = [0, 0, 0] ts.alpha = 1 - ts.lineWidth = 0.5 + ts.lineWidth = 2 ts.lineStyle = ':' ts.label = None @@ -162,5 +162,6 @@ class TimeSeriesPanel(plotpanel.PlotPanel): self.__calcCurrent() current = self.getCurrent() - if current is not None: self.drawDataSeries([current]) + if self.showCurrent and \ + current is not None: self.drawDataSeries([current]) else: self.drawDataSeries() -- GitLab