diff --git a/doc/images/canvassettingspanel.png b/doc/images/canvassettingspanel.png new file mode 100644 index 0000000000000000000000000000000000000000..6a2d17378c83635af6d022bd4fef4ded588b6d0d Binary files /dev/null and b/doc/images/canvassettingspanel.png differ diff --git a/doc/images/clusterpanel.png b/doc/images/clusterpanel.png new file mode 100644 index 0000000000000000000000000000000000000000..97188d1a4f62406cb97b52a4a00029c69a164975 Binary files /dev/null and b/doc/images/clusterpanel.png differ diff --git a/doc/images/histogramcontrolpanel.png b/doc/images/histogramcontrolpanel.png new file mode 100644 index 0000000000000000000000000000000000000000..f6ab2e0341e69cc8f004585edeac023e09f26fcf Binary files /dev/null and b/doc/images/histogramcontrolpanel.png differ diff --git a/doc/images/histogramlistpanel.png b/doc/images/histogramlistpanel.png new file mode 100644 index 0000000000000000000000000000000000000000..365f343b7a1ed20dda066ff4bdd23a5995b98c5c Binary files /dev/null and b/doc/images/histogramlistpanel.png differ diff --git a/doc/images/timeserieslistpanel.png b/doc/images/timeserieslistpanel.png new file mode 100644 index 0000000000000000000000000000000000000000..d60dedbb7873f796248eccb4a9e10753cc00b280 Binary files /dev/null and b/doc/images/timeserieslistpanel.png differ diff --git a/fsl/fsleyes/controls/canvassettingspanel.py b/fsl/fsleyes/controls/canvassettingspanel.py index af21df901d569bf7d9d4964cf41f76141d4246be..a4ac500b6f21971990cbcadc40829025687f472b 100644 --- a/fsl/fsleyes/controls/canvassettingspanel.py +++ b/fsl/fsleyes/controls/canvassettingspanel.py @@ -1,9 +1,13 @@ #!/usr/bin/env python # -# canvassettingspanel.py - +# canvassettingspanel.py - The CanvasSettingsPanel class. # # Author: Paul McCarthy <pauldmccarthy@gmail.com> # +"""This module provides the :class:`CanvasSettingsPanel` class, a *FSLeyes +control* panel which displays settings for a :class:`.CanvasPanel`. +""" + import wx @@ -16,59 +20,55 @@ import fsl.fsleyes.panel as fslpanel import fsl.fsleyes.tooltips as fsltooltips -_CANVASPANEL_PROPS = [ - props.Widget('syncOverlayOrder'), - props.Widget('syncLocation'), - props.Widget('syncOverlayDisplay'), - props.Widget('movieMode'), - props.Widget('movieRate', spin=False, showLimits=False), -] +class CanvasSettingsPanel(fslpanel.FSLEyesPanel): + """The ``CanvasSettingsPanel`` is a *FSLeyes control* which displays + settings for a :class:`.CanvasPanel` instance. A ``CanvasSettingsPanel`` + looks something like this: -_SCENEOPTS_PROPS = [ - props.Widget('showCursor'), - props.Widget('bgColour'), - props.Widget('cursorColour'), - props.Widget('performance', - spin=False, - showLimits=False, - labels=strings.choices['SceneOpts.performance']), - props.Widget('showColourBar'), - props.Widget('colourBarLabelSide', - labels=strings.choices['SceneOpts.colourBarLabelSide'], - enabledWhen=lambda o: o.showColourBar), - props.Widget('colourBarLocation', - labels=strings.choices['SceneOpts.colourBarLocation'], - enabledWhen=lambda o: o.showColourBar) -] + .. image:: images/canvassettingspanel.png + :scale: 50% + :align: center -_ORTHOOPTS_PROPS = [ - props.Widget('layout', labels=strings.choices['OrthoOpts.layout']), - props.Widget('zoom', spin=False, showLimits=False), - props.Widget('showLabels'), - props.Widget('showXCanvas'), - props.Widget('showYCanvas'), - props.Widget('showZCanvas') -] -_LIGHTBOXOPTS_PROPS = [ - props.Widget('zax', labels=strings.choices['CanvasOpts.zax']), - props.Widget('zoom', showLimits=False, spin=False), - props.Widget('sliceSpacing', showLimits=False), - props.Widget('zrange', showLimits=False), - props.Widget('highlightSlice'), - props.Widget('showGridLines') -] + The ``CanvasSettingsPanel`` displays controls which modify properties on + the following classes: + .. autosummary:: + :nosignatures: -class CanvasSettingsPanel(fslpanel.FSLEyesPanel): + ~fsl.fsleyes.views.CanvasPanel + ~fsl.fsleyes.displaycontext.SceneOpts + ~fsl.fsleyes.displaycontext.OrthoOpts + ~fsl.fsleyes.displaycontext.LightBoxOpts - def __init__(self, parent, overlayList, displayCtx, canvasPanel): + The ``CanvasSettingsPanel`` divides the displayed settings into those + which are common to all :class:`.CanvasPanel` instances, and those which + are specific to the :class:`.CanvasPanel` sub-class (i.e. + :class:`.OrthoPanel` or :class:`.LightBoxPanel`). The specific settings + that are displayed are defined in the following lists: + + .. autosummary:: + + _CANVASPANEL_PROPS + _SCENEOPTS_PROPS + _ORTHOOPTS_PROPS + _LIGHTBOXOPTS_PROPS + """ + + def __init__(self, parent, overlayList, displayCtx, canvasPanel): + """Create a ``CanvasSettingsPanel``. + + :arg parent: The :mod:`wx` parent object + :arg overlayList: The :class:`.OverlayList` instance. + :arg displayCtx: The :class:`.DisplayContext` instance. + :arg canvasPanel: The :class:`.CanvasPanel` instance. + """ + fslpanel.FSLEyesPanel.__init__(self, parent, overlayList, displayCtx) self.__widgets = widgetlist.WidgetList(self) - - self.__sizer = wx.BoxSizer(wx.VERTICAL) + self.__sizer = wx.BoxSizer(wx.VERTICAL) self.SetSizer(self.__sizer) @@ -133,3 +133,62 @@ class CanvasSettingsPanel(fslpanel.FSLEyesPanel): self.__widgets.Expand(panelGroup) self.SetMinSize((21, 21)) + + +_CANVASPANEL_PROPS = [ + props.Widget('syncOverlayOrder'), + props.Widget('syncLocation'), + props.Widget('syncOverlayDisplay'), + props.Widget('movieMode'), + props.Widget('movieRate', spin=False, showLimits=False), +] +"""A list of :class:`props.Widget` items defining controls to +display for :class:`.CanvasPanel` properties. +""" + + +_SCENEOPTS_PROPS = [ + props.Widget('showCursor'), + props.Widget('bgColour'), + props.Widget('cursorColour'), + props.Widget('performance', + spin=False, + showLimits=False, + labels=strings.choices['SceneOpts.performance']), + props.Widget('showColourBar'), + props.Widget('colourBarLabelSide', + labels=strings.choices['SceneOpts.colourBarLabelSide'], + enabledWhen=lambda o: o.showColourBar), + props.Widget('colourBarLocation', + labels=strings.choices['SceneOpts.colourBarLocation'], + enabledWhen=lambda o: o.showColourBar) +] +"""A list of :class:`props.Widget` items defining controls to +display for :class:`.SceneOpts` properties. +""" + + +_ORTHOOPTS_PROPS = [ + props.Widget('layout', labels=strings.choices['OrthoOpts.layout']), + props.Widget('zoom', spin=False, showLimits=False), + props.Widget('showLabels'), + props.Widget('showXCanvas'), + props.Widget('showYCanvas'), + props.Widget('showZCanvas') +] +"""A list of :class:`props.Widget` items defining controls to +display for :class:`.OrthoOpts` properties. +""" + + +_LIGHTBOXOPTS_PROPS = [ + props.Widget('zax', labels=strings.choices['CanvasOpts.zax']), + props.Widget('zoom', showLimits=False, spin=False), + props.Widget('sliceSpacing', showLimits=False), + props.Widget('zrange', showLimits=False), + props.Widget('highlightSlice'), + props.Widget('showGridLines') +] +"""A list of :class:`props.Widget` items defining controls to +display for :class:`.LightBoxOpts` properties. +""" diff --git a/fsl/fsleyes/controls/clusterpanel.py b/fsl/fsleyes/controls/clusterpanel.py index f9b9a0ccc6eaf7f81fbefed0e135a4b899573ae5..c6900e8cf432faab6852b2cd43d9415eb16e7b09 100644 --- a/fsl/fsleyes/controls/clusterpanel.py +++ b/fsl/fsleyes/controls/clusterpanel.py @@ -1,9 +1,12 @@ #!/usr/bin/env python # -# clusterpanel.py - +# clusterpanel.py - The ClusterPanel class. # # Author: Paul McCarthy <pauldmccarthy@gmail.com> # +"""This module provides the :class:`ClusterPanel` class, a *FSLeyes control* +panel for viewing cluster results from a FEAT analysis. +""" import logging import wx @@ -11,7 +14,6 @@ import wx import pwidgets.widgetgrid as widgetgrid import fsl.fsleyes.panel as fslpanel -import fsl.utils.transform as transform import fsl.utils.dialog as fsldlg import fsl.data.strings as strings import fsl.data.featimage as featimage @@ -21,8 +23,35 @@ log = logging.getLogger(__name__) class ClusterPanel(fslpanel.FSLEyesPanel): + """The ``ClusterPanel`` shows a table of cluster results from the analysis + associated with a :class:`.FEATImage` overlay. A ``ClusterPanel`` looks + something like the following: + + .. image:: images/clusterpanel.png + :scale: 50% + :align: center + + The ``ClusterPanel`` contains controls which allow the user to: + + - Select the COPE for which cluster results are displayed + + - Add a Z statistic overlay for the currently displayed COPE + + - Add a cluster mask overlay for the currently displayed COPE + + - Navigate to the Z maximum location, Z centre-of-gravity location, + or COPE maximum location, for a specific cluster. + """ def __init__(self, parent, overlayList, displayCtx): + """Create a ``ClusterPanel``. + + :arg parent: The :mod:`wx` parent object. + + :arg overlayList: The :class:`.OverlayList` instance. + + :arg displayCtx: The :class:`.DisplayContext` instance. + """ fslpanel.FSLEyesPanel.__init__(self, parent, overlayList, displayCtx) self.__disabledText = wx.StaticText( @@ -80,9 +109,19 @@ class ClusterPanel(fslpanel.FSLEyesPanel): self.__selectedOverlayChanged() + def destroy(self): + """Must be called when this ``ClusterPanel`` is no longer needed. + Removes some property listeners, and calls + :meth:`.FSLEyesPanel.destroy`. + """ + self._overlayList.removeListener('overlays', self._name) + self._displayCtx .removeListener('selectedOverlay', self._name) + fslpanel.FSLEyesPanel.destroy(self) + + def __calcMinSize(self): """Figures out the minimum size that this ``ClusterPanel`` should - have. + have. Called by :meth:`__init__`. When the ``ClusterPanel`` is created, the COPE combo box is not populated, so has no minimum size. Here, we figure out a good minimum @@ -101,22 +140,36 @@ class ClusterPanel(fslpanel.FSLEyesPanel): return self.__sizer.GetMinSize() - - def destroy(self): - self._overlayList.removeListener('overlays', self._name) - self._displayCtx .removeListener('selectedOverlay', self ._name) - fslpanel.FSLEyesPanel.destroy(self) - def __disable(self, message): + """Disables the ``ClusterPanel``, and displays the given message. + Called when the selected overlay is not a :class:`.FEATImage`, or + when cluster results cannot be displayed for some reason. + """ self.__disabledText.SetLabel(message) self.__sizer.Show(self.__disabledText, True) self.__sizer.Show(self.__mainSizer, False) self.Layout() + + def __statSelected(self, ev): + """Called when a COPE is selected. Clears the cluster table, and + displays clusters for the newly selected COPE (see the + :meth:`__displayClusterData` method) + """ + idx = self.__statSelect.GetSelection() + data = self.__statSelect.GetClientData(idx) + self.__displayClusterData(data) + self.__enableOverlayButtons() + def __addZStatsClick(self, ev): + """Called when the *Add Z statistics* button is pushed. Retrieves + the Z statistic image for the current COPE (see the + :meth:`.FEATImage.getZStats` method), and adds it as an overlay + to the :class:`.OverlayList`. + """ overlay = self.__selectedOverlay contrast = self.__statSelect.GetSelection() @@ -147,6 +200,11 @@ class ClusterPanel(fslpanel.FSLEyesPanel): def __addClustMaskClick(self, ev): + """Called when the *Add cluster mask* button is pushed. Retrieves the + cluster mask image for the currewnt contrast (see the + :meth:`.FEATImage.getClusterMask` method) + + """ overlay = self.__selectedOverlay contrast = self.__statSelect.GetSelection() mask = overlay.getClusterMask(contrast) @@ -161,13 +219,102 @@ class ClusterPanel(fslpanel.FSLEyesPanel): self._overlayList.append(mask) self._displayCtx.getDisplay(mask).overlayType = 'label' + + def __displayClusterData(self, clusters): + """Updates the cluster table so that it is displaying the given list + of clusters. + + :arg clusters: A sequence of objects, each representing one cluster. + See the :meth:`.FEATImage.clusterResults` method. + """ + + cols = {'index' : 0, + 'nvoxels' : 1, + 'p' : 2, + 'logp' : 3, + 'zmax' : 4, + 'zmaxcoords' : 5, + 'zcogcoords' : 6, + 'copemax' : 7, + 'copemaxcoords' : 8, + 'copemean' : 9} + + grid = self.__clusterList + overlay = self.__selectedOverlay + opts = self._displayCtx.getOpts(overlay) + + grid.ClearGrid() + grid.SetGridSize(len(clusters), 10) + + for col, i in cols.items(): + grid.SetColLabel(i, strings.labels[self, col]) + + def makeCoordButton(coords): + + label = wx.StaticText(grid, label='[{} {} {}]'.format(*coords)) + btn = wx.Button(grid, label=u'\u2192', style=wx.BU_EXACTFIT) + + sizer = wx.BoxSizer(wx.HORIZONTAL) + sizer.Add(label, flag=wx.EXPAND, proportion=1) + sizer.Add(btn) + + def onClick(ev): + dloc = opts.transformCoords([coords], 'voxel', 'display')[0] + self._displayCtx.location = dloc + + btn.Bind(wx.EVT_BUTTON, onClick) + + return sizer + + dlg = fsldlg.SimpleMessageDialog() + + dlg.Show() + + for i, clust in enumerate(clusters): + + dlg.SetMessage( + strings.messages[self, 'loadingCluster'].format(clust.index)) + + zmaxbtn = makeCoordButton((clust.zmaxx, + clust.zmaxy, + clust.zmaxz)) + zcogbtn = makeCoordButton((clust.zcogx, + clust.zcogy, + clust.zcogz)) + copemaxbtn = makeCoordButton((clust.copemaxx, + clust.copemaxy, + clust.copemaxz)) + + fmt = lambda v: '{}'.format(v) + grid.SetText( i, cols['index'], fmt(clust.index)) + grid.SetText( i, cols['nvoxels'], fmt(clust.nvoxels)) + grid.SetText( i, cols['p'], fmt(clust.p)) + grid.SetText( i, cols['logp'], fmt(clust.logp)) + grid.SetText( i, cols['zmax'], fmt(clust.zmax)) + grid.SetWidget(i, cols['zmaxcoords'], zmaxbtn) + grid.SetWidget(i, cols['zcogcoords'], zcogbtn) + grid.SetText( i, cols['copemax'], fmt(clust.copemax)) + grid.SetWidget(i, cols['copemaxcoords'], copemaxbtn) + grid.SetText( i, cols['copemean'], fmt(clust.copemean)) + + dlg.Close() + dlg.Destroy() + def __overlayListChanged(self, *a): + """Called when the :class:`.OverlayList` changes. Updates the *Add Z + statistic* and *Add cluster mask* buttons, in case the user removed + them. Also calls :meth:`__selectedOverlayChanged`. + """ self.__selectedOverlayChanged() self.__enableOverlayButtons() def __enableOverlayButtons(self): + """Enables/disables the *Add Z statistic* and *Add cluster mask* + buttons depending on whether the corresponding overlays are in the + :class:`.OverlayList`. + """ if self.__selectedOverlay is None: return @@ -185,6 +332,13 @@ class ClusterPanel(fslpanel.FSLEyesPanel): def __selectedOverlayChanged(self, *a): + """Called when the :attr:`.DisplayContext.selectedOverlay` changes, + and by the :meth:`__overlayListChanged` method. + + If the newly selected overlay is a :class:`.FEATImage` which has + cluster results, they are loaded in, and displayed on the cluster + table. + """ prevOverlay = self.__selectedOverlay self.__selectedOverlay = None @@ -255,85 +409,3 @@ class ClusterPanel(fslpanel.FSLEyesPanel): self.Layout() return - - - def __statSelected(self, ev): - idx = self.__statSelect.GetSelection() - data = self.__statSelect.GetClientData(idx) - self.__displayClusterData(data) - self.__enableOverlayButtons() - - - def __displayClusterData(self, clusters): - - cols = {'index' : 0, - 'nvoxels' : 1, - 'p' : 2, - 'logp' : 3, - 'zmax' : 4, - 'zmaxcoords' : 5, - 'zcogcoords' : 6, - 'copemax' : 7, - 'copemaxcoords' : 8, - 'copemean' : 9} - - grid = self.__clusterList - overlay = self.__selectedOverlay - opts = self._displayCtx.getOpts(overlay) - - grid.ClearGrid() - grid.SetGridSize(len(clusters), 10) - - for col, i in cols.items(): - grid.SetColLabel(i, strings.labels[self, col]) - - def makeCoordButton(coords): - - label = wx.StaticText(grid, label='[{} {} {}]'.format(*coords)) - btn = wx.Button(grid, label=u'\u2192', style=wx.BU_EXACTFIT) - - sizer = wx.BoxSizer(wx.HORIZONTAL) - sizer.Add(label, flag=wx.EXPAND, proportion=1) - sizer.Add(btn) - - def onClick(ev): - dloc = opts.transformCoords([coords], 'voxel', 'display')[0] - self._displayCtx.location = dloc - - btn.Bind(wx.EVT_BUTTON, onClick) - - return sizer - - dlg = fsldlg.SimpleMessageDialog() - - dlg.Show() - - for i, clust in enumerate(clusters): - - dlg.SetMessage( - strings.messages[self, 'loadingCluster'].format(clust.index)) - - zmaxbtn = makeCoordButton((clust.zmaxx, - clust.zmaxy, - clust.zmaxz)) - zcogbtn = makeCoordButton((clust.zcogx, - clust.zcogy, - clust.zcogz)) - copemaxbtn = makeCoordButton((clust.copemaxx, - clust.copemaxy, - clust.copemaxz)) - - fmt = lambda v: '{}'.format(v) - grid.SetText( i, cols['index'], fmt(clust.index)) - grid.SetText( i, cols['nvoxels'], fmt(clust.nvoxels)) - grid.SetText( i, cols['p'], fmt(clust.p)) - grid.SetText( i, cols['logp'], fmt(clust.logp)) - grid.SetText( i, cols['zmax'], fmt(clust.zmax)) - grid.SetWidget(i, cols['zmaxcoords'], zmaxbtn) - grid.SetWidget(i, cols['zcogcoords'], zcogbtn) - grid.SetText( i, cols['copemax'], fmt(clust.copemax)) - grid.SetWidget(i, cols['copemaxcoords'], copemaxbtn) - grid.SetText( i, cols['copemean'], fmt(clust.copemean)) - - dlg.Close() - dlg.Destroy() diff --git a/fsl/fsleyes/controls/histogramcontrolpanel.py b/fsl/fsleyes/controls/histogramcontrolpanel.py index 47446de99a5a9be2d2c24d94b8dbf70c9c21f7ad..c2f7ff6e2a5e010d32c08aebc4e31a42f7a3030c 100644 --- a/fsl/fsleyes/controls/histogramcontrolpanel.py +++ b/fsl/fsleyes/controls/histogramcontrolpanel.py @@ -1,9 +1,13 @@ #!/usr/bin/env python # -# histogramcontrolpanel.py - +# histogramcontrolpanel.py - The HistogramControlPanel class. # # Author: Paul McCarthy <pauldmccarthy@gmail.com> # +"""This module provides the :class:`HistogramControlPanel` class, a *FSLeyes +control* panel which allows a :class:`.HistogramPanel` to be configured. +""" + import wx @@ -16,8 +20,40 @@ import fsl.data.strings as strings class HistogramControlPanel(fslpanel.FSLEyesPanel): - + """The ``HistogramControlPanel`` is a *FSLeyes control* panel which + allows the user to configure a :class:`.HistogramPanel`. A + ``HistogramControlPanel`` looks something like the following: + + .. image:: images/histogramcontrolpanel.png + :scale: 50% + :align: center + + + The ``HistogramControlPanel`` divides its settings into three main + sections: + + - The *Histogram settings* section contains controls which modify + properties defined in the :class:`.HistogramPanel`. + + - *General plot settings* section contains controls which modify + properties defined in the :class:`.PlotPanel`. + + - The *Settings for the current histogram plot* section contains + controls which modify properties defined in the + :class:`.HistogramSeries` class, and affect the currently + selected plot in the :class:`.HistogramListPanel`. If no plots + have been added to the histogram list, this section is hidden. + """ + + def __init__(self, parent, overlayList, displayCtx, hsPanel): + """Create a ``HistogramControlPanel``. + + :arg parent: The :mod:`wx` parent object. + :arg overlayList: The :class:`.OverlayList` instance. + :arg displayCtx: The :class:`.DisplayContext` instance. + :arg hsPanel: The :class:`.HistogramPanel` instance. + """ fslpanel.FSLEyesPanel.__init__(self, parent, overlayList, displayCtx) @@ -127,6 +163,11 @@ class HistogramControlPanel(fslpanel.FSLEyesPanel): def destroy(self): + """Must be called when this ``HistogramControlPanel`` is no longer + needed. Removes some property listeners, and calls the + :meth:`.FSLEyesPanel.destroy` method. + """ + self.__hsPanel.removeListener('selectedSeries', self._name) self.__hsPanel.removeListener('dataSeries', self._name) if self.__currentHs is not None: @@ -136,6 +177,10 @@ class HistogramControlPanel(fslpanel.FSLEyesPanel): def __selectedSeriesChanged(self, *a): + """Called when the :attr:`.HistogramPanel.selectedSeries` property + changes. Updates the section containing settings for the current + histogram plot (see the :meth:`__updateCurrentProperties` method). + """ panel = self.__hsPanel @@ -149,6 +194,10 @@ class HistogramControlPanel(fslpanel.FSLEyesPanel): def __hsLabelChanged(self, *a): + """Called when the :attr:`.DataSeries.label` property, on the currently + selected :class:`.HistogramSeries`, changes. Updates the label for the + relevant settings section. + """ if self.__currentHs is None: return @@ -159,6 +208,9 @@ class HistogramControlPanel(fslpanel.FSLEyesPanel): def __updateCurrentProperties(self): + """Updates the settings shown in the section for the current histogram + plot. + """ expanded = False scrollPos = self.__widgets.GetViewStart() @@ -185,7 +237,7 @@ class HistogramControlPanel(fslpanel.FSLEyesPanel): self.__nbins = props.makeWidget(wlist, hs, 'nbins', showLimits=False) - volume = props.makeWidget(wlist, hs, 'volume', showLimits=False) + volume = props.makeWidget(wlist, hs, 'volume', showLimits=False) dataRange = props.makeWidget( wlist, hs, @@ -233,6 +285,10 @@ class HistogramControlPanel(fslpanel.FSLEyesPanel): def __autoBinChanged(self, *a): + """Called when the :attr:`.HistogramPanel.autoBin` setting changes. If + necessary, enables/disables the control which is bound to the + :attr:`.HistogramSeries.nbins` property for the current histogram plot. + """ if self.__currentHs is None or self.__nbins is None: return diff --git a/fsl/fsleyes/controls/histogramlistpanel.py b/fsl/fsleyes/controls/histogramlistpanel.py index f4a9bdec5bd043fd284490d70522520e47a502a5..ad23ffea053f2ccff21c82af8cffadd4680cba2f 100644 --- a/fsl/fsleyes/controls/histogramlistpanel.py +++ b/fsl/fsleyes/controls/histogramlistpanel.py @@ -1,9 +1,12 @@ #!/usr/bin/env python # -# histogramlistpanel.py - +# histogramlistpanel.py - The HistogramListPanel class. # # Author: Paul McCarthy <pauldmccarthy@gmail.com> # +"""This module provides the :class:`HistogramListPanel` class, which +is a *FSLeyes control* panel for use with :class:`.HistogramPanel` views. +""" import wx @@ -16,8 +19,34 @@ import timeserieslistpanel class HistogramListPanel(fslpanel.FSLEyesPanel): + """The ``HistogramListPanel`` is a control panel for use with the + :class:`.HistogramPanel` view. It allows the user to add/remove + :class:`.HistogramSeries` plots to/from the :class:`.HistogramPanel`, + and to configure the display settings for each. A ``HistogramListPanel`` + looks something like this: + .. image:: images/histogramlistpanel.png + :scale: 50% + :align: center + + + The ``HistogramListPanel`` performs the same task for the + :class:`.HistogramPanel` that the :class:`.TimeSeriesListPanel` does for + the :class:`.TimeSeriesPanel`. For each :class:`.HistogramSeries` that is + in the :attr:`.PlotPanel.dataSeries` list of the :class:`.HistogramPanel`, + a :class:`.TimeSeriesWidget` control is added to a + :class:`pwidgets.EditableListBox`. + """ + + def __init__(self, parent, overlayList, displayCtx, histPanel): + """Create a ``HistogramListPanel``. + + :arg parent: The :mod:`wx` parent object. + :arg overlayList: The :class:`.OverlayList` instance. + :arg displayCtx: The :class:`.DisplayContext` instance. + :arg histPanel: The :class:`.HistogramPanel` instance. + """ fslpanel.FSLEyesPanel.__init__(self, parent, overlayList, displayCtx) @@ -45,15 +74,26 @@ class HistogramListPanel(fslpanel.FSLEyesPanel): def destroy(self): + """Must be called when this ``HistogramListPanel`` is no longer needed. + Removes some property listeners, and calls the + :meth:`.FSLEyesPanel.destroy` method. + """ self.__hsPanel.removeListener('dataSeries', self._name) fslpanel.FSLEyesPanel.destroy(self) def getListBox(self): + """Returns the :class:`pwidgets.EditableListBox` instance contained + within this ``HistogramListPanel``. + """ return self.__hsList def __histSeriesChanged(self, *a): + """Called when the :attr:`.PlotPanel.dataSeries` list of the + :class:`.HistogramPanel` changes. Refreshes the contents of the + :class:`pwidgets.EditableListBox`. + """ self.__hsList.Clear() @@ -69,6 +109,10 @@ class HistogramListPanel(fslpanel.FSLEyesPanel): def __onListAdd(self, ev): + """Called when the user pushes the *add* button on the + :class:`pwidgets.EditableListBox`. Adds the *current* histogram plot + to the list (see the :class:`.HistogramPanel` class documentation). + """ hs = self.__hsPanel.getCurrent() if hs is None: @@ -85,16 +129,31 @@ class HistogramListPanel(fslpanel.FSLEyesPanel): def __onListEdit(self, ev): + """Called when the user edits a label on the + :class:`pwidgets.EditableListBox`. Updates the + :attr:`.DataSeries.label` property of the corresponding + :class:`.HistogramSeries` instance. + """ ev.data.label = ev.label def __onListSelect(self, ev): + """Called when the user selects an item in the + :class:`pwidgets.EditableListBox`. Sets the + :attr:`.DisplayContext.selectedOverlay` to the overlay associated with + the corresponding :class:`.HistogramSeries` instance. + """ overlay = ev.data.overlay self._displayCtx.selectedOverlay = self._overlayList.index(overlay) self.__hsPanel.selectedSeries = ev.idx def __onListRemove(self, ev): + """Called when the user removes an item from the + :class:`pwidgets.EditableListBox`. Removes the corresponding + :class:`.HistogramSeries` instance from the + :attr:`.PlotPanel.dataSeries` list of the :class:`.HistogramPanel`. + """ self.__hsPanel.dataSeries.remove(ev.data) self.__hsPanel.selectedSeries = self.__hsList.GetSelection() ev.data.destroy() diff --git a/fsl/fsleyes/controls/timeserieslistpanel.py b/fsl/fsleyes/controls/timeserieslistpanel.py index d39e6308d5d35397a767be152672d55df945ed5e..7d94536c8250dc3cf218abf57944568c7e1e3001 100644 --- a/fsl/fsleyes/controls/timeserieslistpanel.py +++ b/fsl/fsleyes/controls/timeserieslistpanel.py @@ -1,9 +1,14 @@ #!/usr/bin/env python # -# timeserieslistpanel.py - +# timeserieslistpanel.py - The TimeSeriesListPanel and TimeSeriesWidget +# classes. # # Author: Paul McCarthy <pauldmccarthy@gmail.com> # +"""This module provides the :class:`TimeSeriesListPanel` class, which is a +*FSLeyes control* panel for use with the :class:`.TimeSeriesPanel` view. +""" + import copy @@ -18,54 +23,42 @@ import fsl.data.strings as strings import fsl.fsleyes.colourmaps as fslcm -class TimeSeriesWidget(wx.Panel): - - def __init__(self, parent, timeSeries): - - wx.Panel.__init__(self, parent) - - self.colour = props.makeWidget(self, - timeSeries, - 'colour') - self.alpha = props.makeWidget(self, - timeSeries, - 'alpha', - slider=True, - spin=False, - showLimits=False) - self.lineWidth = props.makeWidget(self, - timeSeries, - 'lineWidth') - self.lineStyle = props.makeWidget( - self, - timeSeries, - 'lineStyle', - labels=strings.choices['DataSeries.lineStyle']) - - self.colour.SetToolTipString( - fsltooltips.properties[timeSeries, 'colour']) - self.alpha.SetToolTipString( - fsltooltips.properties[timeSeries, 'alpha']) - self.lineWidth.SetToolTipString( - fsltooltips.properties[timeSeries, 'lineWidth']) - self.lineStyle.SetToolTipString( - fsltooltips.properties[timeSeries, 'lineStyle']) - - self.sizer = wx.BoxSizer(wx.HORIZONTAL) - self.SetSizer(self.sizer) - - self.sizer.Add(self.colour) - self.sizer.Add(self.alpha) - self.sizer.Add(self.lineWidth) - self.sizer.Add(self.lineStyle) - - self.Layout() - - class TimeSeriesListPanel(fslpanel.FSLEyesPanel): + """The ``TimeSeriesListPanel`` is a control panel for use with the + :class:`.TimeSeriesPanel` view. It allows the user to add/remove + :class:`.TimeSeries` plots to/from the ``TimeSeriesPanel``, and to + configure the settings for each ``TimeSeries`` in the + :attr:`.PlotPanel.dataSeries` list. A ``TimeSeriesListPanel`` looks + something like the following: + + .. image:: images/timeserieslistpanel.png + :scale: 50% + :align: center + + + For every :class:`.TimeSeries` instance in the + :attr:`.PlotPanel.dataSeries` list of the :class:`.TimeSeriesPanel`, the + ``TimeSeriesListPanel`` creates a :class:`.TimeSeriesWidget`, which allows + the user to change the display settings of the :class:`.TimeSeries` + instance. A :class:`pwidgets.EditableListBox` is used to display the + labels for each :class:`.TimeSeries` instance, and the associated + :class:`.TimeSeriesWidget` controls. + + A label is also shown above the list, containing the name of the currently + selected overlay, and the voxel coordinates of the current + :attr:`.DisplayContext.location`. + """ + def __init__(self, parent, overlayList, displayCtx, timeSeriesPanel): + """Create a ``TimeSeriesListPanel``. + :arg parent: The :mod:`wx` parent object. + :arg overlayList: The :class:`.OverlayList` instance. + :arg displayCtx: The :class:`.DisplayContext` instance. + :arg timeSeriesPanel: The :class:`.TimeSeriesPanel` instance. + """ + fslpanel.FSLEyesPanel.__init__(self, parent, overlayList, displayCtx) self.__tsPanel = timeSeriesPanel @@ -104,6 +97,11 @@ class TimeSeriesListPanel(fslpanel.FSLEyesPanel): def destroy(self): + """Must be called when this ``TimeSeriesListPanel`` is no longer + needed. Removes some property listeners, and calls the + :meth:`.FSLEyesPanel.destroy` method. + """ + self._displayCtx .removeListener('selectedOverlay', self._name) self._displayCtx .removeListener('location', self._name) self._overlayList.removeListener('overlays', self._name) @@ -113,6 +111,8 @@ class TimeSeriesListPanel(fslpanel.FSLEyesPanel): def __makeLabel(self, ts): + """Creates a label to use for the given :class:`.TimeSeries` instance. + """ display = self._displayCtx.getDisplay(ts.overlay) @@ -123,6 +123,9 @@ class TimeSeriesListPanel(fslpanel.FSLEyesPanel): def __makeFEATModelTSLabel(self, parentTs, modelTs): + """Creates a label to use for the given :class:`.FEATTimeSeries` + instance. + """ import fsl.fsleyes.views.timeseriespanel as tsp @@ -157,6 +160,10 @@ class TimeSeriesListPanel(fslpanel.FSLEyesPanel): def __timeSeriesChanged(self, *a): + """Called when the :attr:`.PlotPanel.dataSeries` list of the + :class:`.TimeSeriesPanel` changes. Updates the list of + :class:`.TimeSeriesWidget` controls. + """ self.__tsList.Clear() @@ -170,17 +177,28 @@ class TimeSeriesListPanel(fslpanel.FSLEyesPanel): def __locationChanged(self, *a): + """Called when the :attr:`.DisplayContext.location` changes. + + Updates a label at the top of the ``TimeSeriesListPanel``, displaying + the currently selected overlay, and the current voxel location. + """ ts = self.__tsPanel.getCurrent() if ts is None: self.__currentLabel.SetLabel('') return - + self.__currentLabel.SetLabel(self.__makeLabel(ts)) def __onListAdd(self, ev): + """Called when the user pushes the *add* button on the + :class:`pwidgets.EditableListBox`. Adds the *current* time series + plot to the :attr:`.PlotPanel.dataSeries` list of the + :class:`.TimeSeriesPanel` (see the :class:`.TimeSeriesPanel` class + documentation). + """ import fsl.fsleyes.views.timeseriespanel as tsp @@ -215,10 +233,20 @@ class TimeSeriesListPanel(fslpanel.FSLEyesPanel): def __onListEdit(self, ev): + """Called when the user edits a label on the + :class:`pwidgets.EditableListBox`. Updates the + :attr:`.DataSeries.label` property of the corresponding + :class:`.TimeSeries` instance. + """ ev.data.label = ev.label def __onListSelect(self, ev): + """Called when the user selects an item in the + :class:`pwidgets.EditableListBox`. Sets the + :attr:`.DisplayContext.selectedOverlay` to the overlay associated with + the corresponding :class:`.TimeSeries` instance. + """ overlay = ev.data.overlay coords = ev.data.coords @@ -231,4 +259,68 @@ class TimeSeriesListPanel(fslpanel.FSLEyesPanel): def __onListRemove(self, ev): + """Called when the user removes an item from the + :class:`pwidgets.EditableListBox`. Removes the corresponding + :class:`.TimeSeries` instance from the :attr:`.PlotPanel.dataSeries` + list of the :class:`.TimeSeriesPanel`. + """ self.__tsPanel.dataSeries.remove(ev.data) + + +class TimeSeriesWidget(wx.Panel): + """The ``TimeSeriesWidget`` class is a panel which contains controls + that modify the properties of a :class:`.TimeSeries` instance. A + ``TimeSeriesWidget`` is created by the :class:`TimeSeriesListPanel` for + every ``TimeSeries`` in the :attr:`.TimeSeriesPanel.dataSeries` list. + + A ``TimeSeriesWidget`` may be created for instances of any sub-class + of :class:`.DataSeries`, not just the :class:`.TimeSeries` class. + """ + + + def __init__(self, parent, timeSeries): + """Create a ``TimeSeriesWidget``. + + :arg parent: The :mod:`wx` parent object. + + :arg timeSeries: The :class:`.DataSeries` instance. + """ + + wx.Panel.__init__(self, parent) + + self.__colour = props.makeWidget(self, + timeSeries, + 'colour') + self.__alpha = props.makeWidget(self, + timeSeries, + 'alpha', + slider=True, + spin=False, + showLimits=False) + self.__lineWidth = props.makeWidget(self, + timeSeries, + 'lineWidth') + self.__lineStyle = props.makeWidget( + self, + timeSeries, + 'lineStyle', + labels=strings.choices['DataSeries.lineStyle']) + + self.__colour.SetToolTipString( + fsltooltips.properties[timeSeries, 'colour']) + self.__alpha.SetToolTipString( + fsltooltips.properties[timeSeries, 'alpha']) + self.__lineWidth.SetToolTipString( + fsltooltips.properties[timeSeries, 'lineWidth']) + self.__lineStyle.SetToolTipString( + fsltooltips.properties[timeSeries, 'lineStyle']) + + self.__sizer = wx.BoxSizer(wx.HORIZONTAL) + self.SetSizer(self.__sizer) + + self.__sizer.Add(self.__colour) + self.__sizer.Add(self.__alpha) + self.__sizer.Add(self.__lineWidth) + self.__sizer.Add(self.__lineStyle) + + self.Layout()