Repository: nilearn/nilearn

Issue:
Allow to set target_affine in SecondLevelModel
For my analysis, I would like to use nistats `SecondLevelModel`. I saw that `SecondLevelModel` uses `NiftiMasker` in the background to preprocess and mask the images and to convert them two 2D space. For my analysis, I would like to resample my images prior to the statistical analysis. In `NiftiMasker` there's an option to set a `target_affine`. With this option you can resample your `mask_image` to a certain resolution and accordingly all images will be resampled to your resampled mask. Is there a reason, why this is not implemented in `SecondLevelModel`?


Discussion:
Hi,
Obviously you can always explicitly resample your images prior to `SecondLevelModel` fit.
I think that the reasons why it's not done by default is that most often, these things would be handled during `FirstLevelModel` fit.
But I agree that this may be  an interesting addition, thx for posting !
Note that we're going to freeze nistats and that future developemnts will take place in nilearn.stats instead.

Resolving patch:
diff --git a/nilearn/stats/second_level_model/second_level_model.py b/nilearn/stats/second_level_model/second_level_model.py
--- a/nilearn/stats/second_level_model/second_level_model.py
+++ b/nilearn/stats/second_level_model/second_level_model.py
@@ -242,6 +242,14 @@ class SecondLevelModel(BaseEstimator, TransformerMixin, CacheMixin):
         parameters. Automatic mask computation assumes first level imgs have
         already been masked.
 
+    target_affine : 3x3 or 4x4 matrix, optional
+        This parameter is passed to :func:`nilearn.image.resample_img`.
+        Please see the related documentation for details.
+
+    target_shape : 3-tuple of integers, optional
+        This parameter is passed to :func:`nilearn.image.resample_img`.
+        Please see the related documentation for details.
+
     smoothing_fwhm: float, optional
         If smoothing_fwhm is not None, it gives the size in millimeters of the
         spatial smoothing to apply to the signal.
@@ -271,10 +279,13 @@ class SecondLevelModel(BaseEstimator, TransformerMixin, CacheMixin):
 
     """
 
-    def __init__(self, mask_img=None, smoothing_fwhm=None,
+    def __init__(self, mask_img=None, target_affine=None, target_shape=None,
+                 smoothing_fwhm=None,
                  memory=Memory(None), memory_level=1, verbose=0,
                  n_jobs=1, minimize_memory=True):
         self.mask_img = mask_img
+        self.target_affine = target_affine
+        self.target_shape = target_shape
         self.smoothing_fwhm = smoothing_fwhm
         if isinstance(memory, str):
             self.memory = Memory(memory)
@@ -392,8 +403,12 @@ def fit(self, second_level_input, confounds=None, design_matrix=None):
         # Learn the mask. Assume the first level imgs have been masked.
         if not isinstance(self.mask_img, NiftiMasker):
             self.masker_ = NiftiMasker(
-                mask_img=self.mask_img, smoothing_fwhm=self.smoothing_fwhm,
-                memory=self.memory, verbose=max(0, self.verbose - 1),
+                mask_img=self.mask_img,
+                target_affine=self.target_affine,
+                target_shape=self.target_shape,
+                smoothing_fwhm=self.smoothing_fwhm,
+                memory=self.memory,
+                verbose=max(0, self.verbose - 1),
                 memory_level=self.memory_level)
         else:
             self.masker_ = clone(self.mask_img)


Test patch:
diff --git a/nilearn/stats/tests/test_second_level_model.py b/nilearn/stats/tests/test_second_level_model.py
--- a/nilearn/stats/tests/test_second_level_model.py
+++ b/nilearn/stats/tests/test_second_level_model.py
@@ -51,6 +51,17 @@ def test_high_level_glm_with_paths():
         z_image = model.compute_contrast(c1, output_type='z_score')
         assert isinstance(z_image, Nifti1Image)
         assert_array_equal(z_image.affine, load(mask).affine)
+
+        # try with target_shape
+        target_shape = (10, 10, 10)
+        target_affine = np.eye(4)
+        target_affine[0, 3] = 1
+        model = SecondLevelModel(mask_img=mask, target_shape=target_shape,
+                                 target_affine=target_affine)
+        z_image = model.fit(Y, design_matrix=X).compute_contrast(c1)
+        assert_array_equal(z_image.shape, target_shape)
+        assert_array_equal(z_image.affine, target_affine)
+        
         # Delete objects attached to files to avoid WindowsError when deleting
         # temporary directory (in Windows)
         del Y, FUNCFILE, func_img, model