Skip to content

Commit b1ce64b

Browse files
authored
Merge pull request #1769 from FCP-INDI/feature/total_readout_time
Ingress and use TotalReadoutTime from epi fmap meta-data in FSL topup
2 parents 37a6874 + c6ec0a0 commit b1ce64b

File tree

5 files changed

+69
-31
lines changed

5 files changed

+69
-31
lines changed

CHANGELOG.md

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

1919
### Added
2020
- Added the ability to downsample to 10K or 2K resolution for freesurfer runs
21+
- Added the ability to ingress TotalReadoutTime from epi field map meta-data from the JSON sidecars.
22+
- Added the ability to use TotalReadoutTime of epi field maps in the calculation of FSL topup distortion correction.
2123
- Difference method (``-``) for ``CPAC.utils.configuration.Configuration`` instances
2224

2325
### Changed
@@ -35,6 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3537
### Fixed
3638
- Fixed [bug](https://github.com/FCP-INDI/C-PAC/issues/1795) that was causing `cpac run` to fail when passing a manual random seed via `--random_seed`.
3739
- Replaces ``DwellTime`` with ``EffectiveEchoSpacing`` for FSL usage of the term
40+
- Fixed an issue that was causing some epi field maps to not be ingressed if the BIDS tags were not in the correct order.
3841

3942
## [v1.8.4] - 2022-06-27
4043

CPAC/distortion_correction/distortion_correction.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -501,10 +501,12 @@ def distcor_blip_fsl_topup(wf, cfg, strat_pool, pipe_num, opt=None):
501501
"epi-1-pedir",
502502
"epi-1-TE",
503503
"epi-1-dwell",
504+
"epi-1-total-readout",
504505
"epi-2",
505506
"epi-2-pedir",
506507
"epi-2-TE",
507-
"epi-2-dwell"],
508+
"epi-2-dwell",
509+
"epi-2-total-readout"],
508510
"outputs": ["desc-mean_bold",
509511
"space-bold_desc-brain_mask",
510512
"blip-warp"]}
@@ -605,7 +607,9 @@ def distcor_blip_fsl_topup(wf, cfg, strat_pool, pipe_num, opt=None):
605607
"phase_one",
606608
"phase_two",
607609
"dwell_time_one",
608-
"dwell_time_two"
610+
"dwell_time_two",
611+
"ro_time_one",
612+
"ro_time_two"
609613
],
610614
output_names=["acq_params"],
611615
function=phase_encode,
@@ -621,12 +625,24 @@ def distcor_blip_fsl_topup(wf, cfg, strat_pool, pipe_num, opt=None):
621625

622626
node, out = strat_pool.get_data('pe-direction')
623627
wf.connect(node, out, phase_encoding, 'unwarp_dir')
628+
629+
if strat_pool.check_rpool('epi-1-dwell') and \
630+
strat_pool.check_rpool('epi-2-dwell'):
624631

625-
node, out = strat_pool.get_data('epi-1-dwell')
626-
wf.connect(node, out, phase_encoding, 'dwell_time_one')
632+
node, out = strat_pool.get_data('epi-1-dwell')
633+
wf.connect(node, out, phase_encoding, 'dwell_time_one')
627634

628-
node, out = strat_pool.get_data('epi-2-dwell')
629-
wf.connect(node, out, phase_encoding, 'dwell_time_two')
635+
node, out = strat_pool.get_data('epi-2-dwell')
636+
wf.connect(node, out, phase_encoding, 'dwell_time_two')
637+
638+
if strat_pool.check_rpool('epi-1-total-readout') and \
639+
strat_pool.check_rpool('epi-2-total-readout'):
640+
641+
node, out = strat_pool.get_data('epi-1-total-readout')
642+
wf.connect(node, out, phase_encoding, 'ro_time_one')
643+
644+
node, out = strat_pool.get_data('epi-2-total-readout')
645+
wf.connect(node, out, phase_encoding, 'ro_time_two')
630646

631647
topup_imports = ["import os",
632648
"import subprocess"]

CPAC/distortion_correction/utils.py

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,9 @@ def gradient_distortion_correction(wf, inp_image, name):
140140
return (wf, out_warpmask, out_applywarp)
141141

142142

143-
def phase_encode(unwarp_dir, phase_one, phase_two, dwell_time_one,
144-
dwell_time_two):
145-
"""
146-
147-
Calculate readout time and populate parameter file
143+
def phase_encode(unwarp_dir, phase_one, phase_two, dwell_time_one=None,
144+
dwell_time_two=None, ro_time_one=None, ro_time_two=None):
145+
"""Calculate readout time and populate parameter file
148146
149147
Parameters
150148
__________
@@ -159,8 +157,10 @@ def phase_encode(unwarp_dir, phase_one, phase_two, dwell_time_one,
159157
echo spacing of phase one
160158
dwell_time_two
161159
echo spacing of phase two
162-
fsl_dir
163-
FSL directory
160+
ro_time_one
161+
total readout time of phase one
162+
ro_time_two
163+
total readout time of phase two
164164
165165
Returns
166166
_______
@@ -170,29 +170,44 @@ def phase_encode(unwarp_dir, phase_one, phase_two, dwell_time_one,
170170
171171
"""
172172

173+
meta_data = [dwell_time_one, dwell_time_two,
174+
ro_time_one, ro_time_two]
175+
if not any(meta_data):
176+
raise Exception("\n[!] Blip-FSL-TOPUP workflow: neither "
177+
"TotalReadoutTime nor DwellTime is present in the "
178+
"epi field map meta-data.")
179+
173180
# create text file
174181
acq_params = os.path.join(os.getcwd(), "acqparams.txt")
175182

176183
if isinstance(unwarp_dir, bytes):
177184
unwarp_dir = unwarp_dir.decode()
178185

179186
if unwarp_dir in ["x", "x-", "-x","i","-i","i-"]:
180-
dim = nibabel.load(phase_one).shape[0]
181-
n_PE_steps = dim - 1
182-
ro_time_one = np.round(dwell_time_one * n_PE_steps, 6)
183-
ro_time_two = np.round(dwell_time_two * n_PE_steps, 6)
184-
ro_times = [f"-1 0 0 {ro_time_one}", f"1 0 0 {ro_time_two}"]
187+
if dwell_time_one and dwell_time_two:
188+
dim = nibabel.load(phase_one).shape[0]
189+
n_PE_steps = dim - 1
190+
ro_time_one = np.round(dwell_time_one * n_PE_steps, 6)
191+
ro_time_two = np.round(dwell_time_two * n_PE_steps, 6)
192+
elif ro_time_one and ro_time_two:
193+
ro_times = [f"-1 0 0 {ro_time_one}", f"1 0 0 {ro_time_two}"]
194+
else:
195+
raise Exception("[!] No dwell time or total readout time "
196+
"present for the acq-fMRI EPI field maps.")
185197
elif unwarp_dir in ["y", "y-", "-y","j","-j","j-"]:
186-
dim = nibabel.load(phase_one).shape[1]
187-
n_PE_steps = dim - 1
188-
ro_time_one = np.round(dwell_time_one * n_PE_steps, 6)
189-
ro_time_two = np.round(dwell_time_two * n_PE_steps, 6)
190-
ro_times = [f"0 -1 0 {ro_time_one}", f"0 1 0 {ro_time_two}"]
198+
if dwell_time_one and dwell_time_two:
199+
dim = nibabel.load(phase_one).shape[1]
200+
n_PE_steps = dim - 1
201+
ro_time_one = np.round(dwell_time_one * n_PE_steps, 6)
202+
ro_time_two = np.round(dwell_time_two * n_PE_steps, 6)
203+
elif ro_time_one and ro_time_two:
204+
ro_times = [f"0 -1 0 {ro_time_one}", f"0 1 0 {ro_time_two}"]
205+
else:
206+
raise Exception("[!] No dwell time or total readout time "
207+
"present for the acq-fMRI EPI field maps.")
191208
else:
192209
raise Exception(f"unwarp_dir={unwarp_dir} is unsupported.")
193210

194-
195-
196211
# get number of volumes
197212
dims = [
198213
int(subprocess.check_output([f"fslval", phase_one, "dim4"]).decode(sys.stdout.encoding)),

CPAC/utils/build_data_config.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -564,11 +564,11 @@ def get_BIDS_data_dct(bids_base_dir, file_list=None, anat_scan=None,
564564
fmap_pedir_sess = os.path.join(bids_base_dir,
565565
"sub-{participant}/ses-{session}/fmap/"
566566
"sub-{participant}_ses-{session}/"
567-
"dir-*_acq-fMRI_epi.nii.gz")
567+
"*acq-fMR*_epi.nii.gz")
568568

569569
fmap_pedir = os.path.join(bids_base_dir,
570570
"sub-{participant}/fmap/sub-{participant}"
571-
"_dir-*_acq-fMRI_epi.nii.gz")
571+
"*acq-fMR*_epi.nii.gz")
572572

573573
sess_glob = os.path.join(bids_base_dir, "sub-*/ses-*/*")
574574

@@ -582,7 +582,7 @@ def get_BIDS_data_dct(bids_base_dir, file_list=None, anat_scan=None,
582582

583583
fmap_pedir_scan_glob = os.path.join(bids_base_dir,
584584
"sub-*fmap/"
585-
"sub-*_dir-*_acq-fMRI_epi.nii.gz")
585+
"sub-*_*acq-fMR*_epi.nii.gz")
586586

587587
part_tsv_glob = os.path.join(bids_base_dir, "*participants.tsv")
588588

@@ -648,7 +648,7 @@ def get_BIDS_data_dct(bids_base_dir, file_list=None, anat_scan=None,
648648
fmap_mag = os.path.join(bids_base_dir,
649649
"sub-{participant}/fmap/sub-{participant}"
650650
"*magnitud*.nii.gz")
651-
651+
652652
'''
653653
if fnmatch.fnmatch(filepath, fmap_pedir_scan_glob):
654654
# check if there is a scan level for the fmap magnitude files

CPAC/utils/datasource.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,8 +350,9 @@ def get_fmap_phasediff_metadata(data_config_scan_params):
350350
echo_time = data_config_scan_params.get("EchoTime")
351351
dwell_time = data_config_scan_params.get("DwellTime")
352352
pe_direction = data_config_scan_params.get("PhaseEncodingDirection")
353+
total_readout = data_config_scan_params.get("TotalReadoutTime")
353354

354-
return echo_time, dwell_time, pe_direction
355+
return (echo_time, dwell_time, pe_direction, total_readout)
355356

356357

357358
def calc_delta_te_and_asym_ratio(effective_echo_spacing: float,
@@ -484,7 +485,8 @@ def ingress_func_metadata(wf, cfg, rpool, sub_dict, subject_id,
484485
input_names=['data_config_scan_params'],
485486
output_names=['echo_time',
486487
'dwell_time',
487-
'pe_direction'],
488+
'pe_direction',
489+
'total_readout'],
488490
function=get_fmap_phasediff_metadata,
489491
imports=get_fmap_metadata_imports),
490492
name=f'{key}_get_metadata{name_suffix}')
@@ -498,6 +500,8 @@ def ingress_func_metadata(wf, cfg, rpool, sub_dict, subject_id,
498500
'dwell_time', {}, "", "fmap_dwell_ingress")
499501
rpool.set_data(f'{key}-pedir', get_fmap_metadata,
500502
'pe_direction', {}, "", "fmap_pedir_ingress")
503+
rpool.set_data(f'{key}-total-readout', get_fmap_metadata,
504+
'total_readout', {}, "", "fmap_readout_ingress")
501505

502506
fmap_TE_list.append(f"{key}-TE")
503507

0 commit comments

Comments
 (0)