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
[ENH] Adapt FirstLevelModel to work with surface data objects from new API #4126
base: main
Are you sure you want to change the base?
Conversation
👋 @ymzayek Thanks for creating a PR! Until this PR is ready for review, you can include the [WIP] tag in its title, or leave it as a github draft. Please make sure it is compliant with our contributing guidelines. In particular, be sure it checks the boxes listed below.
For new features:
For bug fixes:
We will review it as quick as possible, feel free to ping us with questions if needed. |
@bthirion I started this to work on adapting the glm module. Still very WIP but we can see better what needs to be done. Let me know what you think |
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## main #4126 +/- ##
=======================================
Coverage 92.06% 92.06%
=======================================
Files 144 144
Lines 16419 16441 +22
Branches 3434 3443 +9
=======================================
+ Hits 15116 15137 +21
Misses 761 761
- Partials 542 543 +1 Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
We're getting close ! It mostly needs a few more tests. |
Regarding L628 nilearn/nilearn/glm/tests/test_first_level.py Lines 62 to 72 in 0a08873
EDIT: The issue below was handled in #4151 Another issue I saw while examining this part of the code that may be worth a separate issue is that the error message when both mask_img and img are None when calling fit on a NiftMasker is nilearn/nilearn/experimental/surface/_maskers.py Lines 79 to 82 in 0a08873
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thx, this is super important !
Do you plan to add the example in a forthcoming PR ?
Was going through our backlog of issues and wondering if this PR resolves:
Also wondering more generally if we feel OK closing as "solved" something that is not yet considered "stable" (depends on experimental features). |
@Remi-Gau seems to me to address those. We can list them in the main issue to keep track without closing them for the moment |
Going through the PR. What I am seeing makes sense to me. Thanks a bunch @ymzayek Was trying to think of a follow up example for this. We mentioned this example during the last hackathon: https://nilearn.github.io/stable/auto_examples/04_glm_first_level/plot_localizer_surface_analysis.html However this requires to project the fMRI image to the surface which is something we do not yet support with this new Surface object. Do we have a dataset available with first level surface data? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me too. Thanks @ymzayek
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, we just need to handle the end of the experimental module.
@bthirion this is what I'm doing in #4229 so that needs to be merged before this one and then this needs to be rebased and the imports changed. This will be quite easy to do in this PR so the focus should be on finishing 4229. This goes also for the decoder PR (will need to be rebased on 4229 and imports fixed). I used the labels |
# We can now simply run a GLM by directly passing | ||
# our :class:`nilearn.experimental.surface.SurfaceImage` instance | ||
# as input to FirstLevelModel.fit | ||
# | ||
# Here we use an :term:`HRF` model | ||
# containing the Glover model and its time derivative | ||
# The drift model is implicitly a cosine basis with a period cutoff at 128s. | ||
from nilearn.glm.first_level import FirstLevelModel | ||
|
||
glm = FirstLevelModel( | ||
t_r, | ||
slice_time_ref=slice_time_ref, | ||
hrf_model="glover + derivative", | ||
).fit(image, events) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this change compare to the original example
# We use the new :class:`nilearn.experimental.surface.SurfaceImage` | ||
# to create an surface object instance | ||
# that contains both the mesh | ||
# (here we use the one from the fsaverage5 templates) | ||
# and the BOLD data that we project on the surface. | ||
from nilearn import surface | ||
from nilearn.experimental.surface import SurfaceImage, load_fsaverage | ||
|
||
fsaverage5 = load_fsaverage() | ||
texture_left = surface.vol_to_surf( | ||
fmri_img, fsaverage5["pial"]["left_hemisphere"] | ||
) | ||
texture_right = surface.vol_to_surf( | ||
fmri_img, fsaverage5["pial"]["right_hemisphere"] | ||
) | ||
image = SurfaceImage( | ||
mesh={ | ||
"lh": fsaverage5["pial"]["left_hemisphere"], | ||
"rh": fsaverage5["pial"]["right_hemisphere"], | ||
}, | ||
data={ | ||
"lh": texture_left.T, | ||
"rh": texture_right.T, | ||
}, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this change compare to the original example
# %% | ||
# Group study | ||
# ----------- | ||
# | ||
# Prepare figure for concurrent plot of individual maps | ||
# compute population-level maps for left and right hemisphere | ||
# We directly do that on the value arrays. | ||
import numpy as np | ||
from scipy.stats import norm, ttest_1samp | ||
|
||
_, pval_left = ttest_1samp(np.array(z_scores_left), 0) | ||
_, pval_right = ttest_1samp(np.array(z_scores_right), 0) | ||
|
||
# %% | ||
# What we have so far are p-values: we convert them to z-values for plotting. | ||
z_val_left = norm.isf(pval_left) | ||
z_val_right = norm.isf(pval_right) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to also have second level support for surface images?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for first_level_glm, fmri_img, confound, events in zip( | ||
models, run_imgs, confounds, events | ||
): | ||
texture_left = surface.vol_to_surf( | ||
fmri_img[0], fsaverage5["pial"]["left_hemisphere"] | ||
) | ||
texture_right = surface.vol_to_surf( | ||
fmri_img[0], fsaverage5["pial"]["right_hemisphere"] | ||
) | ||
image = SurfaceImage( | ||
mesh={ | ||
"lh": fsaverage5["pial"]["left_hemisphere"], | ||
"rh": fsaverage5["pial"]["right_hemisphere"], | ||
}, | ||
data={ | ||
"lh": texture_left.T, | ||
"rh": texture_right.T, | ||
}, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
given that fmriprep datasets can contain surface data, it would make sense for first_level_from_bids
to return the path to the surface images if the space label corresponds to some fsaverage.
probably for a follow up PR currently for fitting GLM:
I think we should have "parity" for those 2 and users should be able to also just pass path to surfaces to fir their GLM. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- before: https://nilearn.github.io/dev/auto_examples/07_advanced/plot_surface_bids_analysis.html#group-study
- after: https://output.circle-artifacts.com/output/job/628c6b17-5666-4927-9fbd-4a4e5d838c7e/artifacts/0/dev/auto_examples/08_experimental/plot_new_surface_bids_analysis.html#group-study
before | after |
---|---|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are small differences but not sure what they are due to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably better now since I included the confounds
TODO:
examples to adapt: