Coverage for nilearn/_utils/masker_validation.py: 11%

67 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-20 10:58 +0200

1import warnings 

2from collections.abc import Iterable 

3from string import Template 

4 

5import numpy as np 

6 

7from nilearn._utils.logger import find_stack_level 

8from nilearn.surface import SurfaceImage 

9from nilearn.typing import NiimgLike 

10 

11from .cache_mixin import check_memory 

12from .class_inspect import get_params 

13 

14 

15def check_embedded_masker(estimator, masker_type="multi_nii", ignore=None): 

16 """Create a masker from instance parameters. 

17 

18 Base function for using a masker within a BaseEstimator class 

19 

20 This creates a masker from instance parameters : 

21 

22 - If instance contains a mask image in mask parameter, 

23 we use this image as new masker mask_img, forwarding instance parameters to 

24 new masker : smoothing_fwhm, standardize, detrend, low_pass= high_pass, 

25 t_r, target_affine, target_shape, mask_strategy, mask_args... 

26 

27 - If instance contains a masker in mask parameter, we use a copy of 

28 this masker, overriding all instance masker related parameters. 

29 In all case, we forward system parameters of instance to new masker : 

30 memory, memory_level, verbose, n_jobs 

31 

32 Parameters 

33 ---------- 

34 instance : object, instance of BaseEstimator 

35 The object that gives us the values of the parameters 

36 

37 masker_type : {"multi_nii", "nii", "surface"}, default="mutli_nii" 

38 Indicates whether to return a MultiNiftiMasker, NiftiMasker, or a 

39 SurfaceMasker. 

40 

41 ignore : None or list of strings 

42 Names of the parameters of the estimator that should not be 

43 transferred to the new masker. 

44 

45 Returns 

46 ------- 

47 masker : MultiNiftiMasker, NiftiMasker, \ 

48 or :obj:`~nilearn.maskers.SurfaceMasker` 

49 New masker 

50 

51 """ 

52 from nilearn.glm.first_level import FirstLevelModel 

53 from nilearn.glm.second_level import SecondLevelModel 

54 from nilearn.maskers import MultiNiftiMasker, NiftiMasker, SurfaceMasker 

55 

56 if masker_type == "surface": 

57 masker_type = SurfaceMasker 

58 elif masker_type == "multi_nii": 

59 masker_type = MultiNiftiMasker 

60 else: 

61 masker_type = NiftiMasker 

62 

63 estimator_params = get_params(masker_type, estimator, ignore=ignore) 

64 

65 mask = getattr(estimator, "mask", None) 

66 if isinstance(estimator, (FirstLevelModel, SecondLevelModel)): 

67 mask = getattr(estimator, "mask_img", None) 

68 

69 if isinstance(mask, (NiftiMasker, MultiNiftiMasker, SurfaceMasker)): 

70 # Creating masker from provided masker 

71 masker_params = get_params(masker_type, mask) 

72 new_masker_params = masker_params 

73 else: 

74 # Creating a masker with parameters extracted from estimator 

75 new_masker_params = estimator_params 

76 new_masker_params["mask_img"] = mask 

77 # Forwarding system parameters of instance to new masker in all case 

78 if issubclass(masker_type, MultiNiftiMasker) and hasattr( 

79 estimator, "n_jobs" 

80 ): 

81 # For MultiNiftiMasker only 

82 new_masker_params["n_jobs"] = estimator.n_jobs 

83 

84 warning_msg = Template( 

85 "Provided estimator has no $attribute attribute set." 

86 "Setting $attribute to $default_value by default." 

87 ) 

88 

89 if hasattr(estimator, "memory"): 

90 new_masker_params["memory"] = check_memory(estimator.memory) 

91 else: 

92 warnings.warn( 

93 warning_msg.substitute( 

94 attribute="memory", 

95 default_value="Memory(location=None)", 

96 ), 

97 stacklevel=find_stack_level(), 

98 ) 

99 new_masker_params["memory"] = check_memory(None) 

100 

101 if hasattr(estimator, "memory_level"): 

102 new_masker_params["memory_level"] = max(0, estimator.memory_level - 1) 

103 else: 

104 warnings.warn( 

105 warning_msg.substitute( 

106 attribute="memory_level", default_value="0" 

107 ), 

108 stacklevel=find_stack_level(), 

109 ) 

110 new_masker_params["memory_level"] = 0 

111 

112 if hasattr(estimator, "verbose"): 

113 new_masker_params["verbose"] = estimator.verbose 

114 else: 

115 warnings.warn( 

116 warning_msg.substitute(attribute="verbose", default_value="0"), 

117 stacklevel=find_stack_level(), 

118 ) 

119 new_masker_params["verbose"] = 0 

120 

121 conflicting_param = [ 

122 k 

123 for k in sorted(estimator_params) 

124 if np.any(new_masker_params[k] != estimator_params[k]) 

125 ] 

126 if conflicting_param: 

127 conflict_string = "".join( 

128 ( 

129 f"Parameter {k} :\n" 

130 f" Masker parameter {new_masker_params[k]}" 

131 f" - overriding estimator parameter {estimator_params[k]}\n" 

132 ) 

133 for k in conflicting_param 

134 ) 

135 warn_str = ( 

136 "Overriding provided-default estimator parameters with" 

137 f" provided masker parameters :\n{conflict_string}" 

138 ) 

139 warnings.warn(warn_str, stacklevel=find_stack_level()) 

140 

141 masker = masker_type(**new_masker_params) 

142 

143 # Forwarding potential attribute of provided masker 

144 if hasattr(mask, "mask_img_"): 

145 # Allow free fit of returned mask 

146 masker.mask_img = mask.mask_img_ 

147 

148 return masker 

149 

150 

151def check_compatibility_mask_and_images(mask_img, run_imgs): 

152 """Check that mask type and image types are compatible. 

153 

154 Images to fit should be a Niimg-Like 

155 if the mask is a NiftiImage, NiftiMasker or a path. 

156 Similarly, only SurfaceImages can be fitted 

157 with a SurfaceImage or a SurfaceMasker as mask. 

158 """ 

159 from nilearn.maskers import NiftiMasker, SurfaceMasker 

160 

161 if mask_img is None: 

162 return None 

163 

164 if not isinstance(run_imgs, Iterable): 

165 run_imgs = [run_imgs] 

166 

167 msg = ( 

168 "Mask and images to fit must be of compatible types.\n" 

169 f"Got mask of type: {type(mask_img)}, " 

170 f"and images of type: {[type(x) for x in run_imgs]}" 

171 ) 

172 

173 volumetric_type = (*NiimgLike, NiftiMasker) 

174 surface_type = (SurfaceImage, SurfaceMasker) 

175 all_allowed_types = (*volumetric_type, *surface_type) 

176 

177 if not isinstance(mask_img, all_allowed_types): 

178 raise TypeError( 

179 "\nMask should be of type: " 

180 f"{[x.__name__ for x in all_allowed_types]}.\n" 

181 f"Got : '{mask_img.__class__.__name__}'" 

182 ) 

183 

184 if isinstance(mask_img, volumetric_type) and any( 

185 not isinstance(x, NiimgLike) for x in run_imgs 

186 ): 

187 raise TypeError( 

188 f"{msg} " 

189 f"where images should be NiftiImage-like instances " 

190 f"(Nifti1Image or str or Path)." 

191 ) 

192 elif isinstance(mask_img, surface_type) and any( 

193 not isinstance(x, SurfaceImage) for x in run_imgs 

194 ): 

195 raise TypeError( 

196 f"{msg} where SurfaceImage instances would be expected." 

197 )