Hi neurostars,
I am familiar with conducting GLM analysis in SPM for past projects. I have been transitioning my new project pipeline to nilearn and have been setting up a first- and second-level GLM but have run into some confusion about how to use nilearn’s GLM modules correctly.
For simplicity let’s say I have a series of preprocessed BOLD timeseries for different subjects in niftis_all, and each subject’s design matrix in designmats_all.
niftis_all = [...] # list where each element is a file path to preprocessed BOLD
designmats_all = [...] # list where each element is the result of make_first_level_design_matrix()
I have been struggling to find the appropriate method to fit each subject’s first-level, extract each subject’s contrast map (cmap) from their first-level, and then fit an intercept second-level to the first-level cmaps.
After a lot of tweaking, the only way I have found that will yield second-level results that appear reasonable is:
# SET UP FIRST-LEVELS
level1_base = nilearn.glm.first_level.FirstLevelModel(...) # various first-level args
level1s_all = [level1_base] * len(niftis_all) # create list with 1 empty first-level for each subject
# empty list to contain cmaps later
cmaps_all = []
# trivial example contrast on the design matrix
example_contrast = np.array([1, 0, 0, 0])
# FIT FIRST-LEVELS
for model, imgs, designmat in zip(model, niftis_all, all_subs_designmats):
# fit this subject's GLM
# inplace modification seems to work as expected
model.fit(
run_imgs=imgs,
design_matrices=designmat
)
# THIS STEP APPEARS TO BE CRITICAL?
# IMMEDIATELY compute this subject's first-level contrast map
# BEFORE fitting the next subject's GLM
z_map = model.compute_contrast(con_level1s_all, output_type="z_score")
cmaps.append(z_map)
# SET UP AND FIT SECOND-LEVEL
design_matrix_level2 = pd.DataFrame(
[1] * len(glm_level1s_all),
columns=["intercept"]
)
glm_level2 = SecondLevelModel()
# input first-level contrast maps into second-level, not GLM objs
glm_level2 = glm_level2.fit(
con_level1s_all,
design_matrix=design_matrix_level2
)
# save report as I was doing
report_level2 = glm_level2.generate_report(
contrasts=["intercept"],
# example args
alpha=.005,
display_mode="x"
)
report_level2.save_as_html('report_level2.html')
The other first-level → second-level methods I tried seem to return funky results for me that look something like below. The spurious second-levels tend to look like this, with massive and patchy z-values. I think this pattern suggests that the second-level has accidentally been fit on repeated copies of one subject’s cmap (the final one?), as opposed to fitting across cmaps from different subjects.
Here are the methods I’ve tried:- FAILS LIKE IMAGE: Fitting second-level model directly on the first-level model objects, as shown in BIDS dataset first- and second-level analysis - Nilearn
- FAILS LIKE IMAGE: Fitting first-level models in a loop, next extracting first-level cmaps in a second loop, finally inputting the resulting list of cmaps into the second-level model
- WORKS: the current strategy, extracting first-level cmaps inside the loop where first-level models are fit, as shown in Surface-based dataset first and second level analysis of a dataset - Nilearn . While I’m doing a volumetric analysis and not a surface-based analysis, this was the guide document that suggested to me the strategy of saving out each subject’s first-level cmap inside the first-level loop immediately after fitting their model.
Have I been doing something incorrectly to get results inconsistent with the guides? I’m glad I found one method that worked but for future edification I would like to understand what might have been going wrong with my other methods.
All of the examples in “Examples > GLM”, including the ones referenced in “User guide > Second level models: Second level analysis”, demonstrate second-level analysis on cmaps read from file, not estimated from first-levels in the same session, so I wasn’t really able to use them to adjudicate my issue.
Thank you for your help!



