Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: integrate left-right flip detection #3313

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
15 changes: 15 additions & 0 deletions fmriprep/data/reports-spec-func.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,21 @@ sections:
anatomical white matter mask, which appears as a red contour.
static: false
subtitle: Alignment of functional and anatomical MRI data (coregistration)
- button:
type: button
class: btn btn-primary
data-toggle: collapse
data-target: '#flippedcoreg'
text: 'Alignment of functional and flipped anatomical MRI data'
- div:
class: collapse
id: 'flippedcoreg'
reportlets:
- bids: {datatype: figures, desc: flippedcoreg, suffix: bold}
caption: This panel shows the alignment of the reference EPI (BOLD) image to the
left-right flipped anatomical (T1-weighted) image.
static: false
subtitle: Left-right flip check, alignment of functional and flipped anatomical MRI data
- bids: {datatype: figures, desc: rois, suffix: bold}
caption: Brain mask calculated on the BOLD signal (red contour), along with the
regions of interest (ROIs) used for the estimation of physiological and movement
Expand Down
15 changes: 15 additions & 0 deletions fmriprep/data/reports-spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,21 @@ sections:
anatomical white matter mask, which appears as a red contour.
static: false
subtitle: Alignment of functional and anatomical MRI data (coregistration)
- button:
type: button
class: btn btn-primary
data-toggle: collapse
data-target: '#flippedcoreg'
text: 'Alignment of functional and flipped anatomical MRI data'
- div:
class: collapse
id: 'flippedcoreg'
reportlets:
- bids: {datatype: figures, desc: flippedcoreg, suffix: bold}
caption: This panel shows the alignment of the reference EPI (BOLD) image to the
left-right flipped anatomical (T1-weighted) image.
static: false
subtitle: Left-right flip check, alignment of functional and flipped anatomical MRI data
- bids: {datatype: figures, desc: rois, suffix: bold}
caption: Brain mask calculated on the BOLD signal (red contour), along with the
regions of interest (ROIs) used for the estimation of physiological and movement
Expand Down
63 changes: 63 additions & 0 deletions fmriprep/interfaces/reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,17 @@
\t\t\t<li>Slice timing correction: {stc}</li>
\t\t\t<li>Susceptibility distortion correction: {sdc}</li>
\t\t\t<li>Registration: {registration}</li>
\t\t\t<li>Left-right flip check warning: {lr_flip_warning}</li>
\t\t<table>
\t\t\t<tr>
\t\t\t\t<th>Original Registration Cost</th>
\t\t\t\t<th>Flipped Registration Cost</th>
\t\t\t</tr>
\t\t\t<tr>
\t\t\t\t<td>{cost_original}</td>
\t\t\t\t<td>{cost_flipped}</td>
\t\t\t</tr>
\t\t</table>
\t\t\t<li>Non-steady-state volumes: {dummy_scan_desc}</li>
\t\t</ul>
\t\t</details>
Expand Down Expand Up @@ -218,6 +229,12 @@ class FunctionalSummaryInputSpec(TraitedSpec):
desc='Whether to initialize registration with the "header"'
' or by centering the volumes ("t1w" or "t2w")',
)
flip_info = traits.Dict(
traits.Enum('lr_flip_warning', 'cost_original', 'cost_flipped'),
traits.Either(traits.Bool(), traits.Float()),
desc='Left-right flip check warning and registration costs',
mandatory=True,
)
tr = traits.Float(desc='Repetition time', mandatory=True)
dummy_scans = traits.Either(traits.Int(), None, desc='number of dummy scans specified by user')
algo_dummy_scans = traits.Int(desc='number of dummy scans determined by algorithm')
Expand Down Expand Up @@ -279,6 +296,12 @@ def _generate_segment(self):
if n_echos > 2:
multiecho = f'Multi-echo EPI sequence: {n_echos} echoes.'

lr_flip_warning = (
'<span style="color:red;">LR flip detected</span>'
if self.inputs.flip_info.get('lr_flip_warning', False)
else 'none'
)

return FUNCTIONAL_TEMPLATE.format(
pedir=pedir,
stc=stc,
Expand All @@ -288,6 +311,9 @@ def _generate_segment(self):
dummy_scan_desc=dummy_scan_msg,
multiecho=multiecho,
ornt=self.inputs.orientation,
lr_flip_warning=lr_flip_warning,
cost_original=self.input.flip_info.get('cost_original', None),
cost_flipped=self.input.flip_info.get('cost_flipped', None),
)


Expand Down Expand Up @@ -369,3 +395,40 @@ def get_world_pedir(ornt, pe_direction):
f'Orientation: {ornt}; PE dir: {pe_direction}'
)
return 'Could not be determined - assuming Anterior-Posterior'


class _CheckFlipInputSpec(BaseInterfaceInputSpec):
cost_original = File(
exists=True,
mandatory=True,
desc='cost associated with registration of BOLD to original T1w images',
)
cost_flipped = File(
exists=True,
mandatory=True,
desc='cost associated with registration of BOLD to the flipped T1w images',
)


class _CheckFlipOutputSpec(TraitedSpec):
flip_info = traits.Dict(
traits.Enum('warning', 'cost_original', 'cost_flipped'),
traits.Either(traits.Bool(), traits.Float()),
desc='Left-right flip check warning and registration costs',
mandatory=True,
)


class CheckFlip(SimpleInterface):
"""Check for a LR flip by comparing registration cost functions."""

input_spec = _CheckFlipInputSpec
output_spec = _CheckFlipOutputSpec

def _run_interface(self, runtime):
self._results['flip_info'] = {
'warning': self.inputs.cost_flipped < self.inputs.cost_original,
'cost_original': self.inputs.cost_original,
'cost_flipped': self.inputs.cost_flipped,
}
return runtime
11 changes: 10 additions & 1 deletion fmriprep/workflows/bold/fit.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,9 @@ def init_bold_fit_wf(
'boldref2fmap_xfm',
'movpar_file',
'rmsd_file',
# LR flip check
'fboldref2anat_xfm',
'flipped_boldref',
],
),
name='outputnode',
Expand Down Expand Up @@ -373,6 +376,8 @@ def init_bold_fit_wf(
('coreg_boldref', 'inputnode.coreg_boldref'),
('bold_mask', 'inputnode.bold_mask'),
('boldref2anat_xfm', 'inputnode.boldref2anat_xfm'),
('fboldref2anat_xfm', 'inputnode.fboldref2anat_xfm'),
('flipped_boldref', 'inputnode.flipped_boldref'),
]),
(summary, func_fit_reports_wf, [('out_report', 'inputnode.summary_report')]),
])
Expand Down Expand Up @@ -632,7 +637,11 @@ def init_bold_fit_wf(
(regref_buffer, ds_boldreg_wf, [('boldref', 'inputnode.source_files')]),
(bold_reg_wf, ds_boldreg_wf, [('outputnode.itk_bold_to_t1', 'inputnode.xform')]),
(ds_boldreg_wf, outputnode, [('outputnode.xform', 'boldref2anat_xfm')]),
(bold_reg_wf, summary, [('outputnode.fallback', 'fallback')]),
(bold_reg_wf, outputnode, [
('outputnode.itk_fbold_to_t1', 'fboldref2anat_xfm'),
('outputnode.flipped_boldref', 'flipped_boldref')]),
(bold_reg_wf, summary, [('outputnode.fallback', 'fallback'),
('outputnode.flip_info', 'flip_info')]),
])
# fmt:on
else:
Expand Down
64 changes: 64 additions & 0 deletions fmriprep/workflows/bold/outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,9 @@ def init_func_fit_reports_wf(
't1w_dseg',
'fieldmap',
'fmap_ref',
# LR flip check
'fboldref2anat_xfm',
'flipped_boldref',
# May be missing
'subject_id',
'subjects_dir',
Expand Down Expand Up @@ -267,6 +270,29 @@ def init_func_fit_reports_wf(
mem_gb=1,
)

# LR flip check
t1w_flipped_boldref = pe.Node(
ApplyTransforms(
dimension=3,
default_value=0,
float=True,
invert_transform_flags=[True],
interpolation='LanczosWindowedSinc',
),
name='t1w_flipped_boldref',
mem_gb=1,
)
flipped_boldref_wm = pe.Node(
ApplyTransforms(
dimension=3,
default_value=0,
invert_transform_flags=[True],
interpolation='NearestNeighbor',
),
name='boldref_wm',
mem_gb=1,
)

# fmt:off
workflow.connect([
(inputnode, ds_summary, [
Expand All @@ -288,6 +314,18 @@ def init_func_fit_reports_wf(
('boldref2anat_xfm', 'transforms'),
]),
(t1w_wm, boldref_wm, [('out', 'input_image')]),
# LR flip check
(inputnode, t1w_flipped_boldref, [
('t1w_preproc', 'input_image'),
('flipped_boldref', 'reference_image'),
('fboldref2anat_xfm', 'transforms'),
]),
(inputnode, flipped_boldref_wm, [
('flipped_boldref', 'reference_image'),
('fboldref2anat_xfm', 'transforms'),
]),
(t1w_wm, flipped_boldref_wm, [('out', 'input_image')]),

])
# fmt:on

Expand Down Expand Up @@ -409,13 +447,39 @@ def init_func_fit_reports_wf(
name='ds_epi_t1_report',
)

flipped_epi_t1_report = pe.Node(
SimpleBeforeAfter(
before_label='T1w',
after_label='EPI',
dismiss_affine=True,
),
name='flipped_epi_t1_report',
mem_gb=0.1,
)

ds_flipped_epi_t1_report = pe.Node(
DerivativesDataSink(
base_directory=output_dir,
desc='flippedcoreg',
suffix='bold',
datatype='figures',
dismiss_entities=dismiss_echo(),
),
name='ds_flipped_epi_t1_report',
)

# fmt:off
workflow.connect([
(inputnode, epi_t1_report, [('coreg_boldref', 'after')]),
(t1w_boldref, epi_t1_report, [('output_image', 'before')]),
(boldref_wm, epi_t1_report, [('output_image', 'wm_seg')]),
(inputnode, ds_epi_t1_report, [('source_file', 'source_file')]),
(epi_t1_report, ds_epi_t1_report, [('out_report', 'in_file')]),
(inputnode, flipped_epi_t1_report, [('flipped_boldref', 'after')]),
(t1w_flipped_boldref, flipped_epi_t1_report, [('output_image', 'before')]),
(flipped_boldref_wm, flipped_epi_t1_report, [('output_image', 'wm_seg')]),
(inputnode, ds_flipped_epi_t1_report, [('source_file', 'source_file')]),
(flipped_epi_t1_report, ds_flipped_epi_t1_report, [('out_report', 'in_file')]),
])
# fmt:on

Expand Down
Loading
Loading