Closed bthirion closed 3 weeks ago
Code to reproduce
from pathlib import Path
import numpy as np
from nilearn._utils.data_gen import write_fake_fmri_data_and_design
from nilearn.glm.first_level import FirstLevelModel
output_dir = Path() / "tmp"
output_dir.mkdir(exist_ok=True, parents=True)
# %%
# one run
shapes, rk = [(7, 8, 9, 15)], 3
mask, fmri_data, design_matrices = write_fake_fmri_data_and_design(
shapes, rk, file_path=output_dir
)
single_run_model = FirstLevelModel(mask_img=mask).fit(
fmri_data, design_matrices=design_matrices
)
for output_type in ["z_score", "p_value", "stat", "effect_size", "effect_variance"]:
output = single_run_model.compute_contrast(
contrast_def=np.array([1, 0, 0]), stat_type="t", output_type=output_type
)
print(f"{output_type}={output.shape}")
# %%
# Two runs
shapes, rk = ((7, 8, 7, 15), (7, 8, 7, 16)), 3
mask, fmri_data, design_matrices = write_fake_fmri_data_and_design(
shapes, rk, file_path=output_dir
)
multi_run_model = FirstLevelModel(mask_img=mask).fit(
fmri_data, design_matrices=design_matrices
)
for output_type in ["z_score", "p_value", "stat", "effect_size", "effect_variance"]:
output = multi_run_model.compute_contrast(
contrast_def=np.array([1, 0, 0]), stat_type="t", output_type=output_type
)
print(f"{output_type}={output.shape}")
This is a "new" behavior indeed.
on 0.10.1 we used to get:
on 0.10.1✔ 24-06-06 - 16:35:20
╰─(base) ⠠⠵ /home/remi/miniconda3/bin/python /home/remi/github/nilearn/nilearn/tmp.py
z_score=(7, 8, 9)
p_value=(7, 8, 9)
stat=(7, 8, 9)
effect_size=(7, 8, 9)
effect_variance=(7, 8, 9)
/home/remi/github/nilearn/nilearn/nilearn/glm/first_level/first_level.py:706: UserWarning: One contrast given, assuming it for all 2 runs
warn(f'One contrast given, assuming it for all {int(n_runs)} runs')
z_score=(7, 8, 7)
p_value=(7, 8, 7)
stat=(7, 8, 7)
effect_size=(7, 8, 7)
effect_variance=(7, 8, 7)
but from 0.10.2 we then got
on 0.10.2✔ 24-06-06 - 16:37:09
╰─(base) ⠠⠵ /home/remi/miniconda3/bin/python /home/remi/github/nilearn/nilearn/tmp.py
z_score=(7, 8, 9)
p_value=(7, 8, 9)
stat=(7, 8, 9)
effect_size=(7, 8, 9, 1)
effect_variance=(7, 8, 9)
/home/remi/github/nilearn/nilearn/nilearn/glm/first_level/first_level.py:799: UserWarning: One contrast given, assuming it for all 2 runs
warn(f"One contrast given, assuming it for all {int(n_runs)} runs")
z_score=(7, 8, 7)
p_value=(7, 8, 7)
stat=(7, 8, 7)
effect_size=(7, 8, 7, 1)
effect_variance=(7, 8, 7)
Slightly modified version to check t and F contrast:
output_dir = Path() / "tmp"
output_dir.mkdir(exist_ok=True, parents=True)
# %%
shapes, rk = [(7, 8, 9, 15)], 3
mask, fmri_data, design_matrices = write_fake_fmri_data_and_design(
shapes, rk, file_path=output_dir
)
single_run_model = FirstLevelModel(mask_img=mask).fit(
fmri_data, design_matrices=design_matrices
)
t_contrast = np.array([1, 0, 0])
F_contrast = np.eye(rk)[:2]
for output_type in ["z_score", "p_value", "stat", "effect_size", "effect_variance"]:
output = single_run_model.compute_contrast(
contrast_def=t_contrast, output_type=output_type
)
print(f"t contrast: {output_type}={output.shape}")
output = single_run_model.compute_contrast(
contrast_def=F_contrast, output_type=output_type
)
print(f"F contrast: {output_type}={output.shape}")
0.10.1
Note that here the F contrast output is always 3 dimensional
╰─(base) ⠠⠵ /home/remi/miniconda3/bin/python /home/remi/github/nilearn/nilearn/tmp.py
t contrast: z_score=(7, 8, 9)
F contrast: z_score=(7, 8, 9)
t contrast: p_value=(7, 8, 9)
F contrast: p_value=(7, 8, 9)
t contrast: stat=(7, 8, 9)
F contrast: stat=(7, 8, 9)
t contrast: effect_size=(7, 8, 9)
F contrast: effect_size=(7, 8, 9)
t contrast: effect_variance=(7, 8, 9)
F contrast: effect_variance=(7, 8, 9)
on main
Note that here the F contrast output is always 4 dimensional for effect_size
╰─(base) ⠠⠵ /home/remi/miniconda3/bin/python /home/remi/github/nilearn/nilearn/tmp.py
t contrast: z_score=(7, 8, 9)
F contrast: z_score=(7, 8, 9)
t contrast: p_value=(7, 8, 9)
F contrast: p_value=(7, 8, 9)
t contrast: stat=(7, 8, 9)
F contrast: stat=(7, 8, 9)
t contrast: effect_size=(7, 8, 9, 1)
F contrast: effect_size=(7, 8, 9, 2)
t contrast: effect_variance=(7, 8, 9)
F contrast: effect_variance=(7, 8, 9)
Maybe this did it: https://github.com/nilearn/nilearn/pull/3203/files#r1629761075
It looks like this issue is still being worked on, is there a workaround the team can suggest in the meantime?
double check the version but this bug was introduced in version 0.10.2, so working with nilearn==0.10.1 should 'fix' it
Thanks for the quick response. I rolled back to 0.10.1
, and that gave me a different error in the first-level modeling:
ValueError: solve: Input operand 1 has a mismatch in its core dimension 0, with gufunc signature (m,m),(m,n)->(m,n) (size 73161 is different from 1)
So I rolled back to 0.9.2
, the last version I knew to be working (on Linux, I'm running on MacOS now) and get the same dimension mismatch error. Each of these times I created the conda environment from scratch.
python 3.10.14 h2469fbe_0_cpython conda-forge
nibabel 5.2.1 pyha770c72_0 conda-forge
nilearn 0.9.2 pyhd8ed1ab_0 conda-forge
nipype 1.8.6 py310hbe9552e_0 conda-forge
I am not sure whether this points to a package manager issue or maybe this bug is coming from a different package?
Hum... This is surprising. I am thinking that either this something to do with the data you are tryting to analyze or some interaction with an older version of nilearn and more recent versions of its dependencies. If you can do you think you could try to run one of the nilearn examples that covers what you are tying to do? If that fails then we can rule out the data issue.
Sure, I will try that and let you know.
Thx for the feedback. As a hotfix, can't you coerce the contast images to 3D manually (eg by simple reshaping) ?
Unless I'm misunderstanding, it's buried in the methods
sec_lvl_model = sec_lvl_model.fit(second_level_input=fixed_files,
design_matrix=design_matrix)
# Calculate z-statistic from second lvl map
zstat_map = sec_lvl_model.compute_contrast(
second_level_contrast='Intercept',
second_level_stat_type='t',
output_type='z_score',
)
compute_contrast()
is what throws the error, and fixed_files
is just a list of strings pointing to the maps on disk, so there is no chance for my to intervene and reshape.
I could load and reshape, and then invoke compute_contrast()
, I suppose?
I could load and reshape, and then invoke
compute_contrast()
, I suppose?
I think so. Sorry for the mess
Well, this is interesting. I pass a list of files using shell globbing, so the list is explicit.
Under 0.10.1
, it initially gives me a doubled list:
['foo/sub-1_stat-effect.nii.gz', 'foo//sub-1_stat-effect.nii.gz']
I de-duped those and looped over the list:
for i in fixed_files2:
x = nil.image.load_img(i)
print(x.shape)
This works fine and gives me (54, 64, 55)
for each image. But as soon as I try to nil.image.load_img()
more than one file at a time, it gives me the error below again:
Reference shape:
(54, 66, 55)
Input shape:
(54, 65, 55, 1)
I can use a list comprehension to get the list of loaded images, but then that fails again in compute_contrast()
.
@Remi-Gau Are you on this ? LMK if you need me to get into it.
I wanted to work on this in #4439 but I quickly got the feeling that you'd be faster and troubleshooting this one. I won't have time to work on this in the coming week though.
In any case we want to have this fixed in our next release.
If it helps, I noticed a working file had pixdim
values of [-1 3 3 3 0 0 0 0]
but the failing file had [1 3 3 3 2.996 1 1 1 1]
so I edited the header, thinking it was checking one of those values, but it didn't help.
(The 2.996 pixdim3
value is because fMRIPREP left it in native, but SPM conformed it to 3 mm isotropic).
Is there an existing issue for this?
Operating system
Operating system version
Python version
nilearn version
0.10.4
Expected behavior
Regarding the way we handle effect_size maps (aka beta maps) in GLM contrasts, these are always 4 dimensional even if the contrast is one dimensional. I'm unsure whether this is a good behavior. Handling such image as a 3D image would make more sense wouldn't it ? Note: my impression is that it changed recently. Would need to heck when. Note: this is to open a discussion, I don't have a very strong opinion on the matter.
Current behavior & error messages
For instance after running https://nilearn.github.io/stable/auto_examples/00_tutorials/plot_single_subject_single_run.html
Steps and code to reproduce bug