1
1
import os
2
2
import re
3
3
import json
4
- from typing import Optional , Union , List
4
+ from typing import Optional , Union , List , Tuple
5
5
6
6
from pathlib import Path
7
7
from tqdm import tqdm
8
8
import nibabel as nib
9
-
10
9
from nilearn .masking import compute_multi_epi_mask
11
- from nilearn .image import resample_to_img , new_img_like , get_data , math_img
10
+ from nilearn .image import (
11
+ resample_to_img ,
12
+ new_img_like ,
13
+ get_data ,
14
+ math_img ,
15
+ load_img ,
16
+ )
12
17
from nibabel import Nifti1Image
18
+ import numpy as np
13
19
from scipy .ndimage import binary_closing
14
20
15
21
from pkg_resources import resource_filename
@@ -74,6 +80,7 @@ def generate_group_mask(
74
80
template : str = "MNI152NLin2009cAsym" ,
75
81
templateflow_dir : Optional [Path ] = None ,
76
82
n_iter : int = 2 ,
83
+ verbose : int = 1 ,
77
84
) -> Nifti1Image :
78
85
"""
79
86
Generate a group EPI grey matter mask, and overlaid with a MNI grey
@@ -98,6 +105,9 @@ def generate_group_mask(
98
105
Number of repetitions of dilation and erosion steps performed in
99
106
scipy.ndimage.binary_closing function.
100
107
108
+ verbose :
109
+ Level of verbosity.
110
+
101
111
Keyword Arguments
102
112
-----------------
103
113
Used to filter the cirret
@@ -109,7 +119,12 @@ def generate_group_mask(
109
119
nibabel.nifti1.Nifti1Image
110
120
EPI (grey matter) mask for the current group of subjects.
111
121
"""
112
- # TODO: subject native space grey matter mask???
122
+ if verbose > 1 :
123
+ print (f"Found { len (imgs )} masks" )
124
+ if exclude := _check_mask_affine (imgs , verbose ):
125
+ imgs , __annotations__ = _get_consistent_masks (imgs , exclude )
126
+ if verbose > 1 :
127
+ print (f"Remaining: { len (imgs )} masks" )
113
128
114
129
# templateflow environment setting to get around network issue
115
130
if templateflow_dir and templateflow_dir .exists ():
@@ -258,3 +273,107 @@ def _load_config(atlas: Union[str, Path, dict]) -> dict:
258
273
else :
259
274
raise ValueError (f"Invalid input: { atlas } " )
260
275
return atlas_config
276
+
277
+
278
+ def _get_consistent_masks (
279
+ mask_imgs : List [Union [Path , str , Nifti1Image ]], exclude : List [int ]
280
+ ) -> Tuple [List [int ], List [str ]]:
281
+ """Create a list of masks that has the same affine.
282
+
283
+ Parameters
284
+ ----------
285
+
286
+ mask_imgs :
287
+ The original list of functional masks
288
+
289
+ exclude :
290
+ List of index to exclude.
291
+
292
+ Returns
293
+ -------
294
+ List of str
295
+ Functional masks with the same affine.
296
+
297
+ List of str
298
+ Identidiers of scans with a different affine.
299
+ """
300
+ weird_mask_identifiers = []
301
+ odd_masks = np .array (mask_imgs )[np .array (exclude )]
302
+ odd_masks = odd_masks .tolist ()
303
+ for odd_file in odd_masks :
304
+ identifier = Path (odd_file ).name .split ("_space" )[0 ]
305
+ weird_mask_identifiers .append (identifier )
306
+ cleaned_func_masks = set (mask_imgs ) - set (odd_masks )
307
+ cleaned_func_masks = list (cleaned_func_masks )
308
+ return cleaned_func_masks , weird_mask_identifiers
309
+
310
+
311
+ def _check_mask_affine (
312
+ mask_imgs : List [Union [Path , str , Nifti1Image ]], verbose : int = 1
313
+ ) -> Union [list , None ]:
314
+ """Given a list of input mask images, show the most common affine matrix
315
+ and subjects with different values.
316
+
317
+ Parameters
318
+ ----------
319
+ mask_imgs : :obj:`list` of Niimg-like objects
320
+ See :ref:`extracting_data`.
321
+ 3D or 4D EPI image with same affine.
322
+
323
+ verbose :
324
+ Level of verbosity.
325
+
326
+ Returns
327
+ -------
328
+
329
+ List or None
330
+ Index of masks with odd affine matrix. Return None when all masks have
331
+ the same affine matrix.
332
+ """
333
+ # save all header and affine info in hashable type...
334
+ header_info = {"affine" : []}
335
+ key_to_header = {}
336
+ for this_mask in mask_imgs :
337
+ img = load_img (this_mask )
338
+ affine = img .affine
339
+ affine_hashable = str (affine )
340
+ header_info ["affine" ].append (affine_hashable )
341
+ if affine_hashable not in key_to_header :
342
+ key_to_header [affine_hashable ] = affine
343
+
344
+ if isinstance (mask_imgs [0 ], Nifti1Image ):
345
+ mask_imgs = np .arange (len (mask_imgs ))
346
+ else :
347
+ mask_imgs = np .array (mask_imgs )
348
+ # get most common values
349
+ common_affine = max (
350
+ set (header_info ["affine" ]), key = header_info ["affine" ].count
351
+ )
352
+ if verbose > 0 :
353
+ print (
354
+ f"We found { len (set (header_info ['affine' ]))} unique affine "
355
+ f"matrices. The most common one is "
356
+ f"{ key_to_header [common_affine ]} "
357
+ )
358
+ odd_balls = set (header_info ["affine" ]) - {common_affine }
359
+ if not odd_balls :
360
+ return None
361
+
362
+ exclude = []
363
+ for ob in odd_balls :
364
+ ob_index = [
365
+ i for i , aff in enumerate (header_info ["affine" ]) if aff == ob
366
+ ]
367
+ if verbose > 1 :
368
+ print (
369
+ "The following subjects has a different affine matrix "
370
+ f"({ key_to_header [ob ]} ) comparing to the most common value: "
371
+ f"{ mask_imgs [ob_index ]} ."
372
+ )
373
+ exclude += ob_index
374
+ if verbose > 0 :
375
+ print (
376
+ f"{ len (exclude )} out of { len (mask_imgs )} has "
377
+ "different affine matrix. Ignore when creating group mask."
378
+ )
379
+ return sorted (exclude )
0 commit comments