MICA-MNI / BrainStat

A statistics and context decoding toolbox for neuroimaging.
https://brainstat.readthedocs.io
Other
87 stars 21 forks source link

Volumetric mixed effects #342

Open danjgale opened 1 year ago

danjgale commented 1 year ago

I am trying to run a mixed effects model using Brainstat with volumetric data (NIFTIs). I was under the impression that Brainstat can do analyses on volumes (as per the companion paper and the documentation landing page). However, I'm not really sure how to go about this, as the tutorials and documentation are surface-centric.

Is the statistics module strictly surface data after all? If not, then what's the way to go about using SLM with volumetric data?

My code resembles this following basic example, with the ??? indicating where I am confused with regards to volumetric input:

from nilearn.maskers import NiftiMasker
from brainstat.stats.terms import FixedEffect, MixedEffect
from brainstat.stats.SLM import SLM

mask = # a binary brain mask NIFTI file

design = # a pandas dataframe of the design matrix with columns 'time', 'condition', and 'subject'

time = FixedEffect(design['time'])
condition = FixedEffect(design['condition'])
subject = MixedEffect(design['subject'])
model = condition + time + subject

contrast_condition = design['condition']
slm = SLM(
    model,
    contrast_condition,
    surf=???,
    mask=???,
    correction=["fdr", "rft"],
    cluster_threshold=0.01,
)

# get input voxel array required by .fit()
masker = NiftiMasker(mask_img=mask)
voxels = masker.fit_transform(imgs)

# pass in voxel array, which I think I should be doing?
slm.fit(voxels)

The last line raises an internal type error if I set surf=mask in SLM. If I take out surf and mask completely in SLM, I still get internal type errors. What am I missing in order for it to run on volumetric data, unless I am totally mistaken?

Example of the type error I get regardless:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[22], line 1
----> 1 slm.fit(voxels)

File ~/miniconda/envs/nhp-model2/lib/python3.10/site-packages/brainstat/stats/SLM.py:154, in SLM.fit(self, Y)
    152     Y_masked = Y.copy()
    153 self._linear_model(Y_masked)
--> 154 self._t_test()
    155 if self.mask is not None:
    156     self._unmask()

File ~/miniconda/envs/nhp-model2/lib/python3.10/site-packages/brainstat/stats/_t_test.py:45, in _t_test(self)
     42         sys.exit("Contrast is not estimable :-(")
     44 else:
---> 45     c = np.dot(pinvX, self.contrast)
     46     r = self.contrast - np.dot(self.X, c)
     48     if np.square(np.ravel(r, "F")).sum() / np.square(
     49         np.ravel(self.contrast, "F")
     50     ).sum() > np.spacing(1):

File <__array_function__ internals>:180, in dot(*args, **kwargs)

TypeError: can't multiply sequence by non-int of type 'float'
hechlera commented 11 months ago

I am also struggeling with making brainstat work for volumetric input. I do not get the error shown above, but multiple problems keep me from getting valid results.

copes = # first level contrast parameter maps

firstlvl_data = np.array([cope.get_fdata().flatten(order="F") for cope in copes]) # flattening with Fortran order, as this is specified in the function docs
mask_data = img_mni_gm.get_fdata().flatten(order="F").astype(int)

term_conf = FixedEffect(df_behav.memory_confidence)
term_sub = MixedEffect(df_behav.subject)

mixed_model = term_conf + term_sub

slm_mixed = SLM(
    mixed_model,
    df_behav.memory_confidence,
    mask=mask_data,
    # skipping surf argument
    correction=["fdr", "rft"]
)

slm_mixed.fit(firstlvl_data)

File [~/.conda/envs/.../lib/python3.10/site-packages/brainstat/stats/SLM.py:158), in SLM.fit(self, Y) 156 self._unmask() 157 if self.correction is not None: --> 158 self.multiple_comparison_corrections(student_t_test)

File [~/.conda/envs/.../lib/python3.10/site-packages/brainstat/stats/SLM.py:234), in SLM.multiple_comparison_corrections(self, student_t_test) 231 def multiple_comparison_corrections(self, student_t_test: bool) -> None: 232 "Performs multiple comparisons corrections. If a (one-sided) student-t 233 test was run, then make it two-tailed if requested." --> 234 P1, Q1 = self._run_multiple_comparisons() 236 if self.two_tailed and student_t_test: 237 self.t = -self.t ... 71 Q = np.ones((self.mask.shape[0])) ---> 72 Q[self.mask] = np.squeeze(Q_tmp[0, :]) 74 return Q

ValueError: shape mismatch: value array of shape (126006,) could not be broadcast to indexing result of shape (1082035,)



- Even if I leave out the correction, I get an array of t-values that includes two 0 values and every other value is NaN
`slm_mixed.t[~np.isnan(slm_mixed.t)]`
`array([0., 0.])`

Brainstat looks like an amazing tool to properly move from bash/FSL to python, especially since nilearn does not offer the mixed model approach. I am of course totally open to brainstat specializing on surface data, but currently it is not clear to me to which extent this (should) work.
ageoly-git commented 7 months ago

Hello Experts!

Have there been any updates on using the slm_mixed on volumetric NIFTI data? I understand that both the 'rft' feature as well as all of the plotting seems to rely on having a surface that contains both vertices and faces. The foundational paper does say that volumetric analysis is possible but so far this capability is rather elusive.

For context, I am working with cortical thickness data in the Oasis-TRT template space (from ANTs / Mindboggle).

Best, Andrew

lconcha commented 1 month ago

Like the previous posts, I also assume that BrainStat can perform volume analyses, since SurfStat was prepared for the task, and BrainStat is just awesome.

But, also like the others, I cannot get it to work. I cannot figure out how data must be input, since slm.fit seems to expect surface-based data. I'd be very happy if someone who got it to run on volumes could pitch in.