Source code for fsleyes.views.powerspectrumpanel

#
# 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 numpy as np

import fsl.data.image                       as fslimage
import fsl.data.mesh                        as fslmesh
import fsl.data.melodicimage                as fslmelimage
import fsleyes_props                        as props

import fsleyes.actions                      as actions
import fsleyes.views.plotpanel              as plotpanel
import fsleyes.profiles.plotprofile         as plotprofile
import fsleyes.plotting.powerspectrumseries as psseries


log = logging.getLogger(__name__)


[docs]class PowerSpectrumPanel(plotpanel.OverlayPlotPanel): """The ``PowerSpectrumPanel`` class is an :class:`.OverlayPlotPanel` which plots power spectra of overlay data. ``PowerSpectrumPanel`` uses :class:`.PowerSpectrumSeries` to plot the power spectra of overlay data. A couple of control panels may be shown on a ``PowerSpectrumPanel`` via :meth:`.ViewPanel.togglePanel`. .. autosummary:: :nosignatures: ~fsleyes.controls.plotlistpanel.PlotListPanel ~fsleyes.controls.powerspectrumcontrolpanel.PowerSpectrumControlPanel **Melodic images** The :class:`.PowerSpectrumSeries` class uses a fourier transform to calculate the power spectrum of a time course. However, :class:`.MelodicImage` overlays already have an associated power spectrum, meaning that there is no need to calculate one for them.. So for these overlays, a :class:`.MelodicPowerSpectrumSeries` instance is used. """ plotMelodicICs = props.Boolean(default=True) """If ``True``, the power spectra of :class:`.MelodicImage` overlays are plotted using :class:`.MelodicPowerSpectrumSeries` instances. Otherwise, :class:`.MelodicImage` overlays are treated as regular :class:`.Image` overlays, and :class:`.VoxelPowerSpectrumSeries` are used for plotting. """ plotFrequencies = props.Boolean(default=True) """If ``True``, the x axis is scaled so that it represents frequency. """
[docs] @staticmethod def defaultLayout(): """Returns a list of control panel types to be added for the default power spectrum panel layout. """ return ['PowerSpectrumToolBar', 'OverlayListPanel', 'PlotListPanel']
[docs] @staticmethod def controlOrder(): """Returns a list of control panel names, specifying the order in which they should appear in the FSLeyes ortho panel settings menu. """ return ['OverlayListPanel', 'PlotListPanel', 'PowerSpectrumToolBar', 'PowerSpectrumControlPanel']
[docs] def __init__(self, parent, overlayList, displayCtx, frame): """Create a ``PowerSpectrumPanel``. :arg parent: The :mod:`wx` parent object. :arg overlayList: The :class:`.OverlayList`. :arg displayCtx: The :class:`.DisplayContext`. :arg frame: The :class:`.FSLeyesFrame`. """ plotpanel.OverlayPlotPanel.__init__(self, parent, overlayList, displayCtx, frame) self.addListener('plotFrequencies', self.name, self.draw) self.addListener('plotMelodicICs', self.name, self.__plotMelodicICsChanged) self.initProfile(plotprofile.PlotProfile)
[docs] def destroy(self): """Must be called when this ``PowerSpectrumPanel`` is no longer needed. Removes some property listeners, and calls :meth:`.OverlayPlotPanel.destroy`. """ self.removeListener('plotFrequencies', self.name) self.removeListener('plotMelodicICs', self.name) plotpanel.OverlayPlotPanel.destroy(self)
[docs] def getActions(self): """Overrides :meth:`.ActionProvider.getActions`. Returns all of the :mod:`.actions` that are defined on this ``PowerSpectrumPanel``. """ actionz = [self.screenshot, self.importDataSeries, self.exportDataSeries] names = [a.actionName if a is not None else None for a in actionz] return list(zip(names, actionz))
[docs] def draw(self, *a): """Overrides :meth:`.PlotPanel.draw`. Draws some :class:`.PowerSpectrumSeries` using the :meth:`.PlotPanel.drawDataSeries` method. """ if not self or self.destroyed: return canvas = self.canvas pss = self.getDataSeriesToPlot() for ps in pss: with props.suppress(ps, 'label'): ps.label = ps.makeLabel() canvas.drawDataSeries(extraSeries=pss) canvas.drawArtists()
[docs] def createDataSeries(self, overlay): """Overrides :meth:`.OverlayPlotPanel.createDataSeries`. Creates a :class:`.PowerSpectrumSeries` instance for the given overlay. """ displayCtx = self.displayCtx overlayList = self.overlayList psargs = [overlay, overlayList, displayCtx, self.canvas] if isinstance(overlay, fslmesh.Mesh): ps = psseries.MeshPowerSpectrumSeries(*psargs) opts = displayCtx.getOpts(overlay) targets = [displayCtx, opts] propNames = ['location', 'vertexData'] elif isinstance(overlay, fslmelimage.MelodicImage) and \ self.plotMelodicICs: ps = psseries.MelodicPowerSpectrumSeries(*psargs) targets = [displayCtx.getOpts(overlay)] propNames = ['volume'] elif isinstance(overlay, fslimage.Image) and overlay.ndim > 3: if overlay.iscomplex: ps = psseries.ComplexPowerSpectrumSeries(*psargs) else: ps = psseries.VoxelPowerSpectrumSeries(*psargs) opts = displayCtx.getOpts(overlay) targets = [displayCtx, opts] propNames = ['location', 'volumeDim'] else: return None, None, None ps.colour = self.getOverlayPlotColour(overlay) ps.lineStyle = self.getOverlayPlotStyle(overlay) ps.lineWidth = 2 ps.alpha = 1.0 return ps, targets, propNames
[docs] def prepareDataSeries(self, ps): """Overrides :meth:`.PlotPanel.prepareDataSeries`. Performs some pre-processing on the data of the given :class:`.PowerSpectrumSeries` instance. """ xdata, ydata = ps.getData() # if plotFrequencies is disabled, replace # the x axis data with sample numbers if (xdata is not None) and \ (ydata is not None) and \ (len(xdata) > 0) and \ not self.plotFrequencies: xdata = np.arange(len(ydata)) return xdata, ydata
def __plotMelodicICsChanged(self, *a): """Called when the :attr:`plotMelodicICs` property changes. Re-creates the internally cached :class:`.PowerSpectrmSeries` instances for all :class:`.MelodicImage` overlays in the :class:`.OverlayList`. """ for overlay in self.overlayList: if isinstance(overlay, fslmelimage.MelodicImage): self.clearDataSeries(overlay) self.updateDataSeries() self.draw()