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

Other feat data files are also loaded as FEAT images. ClusterPanel can

add Z statistics and cluster masks as overlays.
parent a0ed6150
No related branches found
No related tags found
No related merge requests found
......@@ -4,7 +4,7 @@
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
"""This module provides the :class:`FeatImage` class, a subclass of
"""This module provides the :class:`FEATImage` class, a subclass of
:class:`.Image` designed for the ``filtered_func_data`` file of a FEAT
analysis.
"""
......@@ -13,8 +13,6 @@ import os.path as op
import numpy as np
import nibabel as nib
import image as fslimage
import featresults
......@@ -31,13 +29,15 @@ class FEATImage(fslimage.Image):
raise ValueError('{} does not appear to be data from a '
'FEAT analysis'.format(path))
featDir = op.dirname(path)
if op.isdir(path):
path = op.join(path, 'filtered_func_data')
featDir = featresults.getFEATDir(path)
settings = featresults.loadSettings( featDir)
design = featresults.loadDesign( featDir)
names, cons = featresults.loadContrasts(featDir)
datafile = featresults.getDataFile( featDir)
fslimage.Image.__init__(self, datafile, **kwargs)
fslimage.Image.__init__(self, path, **kwargs)
self.__analysisName = op.splitext(op.basename(featDir))[0]
self.__featDir = featDir
......@@ -50,6 +50,8 @@ class FEATImage(fslimage.Image):
self.__residuals = None
self.__pes = [None] * self.numEVs()
self.__copes = [None] * self.numContrasts()
self.__zstats = [None] * self.numContrasts()
self.__clustMasks = [None] * self.numContrasts()
if 'name' not in kwargs:
self.name = '{}: {}'.format(self.__analysisName, self.name)
......@@ -98,7 +100,12 @@ class FEATImage(fslimage.Image):
if self.__pes[ev] is None:
pefile = featresults.getPEFile(self.__featDir, ev)
self.__pes[ev] = nib.load(pefile).get_data()
self.__pes[ev] = FEATImage(
pefile,
name='{}: PE{} ({})'.format(
self.__analysisName,
ev + 1,
self.evNames()[ev]))
return self.__pes[ev]
......@@ -107,7 +114,9 @@ class FEATImage(fslimage.Image):
if self.__residuals is None:
resfile = featresults.getResidualFile(self.__featDir)
self.__residuals = nib.load(resfile).get_data()
self.__residuals = FEATImage(
resfile,
name='{}: residuals'.format(self.__analysisName))
return self.__residuals
......@@ -116,10 +125,45 @@ class FEATImage(fslimage.Image):
if self.__copes[con] is None:
copefile = featresults.getPEFile(self.__featDir, con)
self.__copes[con] = nib.load(copefile).get_data()
self.__copes[con] = FEATImage(
copefile,
name='{}: COPE{} ({})'.format(
self.__analysisName,
con + 1,
self.contrastNames()[con]))
return self.__copes[con]
return self.__copes[con]
def getZStats(self, con):
if self.__zstats[con] is None:
zfile = featresults.getZStatFile(self.__featDir, con)
self.__zstats[con] = FEATImage(
zfile,
name='{}: zstat{} ({})'.format(
self.__analysisName,
con + 1,
self.contrastNames()[con]))
return self.__zstats[con]
def getClusterMask(self, con):
if self.__clustMasks[con] is None:
mfile = featresults.getClusterMaskFile(self.__featDir, con)
self.__clustMasks[con] = FEATImage(
mfile,
name='{}: cluster mask for zstat{} ({})'.format(
self.__analysisName,
con + 1,
self.contrastNames()[con]))
return self.__clustMasks[con]
def fit(self, contrast, xyz, fullmodel=False):
"""
......@@ -146,7 +190,7 @@ class FEATImage(fslimage.Image):
for i in range(numEVs):
pe = self.getPE(i)[x, y, z]
pe = self.getPE(i).data[x, y, z]
modelfit += X[:, i] * pe * contrast[i]
return modelfit + data.mean()
......@@ -160,7 +204,7 @@ class FEATImage(fslimage.Image):
"""
x, y, z = xyz
residuals = self.getResiduals()[x, y, z, :]
residuals = self.getResiduals().data[x, y, z, :]
modelfit = self.fit(contrast, xyz, fullmodel)
return residuals + modelfit
......@@ -24,32 +24,44 @@ def isFEATDir(path):
looks like the input data for a FEAT analysis, ``False`` otherwise.
"""
if op.isfile(path):
dirname, filename = op.split(path)
dirname, filename = op.split(path)
if filename.startswith('filtered_func_data'):
return True
featDir = getFEATDir(dirname)
isfeatdir = featDir is not None
try:
hasdesfsf = op.exists(op.join(featDir, 'design.fsf'))
hasdesmat = op.exists(op.join(featDir, 'design.mat'))
hasdescon = op.exists(op.join(featDir, 'design.con'))
isfeat = (isfeatdir and
hasdesmat and
hasdescon and
hasdesfsf)
return isfeat
except:
return False
dirname = path
keys = ['.feat',
'.gfeat',
'.feat{}' .format(op.sep),
'.gfeat{}'.format(op.sep)]
isfeatdir = any([path.endswith(k) for k in keys])
def getFEATDir(path):
hasdesfsf = op.exists(op.join(dirname, 'design.fsf'))
hasdesmat = op.exists(op.join(dirname, 'design.mat'))
hasdescon = op.exists(op.join(dirname, 'design.con'))
sufs = ['.feat', '.gfeat']
idxs = [(path.rfind(s), s) for s in sufs]
idx, suf = max(idxs, key=lambda (i, s): i)
isfeat = (isfeatdir and
hasdesmat and
hasdescon and
hasdesfsf)
return isfeat
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 loadDesign(featdir):
......@@ -298,6 +310,22 @@ def getCOPEFile(featdir, contrast):
return glob.glob(copefile)[0]
def getZStatFile(featdir, contrast):
"""Returns the path of the Z-statistic file for the specified
``contrast``, which is assumed to be 0-indexed.
"""
zfile = op.join(featdir, 'stats', 'zstat{}.*'.format(contrast + 1))
return glob.glob(zfile)[0]
def getClusterMaskFile(featdir, contrast):
"""Returns the path of the cluster mask file for the specified
``contrast``, which is assumed to be 0-indexed.
"""
mfile = op.join(featdir, 'cluster_mask_zstat{}.*'.format(contrast + 1))
return glob.glob(mfile)[0]
def getEVNames(settings):
"""Returns the names of every EV in the FEAT analysis which has the given
``settings`` (see the :func:`loadSettings` function).
......
......@@ -121,11 +121,15 @@ class Image(props.HasProperties):
# the provided file name, that means that the
# image was opened from a temporary file
if filename != image:
self.name = removeExt(op.basename(self.dataSource))
filepref = removeExt(op.basename(self.dataSource))
self.tempFile = nibImage.get_filename()
else:
self.name = removeExt(op.basename(self.dataSource))
filepref = removeExt(op.basename(self.dataSource))
if name is None:
name = filepref
self.name = name
self.saved = True
# Or a numpy array - we wrap it in a nibabel image,
......
......@@ -240,7 +240,7 @@ labels = TypeDict({
'FEATResidualTimeSeries' : 'Residuals',
'ClusterPanel.clustName' : 'Z statistics for COPE {} ({})',
'ClusterPanel.clustName' : 'Z statistics for COPE{} ({})',
'ClusterPanel.index' : 'Cluster index',
'ClusterPanel.nvoxels' : 'Size (voxels)',
......
......@@ -56,12 +56,12 @@ class ClusterPanel(fslpanel.FSLViewPanel):
self.__topSizer.Add(self.__addClusterMask, **args)
self.__mainSizer.Add(self.__topSizer, flag=wx.EXPAND)
self.__mainSizer.Add(self.__clusterList, flag=wx.EXPAND, proportion=1)
self.__mainSizer.Add(self.__clusterList, **args)
# Only one of the disabledText or
# mainSizer are shown at any one time
self.__sizer.Add(self.__disabledText, flag=wx.EXPAND, proportion=1)
self.__sizer.Add(self.__mainSizer, flag=wx.EXPAND, proportion=1)
self.__sizer.Add(self.__disabledText, **args)
self.__sizer.Add(self.__mainSizer, **args)
overlayList.addListener('overlays',
self._name,
......@@ -70,7 +70,9 @@ class ClusterPanel(fslpanel.FSLViewPanel):
self._name,
self.__selectedOverlayChanged)
self.__statSelect.Bind(wx.EVT_COMBOBOX, self.__statSelected)
self.__statSelect .Bind(wx.EVT_COMBOBOX, self.__statSelected)
self.__addZStats .Bind(wx.EVT_BUTTON, self.__addZStatsClick)
self.__addClusterMask.Bind(wx.EVT_BUTTON, self.__addClusterMaskClick)
self.__selectedOverlay = None
self.__selectedOverlayChanged()
......@@ -80,39 +82,59 @@ class ClusterPanel(fslpanel.FSLViewPanel):
self._overlayList.removeListener('overlays', self._name)
self._displayCtx .removeListener('selectedOverlay', self ._name)
if self.__selctedOverlay is not None:
try:
display = self._displayCtx.getDisplay(self.__selectedOverlay)
display.removeListener('name', self._name)
except:
pass
def __disable(self, message):
self.__disabledText.SetLabel(message)
self.__sizer.Show(self.__disabledText, True)
self.__sizer.Show(self.__mainSizer, False)
self.Layout()
self.Layout()
def __addZStatsClick(self, ev):
overlay = self.__selectedOverlay
contrast = self.__statSelect.GetSelection()
zstats = overlay.getZStats(contrast)
for ol in self._overlayList:
# Already in overlay list
if ol.dataSource == zstats.dataSource:
return
log.debug('Adding Z-statistic {} to overlay list'.format(zstats.name))
self._overlayList.append(zstats)
def __addClusterMaskClick(self, ev):
overlay = self.__selectedOverlay
contrast = self.__statSelect.GetSelection()
mask = overlay.getClusterMask(contrast)
for ol in self._overlayList:
# Already in overlay list
if ol.dataSource == mask.dataSource:
return
log.debug('Adding Cluster mask {} to overlay list'.format(mask.name))
self._overlayList.append(mask)
def __selectedOverlayChanged(self, *a):
self.__statSelect .Clear()
self.__clusterList.ClearGrid()
self.__selectedOverlay = None
# No overlays are loaded
if len(self._overlayList) == 0:
self.__disable(strings.messages[self, 'noOverlays'])
return
if self.__selectedOverlay is not None:
display = self._displayCtx.getDisplay(self.__selectedOverlay)
display.removeListener('name', self._name)
self.__selectedOverlay = None
overlay = self._displayCtx.getSelectedOverlay()
display = self._displayCtx.getDisplay(overlay)
# Not a FEAT image, can't
# do anything with that
......@@ -125,8 +147,8 @@ class ClusterPanel(fslpanel.FSLViewPanel):
self.__sizer.Show(self.__disabledText, False)
self.__sizer.Show(self.__mainSizer, True)
numCons = overlay.numContrasts()
conNames = overlay.contrastNames()
numCons = overlay.numContrasts()
conNames = overlay.contrastNames()
try:
# clusts is a list of (contrast, clusterList) tuples
......@@ -148,21 +170,14 @@ class ClusterPanel(fslpanel.FSLViewPanel):
for contrast, clusterList in clusts:
name = conNames[contrast]
name = strings.labels[self, 'clustName'].format(contrast, name)
name = strings.labels[self, 'clustName'].format(contrast + 1, name)
self.__statSelect.Append(name, clusterList)
self.__overlayName.SetLabel(display.name)
self.__overlayName.SetLabel(overlay.getAnalysisName())
self.__statSelect.SetSelection(0)
self.__displayClusterData(clusts[0][1])
# Update displayed name if
# overlay name is changed
def nameChanged(*a):
self.__overlayName.setLabel(display.name)
display.addListener('name', self._name, nameChanged)
self.Layout()
return
......
......@@ -357,9 +357,9 @@ class FEATEVTimeSeries(TimeSeries):
class FEATResidualTimeSeries(TimeSeries):
def getData(self):
x, y, z = self.coords
data = self.overlay.getResiduals()[x, y, z, :]
data = self.overlay.getResiduals().data[x, y, z, :]
return TimeSeries.getData(self, ydata=data)
return TimeSeries.getData(self, ydata=np.array(data))
class FEATModelFitTimeSeries(TimeSeries):
......
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