diff --git a/fsl/data/melodicimage.py b/fsl/data/melodicimage.py index 3a2087da9a028717e832374be773ff89d3d07dfc..f3b628810e332213a8ec3487a71773c983063b80 100644 --- a/fsl/data/melodicimage.py +++ b/fsl/data/melodicimage.py @@ -26,10 +26,13 @@ class MelodicImage(fslimage.Image): The ``MelodicImage`` class provides a few MELODIC-specific attributes and methods: + .. autosummary:: + :nosignatures: tr getComponentTimeSeries + getComponentPowerSpectrum numComponents getTopLevelAnalysisDir getDataFile @@ -76,8 +79,9 @@ class MelodicImage(fslimage.Image): *args, **kwargs) - self.__meldir = dirname - self.__melmix = melresults.getComponentTimeSeries(dirname) + self.__meldir = dirname + self.__melmix = melresults.getComponentTimeSeries( dirname) + self.__melFTmix = melresults.getComponentPowerSpectra(dirname) # Automatically set the # TR value if possible @@ -93,6 +97,13 @@ class MelodicImage(fslimage.Image): """Returns the time course for the specified (0-indexed) component. """ return self.__melmix[:, component] + + def getComponentPowerSpectrum(self, component): + """Returns the power spectrum for the time course of the specified + (0-indexed) component. + """ + return self.__melFTmix[:, component] + def numComponents(self): """Returns the number of components in this ``MelodicImage``. """ diff --git a/fsl/data/melodicresults.py b/fsl/data/melodicresults.py index c31811723bd78e2ce8036072d3c5d3f0e19734d5..105c1ae28aa31dbbbd7f6ae5064dc033aa007dce 100644 --- a/fsl/data/melodicresults.py +++ b/fsl/data/melodicresults.py @@ -10,8 +10,9 @@ MELODIC analysis directory. These functions are primarily intended to be used by the :class:`.MELODICImage` class, but are available for other uses. The following functions are provided: + .. autosummary:: - nosignatures: + :nosignatures: isMelodicDir getMelodicDir @@ -19,8 +20,10 @@ following functions are provided: getDataFile getICFile getMixFile + getFTMixFile getNumComponents getComponentTimeSeries + getComponentPowerSpectra """ @@ -74,9 +77,10 @@ def getMelodicDir(path): except ValueError: return None - # Must contain a file called melodic_mix - if not op.exists(op.join(path, 'melodic_mix')): - return None + # Must contain files called + # melodic_mix and melodic_FTmix + if not op.exists(op.join(path, 'melodic_mix')): return None + if not op.exists(op.join(path, 'melodic_FTmix')): return None return path @@ -142,6 +146,11 @@ def getMixFile(meldir): return op.join(meldir, 'melodic_mix') +def getFTMixFile(meldir): + """Returns the path to the melodic FT mix file. """ + return op.join(meldir, 'melodic_FTmix') + + def getReportFile(meldir): pass @@ -162,3 +171,11 @@ def getComponentTimeSeries(meldir): mixfile = getMixFile(meldir) return np.loadtxt(mixfile) + + +def getComponentPowerSpectra(meldir): + """Returns a ``numpy`` array containing the melodic FT mix for the + given directory. + """ + ftmixfile = getFTMixFile(meldir) + return np.loadtxt(ftmixfile) diff --git a/fsl/data/strings.py b/fsl/data/strings.py index a3846b2c83f02d6e3ac6394ebd5567a5fcff6ce7..8bd5e9353a88316f071568311df5eb672fa77d33 100644 --- a/fsl/data/strings.py +++ b/fsl/data/strings.py @@ -137,10 +137,11 @@ titles = TypeDict({ 'overlay.loadOverlays.error' : 'Error loading overlay', - 'OrthoPanel' : 'Ortho View', - 'LightBoxPanel' : 'Lightbox View', - 'TimeSeriesPanel' : 'Time series', - 'HistogramPanel' : 'Histogram', + 'OrthoPanel' : 'Ortho View', + 'LightBoxPanel' : 'Lightbox View', + 'TimeSeriesPanel' : 'Time series', + 'PowerSpectrumPanel' : 'Power spectra', + 'HistogramPanel' : 'Histogram', 'CanvasPanel.screenshot' : 'Save screenshot', 'CanvasPanel.screenshot.notSaved' : 'Save overlay before continuing', @@ -151,25 +152,26 @@ titles = TypeDict({ 'AtlasInfoPanel' : 'Atlas information', 'AtlasOverlayPanel' : 'Atlas overlays', - 'OverlayListPanel' : 'Overlay list', - 'AtlasPanel' : 'Atlases', - 'LocationPanel' : 'Location', - 'OverlayDisplayToolBar' : 'Display toolbar', - 'CanvasSettingsPanel' : 'View settings', - 'OverlayDisplayPanel' : 'Display settings', - 'OrthoToolBar' : 'Ortho view toolbar', - 'OrthoEditToolBar' : 'Ortho view edit toolbar', - 'LightBoxToolBar' : 'Lightbox view toolbar', - 'LookupTablePanel' : 'Lookup tables', - 'LutLabelDialog' : 'New LUT label', - 'NewLutDialog' : 'New LUT', - 'TimeSeriesListPanel' : 'Time series list', - 'TimeSeriesControlPanel' : 'Time series control', - 'HistogramListPanel' : 'Histogram list', - 'HistogramControlPanel' : 'Histogram control', - 'ClusterPanel' : 'Cluster browser', - 'OverlayInfoPanel' : 'Overlay information', - 'ShellPanel' : 'Python shell', + 'OverlayListPanel' : 'Overlay list', + 'AtlasPanel' : 'Atlases', + 'LocationPanel' : 'Location', + 'OverlayDisplayToolBar' : 'Display toolbar', + 'CanvasSettingsPanel' : 'View settings', + 'OverlayDisplayPanel' : 'Display settings', + 'OrthoToolBar' : 'Ortho view toolbar', + 'OrthoEditToolBar' : 'Ortho view edit toolbar', + 'LightBoxToolBar' : 'Lightbox view toolbar', + 'LookupTablePanel' : 'Lookup tables', + 'LutLabelDialog' : 'New LUT label', + 'NewLutDialog' : 'New LUT', + 'TimeSeriesListPanel' : 'Time series list', + 'TimeSeriesControlPanel' : 'Time series control', + 'HistogramListPanel' : 'Histogram list', + 'HistogramControlPanel' : 'Histogram control', + 'PowerSpectrumControlPanel' : 'Power spectrum control', + 'ClusterPanel' : 'Cluster browser', + 'OverlayInfoPanel' : 'Overlay information', + 'ShellPanel' : 'Python shell', 'LookupTablePanel.loadLut' : 'Select a lookup table file', 'LookupTablePanel.labelExists' : 'Label already exists', @@ -207,11 +209,13 @@ actions = TypeDict({ 'LightBoxPanel.toggleLightBoxToolBar' : 'View properties', - 'PlotPanel.screenshot' : 'Take screenshot', - 'TimeSeriesPanel.toggleTimeSeriesList' : 'Time series list', - 'TimeSeriesPanel.toggleTimeSeriesControl' : 'Time series control', - 'HistogramPanel.toggleHistogramList' : 'Histogram list', - 'HistogramPanel.toggleHistogramControl' : 'Histogram control', + 'PlotPanel.screenshot' : 'Take screenshot', + 'TimeSeriesPanel.toggleTimeSeriesList' : 'Time series list', + 'TimeSeriesPanel.toggleTimeSeriesControl' : 'Time series control', + 'HistogramPanel.toggleHistogramList' : 'Histogram list', + 'HistogramPanel.toggleHistogramControl' : 'Histogram control', + 'PowerSpectrumPanel.togglePowerSpectrumControl' : 'Power spectrum ' + 'control', 'OrthoViewProfile.centreCursor' : 'Centre cursor', 'OrthoViewProfile.resetZoom' : 'Reset zoom', @@ -279,6 +283,8 @@ labels = TypeDict({ 'TimeSeriesControlPanel.currentFEATSettings' : 'FEAT settings for ' 'selected overlay ({})', + 'PowerSpectrumControlPanel.psSettings' : 'Power spectrum plot settings', + 'TimeSeriesListPanel.featReduced' : 'Reduced against {}', 'FEATModelFitTimeSeries.full' : 'Full model fit', @@ -398,6 +404,11 @@ properties = TypeDict({ 'HistogramPanel.autoBin' : 'Automatic histogram binning', 'HistogramPanel.showCurrent' : 'Plot histogram for current overlay', + 'PowerSpectrumPanel.showMode' : 'Power spectra to plot', + 'PowerSpectrumPanel.plotFrequencies' : 'Show frequencies along x axis ', + 'PowerSpectrumPanel.plotMelodicICs' : 'Plot component power spectra for ' + 'Melodic images', + 'DataSeries.colour' : 'Colour', 'DataSeries.alpha' : 'Line transparency', 'DataSeries.lineWidth' : 'Line width', @@ -556,7 +567,14 @@ choices = TypeDict({ 'all' : 'Show the time series ' 'for all overlays', 'none' : 'Only show the time series ' - 'in the time series list'} + 'in the time series list'}, + 'PowerSpectrumPanel.showMode' : {'current' : 'Show the power spectrum for ' + 'the currently selected ' + 'overlay', + 'all' : 'Show the power spectra ' + 'for all overlays', + 'none' : 'Only show the power spectra ' + 'in the power spectra list'} }) diff --git a/fsl/fsleyes/controls/powerspectrumcontrolpanel.py b/fsl/fsleyes/controls/powerspectrumcontrolpanel.py new file mode 100644 index 0000000000000000000000000000000000000000..3e7afb1bcde555b3d0a159697bc96d0d89b932a5 --- /dev/null +++ b/fsl/fsleyes/controls/powerspectrumcontrolpanel.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# +# powerspectrumcontrolpanel.py - The PowerSpectrumControlPanel class. +# +# Author: Paul McCarthy <pauldmccarthy@gmail.com> +# +"""This module provides the :class:`PowerSpectrumControlPanel` class. +""" + + +import wx + +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 timeseriescontrolpanel as tscontrol + + +class PowerSpectrumControlPanel(fslpanel.FSLEyesPanel): + + def __init__(self, parent, overlayList, displayCtx, psPanel): + + fslpanel.FSLEyesPanel.__init__(self, parent, overlayList, displayCtx) + + self.__psPanel = psPanel + self.__widgets = widgetlist.WidgetList(self) + self.__sizer = wx.BoxSizer(wx.VERTICAL) + + self.SetSizer(self.__sizer) + self.__sizer.Add(self.__widgets, flag=wx.EXPAND, proportion=1) + + psProps = ['showMode', + 'plotFrequencies', + 'plotMelodicICs'] + + self.__widgets.AddGroup( + 'psSettings', strings.labels[self, 'psSettings']) + + for prop in psProps: + + kwargs = {} + + if prop == 'showMode': + kwargs['labels'] = strings.choices[psPanel, 'showMode'] + + widget = props.makeWidget(self.__widgets, psPanel, prop, **kwargs) + + self.__widgets.AddWidget( + widget, + displayName=strings.properties[psPanel, prop], + tooltip=fsltooltips.properties[psPanel, prop], + groupName='psSettings') + + self.__widgets.AddGroup( + 'plotSettings', + strings.labels[psPanel, 'plotSettings']) + + tscontrol.generatePlotPanelWidgets(psPanel, + self.__widgets, + 'plotSettings') diff --git a/fsl/fsleyes/plotting/__init__.py b/fsl/fsleyes/plotting/__init__.py index 78101338500a40e1c4257931ceed46cc02cbf97a..4090111ce94950fe5f402657f2d7dcf5e6bc6e04 100644 --- a/fsl/fsleyes/plotting/__init__.py +++ b/fsl/fsleyes/plotting/__init__.py @@ -12,14 +12,16 @@ for plotting data. import dataseries import timeseries import histogramseries +import powerspectrumseries -DataSeries = dataseries.DataSeries -TimeSeries = timeseries.TimeSeries -VoxelTimeSeries = timeseries.VoxelTimeSeries -FEATTimeSeries = timeseries.FEATTimeSeries -FEATPartialFitTimeSeries = timeseries.FEATPartialFitTimeSeries -FEATEVTimeSeries = timeseries.FEATEVTimeSeries -FEATResidualTimeSeries = timeseries.FEATResidualTimeSeries -FEATModelFitTimeSeries = timeseries.FEATModelFitTimeSeries -MelodicTimeSeries = timeseries.MelodicTimeSeries -HistogramSeries = histogramseries.HistogramSeries +DataSeries = dataseries .DataSeries +TimeSeries = timeseries .TimeSeries +VoxelTimeSeries = timeseries .VoxelTimeSeries +FEATTimeSeries = timeseries .FEATTimeSeries +FEATPartialFitTimeSeries = timeseries .FEATPartialFitTimeSeries +FEATEVTimeSeries = timeseries .FEATEVTimeSeries +FEATResidualTimeSeries = timeseries .FEATResidualTimeSeries +FEATModelFitTimeSeries = timeseries .FEATModelFitTimeSeries +MelodicTimeSeries = timeseries .MelodicTimeSeries +HistogramSeries = histogramseries .HistogramSeries +PowerSpectrumSeries = powerspectrumseries.PowerSpectrumSeries diff --git a/fsl/fsleyes/plotting/powerspectrumseries.py b/fsl/fsleyes/plotting/powerspectrumseries.py new file mode 100644 index 0000000000000000000000000000000000000000..1ce6b4789dc0c3c919bf247a34cae390e780c647 --- /dev/null +++ b/fsl/fsleyes/plotting/powerspectrumseries.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python +# +# powerspectrumseries.py - Classes used by the PowerSpectrumPanel. +# +# Author: Paul McCarthy <pauldmccarthy@gmail.com> +# +"""This module provides :class:`.DataSeries` sub-classes which are used +by the :class:`.PowerSpectrumPanel` for plotting power spectra. + +The following classes are provided: + +.. autosummary:: + :nosignature: + + PowerSpectrumSeries + VoxelPowerSpectrumSeries + MelodicPowerSpectrumSeries +""" + +import logging + +import numpy as np +import numpy.fft as fft + +import props + +import dataseries + + +log = logging.getLogger(__name__) + + +class PowerSpectrumSeries(dataseries.DataSeries): + """ + """ + + + varNorm = props.Boolean(default=True) + """Normalise data to unit variance before fourier transformation. """ + + + def __init__(self, overlay, displayCtx): + """ + """ + dataseries.DataSeries.__init__(self, overlay) + self.displayCtx = displayCtx + + + def destroy(self): + """ + """ + self.displayCtx = None + dataseries.DataSeries.destroy(self) + + + def makeLabel(self): + """ + """ + display = self.displayCtx.getDisplay(self.overlay) + return display.name + + + def calcPowerSpectrum(self, data): + if self.varNorm: + data = data - data.mean() + data = data / data.std() + + data = fft.rfft(data)[1:] + data = np.power(data.real, 2) + np.power(data.imag, 2) + + return data + + +class VoxelPowerSpectrumSeries(PowerSpectrumSeries): + + def makeLabel(self): + """ + """ + + display = self.displayCtx.getDisplay(self.overlay) + opts = display.getDisplayOpts() + coords = opts.getVoxel() + + if coords is not None: + return '{} [{} {} {}]'.format(display.name, + coords[0], + coords[1], + coords[2]) + else: + return '{} [out of bounds]'.format(display.name) + + + def getData(self): + """ + """ + + opts = self.displayCtx.getOpts(self.overlay) + voxel = opts.getVoxel() + + if voxel is None: + return [], [] + + x, y, z = voxel + + ydata = self.overlay.data[x, y, z, :] + ydata = self.calcPowerSpectrum(ydata) + xdata = np.arange(len(ydata), dtype=np.float32) + + return xdata, ydata + + +class MelodicPowerSpectrumSeries(PowerSpectrumSeries): + """ + """ + + def makeLabel(self): + """ + """ + display = self.displayCtx.getDisplay(self.overlay) + opts = display.getDisplayOpts() + component = opts.volume + + return '{} [component {}]'.format(display.name, component) + + + def getData(self): + """ + """ + + opts = self.displayCtx.getOpts(self.overlay) + component = opts.volume + + ydata = self.overlay.getComponentPowerSpectrum(component) + xdata = np.arange(len(ydata), dtype=np.float32) + + return xdata, ydata diff --git a/fsl/fsleyes/tooltips.py b/fsl/fsleyes/tooltips.py index ca71b292efc7c2602aba80d5d462e256d0a2328e..c70cf46012228d0ddd34488886c6d7a6a242e8a5 100644 --- a/fsl/fsleyes/tooltips.py +++ b/fsl/fsleyes/tooltips.py @@ -292,6 +292,23 @@ properties = TypeDict({ 'HistogramPanel.histType' : 'Show histogram data as raw counts, or ' 'as probabilities.', + 'PowerSpectrumPanel.plotFrequencies' : 'If checked, the x values ' + 'are transformed into frequency ' + 'values.', + 'PowerSpectrumPanel.plotMelodicICs' : 'If checked, the component power ' + 'spectra are plotted for Melodic ' + 'images. If not checked, Melodic ' + 'images are treated as regular 4D ' + 'images.', + 'PowerSpectrumPanel.showMode' : 'Choose which power spectra to ' + 'plot - you can choose to plot ' + 'the power spectrum for the ' + 'currently selected overlay, the ' + 'power spectra for all compatible ' + 'overlays, or just those that ' + 'have been added to the power ' + 'spectra list.', + # DataSeries 'DataSeries.colour' : 'Line colour.', diff --git a/fsl/fsleyes/views/__init__.py b/fsl/fsleyes/views/__init__.py index 23f14280190570e7e286fbabd355c26e05f42ab1..015e1538e0986409485d356ed519d6ade29b6d60 100644 --- a/fsl/fsleyes/views/__init__.py +++ b/fsl/fsleyes/views/__init__.py @@ -24,6 +24,7 @@ allow dynamic lookup of all :class:`.ViewPanel` sub-classes. The following ~fsl.fsleyes.views.lightboxpanel.LightBoxPanel ~fsl.fsleyes.views.plotpanel.PlotPanel ~fsl.fsleyes.views.timeseriespanel.TimeSeriesPanel + ~fsl.fsleyes.views.timeseriespanel.PowerSpectrumPanel ~fsl.fsleyes.views.histogrampanel.HistogramPanel """ @@ -36,17 +37,19 @@ import canvaspanel import orthopanel import lightboxpanel import timeseriespanel +import powerspectrumpanel import histogrampanel -FSLEyesPanel = fslpanel .FSLEyesPanel -ViewPanel = viewpanel .ViewPanel -PlotPanel = plotpanel .PlotPanel -CanvasPanel = canvaspanel .CanvasPanel -OrthoPanel = orthopanel .OrthoPanel -LightBoxPanel = lightboxpanel .LightBoxPanel -TimeSeriesPanel = timeseriespanel.TimeSeriesPanel -HistogramPanel = histogrampanel .HistogramPanel +FSLEyesPanel = fslpanel .FSLEyesPanel +ViewPanel = viewpanel .ViewPanel +PlotPanel = plotpanel .PlotPanel +CanvasPanel = canvaspanel .CanvasPanel +OrthoPanel = orthopanel .OrthoPanel +LightBoxPanel = lightboxpanel .LightBoxPanel +TimeSeriesPanel = timeseriespanel .TimeSeriesPanel +PowerSpectrumPanel = powerspectrumpanel.PowerSpectrumPanel +HistogramPanel = histogrampanel .HistogramPanel def listViewPanels(): diff --git a/fsl/fsleyes/views/powerspectrumpanel.py b/fsl/fsleyes/views/powerspectrumpanel.py new file mode 100644 index 0000000000000000000000000000000000000000..85dc68282ceadea6a109a4353985e949dcd3fc91 --- /dev/null +++ b/fsl/fsleyes/views/powerspectrumpanel.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python +# +# powerspectrumpanel.py - The PowerSpectrumPanel class. +# +# Author: Paul McCarthy <pauldmccarthy@gmail.com> +# +"""This module provides the :class:`PowerSpectrumPanel` class, a +:class:`.ViewPanel` which plots frequency/power spectra. +""" + + +import logging + +import wx + +import numpy as np + +import props + +import plotpanel +import fsl.fsleyes.plotting.powerspectrumseries as psseries +import fsl.fsleyes.controls.powerspectrumcontrolpanel as pscontrol +import fsl.fsleyes.colourmaps as fslcm +import fsl.data.image as fslimage +import fsl.data.melodicimage as fslmelimage + + +log = logging.getLogger(__name__) + + +class PowerSpectrumPanel(plotpanel.PlotPanel): + """The ``PowerSpectrumPanel`` class is a :class:`.PlotPanel` which plots + power spectra of overlay data. + """ + + + plotMelodicICs = props.Boolean(default=True) + """ + """ + + + plotFrequencies = props.Boolean(default=True) + """ + """ + + + showMode = props.Choice(('current', 'all', 'none')) + """ + """ + + + def __init__(self, parent, overlayList, displayCtx): + """ + """ + + actionz = { + 'togglePowerSpectrumControl' : lambda *a: self.togglePanel( + pscontrol.PowerSpectrumControlPanel, + self, + location=wx.TOP) + } + + plotpanel.PlotPanel.__init__(self, + parent, + overlayList, + displayCtx, + actionz=actionz) + + figure = self.getFigure() + + figure.subplots_adjust( + top=1.0, bottom=0.0, left=0.0, right=1.0) + + figure.patch.set_visible(False) + + # A dictionary of + # + # {overlay : PowerSpectrumSeries} + # + # instances, one for each (compatible) + # overlay in the overlay list + self.__spectra = {} + self.__refreshProps = {} + + overlayList.addListener('overlays', + self._name, + self.__overlayListChanged) + + displayCtx .addListener('selectedOverlay', + self._name, + self.__selectedOverlayChanged) + + self.__overlayListChanged() + + + def destroy(self): + self._overlayList.removeListener('overlays', self._name) + self._displayCtx .removeListener('selectedOverlay', self._name) + + plotpanel.PlotPanel.destroy(self) + + + def draw(self, *a): + + if self.showMode == 'all': + overlays = self._overlayList[:] + elif self.showMode == 'current': + overlays = [self._displayCtx.getSelectedOverlay()] + else: + overlays = [] + + pss = [self.__spectra.get(o) for o in overlays] + pss = [ps for ps in pss if ps is not None] + + self.drawDataSeries(extraSeries=pss, + preproc=self.__prepareSpectrumData) + + + def __overlayListChanged(self, *a): + + # Destroy any spectrum series for overlays + # that have been removed from the list + for ds in list(self.dataSeries): + if ds.overlay not in self._overlayList: + self.dataSeries.remove(ds) + ds.destroy() + + for overlay, ds in list(self.__spectra.items()): + if overlay not in self._overlayList: + self.__spectra.pop(overlay) + ds.destroy() + + # Create a new spectrum series for overlays + # which have been added to the list + for overlay in self._overlayList: + + ss = self.__spectra.get(overlay) + + if ss is None: + + ss, targets, propNames = self.__createSpectrumSeries(overlay) + + if ss is None: + continue + + self.__spectra[ overlay] = ss + self.__refreshProps[overlay] = targets, propNames + + ss.addGlobalListener(self._name, self.draw, overwrite=True) + + for targets, propNames in self.__refreshProps.values(): + for t, p in zip(targets, propNames): + t.addListener(p, self._name, self.draw, overwrite=True) + + self.draw() + + + def __selectedOverlayChanged(self, *a): + self.draw() + + + def __prepareSpectrumData(self, ps): + + xdata, ydata = ps.getData() + + if self.plotFrequencies: + + nsamples = len(ydata) + sampleTime = 1 + + if isinstance(ps.overlay, fslmelimage.MelodicImage): + sampleTime = ps.overlay.tr + elif isinstance(ps.overlay, fslimage.Image): + sampleTime = ps.overlay.pixdim[3] + + freqStep = 1.0 / (2 * nsamples * sampleTime) + xdata = np.arange(0.0, nsamples * freqStep, freqStep) + + return xdata, ydata + + + def __createSpectrumSeries(self, overlay): + + if self.plotMelodicICs and \ + isinstance(overlay, fslmelimage.MelodicImage): + + ps = psseries.MelodicPowerSpectrumSeries(overlay, + self._displayCtx) + targets = [self._displayCtx.getOpts(overlay)] + propNames = ['volume'] + + elif isinstance(overlay, fslimage.Image): + + ps = psseries.VoxelPowerSpectrumSeries(overlay, + self._displayCtx) + targets = [self._displayCtx] + propNames = ['location'] + + else: + return None, [], [] + + ps.colour = fslcm.randomDarkColour() + ps.alpha = 1.0 + ps.lineWidth = 1 + ps.lineStyle = '-' + + return ps, targets, propNames