From ee993e1daa4920cca79eafdbe8b6314df7b9351d Mon Sep 17 00:00:00 2001 From: Paul McCarthy <pauld.mccarthy@gmail.com> Date: Fri, 26 Jun 2015 17:07:43 +0100 Subject: [PATCH] Improved log scale plotting, and added neato option to apply smoothing to time courses. Improved time series control panel layout too. --- fsl/data/strings.py | 8 ++- .../controls/timeseriescontrolpanel.py | 30 +++++++--- fsl/fslview/controls/timeserieslistpanel.py | 2 +- fsl/fslview/views/timeseriespanel.py | 60 ++++++++++++------- 4 files changed, 66 insertions(+), 34 deletions(-) diff --git a/fsl/data/strings.py b/fsl/data/strings.py index 02057b5b3..c220f46f3 100644 --- a/fsl/data/strings.py +++ b/fsl/data/strings.py @@ -203,8 +203,9 @@ labels = TypeDict({ 'TimeSeriesControlPanel.xlim' : 'X limits', 'TimeSeriesControlPanel.ylim' : 'Y limits', - 'TimeSeriesControlPanel.xlabel' : 'X label', - 'TimeSeriesControlPanel.ylabel' : 'Y label', + 'TimeSeriesControlPanel.labels' : 'Labels', + 'TimeSeriesControlPanel.xlabel' : 'X', + 'TimeSeriesControlPanel.ylabel' : 'Y', }) @@ -247,6 +248,7 @@ properties = TypeDict({ 'TimeSeriesPanel.legend' : 'Show legend', 'TimeSeriesPanel.ticks' : 'Show ticks', 'TimeSeriesPanel.grid' : 'Show grid', + 'TimeSeriesPanel.smooth' : 'Smooth data', 'TimeSeriesPanel.autoScale' : 'Auto-scale', 'TimeSeriesPanel.xLogScale' : 'Log scale (x axis)', 'TimeSeriesPanel.yLogScale' : 'Log scale (y axis)', @@ -355,7 +357,7 @@ choices = TypeDict({ 'ColourBarCanvas.orientation.horizontal' : 'Horizontal', 'ColourBarCanvas.orientation.vertical' : 'Vertical', - 'ColourBarCanvas.labelSide.top-left' : 'Top / Left', + 'ColourBarCanvas.labelSide.top-left' : 'Top / Left', 'ColourBarCanvas.labelSide.bottom-right' : 'Bottom / Right', 'VolumeOpts.displayRange.min' : 'Min.', diff --git a/fsl/fslview/controls/timeseriescontrolpanel.py b/fsl/fslview/controls/timeseriescontrolpanel.py index 7b4bc5b59..7a853d7fa 100644 --- a/fsl/fslview/controls/timeseriescontrolpanel.py +++ b/fsl/fslview/controls/timeseriescontrolpanel.py @@ -25,6 +25,7 @@ class TimeSeriesControlPanel(fslpanel.FSLViewPanel): self.__usePixdim = props.makeWidget(self, tsPanel, 'usePixdim') self.__logx = props.makeWidget(self, tsPanel, 'xLogScale') self.__logy = props.makeWidget(self, tsPanel, 'yLogScale') + self.__smooth = props.makeWidget(self, tsPanel, 'smooth') self.__legend = props.makeWidget(self, tsPanel, 'legend') self.__ticks = props.makeWidget(self, tsPanel, 'ticks') self.__grid = props.makeWidget(self, tsPanel, 'grid') @@ -38,6 +39,7 @@ class TimeSeriesControlPanel(fslpanel.FSLViewPanel): self.__ymin = props.makeWidget(self, tsPanel, 'ymin') self.__ymax = props.makeWidget(self, tsPanel, 'ymax') + self.__lblLabel = wx.StaticText(self) self.__xlblLabel = wx.StaticText(self) self.__ylblLabel = wx.StaticText(self) self.__xlimLabel = wx.StaticText(self) @@ -47,42 +49,56 @@ class TimeSeriesControlPanel(fslpanel.FSLViewPanel): self.__usePixdim.SetLabel(strings.properties[tsPanel, 'usePixdim']) self.__logx .SetLabel(strings.properties[tsPanel, 'xLogScale']) self.__logy .SetLabel(strings.properties[tsPanel, 'yLogScale']) + self.__smooth .SetLabel(strings.properties[tsPanel, 'smooth']) self.__legend .SetLabel(strings.properties[tsPanel, 'legend']) self.__ticks .SetLabel(strings.properties[tsPanel, 'ticks']) self.__grid .SetLabel(strings.properties[tsPanel, 'grid']) self.__autoScale.SetLabel(strings.properties[tsPanel, 'autoScale']) self.__xlimLabel.SetLabel(strings.labels[ self, 'xlim']) self.__ylimLabel.SetLabel(strings.labels[ self, 'ylim']) + self.__lblLabel .SetLabel(strings.labels[ self, 'labels']) self.__xlblLabel.SetLabel(strings.labels[ self, 'xlabel']) self.__ylblLabel.SetLabel(strings.labels[ self, 'ylabel']) - self.__sizer = wx.GridSizer(10, 2) + self.__sizer = wx.GridSizer(6, 3) self.SetSizer(self.__sizer) + self.__xlblSizer = wx.BoxSizer(wx.HORIZONTAL) + self.__xlblSizer.Add(self.__xlblLabel) + self.__xlblSizer.Add(self.__xlabel, flag=wx.EXPAND, proportion=1) + + self.__ylblSizer = wx.BoxSizer(wx.HORIZONTAL) + self.__ylblSizer.Add(self.__ylblLabel) + self.__ylblSizer.Add(self.__ylabel, flag=wx.EXPAND, proportion=1) + self.__sizer.Add(self.__demean, flag=wx.EXPAND) self.__sizer.Add(self.__usePixdim, flag=wx.EXPAND) + self.__sizer.Add(self.__smooth, flag=wx.EXPAND) + self.__sizer.Add(self.__logx, flag=wx.EXPAND) self.__sizer.Add(self.__logy, flag=wx.EXPAND) self.__sizer.Add(self.__legend, flag=wx.EXPAND) + self.__sizer.Add(self.__ticks, flag=wx.EXPAND) self.__sizer.Add(self.__grid, flag=wx.EXPAND) self.__sizer.Add(self.__autoScale, flag=wx.EXPAND) - self.__sizer.Add(self.__xlblLabel, flag=wx.EXPAND) - self.__sizer.Add(self.__ylblLabel, flag=wx.EXPAND) - self.__sizer.Add(self.__xlabel, flag=wx.EXPAND) - self.__sizer.Add(self.__ylabel, flag=wx.EXPAND) + + self.__sizer.Add(self.__lblLabel, flag=wx.EXPAND) + self.__sizer.Add(self.__xlblSizer, flag=wx.EXPAND) + self.__sizer.Add(self.__ylblSizer, flag=wx.EXPAND) + self.__sizer.Add(self.__xlimLabel, flag=wx.EXPAND) - self.__sizer.Add((-1, -1), flag=wx.EXPAND) self.__sizer.Add(self.__xmin, flag=wx.EXPAND) self.__sizer.Add(self.__xmax, flag=wx.EXPAND) + self.__sizer.Add(self.__ylimLabel, flag=wx.EXPAND) - self.__sizer.Add((-1, -1), flag=wx.EXPAND) self.__sizer.Add(self.__ymin, flag=wx.EXPAND) self.__sizer.Add(self.__ymax, flag=wx.EXPAND) self.Layout() self.SetMinSize(self.__sizer.GetMinSize()) + self.SetMaxSize(self.__sizer.GetMinSize()) tsPanel.addListener('autoScale', self._name, self.__autoScaleChanged) diff --git a/fsl/fslview/controls/timeserieslistpanel.py b/fsl/fslview/controls/timeserieslistpanel.py index cea9384e4..21e6670dc 100644 --- a/fsl/fslview/controls/timeserieslistpanel.py +++ b/fsl/fslview/controls/timeserieslistpanel.py @@ -133,7 +133,7 @@ class TimeSeriesListPanel(fslpanel.FSLViewPanel): return ts.alpha = 1 - ts.lineWidth = 1 + ts.lineWidth = 2 ts.lineStyle = '-' ts.colour = fslcm.randomColour() ts.label = self.__makeLabel(ts) diff --git a/fsl/fslview/views/timeseriespanel.py b/fsl/fslview/views/timeseriespanel.py index 4765436ca..0c7a313c5 100644 --- a/fsl/fslview/views/timeseriespanel.py +++ b/fsl/fslview/views/timeseriespanel.py @@ -15,6 +15,7 @@ of overlay objects stored in an :class:`.OverlayList`. import logging import wx +import scipy.interpolate as interp import numpy as np import props @@ -66,6 +67,7 @@ class TimeSeriesPanel(plotpanel.PlotPanel): yLogScale = props.Boolean(default=False) ticks = props.Boolean(default=True) grid = props.Boolean(default=True) + smooth = props.Boolean(default=False) xlabel = props.String() ylabel = props.String() xmin = props.Real() @@ -239,21 +241,9 @@ class TimeSeriesPanel(plotpanel.PlotPanel): xlims.append(xlim) ylims.append(ylim) - if self.xLogScale: axis.set_xscale('log') - else: axis.set_xscale('linear') - if self.yLogScale: axis.set_yscale('log') - else: axis.set_yscale('linear') - - xlim, ylim = self.__calcLimits(xlims, ylims) - - bPad = (ylim[1] - ylim[0]) * (50.0 / height) - tPad = (ylim[1] - ylim[0]) * (20.0 / height) - lPad = (xlim[1] - xlim[0]) * (50.0 / width) - rPad = (xlim[1] - xlim[0]) * (20.0 / width) - - axis.set_xlim((xlim[0] - lPad, xlim[1] + rPad)) - axis.set_ylim((ylim[0] - bPad, ylim[1] + tPad)) + (xmin, xmax), (ymin, ymax) = self.__calcLimits(xlims, ylims) + # x/y axis labels xlabel = self.xlabel ylabel = self.ylabel @@ -271,9 +261,10 @@ class TimeSeriesPanel(plotpanel.PlotPanel): axis.set_ylabel(self.ylabel, va='top') axis.yaxis.set_label_coords(10.0 / width, 0.5) + # Ticks if self.ticks: - xticks = np.linspace(xlim[0], xlim[1], 4) - yticks = np.linspace(ylim[0], ylim[1], 4) + xticks = np.linspace(xmin, xmax, 4) + yticks = np.linspace(ymin, ymax, 4) axis.tick_params(direction='in', pad=-5) @@ -289,6 +280,15 @@ class TimeSeriesPanel(plotpanel.PlotPanel): axis.set_xticks([]) axis.set_yticks([]) + # Limits + bPad = (ymax - ymin) * (50.0 / height) + tPad = (ymax - ymin) * (20.0 / height) + lPad = (xmax - xmin) * (50.0 / width) + rPad = (xmax - xmin) * (20.0 / width) + + axis.set_xlim((xmin - lPad, xmax + rPad)) + axis.set_ylim((ymin - bPad, ymax + tPad)) + # legend - don't show if we're only # plotting the current location if len(self.timeSeries) > 0 and self.legend: @@ -300,7 +300,6 @@ class TimeSeriesPanel(plotpanel.PlotPanel): fancybox=True) legend.get_frame().set_alpha(0.3) - if self.grid: axis.grid() @@ -312,14 +311,29 @@ class TimeSeriesPanel(plotpanel.PlotPanel): if ts.alpha == 0: return (0, 0), (0, 0) - - ydata = ts.data + ydata = np.array(ts.data, dtype=np.float32) + npoints = len(ydata) + if self.demean: ydata = ydata - ydata.mean() - if self.usePixdim: xdata = np.arange(len(ydata)) * ts.overlay.pixdim[3] - else: xdata = np.arange(len(ydata)) + if self.smooth: + tck = interp.splrep(np.arange(npoints), ydata) + ydata = interp.splev(np.linspace(0, npoints - 1, 5 * npoints), tck) + + xdata = np.linspace(0, npoints - 1, len(ydata), dtype=np.float32) + + if self.usePixdim: + xdata *= ts.overlay.pixdim[3] + + if self.xLogScale: xdata = np.log10(xdata) + if self.yLogScale: ydata = np.log10(ydata) + + nans = ~(np.isfinite(xdata) & np.isfinite(ydata)) + + xdata[nans] = np.nan + ydata[nans] = np.nan kwargs = {} kwargs['lw'] = ts.lineWidth @@ -330,5 +344,5 @@ class TimeSeriesPanel(plotpanel.PlotPanel): self.getAxis().plot(xdata, ydata, **kwargs) - return ((xdata.min(), xdata.max()), - (ydata.min(), ydata.max())) + return ((np.nanmin(xdata), np.nanmax(xdata)), + (np.nanmin(ydata), np.nanmax(ydata))) -- GitLab