Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Volumetric mixed effects #342

Open
danjgale opened this issue Mar 9, 2023 · 3 comments
Open

Volumetric mixed effects #342

danjgale opened this issue Mar 9, 2023 · 3 comments

Comments

@danjgale
Copy link

danjgale commented Mar 9, 2023

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
Copy link

hechlera commented Jul 27, 2023

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)
  • as noted above, the argument terminology is highly confusing for volumetric data, even given the function help. The use of "surf" especially is unclear

  • using rft correction will throw an error that it into works for surface data (so I guess no cluster correction for volumetric data?)
    ValueError: Random Field Theory corrections require a surface.

  • during FDR correction, a shape mismatch error is thrown even though the input data and mask have the same format
    shape of data: (42, 1082035) shape of mask: (1082035,)

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
/home/tumnic/ahechler/ebits_notebooks/bold/2nd_level_nilearn.ipynb Cell 43 in ()
      [4]mixed_model = term_conf + term_sub
      [6]slm_mixed = SLM(
      [7]mixed_model,
      [8]df_behav.memory_confidence,
      [9]mask=mask_data,
     [10]correction=["fdr"]
---> [13]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
Copy link

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
Copy link

lconcha commented May 28, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants