Skip to content

Commit a123cd1

Browse files
authored
Merge pull request #1884 from nx10/develop-1.8.6
Merge branch 'develop' into develop-1.8.6
2 parents 1b250fc + e5dc106 commit a123cd1

37 files changed

+757
-564
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2727
- Added `fail_fast` configuration setting and CLI flag
2828
- Added abililty to fork on motion filter
2929
- Added [`sdcflows`](https://www.nipreps.org/sdcflows/2.0/) to CPAC requirements
30+
- Added the ability to ingress FreeSurfer data into CPAC
31+
- Added the ability to toggle FreeSurfer derived masks for brain extraction
3032

3133
### Changed
3234
- Freesurfer output directory ingress moved to the data configuration YAML
@@ -66,6 +68,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6668
- Fixed a bug where ALFF & f/ALFF would not run if frequency filtering was disabled earlier in the pipeline.
6769
- Fixed a bug where `surface_analysis.freesurfer.freesurfer_dir` in the pipeline config was not ingressed at runtime.
6870
- Added public read access to some overly restricted packaged templates
71+
- Fixed a bug where notch filter was always assuming the sampling frequency was `2.0`.
6972

7073
## [v1.8.4] - 2022-06-27
7174

CPAC/anat_preproc/anat_preproc.py

Lines changed: 85 additions & 230 deletions
Large diffs are not rendered by default.

CPAC/anat_preproc/utils.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -174,22 +174,22 @@ def split_hemi_interface():
174174
for label in splits:
175175
wf.connect(reconall, label, splits[label], 'multi_file')
176176
outputs = {
177-
'hemi-L_desc-surface_curv': (splits['curv'], 'lh'),
178-
'hemi-R_desc-surface_curv': (splits['curv'], 'rh'),
179-
'hemi-L_desc-surfaceMesh_pial': (splits['pial'], 'lh'),
180-
'hemi-R_desc-surfaceMesh_pial': (splits['pial'], 'rh'),
181-
'hemi-L_desc-surfaceMesh_smoothwm': (splits['smoothwm'], 'lh'),
182-
'hemi-R_desc-surfaceMesh_smoothwm': (splits['smoothwm'], 'rh'),
183-
'hemi-L_desc-surfaceMesh_sphere': (splits['sphere'], 'lh'),
184-
'hemi-R_desc-surfaceMesh_sphere': (splits['sphere'], 'rh'),
185-
'hemi-L_desc-surfaceMap_sulc': (splits['sulc'], 'lh'),
186-
'hemi-R_desc-surfaceMap_sulc': (splits['sulc'], 'rh'),
187-
'hemi-L_desc-surfaceMap_thickness': (splits['thickness'], 'lh'),
188-
'hemi-R_desc-surfaceMap_thickness': (splits['thickness'], 'rh'),
189-
'hemi-L_desc-surfaceMap_volume': (splits['volume'], 'lh'),
190-
'hemi-R_desc-surfaceMap_volume': (splits['volume'], 'rh'),
191-
'hemi-L_desc-surfaceMesh_white': (splits['white'], 'lh'),
192-
'hemi-R_desc-surfaceMesh_white': (splits['white'], 'rh')}
177+
'pipeline-fs_hemi-L_desc-surface_curv': (splits['curv'], 'lh'),
178+
'pipeline-fs_hemi-R_desc-surface_curv': (splits['curv'], 'rh'),
179+
'pipeline-fs_hemi-L_desc-surfaceMesh_pial': (splits['pial'], 'lh'),
180+
'pipeline-fs_hemi-R_desc-surfaceMesh_pial': (splits['pial'], 'rh'),
181+
'pipeline-fs_hemi-L_desc-surfaceMesh_smoothwm': (splits['smoothwm'], 'lh'),
182+
'pipeline-fs_hemi-R_desc-surfaceMesh_smoothwm': (splits['smoothwm'], 'rh'),
183+
'pipeline-fs_hemi-L_desc-surfaceMesh_sphere': (splits['sphere'], 'lh'),
184+
'pipeline-fs_hemi-R_desc-surfaceMesh_sphere': (splits['sphere'], 'rh'),
185+
'pipeline-fs_hemi-L_desc-surfaceMap_sulc': (splits['sulc'], 'lh'),
186+
'pipeline-fs_hemi-R_desc-surfaceMap_sulc': (splits['sulc'], 'rh'),
187+
'pipeline-fs_hemi-L_desc-surfaceMap_thickness': (splits['thickness'], 'lh'),
188+
'pipeline-fs_hemi-R_desc-surfaceMap_thickness': (splits['thickness'], 'rh'),
189+
'pipeline-fs_hemi-L_desc-surfaceMap_volume': (splits['volume'], 'lh'),
190+
'pipeline-fs_hemi-R_desc-surfaceMap_volume': (splits['volume'], 'rh'),
191+
'pipeline-fs_hemi-L_desc-surfaceMesh_white': (splits['white'], 'lh'),
192+
'pipeline-fs_hemi-R_desc-surfaceMesh_white': (splits['white'], 'rh')}
193193

194194
return wf, outputs
195195

CPAC/connectome/connectivity_matrix.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,26 @@
11
#!/usr/bin/env python
22
# -*- coding: utf-8 -*-
3+
# Copyright (C) 2021-2023 C-PAC Developers
4+
5+
# This file is part of C-PAC.
6+
7+
# C-PAC is free software: you can redistribute it and/or modify it under
8+
# the terms of the GNU Lesser General Public License as published by the
9+
# Free Software Foundation, either version 3 of the License, or (at your
10+
# option) any later version.
11+
12+
# C-PAC is distributed in the hope that it will be useful, but WITHOUT
13+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
15+
# License for more details.
16+
17+
# You should have received a copy of the GNU Lesser General Public
18+
# License along with C-PAC. If not, see <https://www.gnu.org/licenses/>.
319
"""Functions for creating connectome connectivity matrices."""
420
import os
521
from warnings import warn
622
import numpy as np
723
from nilearn.connectome import ConnectivityMeasure
8-
from nilearn.input_data import NiftiLabelsMasker
924
from nipype import logging
1025
from nipype.interfaces import utility as util
1126
from CPAC.pipeline import nipype_pipeline_engine as pe
@@ -99,17 +114,22 @@ def compute_connectome_nilearn(in_rois, in_file, method, atlas_name):
99114
-------
100115
numpy.ndarray or NotImplemented
101116
"""
117+
from nilearn.input_data import NiftiLabelsMasker
118+
from nipype.utils.tmpdirs import TemporaryDirectory
102119
tool = 'Nilearn'
103120
output = connectome_name(atlas_name, tool, method)
104121
method = get_connectome_method(method, tool)
105122
if method is NotImplemented:
106123
return NotImplemented
107-
masker = NiftiLabelsMasker(labels_img=in_rois,
108-
standardize=True,
109-
verbose=True)
110-
timeser = masker.fit_transform(in_file)
111-
correlation_measure = ConnectivityMeasure(kind=method)
112-
corr_matrix = correlation_measure.fit_transform([timeser])[0]
124+
with TemporaryDirectory() as cache_dir:
125+
masker = NiftiLabelsMasker(labels_img=in_rois,
126+
standardize=True,
127+
verbose=True,
128+
memory=cache_dir,
129+
memory_level=3)
130+
timeser = masker.fit_transform(in_file)
131+
correlation_measure = ConnectivityMeasure(kind=method)
132+
corr_matrix = correlation_measure.fit_transform([timeser])[0]
113133
np.fill_diagonal(corr_matrix, 1)
114134
np.savetxt(output, corr_matrix, delimiter='\t')
115135
return output

CPAC/func_preproc/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ def notch_filter_motion(motion_params, filter_type, TR, fc_RR_min=None,
163163
bandwidth = fa[1] - fa[0]
164164

165165
Q = Wn/bw
166-
[b_filt, a_filt] = iirnotch(Wn, Q)
166+
[b_filt, a_filt] = iirnotch(Wn, Q, fs)
167167
num_f_apply = np.floor(filter_order / 2)
168168

169169
filter_info = f"Motion estimate filter information\n\nType: Notch\n" \

CPAC/nuisance/nuisance.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
# Copyright (C) 2012-2023 C-PAC Developers
2+
3+
# This file is part of C-PAC.
4+
5+
# C-PAC is free software: you can redistribute it and/or modify it under
6+
# the terms of the GNU Lesser General Public License as published by the
7+
# Free Software Foundation, either version 3 of the License, or (at your
8+
# option) any later version.
9+
10+
# C-PAC is distributed in the hope that it will be useful, but WITHOUT
11+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
13+
# License for more details.
14+
15+
# You should have received a copy of the GNU Lesser General Public
16+
# License along with C-PAC. If not, see <https://www.gnu.org/licenses/>.
117
import re
218
import os
319
import numpy as np
@@ -2455,8 +2471,9 @@ def nuisance_regression(wf, cfg, strat_pool, pipe_num, opt, space, res=None):
24552471
'2-nuisance_regression',
24562472
'bandpass_filtering_order'] == 'Before'
24572473

2458-
name_suff = (f'{space}_{opt["Name"]}_{pipe_num}' if res is None else
2459-
f'{space}_res-{res}_{opt["Name"]}_{pipe_num}')
2474+
name_suff = (f'space-{space}_reg-{opt["Name"]}_{pipe_num}'
2475+
if res is None else
2476+
f'space-{space}_res-{res}_reg-{opt["Name"]}_{pipe_num}')
24602477
nuis_name = f'nuisance_regression_{name_suff}'
24612478

24622479
nuis = create_nuisance_regression_workflow(opt, name=nuis_name)
@@ -2475,7 +2492,8 @@ def nuisance_regression(wf, cfg, strat_pool, pipe_num, opt, space, res=None):
24752492
# sometimes mm dimensions match but the voxel dimensions don't
24762493
# so here we align the mask to the resampled data before applying
24772494
match_grid = pe.Node(afni.Resample(),
2478-
name='align_template_mask_to_template_data')
2495+
name='align_template_mask_to_template_data_'
2496+
f'{name_suff}')
24792497
match_grid.inputs.outputtype = 'NIFTI_GZ'
24802498
match_grid.inputs.resample_mode = 'Cu'
24812499
node, out = strat_pool.get_data('FSL-AFNI-brain-mask')
@@ -2626,6 +2644,7 @@ def nuisance_regression_template(wf, cfg, strat_pool, pipe_num, opt=None):
26262644
"inputs": [("desc-stc_bold",
26272645
"space-template_desc-preproc_bold",
26282646
"space-template_res-derivative_desc-preproc_bold",
2647+
"movement-parameters",
26292648
"regressors",
26302649
"FSL-AFNI-brain-mask",
26312650
"framewise-displacement-jenkinson",

CPAC/pipeline/cpac_pipeline.py

Lines changed: 36 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
"""Copyright (C) 2022 C-PAC Developers
1+
# Copyright (C) 2012-2023 C-PAC Developers
22

3-
This file is part of C-PAC.
3+
# This file is part of C-PAC.
44

5-
C-PAC is free software: you can redistribute it and/or modify it under
6-
the terms of the GNU Lesser General Public License as published by the
7-
Free Software Foundation, either version 3 of the License, or (at your
8-
option) any later version.
5+
# C-PAC is free software: you can redistribute it and/or modify it under
6+
# the terms of the GNU Lesser General Public License as published by the
7+
# Free Software Foundation, either version 3 of the License, or (at your
8+
# option) any later version.
99

10-
C-PAC is distributed in the hope that it will be useful, but WITHOUT
11-
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12-
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
13-
License for more details.
10+
# C-PAC is distributed in the hope that it will be useful, but WITHOUT
11+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
13+
# License for more details.
1414

15-
You should have received a copy of the GNU Lesser General Public
16-
License along with C-PAC. If not, see <https://www.gnu.org/licenses/>."""
15+
# You should have received a copy of the GNU Lesser General Public
16+
# License along with C-PAC. If not, see <https://www.gnu.org/licenses/>.
1717
import os
1818
import sys
1919
import time
@@ -42,7 +42,6 @@
4242
from CPAC.pipeline.engine import NodeBlock, initiate_rpool
4343
from CPAC.anat_preproc.anat_preproc import (
4444
freesurfer_reconall,
45-
freesurfer_postproc,
4645
freesurfer_abcd_preproc,
4746
anatomical_init,
4847
acpc_align_head,
@@ -82,7 +81,7 @@
8281
brain_mask_T2,
8382
brain_mask_acpc_T2,
8483
brain_extraction_temp_T2,
85-
brain_extraction_T2
84+
brain_extraction_T2,
8685
)
8786

8887
from CPAC.registration.registration import (
@@ -867,40 +866,28 @@ def build_anat_preproc_stack(rpool, cfg, pipeline_blocks=None):
867866
]
868867
pipeline_blocks += anat_init_blocks
869868

870-
if rpool.check_rpool('freesurfer-subject-dir'):
871-
pipeline_blocks += [freesurfer_postproc]
872-
else:
869+
if not rpool.check_rpool('freesurfer-subject-dir'):
873870
pipeline_blocks += [freesurfer_reconall] # includes postproc
874871

875872
if not rpool.check_rpool('desc-preproc_T1w'):
876873

877874
# brain masking for ACPC alignment
878875
if cfg.anatomical_preproc['acpc_alignment']['acpc_target'] == 'brain':
879-
if rpool.check_rpool('space-T1w_desc-brain_mask') or \
880-
cfg.surface_analysis['freesurfer']['run_reconall']:
881-
acpc_blocks = [
882-
brain_extraction_temp,
883-
acpc_align_brain_with_mask
884-
# outputs space-T1w_desc-brain_mask for later - keep the mask (the user provided)
885-
]
886-
acpc_blocks.append(
887-
[brain_mask_acpc_freesurfer_fsl_tight,
888-
brain_mask_acpc_freesurfer_fsl_loose]
889-
)
890-
else:
891876
acpc_blocks = [
892877
[brain_mask_acpc_afni,
893878
brain_mask_acpc_fsl,
894879
brain_mask_acpc_niworkflows_ants,
895880
brain_mask_acpc_unet,
896-
brain_mask_acpc_freesurfer_abcd],
897-
# brain_mask_acpc_freesurfer
898-
# we don't want these masks to be used later
881+
brain_mask_acpc_freesurfer_abcd,
882+
brain_mask_acpc,
883+
brain_mask_acpc_freesurfer,
884+
brain_mask_acpc_freesurfer_fsl_tight,
885+
brain_mask_acpc_freesurfer_fsl_loose],
886+
acpc_align_brain_with_mask,
899887
brain_extraction_temp,
900888
acpc_align_brain
901889
]
902-
elif cfg.anatomical_preproc['acpc_alignment'][
903-
'acpc_target'] == 'whole-head':
890+
elif cfg.anatomical_preproc['acpc_alignment']['acpc_target'] == 'whole-head':
904891
if (rpool.check_rpool('space-T1w_desc-brain_mask') and \
905892
cfg.anatomical_preproc['acpc_alignment']['align_brain_mask']) or \
906893
cfg.surface_analysis['freesurfer']['run_reconall']:
@@ -912,6 +899,7 @@ def build_anat_preproc_stack(rpool, cfg, pipeline_blocks=None):
912899
acpc_blocks = [
913900
acpc_align_head # does not output nor generate a mask
914901
]
902+
915903

916904
anat_preproc_blocks = [
917905
(non_local_means, ('T1w', ['desc-preproc_T1w',
@@ -929,18 +917,18 @@ def build_anat_preproc_stack(rpool, cfg, pipeline_blocks=None):
929917
pipeline_blocks += [freesurfer_abcd_preproc]
930918

931919
# Anatomical T1 brain masking
932-
if not rpool.check_rpool('space-T1w_desc-brain_mask') or \
933-
cfg.surface_analysis['freesurfer']['run_reconall']:
934-
anat_brain_mask_blocks = [
935-
[brain_mask_afni,
936-
brain_mask_fsl,
937-
brain_mask_niworkflows_ants,
938-
brain_mask_unet,
939-
brain_mask_freesurfer_abcd,
940-
brain_mask_freesurfer_fsl_tight,
941-
brain_mask_freesurfer_fsl_loose]
942-
]
943-
pipeline_blocks += anat_brain_mask_blocks
920+
921+
anat_brain_mask_blocks = [
922+
[brain_mask_afni,
923+
brain_mask_fsl,
924+
brain_mask_niworkflows_ants,
925+
brain_mask_unet,
926+
brain_mask_freesurfer_abcd,
927+
brain_mask_freesurfer,
928+
brain_mask_freesurfer_fsl_tight,
929+
brain_mask_freesurfer_fsl_loose]
930+
]
931+
pipeline_blocks += anat_brain_mask_blocks
944932

945933
# T2w Anatomical Preprocessing
946934
if rpool.check_rpool('T2w'):
@@ -1032,7 +1020,6 @@ def build_T1w_registration_stack(rpool, cfg, pipeline_blocks=None):
10321020
warp_T1mask_to_template
10331021
]
10341022

1035-
10361023
if not rpool.check_rpool('desc-restore-brain_T1w'):
10371024
reg_blocks.append(correct_restore_brain_intensity_abcd)
10381025

@@ -1363,6 +1350,7 @@ def build_workflow(subject_id, sub_dict, cfg, pipeline_name=None,
13631350
apply_func_warp['EPI'] = (_r_w_f_r['coregistration']['run'] and _r_w_f_r['func_registration_to_template']['run_EPI'])
13641351
else:
13651352
apply_func_warp['EPI'] = (_r_w_f_r['func_registration_to_template']['run_EPI'])
1353+
13661354
del _r_w_f_r
13671355

13681356
template_funcs = [
@@ -1394,6 +1382,7 @@ def build_workflow(subject_id, sub_dict, cfg, pipeline_name=None,
13941382

13951383
# PostFreeSurfer and fMRISurface
13961384
if not rpool.check_rpool('space-fsLR_den-32k_bold.dtseries'):
1385+
13971386
pipeline_blocks += [surface_postproc]
13981387

13991388
# Extractions and Derivatives

0 commit comments

Comments
 (0)