Skip to content
Snippets Groups Projects
Commit 66ea1688 authored by Paul McCarthy's avatar Paul McCarthy
Browse files

Refactorings to the TimeSeries/Histogram control panels to reduce code duplication.

parent ae8fd580
No related branches found
No related tags found
No related merge requests found
......@@ -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
......
......@@ -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)
......@@ -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)
......
......@@ -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`.
......
......@@ -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
......
......@@ -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()
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment