diff --git a/fsl/fsleyes/controls/histogramcontrolpanel.py b/fsl/fsleyes/controls/histogramcontrolpanel.py index c2f7ff6e2a5e010d32c08aebc4e31a42f7a3030c..70ed0d6eb61bef10c4bffca0ee52260934985fb8 100644 --- a/fsl/fsleyes/controls/histogramcontrolpanel.py +++ b/fsl/fsleyes/controls/histogramcontrolpanel.py @@ -11,12 +11,13 @@ control* panel which allows a :class:`.HistogramPanel` to be configured. import wx -import props -import pwidgets.widgetlist as widgetlist +import props +import pwidgets.widgetlist as widgetlist -import fsl.fsleyes.panel as fslpanel -import fsl.fsleyes.tooltips as fsltooltips -import fsl.data.strings as strings +import fsl.fsleyes.panel as fslpanel +import fsl.fsleyes.tooltips as fsltooltips +import fsl.data.strings as strings +import timeseriescontrolpanel as tscontrol class HistogramControlPanel(fslpanel.FSLEyesPanel): @@ -67,13 +68,6 @@ class HistogramControlPanel(fslpanel.FSLEyesPanel): histProps = ['histType', 'autoBin', 'showCurrent'] - plotProps = ['xLogScale', - 'yLogScale', - 'smooth', - 'legend', - 'ticks', - 'grid', - 'autoScale'] self.__widgets.AddGroup( 'histSettings', strings.labels[self, 'histSettings']) @@ -96,51 +90,11 @@ class HistogramControlPanel(fslpanel.FSLEyesPanel): self.__widgets.AddGroup( 'plotSettings', strings.labels[hsPanel, 'plotSettings']) - - for prop in plotProps: - self.__widgets.AddWidget( - props.makeWidget(self.__widgets, hsPanel, prop), - tooltip=fsltooltips.properties[hsPanel, prop], - displayName=strings.properties[hsPanel, prop], - groupName='plotSettings') - xlabel = props.makeWidget(self.__widgets, hsPanel, 'xlabel') - ylabel = props.makeWidget(self.__widgets, hsPanel, 'ylabel') - - labels = wx.BoxSizer(wx.HORIZONTAL) - - labels.Add(wx.StaticText(self.__widgets, - label=strings.labels[hsPanel, 'xlabel'])) - labels.Add(xlabel, flag=wx.EXPAND, proportion=1) - labels.Add(wx.StaticText(self.__widgets, - label=strings.labels[hsPanel, 'ylabel'])) - labels.Add(ylabel, flag=wx.EXPAND, proportion=1) - - limits = props.makeListWidgets(self.__widgets, hsPanel, 'limits') - xlims = wx.BoxSizer(wx.HORIZONTAL) - ylims = wx.BoxSizer(wx.HORIZONTAL) + tscontrol.generatePlotPanelWidgets(hsPanel, + self.__widgets, + 'plotSettings') - xlims.Add(limits[0], flag=wx.EXPAND, proportion=1) - xlims.Add(limits[1], flag=wx.EXPAND, proportion=1) - ylims.Add(limits[2], flag=wx.EXPAND, proportion=1) - ylims.Add(limits[3], flag=wx.EXPAND, proportion=1) - - self.__widgets.AddWidget( - labels, - displayName=strings.labels[hsPanel, 'labels'], - tooltip=fsltooltips.misc[ hsPanel, 'labels'], - groupName='plotSettings') - self.__widgets.AddWidget( - xlims, - displayName=strings.labels[hsPanel, 'xlim'], - tooltip=fsltooltips.misc[ hsPanel, 'xlim'], - groupName='plotSettings') - self.__widgets.AddWidget( - ylims, - displayName=strings.labels[hsPanel, 'ylim'], - tooltip=fsltooltips.misc[ hsPanel, 'ylim'], - groupName='plotSettings') - # We store a ref to the currently selected # HistogramSeries instance, and to the # HistogramSeries.nbins widget, so we can diff --git a/fsl/fsleyes/controls/timeseriescontrolpanel.py b/fsl/fsleyes/controls/timeseriescontrolpanel.py index 4d755f2b70ea5ed69297b8fb3c3963bca6247014..9787fc16ffd5686f532ef4311caaefdd491bb209 100644 --- a/fsl/fsleyes/controls/timeseriescontrolpanel.py +++ b/fsl/fsleyes/controls/timeseriescontrolpanel.py @@ -6,6 +6,16 @@ # """This module provides the :class:`TimeSeriesControlPanel` a *FSLeyes control* which allows the user to configure a :class:`.TimeSeriesPanel`. + + +This module also provides a couple of general functions which generate widgets +for :class:`.PlotPanel` and :class:`.DataSeries` instances: + +.. autosummary:: + :nosignatures: + + generatePlotPanelWidgets + generateDataSeriesWidgets """ @@ -19,6 +29,7 @@ import fsl.fsleyes.displaycontext as fsldisplay import fsl.fsleyes.plotting.timeseries as timeseries import fsl.fsleyes.tooltips as fsltooltips import fsl.data.strings as strings +import fsl.data.melodicimage as fslmelimage class TimeSeriesControlPanel(fslpanel.FSLEyesPanel): @@ -82,13 +93,6 @@ class TimeSeriesControlPanel(fslpanel.FSLEyesPanel): 'plotMode', 'usePixdim', 'plotMelodicICs'] - plotProps = ['xLogScale', - 'yLogScale', - 'smooth', - 'legend', - 'ticks', - 'grid', - 'autoScale'] self.__widgets.AddGroup( 'tsSettings', @@ -108,54 +112,16 @@ class TimeSeriesControlPanel(fslpanel.FSLEyesPanel): tooltip=fsltooltips.properties[tsPanel, prop], groupName='tsSettings') + self.__widgets.AddGroup( 'plotSettings', strings.labels[tsPanel, 'plotSettings']) - - for prop in plotProps: - self.__widgets.AddWidget( - props.makeWidget(self.__widgets, tsPanel, prop), - displayName=strings.properties[tsPanel, prop], - tooltip=fsltooltips.properties[tsPanel, prop], - groupName='plotSettings') - - xlabel = props.makeWidget(self.__widgets, tsPanel, 'xlabel') - ylabel = props.makeWidget(self.__widgets, tsPanel, 'ylabel') - - labels = wx.BoxSizer(wx.HORIZONTAL) - - labels.Add(wx.StaticText(self.__widgets, - label=strings.labels[tsPanel, 'xlabel'])) - labels.Add(xlabel, flag=wx.EXPAND, proportion=1) - labels.Add(wx.StaticText(self.__widgets, - label=strings.labels[tsPanel, 'ylabel'])) - labels.Add(ylabel, flag=wx.EXPAND, proportion=1) - - limits = props.makeListWidgets(self.__widgets, tsPanel, 'limits') - xlims = wx.BoxSizer(wx.HORIZONTAL) - ylims = wx.BoxSizer(wx.HORIZONTAL) - - xlims.Add(limits[0], flag=wx.EXPAND, proportion=1) - xlims.Add(limits[1], flag=wx.EXPAND, proportion=1) - ylims.Add(limits[2], flag=wx.EXPAND, proportion=1) - ylims.Add(limits[3], flag=wx.EXPAND, proportion=1) - - self.__widgets.AddWidget( - labels, - displayName=strings.labels[tsPanel, 'labels'], - tooltip=fsltooltips.misc[ tsPanel, 'labels'], - groupName='plotSettings') - self.__widgets.AddWidget( - xlims, - displayName=strings.labels[tsPanel, 'xlim'], - tooltip=fsltooltips.misc[ tsPanel, 'xlim'], - groupName='plotSettings') - self.__widgets.AddWidget( - ylims, - displayName=strings.labels[tsPanel, 'ylim'], - tooltip=fsltooltips.misc[ tsPanel, 'ylim'], - groupName='plotSettings') + + generatePlotPanelWidgets(tsPanel, self.__widgets, 'plotSettings') + tsPanel .addListener('plotMelodicICs', + self._name, + self.__plotMelodicICsChanged) displayCtx .addListener('selectedOverlay', self._name, self.__selectedOverlayChanged) @@ -256,6 +222,33 @@ class TimeSeriesControlPanel(fslpanel.FSLEyesPanel): self.__showFEATSettingsForTimeSeries(ts) + def __plotMelodicICsChanged(self, *a): + """Called when the :attr:`.TimeSeriesPanel.plotMelodicICs` property + changes. If the current overlay is a :class:`.MelodicImage`, + re-generates the widgets in the *current time course* section, as + the :class:`.TimeSeries` instance associated with the overlay may + have been re-created. + """ + + overlay = self._displayCtx.getSelectedOverlay() + + if overlay is None: + return + + if not isinstance(overlay, fslmelimage.MelodicImage): + return + + ts = self.__tsPanel.getTimeSeries(overlay) + + if ts is None: + return + + if self.__widgets.HasGroup('currentSettings'): + self.__widgets.RemoveGroup('currentSettings') + + self.__showSettingsForTimeSeries(ts) + + def __showFEATSettingsForTimeSeries(self, ts): """(Re-)crates the *FEAT settings* section for the given :class:`.FEATTimeSeries` instance. @@ -349,33 +342,108 @@ class TimeSeriesControlPanel(fslpanel.FSLEyesPanel): 'currentSettings', strings.labels[self, 'currentSettings'].format(display.name)) - colour = props.makeWidget(widgets, ts, 'colour') - alpha = props.makeWidget(widgets, ts, 'alpha', - showLimits=False, spin=False) - lineWidth = props.makeWidget(widgets, ts, 'lineWidth') - lineStyle = props.makeWidget( - widgets, - ts, - 'lineStyle', - labels=strings.choices['DataSeries.lineStyle']) + generateDataSeriesWidgets(ts, widgets, 'currentSettings') - widgets.AddWidget( - colour, - displayName=strings.properties[ts, 'colour'], - tooltip=fsltooltips.properties[ts, 'colour'], - groupName='currentSettings') - widgets.AddWidget( - alpha, - displayName=strings.properties[ts, 'alpha'], - tooltip=fsltooltips.properties[ts, 'alpha'], - groupName='currentSettings') - widgets.AddWidget( - lineWidth, - displayName=strings.properties[ts, 'lineWidth'], - tooltip=fsltooltips.properties[ts, 'lineWidth'], - groupName='currentSettings') - widgets.AddWidget( - lineStyle, - displayName=strings.properties[ts, 'lineStyle'], - tooltip=fsltooltips.properties[ts, 'lineStyle'], - groupName='currentSettings') + +def generatePlotPanelWidgets(plotPanel, widgetList, groupName): + """Adds a collection of widgets to the given :class:`.WidgetList`, + allowing the properties of the given :class:`.PlotPanel` instance + to be changed. + + :arg plotPanel: The :class:`.PlotPanel` instance. + :arg widgetList: The :class:`.WidgetList` instance. + :arg groupName: The ``WidgetList`` group name to use. + """ + + plotProps = ['xLogScale', + 'yLogScale', + 'smooth', + 'legend', + 'ticks', + 'grid', + 'autoScale'] + + for prop in plotProps: + widgetList.AddWidget( + props.makeWidget(widgetList, plotPanel, prop), + displayName=strings.properties[plotPanel, prop], + tooltip=fsltooltips.properties[plotPanel, prop], + groupName=groupName) + + xlabel = props.makeWidget(widgetList, plotPanel, 'xlabel') + ylabel = props.makeWidget(widgetList, plotPanel, 'ylabel') + + labels = wx.BoxSizer(wx.HORIZONTAL) + + labels.Add(wx.StaticText(widgetList, + label=strings.labels[plotPanel, 'xlabel'])) + labels.Add(xlabel, flag=wx.EXPAND, proportion=1) + labels.Add(wx.StaticText(widgetList, + label=strings.labels[plotPanel, 'ylabel'])) + labels.Add(ylabel, flag=wx.EXPAND, proportion=1) + + limits = props.makeListWidgets(widgetList, plotPanel, 'limits') + xlims = wx.BoxSizer(wx.HORIZONTAL) + ylims = wx.BoxSizer(wx.HORIZONTAL) + + xlims.Add(limits[0], flag=wx.EXPAND, proportion=1) + xlims.Add(limits[1], flag=wx.EXPAND, proportion=1) + ylims.Add(limits[2], flag=wx.EXPAND, proportion=1) + ylims.Add(limits[3], flag=wx.EXPAND, proportion=1) + + widgetList.AddWidget( + labels, + displayName=strings.labels[plotPanel, 'labels'], + tooltip=fsltooltips.misc[ plotPanel, 'labels'], + groupName=groupName) + widgetList.AddWidget( + xlims, + displayName=strings.labels[plotPanel, 'xlim'], + tooltip=fsltooltips.misc[ plotPanel, 'xlim'], + groupName=groupName) + widgetList.AddWidget( + ylims, + displayName=strings.labels[plotPanel, 'ylim'], + tooltip=fsltooltips.misc[ plotPanel, 'ylim'], + groupName=groupName) + + +def generateDataSeriesWidgets(ds, widgetList, groupName): + """Adds a collection of widgets to the given :class:`.WidgetList`, + allowing the properties of the given :class:`.DataSeries` instance + to be changed. + + :arg ds: The :class:`.DataSeries` instance. + :arg widgetList: The :class:`.WidgetList` instance. + :arg groupName: The ``WidgetList`` group name to use. + """ + colour = props.makeWidget(widgetList, ds, 'colour') + alpha = props.makeWidget(widgetList, ds, 'alpha', + showLimits=False, spin=False) + lineWidth = props.makeWidget(widgetList, ds, 'lineWidth') + lineStyle = props.makeWidget( + widgetList, + ds, + 'lineStyle', + labels=strings.choices[ds, 'lineStyle']) + + widgetList.AddWidget( + colour, + displayName=strings.properties[ds, 'colour'], + tooltip=fsltooltips.properties[ds, 'colour'], + groupName=groupName) + widgetList.AddWidget( + alpha, + displayName=strings.properties[ds, 'alpha'], + tooltip=fsltooltips.properties[ds, 'alpha'], + groupName=groupName) + widgetList.AddWidget( + lineWidth, + displayName=strings.properties[ds, 'lineWidth'], + tooltip=fsltooltips.properties[ds, 'lineWidth'], + groupName=groupName) + widgetList.AddWidget( + lineStyle, + displayName=strings.properties[ds, 'lineStyle'], + tooltip=fsltooltips.properties[ds, 'lineStyle'], + groupName=groupName) diff --git a/fsl/fsleyes/controls/timeserieslistpanel.py b/fsl/fsleyes/controls/timeserieslistpanel.py index c211d35b9c7f7d5c0d98ebf94ddf4c53ac4b15c4..76d469d762ffd42a0990ade5a30483d69c965494 100644 --- a/fsl/fsleyes/controls/timeserieslistpanel.py +++ b/fsl/fsleyes/controls/timeserieslistpanel.py @@ -152,6 +152,7 @@ class TimeSeriesListPanel(fslpanel.FSLEyesPanel): """ overlay = self._displayCtx.getSelectedOverlay() + opts = self._displayCtx.getOpts(overlay) if overlay is None: return @@ -189,7 +190,7 @@ class TimeSeriesListPanel(fslpanel.FSLEyesPanel): copy.coord = ts.getComponent() else: copy.tsLoc = 'location' - copy.coord = ts.getVoxel() + copy.coord = opts.getVoxel() copies.append(copy) diff --git a/fsl/fsleyes/displaycontext/volumeopts.py b/fsl/fsleyes/displaycontext/volumeopts.py index 76f29a39fe82c2e00fc87481a204d0d8110b25f1..97813021cc537c9bc8fa38853bf7eab4a4658929 100644 --- a/fsl/fsleyes/displaycontext/volumeopts.py +++ b/fsl/fsleyes/displaycontext/volumeopts.py @@ -387,6 +387,32 @@ class ImageOpts(fsldisplay.DisplayOpts): return coords + post + + def getVoxel(self): + """Calculates and returns the voxel coordinates corresponding to the + current :attr:`.DisplayContext.location` for the :class:`.Image` + associated with this ``ImageOpts`` instance. + + Returns ``None`` if the current location is outside of the image + bounds. + """ + + overlay = self.overlay + x, y, z = self.displayCtx.location.xyz + + vox = self.transformCoords([[x, y, z]], 'display', 'voxel')[0] + vox = map(int, np.round(vox)) + + 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 + + return vox + def displayToStandardCoordinates(self, coords): """Overrides :meth:`.DisplayOpts.displayToStandardCoordinates`. diff --git a/fsl/fsleyes/plotting/timeseries.py b/fsl/fsleyes/plotting/timeseries.py index 64c1631098279f21448f5f4de1bc096b6ba16545..c1eabb0625df1fcaa0c7a19de180ab991d42bfb4 100644 --- a/fsl/fsleyes/plotting/timeseries.py +++ b/fsl/fsleyes/plotting/timeseries.py @@ -120,7 +120,8 @@ class VoxelTimeSeries(TimeSeries): """ display = self.displayCtx.getDisplay(self.overlay) - coords = self.getVoxel() + opts = display.getDisplayOpts() + coords = opts.getVoxel() if coords is not None: return '{} [{} {} {}]'.format(display.name, @@ -130,33 +131,6 @@ class VoxelTimeSeries(TimeSeries): else: return '{} [out of bounds]'.format(display.name) - - def getVoxel(self): - """Calculates and returns the voxel coordinates corresponding to the - current :attr:`.DisplayContext.location` for the overlay associated - with this ``VoxelTimeSeries``. - - Returns ``None`` if the current location is outside of the image - bounds. - """ - - overlay = self.overlay - opts = self.displayCtx.getOpts(overlay) - x, y, z = self.displayCtx.location.xyz - - vox = opts.transformCoords([[x, y, z]], 'display', 'voxel')[0] - vox = map(int, np.round(vox)) - - 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 - - return vox - def getData(self, xdata=None, ydata=None): """Returns the data at the current voxel location. The ``xdata`` and @@ -165,7 +139,8 @@ class VoxelTimeSeries(TimeSeries): """ if ydata is None: - xyz = self.getVoxel() + opts = self.displayCtx.getOpts(self.overlay) + xyz = opts.getVoxel() if xyz is None: return [], [] @@ -566,8 +541,9 @@ class FEATPartialFitTimeSeries(VoxelTimeSeries): See the :meth:`.FEATImage.partialFit` method. """ - - coords = self.getVoxel() + opts = self.displayCtx.getOpts(self.overlay) + coords = opts.getVoxel() + if coords is None: return [], [] @@ -654,7 +630,9 @@ class FEATResidualTimeSeries(VoxelTimeSeries): def getData(self): """Returns the residuals for the current voxel. """ - voxel = self.getVoxel() + + opts = self.displayCtx.getOpts(self.overlay) + voxel = opts.getVoxel() if voxel is None: return [], [] @@ -734,7 +712,8 @@ class FEATModelFitTimeSeries(VoxelTimeSeries): def getData(self): """Returns the FEAT model fit at the current voxel. """ - voxel = self.getVoxel() + opts = self.displayCtx.getOpts(self.overlay) + voxel = opts.getVoxel() fitType = self.fitType contrast = self.contrast diff --git a/fsl/fsleyes/views/timeseriespanel.py b/fsl/fsleyes/views/timeseriespanel.py index d83221c9a1eee94064fc5a21b1cc3f4fa1f6351a..4dc88389572acc2399e1dd127258e8d689bffd31 100644 --- a/fsl/fsleyes/views/timeseriespanel.py +++ b/fsl/fsleyes/views/timeseriespanel.py @@ -225,9 +225,8 @@ class TimeSeriesPanel(plotpanel.PlotPanel): # etc), and a list of property names on each, # defining the properties that need to trigger a # redraw. - self.__currentTss = {} - self.__refreshProps = {} - self.__overlayColours = {} + self.__currentTss = {} + self.__refreshProps = {} def addPanels(): self.run('toggleTimeSeriesControl') @@ -327,7 +326,7 @@ class TimeSeriesPanel(plotpanel.PlotPanel): elif self.plotMode == 'percentChange': mean = ydata.mean() - ydata = 100 * (ydata / mean) - 100 + ydata = 100 * (ydata / mean) - 100 return xdata, ydata @@ -343,7 +342,7 @@ class TimeSeriesPanel(plotpanel.PlotPanel): cached internally. """ - for ds in self.dataSeries: + for ds in list(self.dataSeries): if ds.overlay not in self._overlayList: self.dataSeries.remove(ds) ds.destroy()