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