Skip to content

Commit 975db16

Browse files
committed
enh: implement an interface to compare costs function from registration with original and flipped functional image to detect left-right flip
enh: propagate the flip_info through the workflow so it can be integrated into the visual report
1 parent ff05251 commit 975db16

File tree

2 files changed

+59
-2
lines changed

2 files changed

+59
-2
lines changed

fmriprep/interfaces/reports.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,12 @@ class FunctionalSummaryInputSpec(TraitedSpec):
218218
desc='Whether to initialize registration with the "header"'
219219
' or by centering the volumes ("t1w" or "t2w")',
220220
)
221+
flip_info = traits.Dict(
222+
traits.Enum('lr_flip_warning', 'cost_original', 'cost_flipped'),
223+
traits.Either(traits.Bool(), traits.Float()),
224+
desc='Left-right flip check warning and registration costs',
225+
mandatory=True
226+
)
221227
tr = traits.Float(desc='Repetition time', mandatory=True)
222228
dummy_scans = traits.Either(traits.Int(), None, desc='number of dummy scans specified by user')
223229
algo_dummy_scans = traits.Int(desc='number of dummy scans determined by algorithm')
@@ -369,3 +375,40 @@ def get_world_pedir(ornt, pe_direction):
369375
f'Orientation: {ornt}; PE dir: {pe_direction}'
370376
)
371377
return 'Could not be determined - assuming Anterior-Posterior'
378+
379+
380+
class _CheckFlipInputSpec(BaseInterfaceInputSpec):
381+
cost_original = File(
382+
exists=True,
383+
mandatory=True,
384+
desc='cost associated with registration of BOLD to original T1w images',
385+
)
386+
cost_flipped = File(
387+
exists=True,
388+
mandatory=True,
389+
desc='cost associated with registration of BOLD to the flipped T1w images',
390+
)
391+
392+
393+
class _CheckFlipOutputSpec(TraitedSpec):
394+
flip_info = traits.Dict(
395+
traits.Enum('warning', 'cost_original', 'cost_flipped'),
396+
traits.Either(traits.Bool(), traits.Float()),
397+
desc='Left-right flip check warning and registration costs',
398+
mandatory=True
399+
)
400+
401+
402+
class CheckFlip(SimpleInterface):
403+
"""Check for a LR flip by comparing registration cost functions."""
404+
405+
input_spec = _CheckFlipInputSpec
406+
output_spec = _CheckFlipOutputSpec
407+
408+
def _run_interface(self, runtime):
409+
self._results['flip_info'] = {
410+
'warning': self.inputs.cost_flipped < self.inputs.cost_original,
411+
'cost_original': self.inputs.cost_original,
412+
'cost_flipped': self.inputs.cost_flipped,
413+
}
414+
return runtime

fmriprep/workflows/bold/registration.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ def init_bold_reg_wf(
127127
Affine transform from T1 space to BOLD space (ITK format)
128128
fallback
129129
Boolean indicating whether BBR was rejected (mri_coreg registration returned)
130+
flip_info
131+
Information regarding whether a left-right flip was detected
130132
131133
See Also
132134
--------
@@ -153,7 +155,9 @@ def init_bold_reg_wf(
153155
)
154156

155157
outputnode = pe.Node(
156-
niu.IdentityInterface(fields=['itk_bold_to_t1', 'itk_t1_to_bold', 'fallback']),
158+
niu.IdentityInterface(
159+
fields=['itk_bold_to_t1', 'itk_t1_to_bold', 'fallback', 'flip_info']
160+
),
157161
name='outputnode',
158162
)
159163

@@ -187,6 +191,7 @@ def init_bold_reg_wf(
187191
('outputnode.itk_bold_to_t1', 'itk_bold_to_t1'),
188192
('outputnode.itk_t1_to_bold', 'itk_t1_to_bold'),
189193
('outputnode.fallback', 'fallback'),
194+
('outputnode.flip_info', 'flip_info'),
190195
]),
191196
]) # fmt:skip
192197

@@ -267,6 +272,8 @@ def init_bbreg_wf(
267272
Affine transform from T1 space to BOLD space (ITK format)
268273
fallback
269274
Boolean indicating whether BBR was rejected (mri_coreg registration returned)
275+
flip_info
276+
Information regarding whether a left-right flip was detected
270277
271278
"""
272279
from nipype.interfaces.freesurfer import BBRegister
@@ -275,6 +282,7 @@ def init_bbreg_wf(
275282
from niworkflows.interfaces.nitransforms import ConcatenateXFMs
276283

277284
from fmriprep.interfaces.patches import FreeSurferSource, MRICoreg
285+
from fmriprep.interfaces.reports import CheckFlip
278286

279287
workflow = Workflow(name=name)
280288
workflow.__desc__ = """\
@@ -309,7 +317,7 @@ def init_bbreg_wf(
309317
name='inputnode',
310318
)
311319
outputnode = pe.Node(
312-
niu.IdentityInterface(['itk_bold_to_t1', 'itk_t1_to_bold', 'fallback']),
320+
niu.IdentityInterface(['itk_bold_to_t1', 'itk_t1_to_bold', 'fallback', 'flip_info']),
313321
name='outputnode',
314322
)
315323

@@ -358,6 +366,9 @@ def init_bbreg_wf(
358366
name='bbregister',
359367
mem_gb=12,
360368
)
369+
370+
check_flip = pe.Node(CheckFlip(), name='check_flip')
371+
361372
transforms = pe.Node(niu.Merge(2), run_without_submitting=True, name='transforms')
362373
# In cases where Merge(2) only has `in1` or `in2` defined
363374
# output list will just contain a single element
@@ -411,7 +422,10 @@ def init_bbreg_wf(
411422
('subject_id', 'subject_id')]),
412423
(inputnode, lr_flip), [('in_file', 'in_file')],
413424
(lr_flip, bbregister_flipped), [('out_file', 'source_file')],
425+
(bbregister, check_flip), [('min_cost_file', 'cost_original')],
426+
(bbregister_flipped, check_flip), [('min_cost_file', 'cost_flipped')],
414427
(bbregister, transforms, [('out_lta_file', 'in1')]),
428+
(check_flip, outputnode, [('flip_info', 'flip_info')]),
415429
]) # fmt:skip
416430

417431
# Short-circuit workflow building, use boundary-based registration

0 commit comments

Comments
 (0)