Skip to content

Commit b3a8f76

Browse files
authored
Merge pull request #1630 from FCP-INDI/feature/surface_convenience
Feature/surface convenience
2 parents 8d36d61 + 1351a1c commit b3a8f76

File tree

9 files changed

+93
-50
lines changed

9 files changed

+93
-50
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
- Added changelog
1414
- Added CHD8 mouse template (`/cpac_templates/chd8_functional_template_noise_mask_ag.nii.gz`)
1515
- Added commandline flags `--T1w_label` and `--bold_label`
16+
- Added the ability to ingress an entire FreeSurfer output directory to bypass surface analysis if already completed elsewhere
1617
- Added AFNI and Nilearn implementations of Pearson and partial correlation matrices
1718

1819
### Changed
@@ -34,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3435
- Improved memory management for multi-core node allocation.
3536
- Fixed [bug](https://github.com/FCP-INDI/C-PAC/issues/1548) where `--participant_label [A B C]` would skip first and last labels (`A` and `C`).
3637
- Stripped the ABI tag note (which was preventing the library from loading dynamically on some host operating systems) from `libQt5Core.so.5` in the ABCD-HCP variant image.
38+
- Fixed an issue blocking non-C-PAC output data from being read in without sidecar meta-data.
3739

3840
## [1.8.1] - 2021-09-17
3941

CPAC/pipeline/cpac_pipeline.py

Lines changed: 46 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -791,7 +791,8 @@ def build_anat_preproc_stack(rpool, cfg, pipeline_blocks=None):
791791
]
792792
pipeline_blocks += anat_init_blocks
793793

794-
pipeline_blocks += [freesurfer_preproc]
794+
if not rpool.check_rpool('freesurfer-subject-dir'):
795+
pipeline_blocks += [freesurfer_preproc]
795796

796797
if not rpool.check_rpool('desc-preproc_T1w'):
797798

@@ -806,7 +807,7 @@ def build_anat_preproc_stack(rpool, cfg, pipeline_blocks=None):
806807
]
807808
acpc_blocks.append(
808809
[brain_mask_acpc_freesurfer_fsl_tight,
809-
brain_mask_acpc_freesurfer_fsl_loose]
810+
brain_mask_acpc_freesurfer_fsl_loose]
810811
)
811812
else:
812813
acpc_blocks = [
@@ -846,8 +847,9 @@ def build_anat_preproc_stack(rpool, cfg, pipeline_blocks=None):
846847
anat_blocks = anat_preproc_blocks + acpc_blocks
847848

848849
pipeline_blocks += anat_blocks
849-
850-
pipeline_blocks += [freesurfer_abcd_preproc]
850+
851+
if not rpool.check_rpool('freesurfer-subject-dir'):
852+
pipeline_blocks += [freesurfer_abcd_preproc]
851853

852854
# Anatomical T1 brain masking
853855
if not rpool.check_rpool('space-T1w_desc-brain_mask') or \
@@ -948,9 +950,11 @@ def build_T1w_registration_stack(rpool, cfg, pipeline_blocks=None):
948950
if not rpool.check_rpool('from-T1w_to-template_mode-image_xfm'):
949951
reg_blocks = [
950952
[register_ANTs_anat_to_template, register_FSL_anat_to_template],
951-
overwrite_transform_anat_to_template,
952-
correct_restore_brain_intensity_abcd # ABCD-options pipeline
953+
overwrite_transform_anat_to_template
953954
]
955+
956+
if not rpool.check_rpool('desc-restore-brain_T1w'):
957+
reg_blocks.append(correct_restore_brain_intensity_abcd)
954958

955959
if cfg.voxel_mirrored_homotopic_connectivity['run']:
956960
if not rpool.check_rpool('from-T1w_to-symtemplate_mode-image_xfm'):
@@ -971,7 +975,6 @@ def build_segmentation_stack(rpool, cfg, pipeline_blocks=None):
971975
seg_blocks = [
972976
[tissue_seg_fsl_fast,
973977
tissue_seg_ants_prior]
974-
#tissue_seg_freesurfer
975978
]
976979
if 'T1_Template' in cfg.segmentation['tissue_segmentation'][
977980
'Template_Based']['template_for_segmentation']:
@@ -1076,10 +1079,7 @@ def build_workflow(subject_id, sub_dict, cfg, pipeline_name=None,
10761079
pipeline_blocks = build_segmentation_stack(rpool, cfg, pipeline_blocks)
10771080

10781081
# Functional Preprocessing, including motion correction and BOLD masking
1079-
if cfg.functional_preproc['run'] and \
1080-
(not rpool.check_rpool('desc-brain_bold') or
1081-
not rpool.check_rpool('space-bold_desc-brain_mask') or
1082-
not rpool.check_rpool('movement-parameters')):
1082+
if cfg.functional_preproc['run']:
10831083
func_init_blocks = [
10841084
func_scaling,
10851085
func_truncate
@@ -1089,14 +1089,20 @@ def build_workflow(subject_id, sub_dict, cfg, pipeline_name=None,
10891089
func_slice_time,
10901090
func_reorient
10911091
]
1092+
1093+
if not rpool.check_rpool('desc-mean_bold'):
1094+
func_preproc_blocks.append(func_mean)
1095+
1096+
func_mask_blocks = []
1097+
if not rpool.check_rpool('space-bold_desc-brain_mask'):
1098+
func_mask_blocks = [
1099+
[bold_mask_afni, bold_mask_fsl, bold_mask_fsl_afni,
1100+
bold_mask_anatomical_refined, bold_mask_anatomical_based,
1101+
bold_mask_anatomical_resampled, bold_mask_ccs],
1102+
bold_masking]
1103+
10921104
func_prep_blocks = [
1093-
[bold_mask_afni, bold_mask_fsl, bold_mask_fsl_afni,
1094-
bold_mask_anatomical_refined, bold_mask_anatomical_based,
1095-
bold_mask_anatomical_resampled,
1096-
bold_mask_ccs],
1097-
bold_masking,
10981105
calc_motion_stats,
1099-
func_mean,
11001106
func_normalize
11011107
]
11021108

@@ -1114,24 +1120,31 @@ def build_workflow(subject_id, sub_dict, cfg, pipeline_name=None,
11141120
distcor_blocks = [distcor_blocks]
11151121
func_prep_blocks += distcor_blocks
11161122

1117-
if cfg['functional_preproc']['motion_estimates_and_correction'][
1118-
'motion_estimates']['calculate_motion_first']:
1119-
func_motion_blocks = [
1120-
get_motion_ref,
1121-
func_motion_estimates,
1122-
motion_estimate_filter
1123-
]
1124-
func_blocks = func_init_blocks + func_motion_blocks + \
1125-
func_preproc_blocks + [func_motion_correct_only] + \
1126-
func_prep_blocks
1123+
func_motion_blocks = []
1124+
if not rpool.check_rpool('movement-parameters'):
1125+
if cfg['functional_preproc']['motion_estimates_and_correction'][
1126+
'motion_estimates']['calculate_motion_first']:
1127+
func_motion_blocks = [
1128+
get_motion_ref,
1129+
func_motion_estimates,
1130+
motion_estimate_filter
1131+
]
1132+
func_blocks = func_init_blocks + func_motion_blocks + \
1133+
func_preproc_blocks + [func_motion_correct_only] + \
1134+
func_mask_blocks + func_prep_blocks
1135+
else:
1136+
func_motion_blocks = [
1137+
get_motion_ref,
1138+
func_motion_correct,
1139+
motion_estimate_filter
1140+
]
1141+
func_blocks = func_init_blocks + func_preproc_blocks + \
1142+
func_motion_blocks + func_mask_blocks + \
1143+
func_prep_blocks
11271144
else:
1128-
func_motion_blocks = [
1129-
get_motion_ref,
1130-
func_motion_correct,
1131-
motion_estimate_filter
1132-
]
11331145
func_blocks = func_init_blocks + func_preproc_blocks + \
1134-
func_motion_blocks + func_prep_blocks
1146+
func_motion_blocks + func_mask_blocks + \
1147+
func_prep_blocks
11351148

11361149
pipeline_blocks += func_blocks
11371150

CPAC/pipeline/engine.py

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1466,6 +1466,12 @@ def ingress_raw_func_data(wf, rpool, cfg, data_paths, unique_id, part_id,
14661466
def ingress_output_dir(cfg, rpool, unique_id, creds_path=None):
14671467

14681468
out_dir = cfg.pipeline_setup['output_directory']['path']
1469+
1470+
if not os.path.isdir(out_dir):
1471+
print(f"\nOutput directory {out_dir} does not exist yet, "
1472+
"initializing.")
1473+
os.makedirs(out_dir)
1474+
14691475
source = False
14701476

14711477
if cfg.pipeline_setup['output_directory']['pull_source_once']:
@@ -1520,7 +1526,7 @@ def ingress_output_dir(cfg, rpool, unique_id, creds_path=None):
15201526
cpac_dir_anat = os.path.join(cpac_dir, 'anat')
15211527
cpac_dir_func = os.path.join(cpac_dir, 'func')
15221528

1523-
exts = ['.nii', '.gz', '.mat', '.1D', '.txt', '.csv', '.rms']
1529+
exts = ['.nii', '.gz', '.mat', '.1D', '.txt', '.csv', '.rms', '.mgz']
15241530

15251531
all_output_dir = []
15261532
if os.path.isdir(cpac_dir_anat):
@@ -1562,10 +1568,6 @@ def ingress_output_dir(cfg, rpool, unique_id, creds_path=None):
15621568

15631569
unique_data_label = str(data_label)
15641570

1565-
#if 'sub-' in data_label or 'ses-' in data_label:
1566-
# raise Exception('\n\n[!] Possibly wrong participant or '
1567-
# 'session in this directory?\n\nDirectory: '
1568-
# f'{cpac_dir_anat}\nFilepath: {filepath}\n\n')
15691571
suffix = data_label.split('_')[-1]
15701572
desc_val = None
15711573
for tag in data_label.split('_'):
@@ -1578,16 +1580,21 @@ def ingress_output_dir(cfg, rpool, unique_id, creds_path=None):
15781580
jsonpath = f"{jsonpath}.json"
15791581

15801582
if not os.path.exists(jsonpath):
1581-
print(f'\n\n[!] No JSON found for file {filepath}.\nCreating '
1582-
f'{jsonpath}..\n\n')
1583+
print(f'\n\n[!] No JSON found for file {filepath}.')
1584+
if not source:
1585+
print(f'Creating {jsonpath}..\n\n')
1586+
else:
1587+
print('Creating meta-data for the data..\n\n')
15831588
json_info = {
1589+
'CpacProvenance': [f'{data_label}:Non-C-PAC Origin'],
15841590
'Description': 'This data was generated elsewhere and '
15851591
'supplied by the user into this C-PAC run\'s '
15861592
'output directory. This JSON file was '
15871593
'automatically generated by C-PAC because a '
15881594
'JSON file was not supplied with the data.'
15891595
}
1590-
write_output_json(json_info, jsonpath)
1596+
if not source:
1597+
write_output_json(json_info, jsonpath)
15911598
else:
15921599
json_info = read_json(jsonpath)
15931600

@@ -1664,7 +1671,7 @@ def ingress_pipeconfig_paths(cfg, rpool, unique_id, creds_path=None):
16641671
res_keys = [x.lstrip() for x in resolution.split(',')]
16651672
tag = res_keys[-1]
16661673

1667-
json_info = {}
1674+
json_info = {}
16681675

16691676
if '$FSLDIR' in val:
16701677
val = val.replace('$FSLDIR', cfg.pipeline_setup[
@@ -1722,7 +1729,20 @@ def ingress_pipeconfig_paths(cfg, rpool, unique_id, creds_path=None):
17221729
)
17231730
rpool.set_data(key, config_ingress, 'outputspec.data', json_info,
17241731
"", f"{key}_config_ingress")
1725-
1732+
1733+
# Freesurfer directory, not a template, so not in cpac_templates.tsv
1734+
if cfg.surface_analysis['freesurfer']['freesurfer_dir']:
1735+
fs_ingress = create_general_datasource(f'gather_freesurfer_dir')
1736+
fs_ingress.inputs.inputnode.set(
1737+
unique_id=unique_id,
1738+
data=cfg.surface_analysis['freesurfer']['freesurfer_dir'],
1739+
creds_path=creds_path,
1740+
dl_dir=cfg.pipeline_setup['working_directory']['path']
1741+
)
1742+
rpool.set_data("freesurfer-subject-dir", fs_ingress, 'outputspec.data',
1743+
json_info, "", f"freesurfer_config_ingress")
1744+
1745+
17261746
# templates, resampling from config
17271747
'''
17281748
template_keys = [

CPAC/pipeline/schema.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,7 @@ def _changes_1_8_0_to_1_8_1(config_dict):
687687
'freesurfer': {
688688
'run': bool,
689689
'reconall_args': Maybe(str),
690+
'freesurfer_dir': Maybe(str)
690691
},
691692
'post_freesurfer': {
692693
'run': bool,

CPAC/registration/registration.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2429,10 +2429,10 @@ def overwrite_transform_anat_to_template(wf, cfg, strat_pool, pipe_num, opt=None
24292429
["desc-restore_T1w", "desc-preproc_T1w", "desc-reorient_T1w", "T1w"],
24302430
["desc-preproc_T1w", "desc-reorient_T1w", "T1w"],
24312431
"space-T1w_desc-brain_mask",
2432+
"T1w-template",
24322433
"from-T1w_to-template_mode-image_xfm",
24332434
"from-template_to-T1w_mode-image_xfm",
2434-
"space-template_desc-brain_T1w"),
2435-
"T1w-template",],
2435+
"space-template_desc-brain_T1w")],
24362436
"outputs": ["space-template_desc-brain_T1w",
24372437
"space-template_desc-head_T1w",
24382438
"space-template_desc-T1w_mask",

CPAC/resources/cpac_outputs.tsv

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ rh-pial-surface-mesh surface-derived anat
139139
raw-average surface-derived anat
140140
lh-smoothed-surface-mesh surface-derived anat
141141
rh-smoothed-surface-mesh surface-derived anat
142+
space-fsLR_den-32k_bold-dtseries surface-derived anat
142143
lh-spherical-surface-mesh surface-derived anat Yes
143144
rh-spherical-surface-mesh surface-derived anat Yes
144145
lh-sulcal-depth-surface-maps surface-derived anat Yes
@@ -147,10 +148,13 @@ lh-surface-curvature surface-derived anat
147148
rh-surface-curvature surface-derived anat
148149
lh-white-matter-surface-mesh surface-derived anat Yes
149150
rh-white-matter-surface-mesh surface-derived anat Yes
151+
wmparc surface-derived anat Yes
150152
space-symtemplate_desc-brain_T1w T1w symmetric template anat NIfTI
151153
desc-brain_T1w T1w T1w anat NIfTI
152154
desc-preproc_T1w T1w T1w anat NIfTI
153155
desc-reorient_T1w T1w T1w anat NIfTI
156+
desc-restore_T1w T1w T1w anat NIfTI
157+
desc-restore-brain_T1w T1w T1w anat NIfTI
154158
space-template_desc-brain_T1w T1w template anat NIfTI
155159
desc-Mean_timeseries timeseries func 1D
156160
desc-MeanSCA_timeseries timeseries func 1D

CPAC/surface/surf_preproc.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ def surface_connector(wf, cfg, strat_pool, pipe_num, opt):
116116
wf.connect(node, out, surf, 'scout_bold')
117117

118118
outputs = {
119-
'space-fsLR_den-32k_bold.dtseries': (surf, 'out_file')
119+
'space-fsLR_den-32k_bold-dtseries': (surf, 'out_file')
120120
}
121121

122122
return wf, outputs
@@ -136,9 +136,9 @@ def surface_preproc(wf, cfg, strat_pool, pipe_num, opt=None):
136136
"from-template_to-T1w_mode-image_xfm",
137137
"space-template_desc-brain_bold",
138138
"space-template_desc-scout_bold"],
139-
"outputs": ["space-fsLR_den-32k_bold.dtseries"]}
139+
"outputs": ["space-fsLR_den-32k_bold-dtseries"]}
140140
'''
141141

142142
wf, outputs = surface_connector(wf, cfg, strat_pool, pipe_num, opt)
143143

144-
return (wf, outputs)
144+
return (wf, outputs)

CPAC/utils/bids_utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ def bids_decode_fname(file_path, dbg=False, raise_error=True):
3030

3131
if len(sub) > 1:
3232
print("Odd that there is more than one subject directory" +
33-
"in (%s), does the filename conform to" % file_path +
34-
" BIDS format?")
33+
"in (%s), does the filename conform to" % file_path +
34+
" BIDS format?")
3535
if sub:
3636
sub_ndx = file_path_vals.index(sub[0])
3737
if sub_ndx > 0 and file_path_vals[sub_ndx - 1]:

dev/docker_data/default_pipeline.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,9 @@ surface_analysis:
162162

163163
# Add extra arguments to recon-all command
164164
reconall_args: None
165+
166+
# (Optional) Provide an already-existing FreeSurfer output directory to ingress already-computed surfaces
167+
freesurfer_dir: None
165168

166169
# Run ABCD-HCP post FreeSurfer and fMRISurface pipeline
167170
post_freesurfer:

0 commit comments

Comments
 (0)