Custom NiftiMasker throws TypeError

I have a custom transformer class called NiftiProcessor which is a wrapper around NiftiMasker. This class takes one or multiple images, binarizes them depending on a threshold and passes them over to NiftiMasker. This class is also supposed to take no image(s) at all, in this case NiftiMasker should be initialized with the keyword argument mask_img = None and mask_strategy = 'template'.

The option for one or multiple images work, however if I initialize NiftiProcessor with tpl_imgs = None. I get the following error:

TypeError: Data given cannot be loaded because it is not compatible with nibabel format:
None

Strangely enough, If I initialize NiftiMasker with the (as I hope) same arguments everything seems to work fine. I checked my code several times and actually NiftiProcessor should ‘fall back’ to the same settings as initializing NiftiMasker. See the section:

    if self.tpl_imgs is None:
        self.mask_img_ = None
        self.mask_strategy_ = 'template'

Here’s the code for my class:

class NiftiProcessor(BaseEstimator,TransformerMixin):
    '''Wrapper Class around NiftiMasker.
    
    Parameters
    ----------
    tpl_imgs: One Niimg-like object or a list of multiple Niimg-like objects.
    
    threshold: float or str
    
    See documentation of NiftiMasker for all other parameters.
    http://nilearn.github.io/modules/generated/nilearn.input_data.NiftiMasker.html
    '''
    
    def __init__(self, tpl_imgs=None, threshold=None,smoothing_fwhm=None,
                 standardize=True,memory=None):
        
        self.tpl_imgs = tpl_imgs
        self.threshold = threshold
        self.smoothing_fwhm = smoothing_fwhm
        self.standardize = standardize
        self.memory = memory

    def fit(self, X, y=None):
        
        if self.tpl_imgs is None:
            self.mask_img_ = None
            self.mask_strategy_ = 'template'

        # if one or multiple masks are provided set mask_strategy to None.
        # you could also set it to one of the possible strategies, they would
        # still be ignored since a mask image is provided. But None is more
        # explicit here. 
        elif isinstance(self.tpl_imgs,list):
            self.mask_img_ = intersect_masks(
                    [binarize_img(img,self.threshold) for img in self.tpl_imgs],
                    threshold=0,
                    connected=False
                    )
            self.mask_strategy_ = None
            
        else:
            self.mask_img_ = binarize_img(self.tpl_imgs,self.threshold)
            self.mask_strategy_ = None

        self.masker_ = NiftiMasker(mask_img=self.mask_img_,
                                   smoothing_fwhm=self.smoothing_fwhm,
                                   standardize=self.standardize,
                                   mask_strategy=self.mask_strategy_,
                                   memory=self.memory).fit()
        return self

    def transform(self,X,y=None):
        if not hasattr(self, 'mask_img_'):
            raise ValueError('transformer not fitted yet.')
        return self.masker_.transform(X)

And here are two ways to initialize both classes (NiftiMasker works, whereas NiftiProcessor throws error, although they should be the same):

masked_mri_niftiprocessor = masking.NiftiProcessor(tpl_imgs=None,
                                                   threshold=MASK_THRESHOLD,
                                                   smoothing_fwhm=8,
                                                   standardize=True,
                                                   memory=niftiprocessor_cache
                                                   ).fit_transform(imgs_paths)

masked_mri_niftimasker = NiftiMasker(mask_img=None,
                                     mask_strategy='template',
                                     smoothing_fwhm=8,
                                     standardize=True,
                                     memory=niftimasker_cache
                                     ).fit_transform(imgs_paths)

Does anyone know what could have gone wrong here?

mask_img_ in NiftiMasker is never None. the mask provided by the user in __init__, stored in mask_img (no trailing underscore) can be None, but mask_img_ is always an image, derived durning fit, either from mask_img or from the data provided to fit.

I know that mask_img_ is never None within NiftiMasker. But in my case mask_img_ from the NiftiProcessor Class (so that’s a different self.mask_img_) is None and this variable is passed over as keyword argument to mask_img in NiftiMasker. So NiftiMasker should be initialized with no mask at all. It should then compute it’s own mask depending on the mask_strategy keyword argument and compute it’s own self.mask_img_ using the data. But somehow NiftiMasker inside NiftiProcessor seems to ‘think’ that it was provided with a mask image and is trying to read that. Then it puts out this error because it can’t read in None. I also printed out the attributes from NiftiMasker after beeing initialised both within and outside NiftiProcessor and they are the same:

Attributes from NiftiMasker within NiftiProcessor:
dict_items([(‘mask_img’, None), (‘sessions’, None), (‘smoothing_fwhm’, 8), (‘standardize’, True), (‘detrend’, False), (‘low_pass’, None), (‘high_pass’, None), (‘t_r’, None), (‘target_affine’, None), (‘target_shape’, None), (‘mask_strategy’, ‘template’), (‘mask_args’, None), (‘sample_mask’, None), (‘dtype’, None), (‘memory’, ‘./niftiprocessor_cache/’), (‘memory_level’, 1), (‘verbose’, 0), (’_shelving’, False)])

Attributes from NiftiMasker outside NiftiProcessor:
dict_items([(‘mask_img’, None), (‘sessions’, None), (‘smoothing_fwhm’, 8), (‘standardize’, True), (‘detrend’, False), (‘low_pass’, None), (‘high_pass’, None), (‘t_r’, None), (‘target_affine’, None), (‘target_shape’, None), (‘mask_strategy’, ‘template’), (‘mask_args’, None), (‘sample_mask’, None), (‘dtype’, None), (‘memory’, ‘./niftimasker_cache/’), (‘memory_level’, 1), (‘verbose’, 0), (’_shelving’, False)])

the problem is that you are providing the NiftiMasker neither with a mask_img in its __init__ (you give it None), nor data in its fit (you call it like this: masker.fit()). so it has no way to build a mask image. you must either give it a mask, or some images from which it can compute a mask

1 Like

Oh wow. How could I oversee that…Thanks Jérôme!

no problem you’re welcome!