1010
1111import h5py
1212import numpy as np
13+ from nilearn import datasets
14+ from nilearn .interfaces .fmriprep import load_confounds , load_confounds_strategy
15+ from nilearn .maskers import NiftiLabelsMasker , NiftiSpheresMasker
16+ from nilearn .plotting import find_parcellation_cut_coords
1317
1418from .dfc_utils import intersection , label2network
1519from .time_series import TIME_SERIES
@@ -150,13 +154,18 @@ def load_from_array(subj_id2load=None, **params):
150154 return BOLD
151155
152156
153- def nifti2array (nifti_file , confound_strategy = "none" , standardize = False , n_rois = 100 ):
157+ def extract_region_signals (
158+ nifti_file ,
159+ masker_type = "NiftiLabelsMasker" ,
160+ confound_strategy = "none" ,
161+ standardize = False ,
162+ labels_img = None ,
163+ seeds = None ,
164+ radius = None ,
165+ ):
154166 """
155167 this function uses nilearn maskers to extract
156168 BOLD signals from nifti files
157- For now it only works with schaefer atlas,
158- but you can set the number of rois to extract
159- {100, 200, 300, 400, 500, 600, 700, 800, 900, 1000}
160169
161170 returns a numpy array of shape (time, roi)
162171 and labels and locs of rois
@@ -167,37 +176,38 @@ def nifti2array(nifti_file, confound_strategy="none", standardize=False, n_rois=
167176 'no_motion_no_gsr': motion parameters are used
168177 and global signal regression
169178 is applied.
170- """
171- from nilearn import datasets
172- from nilearn .interfaces .fmriprep import load_confounds
173- from nilearn .maskers import NiftiLabelsMasker
174- from nilearn .plotting import find_parcellation_cut_coords
175-
176- parc = datasets .fetch_atlas_schaefer_2018 (n_rois = n_rois )
177- atlas_filename = parc .maps
178- labels = parc .labels
179- # The list of labels does not contain ‘Background’ by default.
180- # To have proper indexing, you should either manually add ‘Background’ to the list of labels:
181- # Prepend background label
182- labels = np .insert (labels , 0 , "Background" )
183-
184- # extract locs
185- # test!
186- # check if order is the same as labels
187- locs , labels_ = find_parcellation_cut_coords (
188- atlas_filename , background_label = 0 , return_label_names = True
189- )
190-
191- # create the masker for extracting time series
192- masker = NiftiLabelsMasker (
193- labels_img = atlas_filename ,
194- labels = labels ,
195- resampling_target = "data" ,
196- standardize = standardize ,
197- )
179+ 'simple': nilearn's simple preprocessing with
180+ full motion and basic wm_csf
181+ and high_pass
198182
199- labels = np .delete (labels , 0 ) # remove the background label
200- labels = [label .decode () for label in labels ]
183+ For now it only works with NiftiLabelsMasker and NiftiSpheresMasker and not with NiftiMapsMasker
184+ masker_type: "NiftiLabelsMasker" or "NiftiSpheresMasker"
185+ """
186+ if masker_type == "NiftiSpheresMasker" :
187+ # check if seeds and radius are provided
188+ if seeds is None or radius is None :
189+ raise ValueError ("For NiftiSpheresMasker, seeds and radius must be provided." )
190+ # create the masker for extracting time series
191+ masker = NiftiSpheresMasker (
192+ seeds = seeds ,
193+ radius = radius , # radius in mm
194+ standardize = standardize ,
195+ )
196+ elif masker_type == "NiftiLabelsMasker" :
197+ # check if labels_img is provided
198+ if labels_img is None :
199+ raise ValueError ("For NiftiLabelsMasker, labels_img must be provided." )
200+ # create the masker for extracting time series
201+ masker = NiftiLabelsMasker (
202+ labels_img = labels_img ,
203+ resampling_target = "data" ,
204+ standardize = standardize ,
205+ )
206+ else :
207+ raise ValueError (
208+ "masker_type must be 'NiftiLabelsMasker' or 'NiftiSpheresMasker', "
209+ f"but got { masker_type } "
210+ )
201211
202212 ### extract the timeseries
203213 if confound_strategy == "none" :
@@ -223,16 +233,146 @@ def nifti2array(nifti_file, confound_strategy="none", standardize=False, n_rois=
223233 time_series = masker .fit_transform (
224234 nifti_file , confounds = confounds_simple , sample_mask = sample_mask
225235 )
236+ elif confound_strategy == "simple" :
237+ confounds_simple , sample_mask = load_confounds_strategy (
238+ nifti_file , denoise_strategy = "simple"
239+ )
240+ time_series = masker .fit_transform (
241+ nifti_file , confounds = confounds_simple , sample_mask = sample_mask
242+ )
243+ else :
244+ raise ValueError (
245+ "confound_strategy must be one of 'none', 'no_motion', 'no_motion_no_gsr', or 'simple', "
246+ f"but got { confound_strategy } "
247+ )
248+
249+ return time_series
250+
251+
252+ def nifti2array (
253+ nifti_file ,
254+ masker_type = "NiftiLabelsMasker" ,
255+ confound_strategy = "none" ,
256+ standardize = False ,
257+ n_rois = 100 ,
258+ labels_img = None ,
259+ seeds = None ,
260+ radius = None ,
261+ region_names = None ,
262+ ):
263+ """
264+ this function uses nilearn maskers to extract
265+ BOLD signals from nifti files
266+
267+ returns a numpy array of shape (time, roi)
268+ and labels and locs of rois
269+
270+ confound_strategy:
271+ 'none': no confounds are used
272+ 'no_motion': motion parameters are used
273+ 'no_motion_no_gsr': motion parameters are used
274+ and global signal regression
275+ is applied.
276+ 'simple': nilearn's simple preprocessing with
277+ full motion and basic wm_csf
278+ and high_pass
279+
280+ For now it only works with NiftiLabelsMasker and NiftiSpheresMasker and not with NiftiMapsMasker
281+ masker_type: "NiftiLabelsMasker" or "NiftiSpheresMasker"
282+ if masker_type is "NiftiLabelsMasker",
283+ labels_img must be provided or n_rois must be provided
284+ if masker_type is "NiftiSpheresMasker",
285+ seeds and radius must be provided
286+
287+ Note:
288+ when not using Schaefer atlas, make sure
289+ that the labels_img/seeds and region_names are in the same order.
290+ """
291+ if masker_type == "NiftiLabelsMasker" :
292+ if labels_img is None :
293+ # in this case, we will use the schaefer atlas
294+ # we use n_rois to determine the number of rois
295+ assert n_rois in [
296+ 100 ,
297+ 200 ,
298+ 300 ,
299+ 400 ,
300+ 500 ,
301+ 600 ,
302+ 700 ,
303+ 800 ,
304+ 900 ,
305+ 1000 ,
306+ ], "n_rois must be one of {100, 200, 300, 400, 500, 600, 700, 800, 900, 1000}"
307+ # fetch the schaefer atlas
308+ parc = datasets .fetch_atlas_schaefer_2018 (n_rois = n_rois )
309+ labels_img = parc .maps
310+ labels = parc .labels
311+ labels = [label .decode () for label in labels ]
312+ else :
313+ assert (
314+ region_names is not None
315+ ), "region_names must be provided if labels_img is provided"
316+ assert type (region_names ) is list , "region_names must be a list of strings"
317+
318+ labels = region_names
319+
320+ # extract locs from labels_img
321+ # check if order is the same as labels
322+ locs , labels_ = find_parcellation_cut_coords (
323+ labels_img , background_label = 0 , return_label_names = True
324+ ) # numpy.ndarray of shape (n_labels, 3)
325+
326+ elif masker_type == "NiftiSpheresMasker" :
327+
328+ # make sure seeds is a list of tuples (x, y, z)
329+ assert seeds is not None , "seeds must be provided for NiftiSpheresMasker"
330+ assert radius is not None , "radius must be provided for NiftiSpheresMasker"
331+ assert type (seeds ) is list , "seeds must be a list of tuples (x, y, z)"
332+ assert all (
333+ isinstance (seed , tuple ) and len (seed ) == 3 for seed in seeds
334+ ), "seeds must be a list of tuples (x, y, z) with 3 elements each"
335+
336+ locs = np .array (seeds ) # seeds should be a list of tuples (x, y, z)
337+
338+ assert (
339+ region_names is not None
340+ ), "region_names must be provided if seeds are provided"
341+ assert type (region_names ) is list , "region_names must be a list of strings"
342+
343+ labels = region_names
344+
345+ else :
346+ raise ValueError (
347+ "masker_type must be 'NiftiLabelsMasker' or 'NiftiSpheresMasker', "
348+ f"but got { masker_type } "
349+ )
350+
351+ # extract the timeseries
352+ time_series = extract_region_signals (
353+ nifti_file = nifti_file ,
354+ masker_type = masker_type ,
355+ confound_strategy = confound_strategy ,
356+ standardize = standardize ,
357+ labels_img = labels_img ,
358+ seeds = seeds ,
359+ radius = radius ,
360+ )
226361
227362 return time_series , labels , locs
228363
229364
230365def nifti2timeseries (
231366 nifti_file ,
232- n_rois ,
233367 Fs ,
234368 subj_id ,
235369 confound_strategy = "none" ,
370+ masker_type = "NiftiLabelsMasker" ,
371+ n_rois = 100 ,
372+ labels_img = None ,
373+ seeds = None ,
374+ radius = None ,
375+ region_names = None ,
236376 standardize = False ,
237377 TS_name = None ,
238378 session = None ,
@@ -242,15 +382,50 @@ def nifti2timeseries(
242382 it uses nilearn maskers to extract ROI signals from nifti files
243383 and returns a TIME_SERIES object
244384
245- For now it only works with schaefer atlas,
246- but you can set the number of rois to extract
247- {100, 200, 300, 400, 500, 600, 700, 800, 900, 1000}
385+ Parameters
386+ ----------
387+ nifti_file : str
388+ path to the nifti file
389+ Fs : float
390+ sampling frequency of the data
391+ subj_id : str
392+ subject ID, must start with 'sub-'
393+ confound_strategy : str, optional
394+ strategy for confound regression, by default "none"
395+ masker_type : str, optional
396+ type of masker to use, by default "NiftiLabelsMasker"
397+ n_rois : int, optional
398+ number of regions of interest to extract, by default 100
399+ labels_img : str, optional
400+ path to the labels image, by default None
401+ seeds : list, optional
402+ list of tuples (x, y, z) for NiftiSpheresMasker
403+ by default None
404+ radius : float, optional
405+ radius in mm for NiftiSpheresMasker, by default None
406+ region_names : list, optional
407+ list of region names for NiftiLabelsMasker or NiftiSpheresMasker,
408+ by default None
409+ standardize : bool, optional
410+ whether to standardize the time series, by default False
411+ TS_name : str, optional
412+ name of the time series, by default None
413+ session : str, optional
414+ session name, by default None
415+
416+ For more information on confound_strategy, masker_type, and other parameters,
417+ see the documentation of the nifti2array function.
248418 """
249419 time_series , labels , locs = nifti2array (
250420 nifti_file = nifti_file ,
251421 confound_strategy = confound_strategy ,
252422 standardize = standardize ,
423+ masker_type = masker_type ,
253424 n_rois = n_rois ,
425+ labels_img = labels_img ,
426+ seeds = seeds ,
427+ radius = radius ,
428+ region_names = region_names ,
254429 )
255430
256431 assert type (locs ) is np .ndarray , "locs must be a numpy array"
@@ -280,8 +455,13 @@ def nifti2timeseries(
280455def multi_nifti2timeseries (
281456 nifti_files_list ,
282457 subj_id_list ,
283- n_rois ,
284458 Fs ,
459+ masker_type = "NiftiLabelsMasker" ,
460+ n_rois = 100 ,
461+ labels_img = None ,
462+ seeds = None ,
463+ radius = None ,
464+ region_names = None ,
285465 confound_strategy = "none" ,
286466 standardize = False ,
287467 TS_name = None ,
@@ -295,10 +475,15 @@ def multi_nifti2timeseries(
295475 if BOLD_multi is None :
296476 BOLD_multi = nifti2timeseries (
297477 nifti_file = nifti_file ,
298- n_rois = n_rois ,
299- Fs = Fs ,
300478 subj_id = subj_id ,
479+ Fs = Fs ,
301480 confound_strategy = confound_strategy ,
481+ masker_type = masker_type ,
482+ n_rois = n_rois ,
483+ labels_img = labels_img ,
484+ seeds = seeds ,
485+ radius = radius ,
486+ region_names = region_names ,
302487 standardize = standardize ,
303488 TS_name = TS_name ,
304489 session = session ,
@@ -307,10 +492,15 @@ def multi_nifti2timeseries(
307492 BOLD_multi .concat_ts (
308493 nifti2timeseries (
309494 nifti_file = nifti_file ,
310- n_rois = n_rois ,
311- Fs = Fs ,
312495 subj_id = subj_id ,
496+ Fs = Fs ,
313497 confound_strategy = confound_strategy ,
498+ masker_type = masker_type ,
499+ n_rois = n_rois ,
500+ labels_img = labels_img ,
501+ seeds = seeds ,
502+ radius = radius ,
503+ region_names = region_names ,
314504 standardize = standardize ,
315505 TS_name = TS_name ,
316506 session = session ,
0 commit comments