diff --git a/fsl/data/melodicimage.py b/fsl/data/melodicimage.py index f3b628810e332213a8ec3487a71773c983063b80..258e5663288956e718205404ddd67ce72bc71e28 100644 --- a/fsl/data/melodicimage.py +++ b/fsl/data/melodicimage.py @@ -79,9 +79,11 @@ class MelodicImage(fslimage.Image): *args, **kwargs) - self.__meldir = dirname - self.__melmix = melresults.getComponentTimeSeries( dirname) - self.__melFTmix = melresults.getComponentPowerSpectra(dirname) + self.__meldir = dirname + self.__melmix = melresults.getComponentTimeSeries( dirname) + self.__melFTmix = melresults.getComponentPowerSpectra(dirname) + self.__melICClass = melresults.MelodicClassification( + self.numComponents()) # Automatically set the # TR value if possible @@ -92,6 +94,8 @@ class MelodicImage(fslimage.Image): if dataImage.is4DImage(): self.tr = dataImage.pixdim[3] + # TODO load classifications if present + def getComponentTimeSeries(self, component): """Returns the time course for the specified (0-indexed) component. """ @@ -125,3 +129,7 @@ class MelodicImage(fslimage.Image): :func:`.melodicresults.getDataFile` function. """ return melresults.getDataFile(self.__meldir) + + + def getICClassification(self): + return self.__melICClass diff --git a/fsl/data/melodicresults.py b/fsl/data/melodicresults.py index 105c1ae28aa31dbbbd7f6ae5064dc033aa007dce..85a538198513cd4a1a44841fc2d6eb0922335cac 100644 --- a/fsl/data/melodicresults.py +++ b/fsl/data/melodicresults.py @@ -179,3 +179,82 @@ def getComponentPowerSpectra(meldir): """ ftmixfile = getFTMixFile(meldir) return np.loadtxt(ftmixfile) + + + +class MelodicClassification(object): + """ + """ + + def __init__(self, ncomps): + """Create a ``MelodicClassification`` instance. + """ + + self.__ncomps = ncomps + + self.__componentLabels = [[] for i in range(ncomps)] + self.__labelComponents = {} + + + def load(self, filename): + pass + + + def save(self, filename): + pass + + + def getLabels(self, component): + return list(self.__componentLabels[component]) + + + def addLabel(self, component, label): + + cmpLabels = self.__componentLabels[component] + labelCmps = self.__labelComponents.get(label, []) + + if label in cmpLabels: + return + + cmpLabels[component].append(label) + labelCmps[label] .append(component) + + self.__componentLabels[component] = cmpLabels + self.__labelComponents[label] = labelCmps + + + def removeLabel(self, component, label): + + if label not in self.__componentLabels[component]: + return + + self.__componentLabels[component].remove(label) + self.__labelComponents[label] .remove(component) + + + def clearLabels(self, component): + + labels = self.getLabels(component) + + for l in labels: + self.removeLabel(component, l) + + + def getComponents(self, label): + return list(self.__labelComponents.get(label, [])) + + + def addComponent(self, label, component): + self.addLabel(component, label) + + + def removeComponent(self, label, component): + self.removeLabel(component, label) + + + def clearComponents(self, label): + + components = self.getComponents(label) + + for c in components: + self.removeComponent(label, c) diff --git a/fsl/data/strings.py b/fsl/data/strings.py index 14621ff482b00e3cb434a23b05ff8195ba7a644c..3a761984607d6bc13514c56cbe9211fc7240c6a2 100644 --- a/fsl/data/strings.py +++ b/fsl/data/strings.py @@ -122,6 +122,9 @@ messages = TypeDict({ 'OrthoEditProfile.displaySpaceChange' : 'Setting {} as the display ' 'space reference image - this ' 'is necessary for editing.', + + + 'MelodicClassificationPanel.disabled' : 'Choose a melodic image.', }) @@ -173,6 +176,8 @@ titles = TypeDict({ 'OverlayInfoPanel' : 'Overlay information', 'ShellPanel' : 'Python shell', + 'MelodicClassificationPanel' : 'Melodic IC classification', + 'LookupTablePanel.loadLut' : 'Select a lookup table file', 'LookupTablePanel.labelExists' : 'Label already exists', }) @@ -188,17 +193,18 @@ actions = TypeDict({ 'FSLEyesFrame.closeViewPanel' : 'Close', - 'CanvasPanel.screenshot' : 'Take screenshot', - 'CanvasPanel.showCommandLineArgs' : 'Show command line for scene', - 'CanvasPanel.toggleColourBar' : 'Colour bar', - 'CanvasPanel.toggleOverlayList' : 'Overlay list', - 'CanvasPanel.toggleDisplayProperties' : 'Overlay display properties', - 'CanvasPanel.toggleLocationPanel' : 'Location panel', - 'CanvasPanel.toggleAtlasPanel' : 'Atlas panel', - 'CanvasPanel.toggleLookupTablePanel' : 'Lookup tables', - 'CanvasPanel.toggleClusterPanel' : 'Cluster browser', - 'CanvasPanel.toggleOverlayInfo' : 'Overlay information', - 'CanvasPanel.toggleShell' : 'Python shell', + 'CanvasPanel.screenshot' : 'Take screenshot', + 'CanvasPanel.showCommandLineArgs' : 'Show command line for scene', + 'CanvasPanel.toggleColourBar' : 'Colour bar', + 'CanvasPanel.toggleOverlayList' : 'Overlay list', + 'CanvasPanel.toggleDisplayProperties' : 'Overlay display properties', + 'CanvasPanel.toggleLocationPanel' : 'Location panel', + 'CanvasPanel.toggleAtlasPanel' : 'Atlas panel', + 'CanvasPanel.toggleLookupTablePanel' : 'Lookup tables', + 'CanvasPanel.toggleClusterPanel' : 'Cluster browser', + 'CanvasPanel.toggleOverlayInfo' : 'Overlay information', + 'CanvasPanel.toggleClassificationPanel' : 'Melodic IC classification', + 'CanvasPanel.toggleShell' : 'Python shell', 'OrthoPanel.toggleOrthoToolBar' : 'View properties', 'OrthoPanel.toggleEditToolBar' : 'Edit toolbar', diff --git a/fsl/fsleyes/controls/melodicclassificationpanel.py b/fsl/fsleyes/controls/melodicclassificationpanel.py new file mode 100644 index 0000000000000000000000000000000000000000..861c96c44493cfa9c749f00ee21a2854165ccaf8 --- /dev/null +++ b/fsl/fsleyes/controls/melodicclassificationpanel.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python +# +# melodicclassificationpanel.py - The MelodicClassificationPanel class. +# +# Author: Paul McCarthy <pauldmccarthy@gmail.com> +# +"""This module provides the :class:`MelodicClassificationPanel` class, a +*FSLeyes control* panel which allows the user to classify the components +of a :class:`.MelodicImage`. +""" + +import wx + + +import pwidgets.widgetgrid as widgetgrid +import pwidgets.texttag as texttag +import pwidgets.notebook as notebook + +import fsl.data.strings as strings +import fsl.data.melodicimage as fslmelimage +import fsl.fsleyes.panel as fslpanel + + +class MelodicClassificationPanel(fslpanel.FSLEyesPanel): + """The ``MelodicClassificationPanel`` + """ + + # + # Choose label colours + # Load/save from/to file + # + def __init__(self, parent, overlayList, displayCtx): + """Create a ``MelodicClassificationPanel``. + + :arg parent: The :mod:`wx` parent object. + + :arg overlayList: The :class:`.OverlayList`. + + :arg displayCtx: The :class:`.DisplayContext` instance. + """ + fslpanel.FSLEyesPanel.__init__(self, parent, overlayList, displayCtx) + + self.__disabledText = wx.StaticText( + self, + style=(wx.ALIGN_CENTRE_HORIZONTAL | + wx.ALIGN_CENTRE_VERTICAL)) + + self.__notebook = notebook.Notebook(self) + self.__componentGrid = ComponentGrid( self.__notebook) + self.__labelGrid = LabelGrid( self.__notebook) + + self.__notebook.AddPage(self.__componentGrid, 'Components') + self.__notebook.AddPage(self.__labelGrid, 'Labels') + + self.__mainSizer = wx.BoxSizer(wx.HORIZONTAL) + self.__mainSizer.Add(self.__notebook, flag=wx.EXPAND, proportion=1) + + # TODO Things which you don't want shown when + # a melodic image is not selected should + # be added to __mainSizer. Things which + # you always want displayed should be + # added to __sizer (but need to be laid + # out w.r.t. __disabledText/__mainSizer) + + self.__sizer = wx.BoxSizer(wx.HORIZONTAL) + self.__sizer.Add(self.__disabledText, flag=wx.EXPAND, proportion=1) + self.__sizer.Add(self.__mainSizer, flag=wx.EXPAND, proportion=1) + + self.SetSizer(self.__sizer) + + overlayList.addListener('overlays', + self._name, + self.__selectedOverlayChanged) + displayCtx .addListener('selectedOverlay', + self._name, + self.__selectedOverlayChanged) + + self.__overlay = None + self.__selectedOverlayChanged() + + + def destroy(self): + """ + """ + self._displayCtx .removeListener('selectedOverlay', self._name) + self._overlayList.removeListener('overlays', self._name) + fslpanel.FSLEyesPanel.destroy(self) + + + def __enable(self, enable=True, message=''): + """ + """ + + self.__disabledText.SetLabel(message) + + self.__sizer.Show(self.__disabledText, not enable) + self.__sizer.Show(self.__mainSizer, enable) + + self.Layout() + + + def __selectedOverlayChanged(self, *a): + + overlay = self._displayCtx.getSelectedOverlay() + + if (overlay is None) or \ + not isinstance(overlay, fslmelimage.MelodicImage): + self.__enable(False, strings.messages[self, 'disabled']) + return + + self.__overlay = overlay + + self.__componentGrid.setOverlay(overlay) + self.__labelGrid .setOverlay(overlay) + + self.__enable(True) + + +class ComponentGrid(wx.Panel): + + def __init__(self, parent): + wx.Panel.__init__(self, parent) + + self.__grid = widgetgrid.WidgetGrid(self) + self.__sizer = wx.BoxSizer(wx.HORIZONTAL) + + self.__grid.ShowRowLabels(False) + self.__grid.ShowColLabels(True) + + self.__sizer.Add(self.__grid, flag=wx.EXPAND, proportion=1) + + self.SetSizer(self.__sizer) + + + def setOverlay(self, overlay): + + numComps = overlay.numComponents() + + self.__grid.ClearGrid() + self.__grid.SetGridSize(numComps, 2, growCols=[1]) + + self.__grid.SetColLabel(0, 'Component #') + self.__grid.SetColLabel(1, 'Labels') + + for i in range(numComps): + + tags = texttag.TextTagPanel(self.__grid, + style=(texttag.TTP_ALLOW_NEW_TAGS | + texttag.TTP_ADD_NEW_TAGS | + texttag.TTP_NO_DUPLICATES)) + + self.__grid.SetText( i, 0, str(i)) + self.__grid.SetWidget(i, 1, tags) + + self.Layout() + + + +class LabelGrid(wx.Panel): + + def __init__(self, parent): + + wx.Panel.__init__(self, parent) + + self.__grid = widgetgrid.WidgetGrid(self) + self.__sizer = wx.BoxSizer(wx.HORIZONTAL) + + self.__sizer.Add(self.__grid, flag=wx.EXPAND, proportion=1) + + self.SetSizer(self.__sizer) + + + def setOverlay(self, overlay): + pass diff --git a/fsl/fsleyes/views/canvaspanel.py b/fsl/fsleyes/views/canvaspanel.py index 4ca4ad4068d0bdd2d7ecd5ff81c84f746edc768e..3822151e78eae02c5f4eae506c3f5640583641dd 100644 --- a/fsl/fsleyes/views/canvaspanel.py +++ b/fsl/fsleyes/views/canvaspanel.py @@ -21,23 +21,23 @@ import matplotlib.image as mplimg import props -import fsl.fsleyes.fsleyes_parseargs as fsleyes_parseargs -import fsl.utils.dialog as fsldlg -import fsl.utils.settings as fslsettings -import fsl.data.image as fslimage -import fsl.data.strings as strings -import fsl.fsleyes.displaycontext as displayctx -import fsl.fsleyes.controls.overlaylistpanel as overlaylistpanel -import fsl.fsleyes.controls.overlayinfopanel as overlayinfopanel -import fsl.fsleyes.controls.atlaspanel as atlaspanel -import fsl.fsleyes.controls.overlaydisplaytoolbar as overlaydisplaytoolbar -import fsl.fsleyes.controls.locationpanel as locationpanel -import fsl.fsleyes.controls.clusterpanel as clusterpanel -import fsl.fsleyes.controls.lookuptablepanel as lookuptablepanel -import fsl.fsleyes.controls.shellpanel as shellpanel - -import colourbarpanel -import viewpanel +import fsl.fsleyes.fsleyes_parseargs as fsleyes_parseargs +import fsl.utils.dialog as fsldlg +import fsl.utils.settings as fslsettings +import fsl.data.image as fslimage +import fsl.data.strings as strings +import fsl.fsleyes.displaycontext as displayctx +import fsl.fsleyes.controls.overlaylistpanel as overlaylistpanel +import fsl.fsleyes.controls.overlayinfopanel as overlayinfopanel +import fsl.fsleyes.controls.atlaspanel as atlaspanel +import fsl.fsleyes.controls.overlaydisplaytoolbar as overlaydisplaytoolbar +import fsl.fsleyes.controls.locationpanel as locationpanel +import fsl.fsleyes.controls.clusterpanel as clusterpanel +import fsl.fsleyes.controls.lookuptablepanel as lookuptablepanel +import fsl.fsleyes.controls.melodicclassificationpanel as melclasspanel +import fsl.fsleyes.controls.shellpanel as shellpanel +import colourbarpanel +import viewpanel log = logging.getLogger(__name__) @@ -84,16 +84,18 @@ class CanvasPanel(viewpanel.ViewPanel): :mod:`control <.controls>` panels: - =========================== =========================================== - ``toggleOverlayList`` Toggles an :class:`.OverlayListPanel`. - ``toggleOverlayInfo`` Toggles an :class:`.OverlayInfoPanel`. - ``toggleAtlasPanel`` Toggles an :class:`.AtlasPanel`. - ``toggleDisplayProperties`` Toggles an :class:`.OverlayDisplayToolBar`. - ``toggleLocationPanel`` Toggles a :class:`.LocationPanel`. - ``toggleClusterPanel`` Toggles a :class:`.ClusterPanel`. - ``toggleLookupTablePanel`` Toggles a :class:`.LookupTablePanel`. - ``toggleShell`` Toggles a :class:`.ShellPanel`. - =========================== =========================================== + ============================== =========================================== + ``toggleOverlayList`` Toggles an :class:`.OverlayListPanel`. + ``toggleOverlayInfo`` Toggles an :class:`.OverlayInfoPanel`. + ``toggleAtlasPanel`` Toggles an :class:`.AtlasPanel`. + ``toggleDisplayProperties`` Toggles an :class:`.OverlayDisplayToolBar`. + ``toggleLocationPanel`` Toggles a :class:`.LocationPanel`. + ``toggleClusterPanel`` Toggles a :class:`.ClusterPanel`. + ``toggleLookupTablePanel`` Toggles a :class:`.LookupTablePanel`. + ``toggleClassificationPanel`` Toggles a + :class:`.MelodicClassificationPanel`. + ``toggleShell`` Toggles a :class:`.ShellPanel`. + ============================== =========================================== A couple of other actions are also provided, with the same names as their @@ -246,29 +248,32 @@ class CanvasPanel(viewpanel.ViewPanel): extraActions = {} actionz = [ - ('screenshot', self.screenshot), - ('showCommandLineArgs', self.showCommandLineArgs), - ('toggleOverlayList', lambda *a: self.togglePanel( + ('screenshot', self.screenshot), + ('showCommandLineArgs', self.showCommandLineArgs), + ('toggleOverlayList', lambda *a: self.togglePanel( overlaylistpanel.OverlayListPanel, location=wx.BOTTOM)), - ('toggleOverlayInfo', lambda *a: self.togglePanel( + ('toggleOverlayInfo', lambda *a: self.togglePanel( overlayinfopanel.OverlayInfoPanel, - location=wx.RIGHT)), - ('toggleAtlasPanel', lambda *a: self.togglePanel( + location=wx.LEFT)), + ('toggleAtlasPanel', lambda *a: self.togglePanel( atlaspanel.AtlasPanel, location=wx.BOTTOM)), - ('toggleDisplayProperties', lambda *a: self.togglePanel( + ('toggleDisplayProperties', lambda *a: self.togglePanel( overlaydisplaytoolbar.OverlayDisplayToolBar, viewPanel=self)), - ('toggleLocationPanel', lambda *a: self.togglePanel( + ('toggleLocationPanel', lambda *a: self.togglePanel( locationpanel.LocationPanel, location=wx.BOTTOM)), - ('toggleClusterPanel', lambda *a: self.togglePanel( + ('toggleClusterPanel', lambda *a: self.togglePanel( clusterpanel.ClusterPanel, location=wx.TOP)), - ('toggleLookupTablePanel', lambda *a: self.togglePanel( + ('toggleLookupTablePanel', lambda *a: self.togglePanel( lookuptablepanel.LookupTablePanel, - location=wx.TOP))] + location=wx.TOP)), + ('toggleClassificationPanel', lambda *a: self.togglePanel( + melclasspanel.MelodicClassificationPanel, + location=wx.RIGHT))] actionz += extraActions.items()