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

Basic (buggy) support for MelodicImage overlays in

TimeSeriesPanel. Things are going to change.
parent 8e7afb00
No related branches found
No related tags found
No related merge requests found
......@@ -8,6 +8,7 @@
"""This module provides the :class:`MelodicImage` class, an :class:`.Image`
sub-class which encapsulates data from a MELODIC analysis.
"""
import os.path as op
import image as fslimage
import melodicresults as melresults
......@@ -17,7 +18,42 @@ class MelodicImage(fslimage.Image):
"""
"""
def __init__(self, path):
def __init__(self, image, *args, **kwargs):
"""
"""
pass
if op.isdir(image):
dirname = image
filename = 'melodic_IC'
else:
dirname = op.dirname( image)
filename = op.basename(image)
dirname = dirname.rstrip(op.sep)
if not melresults.isMelodicDir(dirname):
raise ValueError('{} does not appear to be a '
'MELODIC directory'.format(dirname))
if not filename.startswith('melodic_IC'):
raise ValueError('{} does not appear to be a MELODIC '
'component file'.format(filename))
fslimage.Image.__init__(self,
op.join(dirname, filename),
*args,
**kwargs)
self.__melmix = melresults.getComponentTimeSeries(dirname)
def getComponentTimeSeries(self, component):
return self.__melmix[:, component]
def numComponents(self):
return self.shape[3]
......@@ -13,21 +13,90 @@ following functions are provided:
.. autosummary::
nosignatures:
isMELODICDir
getMELODICDir
isMelodicDir
getMelodicDir
getICFile
getNumComponents
getComponentTimeSeries
"""
def isMELODICDir(path):
import os
import os.path as op
import numpy as np
import fsl.data.image as fslimage
def isMelodicDir(path):
"""
"""
# Must be named *.ica or *.gica
meldir = getMelodicDir(path)
if meldir is None:
return False
# Must contain an image file called melodic_IC
try:
fslimage.addExt(op.join(meldir, 'melodic_IC'), mustExist=True)
except ValueError:
return False
# Must contain a file called melodic_mix
if not op.exists(op.join(meldir, 'melodic_mix')):
return False
return True
def getMelodicDir(path):
"""
"""
# TODO This code is identical to featresults.getFEATDir.
# Can you generalise it and put it somewhere in fsl.utils?
sufs = ['.ica', '.gica']
idxs = [(path.rfind(s), s) for s in sufs]
idx, suf = max(idxs, key=lambda (i, s): i)
if idx == -1:
return None
idx += len(suf)
path = path[:idx]
if path.endswith(suf) or path.endswith('{}{}'.format(suf, op.sep)):
return path
return None
def getICFile(meldir):
"""
"""
return fslimage.addExt(op.join(meldir, 'melodic_IC'))
def getMixFile(meldir):
"""
"""
return op.join(meldir, 'melodic_mix')
def getNumComponents(meldir):
"""
"""
icImg = fslimage.Image(getICFile(meldir), loadData=False)
return icImg.shape[3]
def getComponentTimeSeries(meldir):
"""
"""
# A MELODIC directory:
# - Must be called *.ica
# - Must contain melodic_IC.nii.gz
# - Must contain melodic_mix
mixfile = getMixFile(meldir)
return np.loadtxt(mixfile)
......@@ -15,12 +15,12 @@ import copy
import wx
import numpy as np
import props
import pwidgets.elistbox as elistbox
import fsl.fsleyes.panel as fslpanel
import fsl.fsleyes.tooltips as fsltooltips
import fsl.data.strings as strings
import fsl.fsleyes.colourmaps as fslcm
import props
import pwidgets.elistbox as elistbox
import fsl.fsleyes.panel as fslpanel
import fsl.fsleyes.tooltips as fsltooltips
import fsl.data.strings as strings
import fsl.fsleyes.colourmaps as fslcm
class TimeSeriesListPanel(fslpanel.FSLEyesPanel):
......@@ -114,12 +114,17 @@ class TimeSeriesListPanel(fslpanel.FSLEyesPanel):
"""Creates a label to use for the given :class:`.TimeSeries` instance.
"""
import fsl.fsleyes.views.timeseriespanel as tsp
display = self._displayCtx.getDisplay(ts.overlay)
return '{} [{} {} {}]'.format(display.name,
ts.coords[0],
ts.coords[1],
ts.coords[2])
if isinstance(ts, tsp.MelodicTimeSeries):
return '{} [component {}]'.format(display.name, ts.coords)
else:
return '{} [{} {} {}]'.format(display.name,
ts.coords[0],
ts.coords[1],
ts.coords[2])
def __makeFEATModelTSLabel(self, parentTs, modelTs):
......
......@@ -191,19 +191,24 @@ def guessDataSourceType(filename):
is unrecognised, the first tuple value will be ``None``.
"""
import fsl.data.image as fslimage
import fsl.data.model as fslmodel
import fsl.data.featimage as fslfeatimage
import fsl.data.featresults as featresults
import fsl.data.image as fslimage
import fsl.data.model as fslmodel
import fsl.data.featimage as fslfeatimage
import fsl.data.melodicimage as fslmelimage
import fsl.data.melodicresults as melresults
import fsl.data.featresults as featresults
filename = op.abspath(filename)
if filename.endswith('.vtk'):
return fslmodel.Model, filename
else:
if op.isdir(filename):
if featresults.isFEATDir(filename):
return fslfeatimage.FEATImage, filename
elif melresults.isMelodicDir(filename):
return fslmelimage.MelodicImage, filename
else:
filename = fslimage.addExt(filename, False)
......@@ -212,6 +217,8 @@ def guessDataSourceType(filename):
if featresults.isFEATDir(filename):
return fslfeatimage.FEATImage, filename
elif melresults.isMelodicDir(filename):
return fslmelimage.MelodicImage, filename
else:
return fslimage.Image, filename
......@@ -288,7 +295,8 @@ def loadOverlays(paths, loadFunc='default', errorFunc='default', saveDir=True):
e = str(e)
msg = strings.messages['overlay.loadOverlays.error'].format(s, e)
title = strings.titles[ 'overlay.loadOverlays.error']
log.debug('Error loading overlay ({}), ({})'.format(s, e))
log.debug('Error loading overlay ({}), ({})'.format(s, e),
exc_info=True)
wx.MessageBox(msg, title, wx.ICON_ERROR | wx.OK)
# If loadFunc or errorFunc are explicitly set to
......
......@@ -488,7 +488,8 @@ class PlotPanel(viewpanel.ViewPanel):
if ds.alpha == 0:
return (0, 0), (0, 0)
log.debug('Drawing plot for {}'.format(ds.overlay))
log.debug('Drawing {} for {}'.format(type(ds).__name__,
ds.overlay))
xdata, ydata = ds.getData()
......
......@@ -23,6 +23,7 @@ details):
TimeSeries
FEATTimeSeries
MelodicTimeSeries
"""
......@@ -37,6 +38,7 @@ import props
import plotpanel
import fsl.data.featimage as fslfeatimage
import fsl.data.melodicimage as fslmelimage
import fsl.data.image as fslimage
import fsl.fsleyes.displaycontext as fsldisplay
import fsl.fsleyes.colourmaps as fslcmaps
......@@ -66,8 +68,9 @@ class TimeSeries(plotpanel.DataSeries):
plotpanel.DataSeries.__init__(self, overlay)
self.tsPanel = tsPanel
self.coords = map(int, coords)
self.data = overlay.data[coords[0], coords[1], coords[2], :]
self.coords = None
self.data = None
self.update(coords)
def __copy__(self):
......@@ -91,15 +94,15 @@ class TimeSeries(plotpanel.DataSeries):
destroyed/recreated whenever the
:attr:`.DisplayContext.location` changes.
"""
coords = map(int, coords)
if coords == self.coords:
return False
self.coords = coords
self.data = self.overlay.data[coords[0], coords[1], coords[2], :]
self.data = self._getData(coords)
return True
def _getData(self, coords):
return self.overlay.data[coords[0], coords[1], coords[2], :]
def getData(self, xdata=None, ydata=None):
"""Overrides :meth:`.DataSeries.getData` Returns the data associated
......@@ -648,6 +651,17 @@ class FEATModelFitTimeSeries(TimeSeries):
self.data = self.overlay.fit(contrast, xyz, fitType == 'full')
class MelodicTimeSeries(TimeSeries):
def __init__(self, tsPanel, overlay, component):
TimeSeries.__init__(self, tsPanel, overlay, component)
def _getData(self, component):
return self.overlay.getComponentTimeSeries(component)
class TimeSeriesPanel(plotpanel.PlotPanel):
"""A :class:`.PlotPanel` which plots time series data from
:class:`.Image` overlays.
......@@ -707,12 +721,12 @@ class TimeSeriesPanel(plotpanel.PlotPanel):
The ``TimeSeriesPanel`` has some extra functionality for
:class:`.FEATImage` overlays. For these overlays, a :class:`FEATTimeSeries`
instance is plotted, instead of a regular :class:`TimeSeries` instance. The
``FEATTimeSeries`` class, in turn, has the ability to generate more
``TimeSeries`` instances which represent various aspects of the FEAT model
fit. See the :class:`FEATTimeSeries` and the
:class:`.TimeSeriesControlPanel` classes for more details.
:class:`.FEATImage` overlays. For these overlays, a
:class:`.FEATTimeSeries` instance is plotted, instead of a regular
:class:`.TimeSeries` instance. The ``FEATTimeSeries`` class, in turn, has
the ability to generate more ``TimeSeries`` instances which represent
various aspects of the FEAT model fit. See the :class:`.FEATTimeSeries`
and the :class:`.TimeSeriesControlPanel` classes for more details.
"""
......@@ -844,7 +858,7 @@ class TimeSeriesPanel(plotpanel.PlotPanel):
def getCurrent(self):
"""Returns the :class:`TimeSeries` instance for the current time
"""Returns the :class:`.TimeSeries` instance for the current time
course. If :attr:`showCurrent` is ``False``, or the currently
selected overlay is not a :class:`.Image` (see
:attr:`.DisplayContext.selectedOverlay`) this method will return
......@@ -881,34 +895,25 @@ class TimeSeriesPanel(plotpanel.PlotPanel):
overlays = [o for o in self._overlayList
if o is not currOverlay]
# Remove overlays for which the
# current location is out of bounds
locs = map(self.__getTimeSeriesLocation, overlays)
locovl = filter(lambda (l, o): l is not None,
zip(locs, overlays))
if len(locovl) > 0:
locs, overlays = zip(*locovl)
tss = map(self.__genTimeSeries, overlays, locs)
tss = map(self.__genTimeSeries, overlays)
extras.extend([ts for ts in tss if ts is not None])
extras.extend([ts for ts in tss if ts is not None])
for ts in tss:
ts.alpha = 1
ts.lineWidth = 0.5
# Use a random colour for each overlay,
# but use the same random colour each time
colour = self.__overlayColours.get(
ts.overlay,
fslcmaps.randomBrightColour())
for ts in tss:
ts.alpha = 1
ts.lineWidth = 0.5
# Use a random colour for each overlay,
# but use the same random colour each time
colour = self.__overlayColours.get(
ts.overlay,
fslcmaps.randomBrightColour())
ts.colour = colour
self.__overlayColours[ts.overlay] = colour
ts.colour = colour
self.__overlayColours[ts.overlay] = colour
if isinstance(ts, FEATTimeSeries):
extras.extend(ts.getModelTimeSeries())
if isinstance(ts, FEATTimeSeries):
extras.extend(ts.getModelTimeSeries())
self.drawDataSeries(extras)
else:
......@@ -918,9 +923,9 @@ class TimeSeriesPanel(plotpanel.PlotPanel):
def __currentSettingsChanged(self, *a):
"""Called when the settings controlling the display of the current time
course(s) changes. If the current time course is a
:class:`FEATTimeSeries`, the display settings are propagated to all of
:class:`.FEATTimeSeries`, the display settings are propagated to all of
its associated time courses (see the
:meth:`FEATTimeSeries.getModelTimeSeries` method).
:meth:`.FEATTimeSeries.getModelTimeSeries` method).
"""
if self.__currentTs is None:
return
......@@ -948,7 +953,7 @@ class TimeSeriesPanel(plotpanel.PlotPanel):
def __overlaysChanged(self, *a):
"""Called when the :class:`.OverlayList` changes. Makes sure
that there are no :class:`TimeSeries` instances in the
that there are no :class:`.TimeSeries` instances in the
:attr:`.PlotPanel.dataSeries` list which refer to overlays that
no longer exist.
"""
......@@ -962,7 +967,7 @@ class TimeSeriesPanel(plotpanel.PlotPanel):
def __bindCurrentProps(self, ts, bind=True):
"""Binds or unbinds the properties of the given :class:`TimeSeries`
"""Binds or unbinds the properties of the given :class:`.TimeSeries`
instance with the current display settings (e.g.
:attr:`currentColour`, :attr:`currentAlpha`, etc).
"""
......@@ -992,6 +997,9 @@ class TimeSeriesPanel(plotpanel.PlotPanel):
not overlay.is4DImage():
return None
if isinstance(overlay, fslmelimage.MelodicImage):
return opts.volume
vox = opts.transformCoords([[x, y, z]], 'display', 'voxel')[0]
vox = np.round(vox)
......@@ -1006,15 +1014,25 @@ class TimeSeriesPanel(plotpanel.PlotPanel):
return vox
def __genTimeSeries(self, overlay, vox):
"""Creates and returns a :class:`TimeSeries` or :class:`FEATTimeSeries`
instance for the specified voxel of the specified overlay.
def __genTimeSeries(self, overlay):
"""Creates and returns a :class:`.TimeSeries` or
:class:`.FEATTimeSeries` instance for the specified voxel of the
specified overlay.
"""
loc = self.__getTimeSeriesLocation(overlay)
if loc is None:
return None
if isinstance(overlay, fslfeatimage.FEATImage):
ts = FEATTimeSeries(self, overlay, vox)
ts = FEATTimeSeries(self, overlay, loc)
elif isinstance(overlay, fslmelimage.MelodicImage):
ts = MelodicTimeSeries(self, overlay, loc)
else:
ts = TimeSeries(self, overlay, vox)
ts = TimeSeries(self, overlay, loc)
ts.colour = self.currentColour
ts.alpha = self.currentAlpha
......@@ -1043,18 +1061,14 @@ class TimeSeriesPanel(plotpanel.PlotPanel):
return
overlay = self._displayCtx.getSelectedOverlay()
vox = self.__getTimeSeriesLocation(overlay)
if vox is None:
return
if overlay is prevOverlay:
self.__currentOverlay = prevOverlay
self.__currentTs = prevTs
prevTs.update(vox)
prevTs.update(self.__getTimeSeriesLocation(overlay))
else:
ts = self.__genTimeSeries(overlay, vox)
ts = self.__genTimeSeries(overlay)
self.__currentTs = ts
self.__currentOverlay = overlay
......
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