Skip to content

Commit 18d2aec

Browse files
authored
Merge pull request #60 from oesteban/enh/extend-2-phases
ENH: Base implementation for phase1/2 fieldmaps
2 parents 378edbb + 3778dad commit 18d2aec

File tree

3 files changed

+145
-6
lines changed

3 files changed

+145
-6
lines changed

sdcflows/interfaces/fmap.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,43 @@
2424
LOGGER = logging.getLogger('nipype.interface')
2525

2626

27+
class _SubtractPhasesInputSpec(BaseInterfaceInputSpec):
28+
in_phases = traits.List(File(exists=True), min=1, max=2,
29+
desc='input phase maps')
30+
in_meta = traits.List(traits.Dict(), min=1, max=2,
31+
desc='metadata corresponding to the inputs')
32+
33+
34+
class _SubtractPhasesOutputSpec(TraitedSpec):
35+
phase_diff = File(exists=True, desc='phase difference map')
36+
metadata = traits.Dict(desc='output metadata')
37+
38+
39+
class SubtractPhases(SimpleInterface):
40+
"""Calculate a phase difference map."""
41+
42+
input_spec = _SubtractPhasesInputSpec
43+
output_spec = _SubtractPhasesOutputSpec
44+
45+
def _run_interface(self, runtime):
46+
if len(self.inputs.in_phases) != len(self.inputs.in_meta):
47+
raise ValueError(
48+
'Length of input phase-difference maps and metadata files '
49+
'should match.')
50+
51+
if len(self.inputs.in_phases) == 1:
52+
self._results['phase_diff'] = self.inputs.in_phases[0]
53+
self._results['metadata'] = self.inputs.in_meta[0]
54+
return runtime
55+
56+
self._results['phase_diff'], self._results['metadata'] = \
57+
_subtract_phases(self.inputs.in_phases,
58+
self.inputs.in_meta,
59+
newpath=runtime.cwd)
60+
61+
return runtime
62+
63+
2764
class _FieldEnhanceInputSpec(BaseInterfaceInputSpec):
2865
in_file = File(exists=True, mandatory=True, desc='input fieldmap')
2966
in_mask = File(exists=True, desc='brain mask')
@@ -649,3 +686,36 @@ def au2rads(in_file, newpath=None):
649686
out_file = fname_presuffix(in_file, suffix='_rads', newpath=newpath)
650687
nb.Nifti1Image(data, im.affine, hdr).to_filename(out_file)
651688
return out_file
689+
690+
691+
def _subtract_phases(in_phases, in_meta, newpath=None):
692+
# Discard traits with copy(), so that pop() works.
693+
in_meta = (in_meta[0].copy(), in_meta[1].copy())
694+
echo_times = tuple([m.pop('EchoTime', None) for m in in_meta])
695+
if not all(echo_times):
696+
raise ValueError(
697+
'One or more missing EchoTime metadata parameter '
698+
'associated to one or more phase map(s).')
699+
700+
if echo_times[0] > echo_times[1]:
701+
in_phases = (in_phases[1], in_phases[0])
702+
in_meta = (in_meta[1], in_meta[0])
703+
echo_times = (echo_times[1], echo_times[0])
704+
705+
in_phases_nii = [nb.load(ph) for ph in in_phases]
706+
sub_data = in_phases_nii[1].get_fdata(dtype='float32') - \
707+
in_phases_nii[0].get_fdata(dtype='float32')
708+
709+
new_meta = in_meta[1].copy()
710+
new_meta.update(in_meta[0])
711+
new_meta['EchoTime1'] = echo_times[0]
712+
new_meta['EchoTime2'] = echo_times[1]
713+
714+
hdr = in_phases_nii[0].header.copy()
715+
hdr.set_data_dtype(np.float32)
716+
hdr.set_xyzt_units('mm')
717+
nii = nb.Nifti1Image(sub_data, in_phases_nii[0].affine, hdr)
718+
out_phdiff = fname_presuffix(in_phases[0], suffix='_phdiff',
719+
newpath=newpath)
720+
nii.to_filename(out_phdiff)
721+
return out_phdiff, new_meta

sdcflows/workflows/phdiff.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from nipype.pipeline import engine as pe
2222
from niworkflows.engine.workflows import LiterateWorkflow as Workflow
2323

24-
from ..interfaces.fmap import Phasediff2Fieldmap, PhaseMap2rads
24+
from ..interfaces.fmap import Phasediff2Fieldmap, PhaseMap2rads, SubtractPhases
2525
from .gre import init_fmap_postproc_wf, init_magnitude_wf
2626

2727

@@ -107,6 +107,9 @@ def init_phdiff_wf(omp_nthreads, name='phdiff_wf'):
107107
# FSL PRELUDE will perform phase-unwrapping
108108
prelude = pe.MapNode(fsl.PRELUDE(), iterfield=['phase_file'], name='prelude')
109109

110+
calc_phdiff = pe.Node(SubtractPhases(), name='calc_phdiff',
111+
run_without_submitting=True)
112+
110113
fmap_postproc_wf = init_fmap_postproc_wf(omp_nthreads=omp_nthreads,
111114
fmap_bspline=False)
112115
compfmap = pe.Node(Phasediff2Fieldmap(), name='compfmap')
@@ -118,8 +121,11 @@ def init_phdiff_wf(omp_nthreads, name='phdiff_wf'):
118121
('outputnode.fmap_mask', 'mask_file')]),
119122
(split, phmap2rads, [('map_file', 'in_file')]),
120123
(phmap2rads, prelude, [('out_file', 'phase_file')]),
121-
(split, fmap_postproc_wf, [('meta', 'inputnode.metadata')]),
122-
(prelude, fmap_postproc_wf, [('unwrapped_phase_file', 'inputnode.fmap')]),
124+
(prelude, calc_phdiff, [('unwrapped_phase_file', 'in_phases')]),
125+
(split, calc_phdiff, [('meta', 'in_meta')]),
126+
(calc_phdiff, fmap_postproc_wf, [
127+
('phase_diff', 'inputnode.fmap'),
128+
('metadata', 'inputnode.metadata')]),
123129
(magnitude_wf, fmap_postproc_wf, [
124130
('outputnode.fmap_mask', 'inputnode.fmap_mask'),
125131
('outputnode.fmap_ref', 'inputnode.fmap_ref')]),

sdcflows/workflows/tests/test_phdiff.py

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,77 @@
1010
'ds001600',
1111
'testdata',
1212
])
13-
def test_workflow(bids_layouts, tmpdir, output_path, dataset, workdir):
13+
def test_phdiff(bids_layouts, tmpdir, output_path, dataset, workdir):
1414
"""Test creation of the workflow."""
1515
tmpdir.chdir()
1616

17+
extra_entities = {}
18+
if dataset == 'ds001600':
19+
extra_entities['acquisition'] = 'v4'
20+
1721
data = bids_layouts[dataset]
1822
wf = Workflow(name='phdiff_%s' % dataset)
1923
phdiff_wf = init_phdiff_wf(omp_nthreads=2)
2024
phdiff_wf.inputs.inputnode.magnitude = data.get(
2125
suffix=['magnitude1', 'magnitude2'],
22-
acq='v4',
26+
return_type='file',
27+
extension=['.nii', '.nii.gz'],
28+
**extra_entities)
29+
30+
phdiff_files = data.get(
31+
suffix='phasediff',
32+
extension=['.nii', '.nii.gz'],
33+
**extra_entities)
34+
35+
phdiff_wf.inputs.inputnode.phasediff = [
36+
(ph.path, ph.get_metadata()) for ph in phdiff_files]
37+
38+
if output_path:
39+
from ...interfaces.reportlets import FieldmapReportlet
40+
rep = pe.Node(FieldmapReportlet(reference_label='Magnitude'), 'simple_report')
41+
rep.interface._always_run = True
42+
43+
dsink = pe.Node(DerivativesDataSink(
44+
base_directory=str(output_path), keep_dtype=True), name='dsink')
45+
dsink.interface.out_path_base = 'sdcflows'
46+
dsink.inputs.source_file = phdiff_files[0].path
47+
48+
dsink_fmap = pe.Node(DerivativesDataSink(
49+
base_directory=str(output_path), keep_dtype=True), name='dsink_fmap')
50+
dsink_fmap.interface.out_path_base = 'sdcflows'
51+
dsink_fmap.inputs.source_file = phdiff_files[0].path
52+
53+
wf.connect([
54+
(phdiff_wf, rep, [
55+
('outputnode.fmap', 'fieldmap'),
56+
('outputnode.fmap_ref', 'reference'),
57+
('outputnode.fmap_mask', 'mask')]),
58+
(rep, dsink, [('out_report', 'in_file')]),
59+
(phdiff_wf, dsink_fmap, [('outputnode.fmap', 'in_file')]),
60+
])
61+
else:
62+
wf.add_nodes([phdiff_wf])
63+
64+
if workdir:
65+
wf.base_dir = str(workdir)
66+
67+
wf.run()
68+
69+
70+
def test_phases(bids_layouts, tmpdir, output_path, workdir):
71+
"""Test creation of the workflow."""
72+
tmpdir.chdir()
73+
74+
data = bids_layouts['ds001600']
75+
wf = Workflow(name='phases_ds001600')
76+
phdiff_wf = init_phdiff_wf(omp_nthreads=2)
77+
phdiff_wf.inputs.inputnode.magnitude = data.get(
78+
suffix=['magnitude1', 'magnitude2'],
79+
acquisition='v2',
2380
return_type='file',
2481
extension=['.nii', '.nii.gz'])
2582

26-
phdiff_files = data.get(suffix='phasediff', acq='v4',
83+
phdiff_files = data.get(suffix=['phase1', 'phase2'], acquisition='v2',
2784
extension=['.nii', '.nii.gz'])
2885

2986
phdiff_wf.inputs.inputnode.phasediff = [
@@ -39,12 +96,18 @@ def test_workflow(bids_layouts, tmpdir, output_path, dataset, workdir):
3996
dsink.interface.out_path_base = 'sdcflows'
4097
dsink.inputs.source_file = phdiff_files[0].path
4198

99+
dsink_fmap = pe.Node(DerivativesDataSink(
100+
base_directory=str(output_path), keep_dtype=True), name='dsink_fmap')
101+
dsink_fmap.interface.out_path_base = 'sdcflows'
102+
dsink_fmap.inputs.source_file = phdiff_files[0].path
103+
42104
wf.connect([
43105
(phdiff_wf, rep, [
44106
('outputnode.fmap', 'fieldmap'),
45107
('outputnode.fmap_ref', 'reference'),
46108
('outputnode.fmap_mask', 'mask')]),
47109
(rep, dsink, [('out_report', 'in_file')]),
110+
(phdiff_wf, dsink_fmap, [('outputnode.fmap', 'in_file')]),
48111
])
49112
else:
50113
wf.add_nodes([phdiff_wf])

0 commit comments

Comments
 (0)