How does NiftiMasker work when dealing with mask and data images of different resolutions?

I use NiftiMasker to mask my data to different brain regions. My mask images and my data have different voxel resolutions. When I use img.header.get_zooms() on both the mask image and the data images I find that the mask image has a resolution of 2x2x2mm and the subject images have a resolution of 1,5x1,5x1,5.

I have several questions to the masking process:

1.) What happens in the background when the mask image and the data images have different resolutions as in my case? My naive intuition is that the mask image and the subject images must have the same resolutions for successful masking (where a 1 in the mask image points to one particular voxel in the subject images, or to imagine it in 2D: The two data arrays must have the same number of columns, where the 1s in the mask columns determine which columns in the data arrays to keep or to drop). Correct me if I’m wrong here.

If my assumption above is true I guess that either the mask resolution has to be adapted to the subject image resolution or vice versa. From the documentation of NiftiMasker it is not clear to me which one of the two is adapted to the other one. Moreover, for my project, it is very important that the mask is not changed.

2.) There’s also the option to provide a target_affine or target_shape to NiftiMasker. Again I find the class a little bit underdocumented, it only says:

target_affine : 3x3 or 4x4 matrix, optional

This parameter is passed to image.resample_img. Please see the related documentation for details.

target_shape : 3-tuple of integers, optional

This parameter is passed to image.resample_img. Please see the related documentation for details.

I cannot find any the mentioned related documentation concerning these two methods. I also would like to know how these two methods interact with the above-mentioned resampling processes. Again is the mask image changed or the subject images?

P.S.:

In this section in the NiftiMasker looks like that the mask resolution is changed and not the image resolution?:

# If resampling is requested, resample also the mask
# Resampling: allows the user to change the affine, the shape or both
if self.verbose > 0:
    print("[%s.fit] Resampling mask" % self.__class__.__name__)
self.mask_img_ = self._cache(image.resample_img)(
    self.mask_img_,
    target_affine=self.target_affine,
    target_shape=self.target_shape,
    copy=False, interpolation='nearest')
if self.target_affine is not None:
    self.affine_ = self.target_affine
else:
    self.affine_ = self.mask_img_.affine
# Load data in memory
self.mask_img_.get_data()
if self.verbose > 10:
    print("[%s.fit] Finished fit" % self.__class__.__name__)
return self

the images get resampled to the mask resolution, it happens here:

https://github.com/nilearn/nilearn/blob/679ff38923629d0e31b7098fd88200b988aab5f8/nilearn/input_data/base_masker.py#L80

I agree that this should be stated very explicitly in the documentation.

what you mention above is that if you also specify (force) a target affine and
shape, then the mask image (whether it is computed or provided by you) gets
resampled to this target affine, at the end of fit, as you pointed out.

https://github.com/nilearn/nilearn/blob/679ff38923629d0e31b7098fd88200b988aab5f8/nilearn/input_data/nifti_masker.py#L261

then, images that are transformed will still be resampled to match the
(resampled) mask image. If you do not specify target_shape and
target_affine, the mask is not resampled.

to answer your question, I think nilearn will do what suits you in this case,
i.e. not change the mask and resample the images to the mask resolution.
If you wanted the opposite behaviour, you would have to specify the
target_shape and target_affine, or resample the mask yourself.

    import numpy as np
    from nilearn import datasets, input_data, image
    
    mni_mask = datasets.load_mni152_brain_mask()
    mni_resampled = image.resample_img(
        mni_mask, target_affine=np.eye(3) * 4, interpolation="nearest")
    
    masker = input_data.NiftiMasker(mni_mask).fit()
    masker.transform(mni_resampled).shape[1] == mni_mask.get_data().sum()
    True
`
1 Like