Skip to content

Commit 47f9a56

Browse files
committed
ENH: Add reporting workflow for BOLD fit
1 parent d0dbc02 commit 47f9a56

File tree

2 files changed

+191
-1
lines changed

2 files changed

+191
-1
lines changed

fmriprep/workflows/bold/fit.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,12 @@
4848

4949
# BOLD workflows
5050
from .hmc import init_bold_hmc_wf
51-
from .outputs import init_ds_boldref_wf, init_ds_hmc_wf, init_ds_registration_wf
51+
from .outputs import (
52+
init_ds_boldref_wf,
53+
init_ds_hmc_wf,
54+
init_ds_registration_wf,
55+
init_func_fit_reports_wf,
56+
)
5257
from .reference import init_raw_boldref_wf
5358
from .registration import init_bold_reg_wf
5459

@@ -183,12 +188,32 @@ def init_bold_fit_wf(
183188
niu.IdentityInterface(fields=["boldref", "boldmask"]), name="regref_buffer"
184189
)
185190

191+
func_fit_reports_wf = init_func_fit_reports_wf(
192+
# TODO: Enable sdc report even if we find coregref
193+
sdc_correction=not (have_coregref or fieldmap_id is None),
194+
freesurfer=config.workflow.run_reconall,
195+
output_dir=config.execution.fmriprep_dir,
196+
)
197+
186198
# fmt:off
187199
workflow.connect([
188200
(hmcref_buffer, outputnode, [("boldref", "hmc_boldref")]),
189201
(regref_buffer, outputnode, [("boldref", "coreg_boldref"),
190202
("boldmask", "bold_mask")]),
191203
(hmc_buffer, outputnode, [("hmc_xforms", "motion_xfm")]),
204+
(inputnode, func_fit_reports_wf, [
205+
("bold_file", "inputnode.source_file"),
206+
("t1w_preproc", "inputnode.t1w_preproc"),
207+
# May not need all of these
208+
("t1w_mask", "inputnode.t1w_mask"),
209+
("t1w_dseg", "inputnode.t1w_dseg"),
210+
("subjects_dir", "inputnode.subjects_dir"),
211+
("subject_id", "inputnode.subject_id"),
212+
]),
213+
(outputnode, func_fit_reports_wf, [
214+
("coreg_boldref", "inputnode.coreg_boldref"),
215+
("boldref2anat_xfm", "inputnode.boldref2anat_xfm"),
216+
]),
192217
])
193218
# fmt:on
194219

@@ -275,6 +300,7 @@ def init_bold_fit_wf(
275300
(fmapref_buffer, enhance_boldref_wf, [("out", "inputnode.in_file")]),
276301
(fmapref_buffer, ds_coreg_boldref_wf, [("out", "inputnode.source_files")]),
277302
(ds_coreg_boldref_wf, regref_buffer, [("outputnode.boldref", "boldref")]),
303+
(fmapref_buffer, func_fit_reports_wf, [("out", "inputnode.sdc_boldref")]),
278304
])
279305
# fmt:on
280306

fmriprep/workflows/bold/outputs.py

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import numpy as np
2929
from nipype.interfaces import utility as niu
3030
from nipype.pipeline import engine as pe
31+
from niworkflows.interfaces.fixes import FixHeaderApplyTransforms as ApplyTransforms
3132
from smriprep.workflows.outputs import _bids_relative
3233

3334
from fmriprep import config
@@ -140,6 +141,169 @@ def prepare_timing_parameters(metadata: dict):
140141
return timing_parameters
141142

142143

144+
def init_func_fit_reports_wf(
145+
*,
146+
sdc_correction: bool,
147+
freesurfer: bool,
148+
output_dir: str,
149+
name="func_fit_reports_wf",
150+
) -> pe.Workflow:
151+
"""
152+
Set up a battery of datasinks to store reports in the right location.
153+
154+
Parameters
155+
----------
156+
freesurfer : :obj:`bool`
157+
FreeSurfer was enabled
158+
output_dir : :obj:`str`
159+
Directory in which to save derivatives
160+
name : :obj:`str`
161+
Workflow name (default: anat_reports_wf)
162+
163+
Inputs
164+
------
165+
source_file
166+
Input BOLD images
167+
168+
std_t1w
169+
T1w image resampled to standard space
170+
std_mask
171+
Mask of skull-stripped template
172+
subject_dir
173+
FreeSurfer SUBJECTS_DIR
174+
subject_id
175+
FreeSurfer subject ID
176+
t1w_conform_report
177+
Conformation report
178+
t1w_preproc
179+
The T1w reference map, which is calculated as the average of bias-corrected
180+
and preprocessed T1w images, defining the anatomical space.
181+
t1w_dseg
182+
Segmentation in T1w space
183+
t1w_mask
184+
Brain (binary) mask estimated by brain extraction.
185+
template
186+
Template space and specifications
187+
188+
"""
189+
from niworkflows.interfaces.reportlets.masks import ROIsPlot
190+
from niworkflows.interfaces.reportlets.registration import (
191+
SimpleBeforeAfterRPT as SimpleBeforeAfter,
192+
)
193+
194+
workflow = pe.Workflow(name=name)
195+
196+
inputfields = [
197+
"source_file",
198+
"sdc_boldref",
199+
"coreg_boldref",
200+
"boldref2anat_xfm",
201+
"t1w_preproc",
202+
"t1w_mask",
203+
"t1w_dseg",
204+
# May be missing
205+
"subject_id",
206+
"subjects_dir",
207+
]
208+
inputnode = pe.Node(niu.IdentityInterface(fields=inputfields), name="inputnode")
209+
210+
# Reportlets follow the structure of init_bold_fit_wf stages
211+
# - SDC1:
212+
# Before: Pre-SDC boldref
213+
# After: Fieldmap reference resampled on boldref
214+
# Three-way: Fieldmap resampled on boldref
215+
# - SDC2:
216+
# Before: Pre-SDC boldref with white matter mask
217+
# After: Post-SDC boldref with white matter mask
218+
# - EPI-T1 registration:
219+
# Before: T1w brain with white matter mask
220+
# After: Resampled boldref with white matter mask
221+
222+
if sdc_correction:
223+
# SDC2
224+
sdc_report = pe.Node(
225+
SimpleBeforeAfter(
226+
before_label="Distorted",
227+
after_label="Corrected",
228+
dismiss_affine=True,
229+
),
230+
name="sdc_report",
231+
mem_gb=0.1,
232+
)
233+
234+
ds_sdc_report = pe.Node(
235+
DerivativesDataSink(
236+
base_directory=output_dir,
237+
desc="sdc",
238+
suffix="bold",
239+
datatype="figures",
240+
dismiss_entities=("echo",),
241+
),
242+
name="ds_sdc_report",
243+
)
244+
245+
# fmt:off
246+
workflow.connect([
247+
(inputnode, sdc_report, [
248+
('sdc_boldref', 'before'),
249+
('coreg_boldref', 'after'),
250+
]),
251+
(inputnode, ds_sdc_report, [('source_file', 'source_file')]),
252+
(sdc_report, ds_sdc_report, [('out_report', 'in_file')]),
253+
])
254+
# fmt:on
255+
256+
# EPI-T1 registration
257+
# Resample T1w image onto EPI-space
258+
t1w_boldref = pe.Node(
259+
ApplyTransforms(
260+
dimension=3,
261+
default_value=0,
262+
float=True,
263+
invert_transform_flags=[True],
264+
interpolation="LanczosWindowedSinc",
265+
),
266+
name="t1w_boldref",
267+
mem_gb=1,
268+
)
269+
270+
epi_t1_report = pe.Node(
271+
SimpleBeforeAfter(
272+
before_label="T1w",
273+
after_label="EPI",
274+
),
275+
name="epi_t1_report",
276+
mem_gb=0.1,
277+
)
278+
279+
ds_epi_t1_report = pe.Node(
280+
DerivativesDataSink(
281+
base_directory=output_dir,
282+
desc="coreg",
283+
suffix="bold",
284+
datatype="figures",
285+
dismiss_entities=("echo",),
286+
),
287+
name="ds_epi_t1_report",
288+
)
289+
290+
# fmt:off
291+
workflow.connect([
292+
(inputnode, t1w_boldref, [
293+
('t1w_preproc', 'input_image'),
294+
('coreg_boldref', 'reference_image'),
295+
('boldref2anat_xfm', 'transforms'),
296+
]),
297+
(inputnode, epi_t1_report, [('coreg_boldref', 'after')]),
298+
(t1w_boldref, epi_t1_report, [('output_image', 'before')]),
299+
(inputnode, ds_epi_t1_report, [('source_file', 'source_file')]),
300+
(epi_t1_report, ds_epi_t1_report, [('out_report', 'in_file')]),
301+
])
302+
# fmt:on
303+
304+
return workflow
305+
306+
143307
def init_ds_boldref_wf(
144308
*,
145309
bids_root,

0 commit comments

Comments
 (0)