Skip to content

Commit de04fc6

Browse files
authored
Merge pull request #39 from mtorabi59/main
adding the new commits
2 parents ca2dac9 + 888551b commit de04fc6

11 files changed

Lines changed: 419 additions & 87 deletions

pydfc/data_loader.py

Lines changed: 233 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010

1111
import h5py
1212
import 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

1418
from .dfc_utils import intersection, label2network
1519
from .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

230365
def 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(
280455
def 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

Comments
 (0)