From c1db01a517da761479304f3426e97fdcd0eb17b4 Mon Sep 17 00:00:00 2001
From: Paul McCarthy <pauld.mccarthy@gmail.com>
Date: Thu, 25 Jun 2015 17:22:50 +0100
Subject: [PATCH] In process of improving time series functionality.

---
 fsl/data/strings.py                         |   7 +-
 fsl/fslview/controls/__init__.py            |   1 +
 fsl/fslview/controls/timeserieslistpanel.py |  59 ++++++
 fsl/fslview/views/timeseriespanel.py        | 208 +++++++++-----------
 4 files changed, 156 insertions(+), 119 deletions(-)
 create mode 100644 fsl/fslview/controls/timeserieslistpanel.py

diff --git a/fsl/data/strings.py b/fsl/data/strings.py
index fab0d8759..ada85f980 100644
--- a/fsl/data/strings.py
+++ b/fsl/data/strings.py
@@ -116,6 +116,7 @@ titles = TypeDict({
     'LookupTablePanel'      : 'Lookup tables',
     'LutLabelDialog'        : 'New LUT label',
     'NewLutDialog'          : 'New LUT',
+    'TimeSeriesListPanel'   : 'Time series list',
 
     'LookupTablePanel.loadLut'     : 'Select a lookup table file',
     'LookupTablePanel.labelExists' : 'Label already exists',
@@ -148,9 +149,11 @@ actions = TypeDict({
     'LightBoxPanel.toggleLightBoxToolBar' : 'View properties',
 
 
-    'PlotPanel.screenshot' : 'Take screenshot',
+    'PlotPanel.screenshot'                 : 'Take screenshot',
 
-    'HistogramPanel.toggleToolbar' : 'Histogram controls',
+    'TimeSeriesPanel.toggleTimeSeriesList' : 'Time series list',
+    'HistogramPanel.toggleToolbar'         : 'Histogram controls',
+    
 
 
     'OrthoViewProfile.centreCursor' : 'Centre cursor',
diff --git a/fsl/fslview/controls/__init__.py b/fsl/fslview/controls/__init__.py
index 9ee27b304..fbfd7642b 100644
--- a/fsl/fslview/controls/__init__.py
+++ b/fsl/fslview/controls/__init__.py
@@ -13,6 +13,7 @@ from lightboxsettingspanel import LightBoxSettingsPanel
 from locationpanel         import LocationPanel
 from orthosettingspanel    import OrthoSettingsPanel
 from lookuptablepanel      import LookupTablePanel
+from timeserieslistpanel   import TimeSeriesListPanel
 
 from orthotoolbar          import OrthoToolBar
 from orthoprofiletoolbar   import OrthoProfileToolBar
diff --git a/fsl/fslview/controls/timeserieslistpanel.py b/fsl/fslview/controls/timeserieslistpanel.py
new file mode 100644
index 000000000..346be2209
--- /dev/null
+++ b/fsl/fslview/controls/timeserieslistpanel.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+#
+# timeserieslistpanel.py -
+#
+# Author: Paul McCarthy <pauldmccarthy@gmail.com>
+#
+
+import wx
+
+import pwidgets.elistbox      as elistbox
+import fsl.fslview.panel      as fslpanel
+import fsl.fslview.colourmaps as fslcm
+
+
+class TimeSeriesListPanel(fslpanel.FSLViewPanel):
+
+    def __init__(self, parent, overlayList, displayCtx, timeSeriesPanel):
+
+        fslpanel.FSLViewPanel.__init__(self, parent, overlayList, displayCtx)
+
+        self.__tsPanel      = timeSeriesPanel
+        self.__currentLabel = wx.StaticText(           self)
+        self.__tsList       = elistbox.EditableListBox(self)
+
+        self.__sizer = wx.BoxSizer(wx.VERTICAL)
+        self.SetSizer(self.__sizer)
+
+        self.__sizer.Add(self.__currentLabel, flag=wx.EXPAND)
+        self.__sizer.Add(self.__tsList,       flag=wx.EXPAND, proportion=1)
+
+
+        displayCtx .addListener('selectedOverlay',
+                                self._name,
+                                self.__locationChanged)
+        overlayList.addListener('overlays',
+                                self._name,
+                                self.__locationChanged)
+
+        self.Layout()
+
+        
+    def destroy(self):
+        fslpanel.FSLViewPanel.destroy(self)
+        self._displayCtx .removeListener('selectedOverlay', self._name)
+        self._overlayList.removeListener('overlays',        self._name)
+
+
+    def __locationChanged(self, *a):
+        pass
+
+    
+    def __onListAdd(self, ev):
+        
+        ts = self.__tsPanel.getCurrent()
+
+        ts.colour = fslcm.randomColour()
+
+        self.__tsPanel.timeSeries.append(ts)
+    
diff --git a/fsl/fslview/views/timeseriespanel.py b/fsl/fslview/views/timeseriespanel.py
index bb61bfbca..310734d23 100644
--- a/fsl/fslview/views/timeseriespanel.py
+++ b/fsl/fslview/views/timeseriespanel.py
@@ -16,10 +16,14 @@ import logging
 
 import numpy                      as np
 
+import                               props
+
 import                               plotpanel
 import fsl.data.image             as fslimage
 import fsl.data.strings           as strings
 import fsl.fslview.displaycontext as fsldisplay
+import fsl.fslview.colourmaps     as fslcm
+import fsl.fslview.controls       as fslcontrols
 import fsl.utils.transform        as transform
 
 
@@ -36,6 +40,16 @@ log = logging.getLogger(__name__)
 #        of each persistent time course
 
 
+class TimeSeries(object):
+
+    def __init__(self, overlay, coords, data, colour, label):
+        self.overlay = overlay
+        self.coords  = coords
+        self.data    = data
+        self.colour  = colour
+        self.label   = label
+
+
 class TimeSeriesPanel(plotpanel.PlotPanel):
     """A panel with a :mod:`matplotlib` canvas embedded within.
 
@@ -44,38 +58,37 @@ class TimeSeriesPanel(plotpanel.PlotPanel):
     plotted on the canvas.
     """
 
+
+    timeSeries = props.List()
+    
+
     def __init__(self, parent, overlayList, displayCtx):
 
-        plotpanel.PlotPanel.__init__(self, parent, overlayList, displayCtx)
+        actionz = {
+            'toggleTimeSeriesList' : lambda *a: self.togglePanel(
+                fslcontrols.TimeSeriesListPanel, False, self)
+        }
+
+        plotpanel.PlotPanel.__init__(
+            self, parent, overlayList, displayCtx, actionz=actionz)
 
         figure = self.getFigure()
-        canvas = self.getCanvas()
 
         figure.subplots_adjust(
             top=1.0, bottom=0.0, left=0.0, right=1.0)
 
         figure.patch.set_visible(False)
 
-        self._mouseDown = False
-        canvas.mpl_connect('button_press_event',   self._onPlotMouseDown)
-        canvas.mpl_connect('button_release_event', self._onPlotMouseUp)
-        canvas.mpl_connect('motion_notify_event',  self._onPlotMouseMove)
-
-        self._overlayList.addListener(
-            'overlays',
-            self._name,
-            self._selectedOverlayChanged)
-        self._displayCtx.addListener(
-            'selectedOverlay',
-            self._name,
-            self._selectedOverlayChanged) 
-        self._displayCtx.addListener(
-            'location',
-            self._name,
-            self._locationChanged)
+        name = self._name
+        draw = self._draw
+
+        self._overlayList.addListener('overlays',        name, draw)
+        self._displayCtx .addListener('selectedOverlay', name, draw) 
+        self._displayCtx .addListener('location',        name, draw)
+        self             .addListener('timeSeries',      name, draw)
 
         self.Layout()
-        self._selectedOverlayChanged()
+        self._draw()
 
 
     def destroy(self):
@@ -85,126 +98,87 @@ class TimeSeriesPanel(plotpanel.PlotPanel):
         self._displayCtx .removeListener('selectedOverlay', self._name)
         self._displayCtx .removeListener('location',        self._name)
 
-        for ovl in self._overlayList:
-            opts = self._displayCtx.getOpts(ovl)
-
-            if isinstance(opts, fsldisplay.ImageOpts):
-                opts.removeListener('volume', self._name)
-
-
-    def _selectedOverlayChanged(self, *a):
-
-        overlay = self._displayCtx.getSelectedOverlay()
-
-        for ovl in self._overlayList:
-
-            if not isinstance(ovl, fslimage.Image):
-                continue
-
-            opts = self._displayCtx.getOpts(ovl)
-
-            if ovl is overlay:
-                opts.addListener('volume',
-                                 self._name,
-                                 self._locationChanged,
-                                 overwrite=True)
-            else:
-                opts.removeListener('volume', self._name)
-
-        self._locationChanged()
-        
-        
         
-    def _locationChanged(self, *a):
-        
-        self.getAxis().clear()
-        if len(self._overlayList) == 0:
-            self.getCanvas().draw()
-            self.Refresh()
-        else:
-            self._drawPlot() 
-
+    def getCurrent(self):
 
-    def _drawPlot(self):
-
-        axis    = self.getAxis()
-        canvas  = self.getCanvas()
         x, y, z = self._displayCtx.location.xyz
         overlay = self._displayCtx.getSelectedOverlay()
+        opts    = self._displayCtx.getOpts(overlay)
 
-        if not isinstance(overlay, fslimage.Image):
-            self.message(strings.messages[self, 'noData'])
-
-        elif not overlay.is4DImage():
-            self.message(strings.messages[self, 'not4D'])
+        if not isinstance(overlay, fslimage.Image)        or \
+           not isinstance(opts,    fsldisplay.VolumeOpts) or \
+           not overlay.is4DImage():
+            return None
+        
+        xform = opts.getTransform('display', 'voxel')
+        vox   = transform.transform([[x, y, z]], xform)[0]
+        vox   = np.floor(vox + 0.5)
 
-        else:
-            opts  = self._displayCtx.getOpts(overlay)
-            xform = opts.getTransform('display', 'voxel')
+        if vox[0] < 0                 or \
+           vox[1] < 0                 or \
+           vox[2] < 0                 or \
+           vox[0] >= overlay.shape[0] or \
+           vox[1] >= overlay.shape[1] or \
+           vox[2] >= overlay.shape[2]:
+            return None
 
-            vox = transform.transform([[x, y, z]], xform)[0]
-            vox = np.floor(vox + 0.5)
+        return TimeSeries(
+            overlay,
+            vox,
+            overlay.data[vox[0], vox[1], vox[2], :],
+            [0.5, 0.5, 0.5],
+            '{} [{} {} {}]'.format(overlay.name, vox[0], vox[1], vox[2]))
 
-            if vox[0] < 0                 or \
-               vox[1] < 0                 or \
-               vox[2] < 0                 or \
-               vox[0] >= overlay.shape[0] or \
-               vox[1] >= overlay.shape[1] or \
-               vox[2] >= overlay.shape[2]:
-                
-                self.message(strings.messages[self, 'outOfBounds'])
 
-            else:
-                self._drawPlotOneOverlay(overlay, *vox)
-                axis.axvline(opts.volume, c='#000080', lw=2, alpha=0.4)
+    def _draw(self, *a):
 
-        canvas.draw()
-        self.Refresh()
+        axis   = self.getAxis()
+        canvas = self.getCanvas()
 
+        axis.clear()
         
-    def _drawPlotOneOverlay(self, overlay, x, y, z):
+        toPlot    = self.timeSeries[:]
+        currentTs = self.getCurrent()
 
-        display = self._displayCtx.getDisplay(overlay)
+        if currentTs is not None:
+            toPlot = [currentTs] + toPlot
 
-        if not overlay.is4DImage(): return None
-        if not display.enabled:     return None
+        if len(toPlot) == 0:
+            canvas.draw()
+            self.Refresh()
+            return
 
-        for vox, shape in zip((x, y, z), overlay.shape):
-            if vox >= shape or vox < 0:
-                return None
+        xlims = []
+        ylims = []
 
-        data = overlay.data[x, y, z, :]
-        self.getAxis().plot(data, lw=2)
+        for ts in toPlot:
+            xlim, ylim = self._drawTimeSeries(ts)
+            xlims.append(xlim)
+            ylims.append(ylim)
 
-        return data.min(), data.max(), len(data)
+        # Set x/ylim
+        xmin = min([lim[0] for lim in xlims])
+        xmax = max([lim[1] for lim in xlims])
+        ymin = min([lim[0] for lim in ylims])
+        ymax = max([lim[1] for lim in ylims])
 
+        axis.set_xlim((xmin, xmax))
+        axis.set_ylim((ymin, ymax))
 
-    def _onPlotMouseDown(self, ev):
-        if ev.inaxes != self.getAxis(): return
+        canvas.draw()
+        self.Refresh()
 
-        overlay = self._displayCtx.getSelectedOverlay()
 
-        if not isinstance(overlay, fslimage.Image) or not overlay.is4DImage():
-            return
-        self._mouseDown = True
+    def _drawTimeSeries(self, ts):
 
-        opts = self._displayCtx.getOpts(overlay)
-        opts.volume = np.floor(ev.xdata)
-        
+        display = self._displayCtx.getDisplay(ts.overlay)
 
-    def _onPlotMouseUp(self, ev):
-        self._mouseDown = False
+        if not display.enabled:
+            return None
 
+        data = ts.data
         
-    def _onPlotMouseMove(self, ev):
-        if not self._mouseDown:         return
-        if ev.inaxes != self.getAxis(): return
-        
-        overlay = self._displayCtx.getSelectedOverlay()
+        self.getAxis().plot(data, lw=2, c=ts.colour, label=ts.label)
 
-        if not isinstance(overlay, fslimage.Image) or not overlay.is4DImage():
-            return
-
-        opts = self._displayCtx.getOpts(overlay)
-        
-        opts.volume = np.floor(ev.xdata)
+        # TODO take into account TR
+        return (0, len(data)), (data.min(), data.max())
-- 
GitLab