Skip to content
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