Skip to content

Commit 1351a1c

Browse files
authored
Merge branch 'develop' into feature/surface_convenience
2 parents 5026411 + 8d36d61 commit 1351a1c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1453
-184
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- Added FSL-TOPUP as an option for distortion correction.
1213
- Added changelog
1314
- Added CHD8 mouse template (`/cpac_templates/chd8_functional_template_noise_mask_ag.nii.gz`)
1415
- Added commandline flags `--T1w_label` and `--bold_label`
1516
- Added the ability to ingress an entire FreeSurfer output directory to bypass surface analysis if already completed elsewhere
17+
- Added AFNI and Nilearn implementations of Pearson and partial correlation matrices
1618

1719
### Changed
1820

21+
- Expanded meta-data ingress for EPI field maps to include more fields when parsing BIDS sidecar JSONs.
1922
- Updated possible inputs for T2w processing and ACPC-alignment blocks to increase the modularity of these pipeline options.
2023
- `master` branch renamed `main`
2124
- Packaged templates in https://github.com/FCP-INDI/C-PAC_templates

CPAC/anat_preproc/anat_preproc.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
from CPAC.seg_preproc.utils import pick_tissue_from_labels_file
2222

23+
2324
def acpc_alignment(config=None, acpc_target='whole-head', mask=False,
2425
wf_name='acpc_align'):
2526
preproc = pe.Workflow(name=wf_name)
@@ -1270,8 +1271,9 @@ def anatomical_init(wf, cfg, strat_pool, pipe_num, opt=None):
12701271
def acpc_align_head(wf, cfg, strat_pool, pipe_num, opt=None):
12711272
'''
12721273
{"name": "acpc_alignment_head",
1273-
"config": ["anatomical_preproc", "acpc_alignment"],
1274-
"switch": ["run"],
1274+
"config": "None",
1275+
"switch": [["anatomical_preproc", "acpc_alignment", "run"],
1276+
["anatomical_preproc", "run"]],
12751277
"option_key": "None",
12761278
"option_val": "None",
12771279
"inputs": [["desc-preproc_T1w", "desc-reorient_T1w", "T1w"],
@@ -1305,8 +1307,9 @@ def acpc_align_head(wf, cfg, strat_pool, pipe_num, opt=None):
13051307
def acpc_align_head_with_mask(wf, cfg, strat_pool, pipe_num, opt=None):
13061308
'''
13071309
{"name": "acpc_alignment_head_with_mask",
1308-
"config": ["anatomical_preproc", "acpc_alignment"],
1309-
"switch": ["run"],
1310+
"config": "None",
1311+
"switch": [["anatomical_preproc", "acpc_alignment", "run"],
1312+
["anatomical_preproc", "run"]],
13101313
"option_key": "None",
13111314
"option_val": "None",
13121315
"inputs": [(["desc-preproc_T1w", "desc-reorient_T1w", "T1w"],
@@ -1352,8 +1355,9 @@ def acpc_align_head_with_mask(wf, cfg, strat_pool, pipe_num, opt=None):
13521355
def acpc_align_brain(wf, cfg, strat_pool, pipe_num, opt=None):
13531356
'''
13541357
{"name": "acpc_alignment_brain",
1355-
"config": ["anatomical_preproc", "acpc_alignment"],
1356-
"switch": ["run"],
1358+
"config": "None",
1359+
"switch": [["anatomical_preproc", "acpc_alignment", "run"],
1360+
["anatomical_preproc", "run"]],
13571361
"option_key": "None",
13581362
"option_val": "None",
13591363
"inputs": [(["desc-preproc_T1w", "desc-reorient_T1w", "T1w"],
@@ -1397,8 +1401,9 @@ def acpc_align_brain(wf, cfg, strat_pool, pipe_num, opt=None):
13971401
def acpc_align_brain_with_mask(wf, cfg, strat_pool, pipe_num, opt=None):
13981402
'''
13991403
{"name": "acpc_alignment_brain_with_mask",
1400-
"config": ["anatomical_preproc", "acpc_alignment"],
1401-
"switch": ["run"],
1404+
"config": "None",
1405+
"switch": [["anatomical_preproc", "acpc_alignment", "run"],
1406+
["anatomical_preproc", "run"]],
14021407
"option_key": "None",
14031408
"option_val": "None",
14041409
"inputs": [(["desc-preproc_T1w", "desc-reorient_T1w", "T1w"],
@@ -1483,8 +1488,9 @@ def registration_T2w_to_T1w(wf, cfg, strat_pool, pipe_num, opt=None):
14831488
def non_local_means(wf, cfg, strat_pool, pipe_num, opt=None):
14841489
'''
14851490
{"name": "nlm_filtering",
1486-
"config": ["anatomical_preproc", "non_local_means_filtering"],
1487-
"switch": ["run"],
1491+
"config": "None",
1492+
"switch": [["anatomical_preproc", "non_local_means_filtering", "run"],
1493+
["anatomical_preproc", "run"]],
14881494
"option_key": "None",
14891495
"option_val": "None",
14901496
"inputs": ["T1w"],
@@ -2367,7 +2373,7 @@ def brain_mask_acpc_niworkflows_ants_T2(wf, cfg, strat_pool, pipe_num, opt=None)
23672373
pipe_num, opt)
23682374

23692375
outputs = {
2370-
'space-T1w_desc-acpcbrain_mask':
2376+
'space-T2w_desc-acpcbrain_mask':
23712377
wf_outputs['space-T2w_desc-brain_mask']
23722378
}
23732379

CPAC/aroma/aroma_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
file, an output_dir which can be either be mentioned or if it is set to
88
None will write it in the current working directory.
99
10-
The argument run can either be tset to true(default) or to false. If
10+
The argument run can either be set to true(default) or to false. If
1111
set to False, it should connect to the nipype workflow and return the
1212
workflow object instead.
1313
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
"""Functions for creating connectome connectivity matrices."""
4+
from warnings import warn
5+
import numpy as np
6+
from nilearn.connectome import ConnectivityMeasure
7+
from nilearn.input_data import NiftiLabelsMasker
8+
from nipype import logging
9+
from nipype.interfaces import utility as util
10+
from CPAC.pipeline import nipype_pipeline_engine as pe
11+
from CPAC.utils.interfaces.function import Function
12+
from CPAC.utils.interfaces.netcorr import NetCorr, strip_afni_output_header
13+
14+
logger = logging.getLogger('nipype.workflow')
15+
connectome_methods = {
16+
'afni': {'Pearson': '',
17+
'Partial': '-part_corr'},
18+
'nilearn': {'Pearson': 'correlation',
19+
'Partial': 'partial correlation'}
20+
}
21+
22+
23+
def connectome_name(timeseries, atlas_name, tool, method):
24+
"""Helper function to create connectome file filename
25+
26+
Parameters
27+
----------
28+
timeseries : str
29+
path to input timeseries
30+
31+
atlas_name : str
32+
atlas name
33+
34+
tool : str
35+
connectome tool
36+
37+
method : str
38+
BIDS entity value for `desc-` key
39+
40+
Returns
41+
-------
42+
str
43+
"""
44+
method = ''.join(word.capitalize() for word in [tool, method])
45+
new_filename_parts = [part for part in timeseries.split('_')[:-1][::-1] if
46+
not part.startswith('space-')]
47+
atlas_index = len(new_filename_parts) - 1
48+
if any(filename_part.startswith('desc-') for filename_part in
49+
new_filename_parts):
50+
for i, filename_part in enumerate(new_filename_parts):
51+
if filename_part.startswith('desc-'):
52+
new_filename_parts[-i] = f'desc-{method}'
53+
atlas_index = -(i - 1)
54+
break
55+
new_filename_parts.insert(atlas_index, f'atlas-{atlas_name}')
56+
return '_'.join([*new_filename_parts[::-1], 'connectome.tsv'])
57+
58+
59+
def get_connectome_method(method, tool):
60+
"""Helper function to get tool's method string
61+
62+
Parameters
63+
----------
64+
method : str
65+
66+
tool : str
67+
68+
Returns
69+
-------
70+
str or NotImplemented
71+
72+
Examples
73+
--------
74+
>>> get_connectome_method('Pearson', 'AFNI')
75+
''
76+
>>> get_connectome_method('Pearson', 'Nilearn')
77+
'correlation'
78+
>>> get_connectome_method('Spearman', 'AFNI')
79+
NotImplemented
80+
"""
81+
cm_method = connectome_methods[tool.lower()].get(method, NotImplemented)
82+
if cm_method is NotImplemented:
83+
warning_message = (
84+
f'{method} has not yet been implemented for {tool} in C-PAC.')
85+
if logger:
86+
logger.warning(NotImplementedError(warning_message))
87+
else:
88+
warn(warning_message, category=Warning)
89+
return cm_method
90+
91+
92+
def compute_connectome_nilearn(in_rois, in_file, method, atlas_name):
93+
"""Function to compute a connectome matrix using Nilearn
94+
95+
Parameters
96+
----------
97+
in_rois : Niimg-like object
98+
http://nilearn.github.io/manipulating_images/input_output.html#niimg-like-objects
99+
Region definitions, as one image of labels.
100+
101+
in_file : str
102+
path to timeseries image
103+
104+
method: str
105+
'Pearson' or 'Partial'
106+
107+
atlas_name: str
108+
109+
Returns
110+
-------
111+
numpy.ndarray or NotImplemented
112+
"""
113+
tool = 'Nilearn'
114+
output = connectome_name(in_file, atlas_name, tool, method)
115+
method = get_connectome_method(method, tool)
116+
if method is NotImplemented:
117+
return NotImplemented
118+
masker = NiftiLabelsMasker(labels_img=in_rois,
119+
standardize=True,
120+
verbose=True)
121+
timeser = masker.fit_transform(in_file)
122+
correlation_measure = ConnectivityMeasure(kind=method)
123+
corr_matrix = correlation_measure.fit_transform([timeser])[0]
124+
np.fill_diagonal(corr_matrix, 0)
125+
np.savetxt(output, corr_matrix, delimiter='\t')
126+
return output
127+
128+
129+
def create_connectome_afni(name, method, pipe_num):
130+
wf = pe.Workflow(name=name)
131+
inputspec = pe.Node(
132+
util.IdentityInterface(fields=[
133+
'in_rois', # parcellation
134+
'in_file', # timeseries,
135+
'mask',
136+
'method',
137+
'atlas_name'
138+
]),
139+
name='inputspec'
140+
)
141+
outputspec = pe.Node(
142+
util.IdentityInterface(fields=[
143+
'out_file',
144+
]),
145+
name='outputspec'
146+
)
147+
148+
timeseries_correlation = pe.Node(NetCorr(), name=name)
149+
if method:
150+
timeseries_correlation.inputs.part_corr = (method == 'Partial')
151+
152+
strip_header_node = pe.Node(Function(
153+
input_names=['in_file', 'out_file'], output_names=['out_file'],
154+
imports=['import subprocess'],
155+
function=strip_afni_output_header),
156+
name='netcorrStripHeader'
157+
f'{method}_{pipe_num}')
158+
159+
name_output_node = pe.Node(Function(input_names=['timeseries',
160+
'atlas_name',
161+
'tool',
162+
'method'],
163+
output_names=['filename'],
164+
function=connectome_name),
165+
name=f'connectomeName{method}_{pipe_num}')
166+
name_output_node.inputs.tool = 'Afni'
167+
168+
wf.connect([
169+
(inputspec, timeseries_correlation, [('in_rois', 'in_rois'),
170+
('in_file', 'in_file'),
171+
('mask', 'mask')]),
172+
(inputspec, name_output_node, [('in_file', 'timeseries'),
173+
('atlas_name', 'atlas_name'),
174+
('method', 'method')]),
175+
(timeseries_correlation, strip_header_node, [
176+
('out_corr_matrix', 'in_file')]),
177+
(name_output_node, strip_header_node, [('filename', 'out_file')]),
178+
(strip_header_node, outputspec, [('out_file', 'out_file')])])
179+
return wf
180+
181+
182+
def create_connectome_nilearn(name='connectomeNilearn'):
183+
wf = pe.Workflow(name=name)
184+
inputspec = pe.Node(
185+
util.IdentityInterface(fields=[
186+
'in_rois', # parcellation
187+
'in_file', # timeseries
188+
'method',
189+
'atlas_name'
190+
]),
191+
name='inputspec'
192+
)
193+
outputspec = pe.Node(
194+
util.IdentityInterface(fields=[
195+
'out_file',
196+
]),
197+
name='outputspec'
198+
)
199+
node = pe.Node(Function(input_names=['in_rois', 'in_file', 'method',
200+
'atlas_name'],
201+
output_names=['out_file'],
202+
function=compute_connectome_nilearn,
203+
as_module=True),
204+
name='connectome')
205+
wf.connect([
206+
(inputspec, node, [('in_rois', 'in_rois'),
207+
('in_file', 'in_file'),
208+
('method', 'method'),
209+
('atlas_name', 'atlas_name')]),
210+
(node, outputspec, [('out_file', 'out_file')]),
211+
])
212+
return wf

CPAC/connectome/pipeline.py

Lines changed: 0 additions & 65 deletions
This file was deleted.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
"""Tests for CPAC.connectome"""
2+
import os
3+
import random
4+
import re
5+
import numpy as np
6+
import pytest
7+
from CPAC.pipeline.schema import valid_options
8+

0 commit comments

Comments
 (0)