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

Further tweaks to FEAT model fit - trying to get visually sensible

scaling on partial model fits. Also added new method
isFirstLevelAnalysis.
parent d56402b4
No related branches found
No related tags found
No related merge requests found
...@@ -19,6 +19,7 @@ following functions are provided: ...@@ -19,6 +19,7 @@ following functions are provided:
hasMelodicDir hasMelodicDir
getAnalysisDir getAnalysisDir
getTopLevelAnalysisDir getTopLevelAnalysisDir
isFirstLevelAnalysis
loadDesign loadDesign
loadContrasts loadContrasts
loadSettings loadSettings
...@@ -279,6 +280,15 @@ def getThresholds(settings): ...@@ -279,6 +280,15 @@ def getThresholds(settings):
} }
def isFirstLevelAnalysis(settings):
"""Returns ``True`` if the FEAT analysis described by ``settings``
is a first level analysis, ``False`` otherwise.
:arg settings: A FEAT settings dictionary (see :func:`loadSettings`).
"""
return settings['level'] == '1'
def loadClusterResults(featdir, settings, contrast): def loadClusterResults(featdir, settings, contrast):
"""If cluster thresholding was used in the FEAT analysis, this function """If cluster thresholding was used in the FEAT analysis, this function
will load and return the cluster results for the specified (0-indexed) will load and return the cluster results for the specified (0-indexed)
......
...@@ -45,7 +45,7 @@ class FEATImage(fslimage.Image): ...@@ -45,7 +45,7 @@ class FEATImage(fslimage.Image):
# [23, 30, 42] (in this example, we # [23, 30, 42] (in this example, we
# have 4 EVs - the first argument # have 4 EVs - the first argument
# is a contrast vector). # is a contrast vector).
img.fit([1, 1, 1, 1], [23, 30, 42], fullmodel=True) img.fit([1, 1, 1, 1], [23, 30, 42], fullModel=True)
""" """
...@@ -111,6 +111,13 @@ class FEATImage(fslimage.Image): ...@@ -111,6 +111,13 @@ class FEATImage(fslimage.Image):
return self.__analysisName return self.__analysisName
def isFirstLevelAnalysis(self):
"""Returns ``True`` if the FEAT analysis described by ``settings``
is a first level analysis, ``False`` otherwise.
"""
return featanalysis.isFirstLevelAnalysis(self.__settings)
def getTopLevelAnalysisDir(self): def getTopLevelAnalysisDir(self):
"""Returns the path to the higher level analysis directory of """Returns the path to the higher level analysis directory of
which this FEAT analysis is a part, or ``None`` if this analysis which this FEAT analysis is a part, or ``None`` if this analysis
...@@ -287,12 +294,12 @@ class FEATImage(fslimage.Image): ...@@ -287,12 +294,12 @@ class FEATImage(fslimage.Image):
return self.__clustMasks[con] return self.__clustMasks[con]
def fit(self, contrast, xyz, fullmodel=False): def fit(self, contrast, xyz, fullModel=False):
"""Calculates the model fit for the given contrast vector """Calculates the model fit for the given contrast vector
at the given voxel. at the given voxel.
Passing in a contrast of all 1s, and ``fullmodel=True`` will Passing in a contrast of all 1s, and ``fullModel=True`` will
get you the full model fit. Pass in ``fullmodel=False`` for get you the full model fit. Pass in ``fullModel=False`` for
all other contrasts, otherwise the model fit values will not all other contrasts, otherwise the model fit values will not
be scaled correctly. be scaled correctly.
...@@ -302,13 +309,20 @@ class FEATImage(fslimage.Image): ...@@ -302,13 +309,20 @@ class FEATImage(fslimage.Image):
:arg xyz: Coordinates of the voxel to calculate the model fit :arg xyz: Coordinates of the voxel to calculate the model fit
for. for.
:arg fullmodel: Set to ``True`` for a full model fit, ``False`` :arg fullModel: Set to ``True`` for a full model fit, ``False``
otherwise. otherwise.
""" """
if self.__design is None: if self.__design is None:
raise RuntimeError('No design') raise RuntimeError('No design')
x, y, z = xyz
firstLevel = self.isFirstLevelAnalysis()
numEVs = self.numEVs()
if len(contrast) != numEVs:
raise ValueError('Contrast is wrong length')
# Here we are basically trying to # Here we are basically trying to
# replicate the behaviour of tsplot. # replicate the behaviour of tsplot.
# There are some differences though - # There are some differences though -
...@@ -316,19 +330,21 @@ class FEATImage(fslimage.Image): ...@@ -316,19 +330,21 @@ class FEATImage(fslimage.Image):
# data by Z statistics. We're not # data by Z statistics. We're not
# doing that here. # doing that here.
# If we are not plotting a full model fit, # Normalise the contrast vector.
# normalise the contrast vector to unit # The scaling factor is arbitrary,
# length. Why? Because that's what tsplot # but should result in a visually
# does. # sensible scaling of the model fit.
if not fullmodel: # For a vector of all 1's (i.e. a
contrast = np.array(contrast) # full model fit) this is a no-op.
contrast = contrast / np.sqrt((contrast ** 2).sum()) #
# We also take the absolute value
x, y, z = xyz # of the values in the contrast
numEVs = self.numEVs() # vector, as the parameter estimates
# should be appropriately signed.
if len(contrast) != numEVs: contrast = np.array(contrast)
raise ValueError('Contrast is wrong length') nonzero = sum(~np.isclose(contrast, 0))
contrast = contrast / np.sqrt((contrast ** 2).sum())
contrast = np.abs(contrast * np.sqrt(nonzero))
X = self.__design.getDesign(xyz) X = self.__design.getDesign(xyz)
data = self[x, y, z, :] data = self[x, y, z, :]
...@@ -340,13 +356,19 @@ class FEATImage(fslimage.Image): ...@@ -340,13 +356,19 @@ class FEATImage(fslimage.Image):
pe = self.getPE(i)[x, y, z] pe = self.getPE(i)[x, y, z]
modelfit += ev * pe * contrast[i] modelfit += ev * pe * contrast[i]
# Make sure the full model fit # Make sure the model fit has an appropriate mean.
# has the same mean as the data #
if fullmodel: return modelfit - modelfit.mean() + data.mean() # Full model fits should, but won't necessarily,
else: return modelfit # have the same mean as the data.
#
# The data in first level analyses is demeaned before
# model fitting, so we need to add it back in.
if fullModel: return modelfit - modelfit.mean() + data.mean()
elif firstLevel: return modelfit - modelfit.mean() + data.mean()
else: return modelfit
def partialFit(self, contrast, xyz, fullmodel=False): def partialFit(self, contrast, xyz, fullModel=False):
"""Calculates and returns the partial model fit for the specified """Calculates and returns the partial model fit for the specified
contrast vector at the specified voxel. contrast vector at the specified voxel.
...@@ -355,6 +377,6 @@ class FEATImage(fslimage.Image): ...@@ -355,6 +377,6 @@ class FEATImage(fslimage.Image):
x, y, z = xyz x, y, z = xyz
residuals = self.getResiduals()[x, y, z, :] residuals = self.getResiduals()[x, y, z, :]
modelfit = self.fit(contrast, xyz, fullmodel) modelfit = self.fit(contrast, xyz, fullModel=fullModel)
return residuals + modelfit return residuals + modelfit
...@@ -320,7 +320,8 @@ def loadLabelFile(filename, includeLabel=None, excludeLabel=None): ...@@ -320,7 +320,8 @@ def loadLabelFile(filename, includeLabel=None, excludeLabel=None):
[2, 5, 6, 7] [2, 5, 6, 7]
The square brackets may or may not be present, i.e. the The square brackets may or may not be present, i.e. the
following format is also accepted:: following format is also accepted (this format is generated
by ICA-AROMA)::
2, 5, 6, 7 2, 5, 6, 7
......
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