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: separate anatomical and functional reports per session for densely sampled dataset #3191

Merged
merged 30 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
0e36362
fix: remove deprecated packagename argument of generate_reports
celprov Dec 15, 2023
78d15a6
enh: beyond a certain number of sessions per subject, we separate the…
celprov Dec 15, 2023
3e557cd
fix: desambiguate config module and config file by renaming the file
celprov Dec 18, 2023
70bbf12
fix: fix functional report generation
celprov Dec 18, 2023
f63b5e8
fix: generate reports only for the session indicated in the bids_filters
celprov Dec 20, 2023
7ff8007
fix: session_list needs to be a list
celprov Dec 20, 2023
ccc60b4
fix: remove ses- prefixes
celprov Dec 21, 2023
fc52e20
Merge remote-tracking branch 'origin/maint/load-report-assembler-from…
celprov Jan 17, 2024
e802a75
Merge remote-tracking branch 'upstream/master' into enh/sep_anat_func…
celprov Jan 18, 2024
3ea5c77
Merge branch 'master' into enh/sep_anat_func_report
oesteban Jan 19, 2024
87db1c9
fix: transform the maximum of aggregated sessions in one subject's re…
celprov Jan 19, 2024
f595064
Merge branch 'enh/sep_anat_func_report' of github.com:celprov/fmripre…
celprov Jan 19, 2024
9eed4fd
fix: strip sub- prefix in the html name of the QC-FC reports
celprov Jan 29, 2024
80630b2
enh: add test to verify that the sub- and ses- prefix are stripped in…
celprov Jan 29, 2024
13c3a54
fix: write separate function to call report generation
celprov Jan 29, 2024
c87e45c
fix: apply Chris' review comments
celprov Jan 29, 2024
4e87c48
fix: rename max-ses-agr to aggregated-session-reports
celprov Jan 29, 2024
6719fd4
enh: add boilerplate in the report. When the report is separated per …
celprov Jan 30, 2024
3249c18
enh: add test to check that the error message has been incorporated i…
celprov Jan 30, 2024
857011d
sty: styling to PEP8 and isort
celprov Jan 30, 2024
edd3d8e
Apply suggestions from code review
celprov Jan 30, 2024
0f6b436
Fix template key names
effigies Jan 30, 2024
81bc459
FIX: Get bids_filters safely
effigies Jan 31, 2024
cf31987
FIX: Get bids_filters safely (again)
effigies Jan 31, 2024
763acde
MNT: Strip SVG contents - we will squash to prune large objects
effigies Jan 31, 2024
6756f45
RF: Use data loader instead of __file__
effigies Jan 31, 2024
f846469
TEST: Use tmp_path to avoid writing files into package directory
effigies Jan 31, 2024
c1a3eac
TEST: Skip test in installed runs
effigies Jan 31, 2024
e89765d
STY: black
effigies Jan 31, 2024
7884e58
Merge branch 'master' into enh/sep_anat_func_report
effigies Jan 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions fmriprep/cli/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,14 @@ def _slice_time_ref(value, parser):
help="Output individual echo time series with slice, motion and susceptibility "
"correction. Useful for further Tedana processing post-fMRIPrep.",
)
g_outputs.add_argument(
"--max-ses-agr",
celprov marked this conversation as resolved.
Show resolved Hide resolved
action="store",
type=PositiveInt,
default=4,
help="Maximum number of sessions aggregated in one subject's visual report."
celprov marked this conversation as resolved.
Show resolved Hide resolved
"If exceeded, visual reports are separated per session.",
celprov marked this conversation as resolved.
Show resolved Hide resolved
)
g_outputs.add_argument(
"--medial-surface-nan",
required=False,
Expand Down
12 changes: 9 additions & 3 deletions fmriprep/cli/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,6 @@
_copy_any(dseg_tsv, str(config.execution.fmriprep_dir / "desc-aparcaseg_dseg.tsv"))
errno = 0
finally:
from .. import data

# Code Carbon
if config.execution.track_carbon:
emissions: float = tracker.stop()
Expand All @@ -214,11 +212,19 @@
from fmriprep.reports.core import generate_reports

# Generate reports phase
session_list = (

Check warning on line 215 in fmriprep/cli/run.py

View check run for this annotation

Codecov / codecov/patch

fmriprep/cli/run.py#L215

Added line #L215 was not covered by tests
config.execution.bids_filters["bold"]["session"]
if config.execution.bids_filters
else None
)
celprov marked this conversation as resolved.
Show resolved Hide resolved
if not isinstance(session_list, list):
session_list = [session_list]

Check warning on line 221 in fmriprep/cli/run.py

View check run for this annotation

Codecov / codecov/patch

fmriprep/cli/run.py#L220-L221

Added lines #L220 - L221 were not covered by tests
celprov marked this conversation as resolved.
Show resolved Hide resolved

failed_reports = generate_reports(
config.execution.participant_label,
config.execution.fmriprep_dir,
config.execution.run_uuid,
config=data.load("reports-spec.yml"),
session_list=session_list,
)
write_derivative_description(config.execution.bids_dir, config.execution.fmriprep_dir)
write_bidsignore(config.execution.fmriprep_dir)
Expand Down
10 changes: 9 additions & 1 deletion fmriprep/cli/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,19 @@
# Called with reports only
if config.execution.reports_only:
build_log.log(25, "Running --reports-only on participants %s", ", ".join(subject_list))
session_list = (

Check warning on line 89 in fmriprep/cli/workflow.py

View check run for this annotation

Codecov / codecov/patch

fmriprep/cli/workflow.py#L89

Added line #L89 was not covered by tests
config.execution.bids_filters["bold"]["session"]
effigies marked this conversation as resolved.
Show resolved Hide resolved
if config.execution.bids_filters
else None
)
if not isinstance(session_list, list):
session_list = [session_list]

Check warning on line 95 in fmriprep/cli/workflow.py

View check run for this annotation

Codecov / codecov/patch

fmriprep/cli/workflow.py#L94-L95

Added lines #L94 - L95 were not covered by tests
celprov marked this conversation as resolved.
Show resolved Hide resolved

failed_reports = generate_reports(
config.execution.participant_label,
config.execution.fmriprep_dir,
config.execution.run_uuid,
config=data.load("reports-spec.yml"),
session_list=session_list,
)
if failed_reports:
config.loggers.cli.error(
Expand Down
2 changes: 2 additions & 0 deletions fmriprep/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,8 @@ class execution(_Config):
"""Folder where derivatives will be stored."""
me_output_echos = False
"""Output individual echo time series with slice, motion and susceptibility correction"""
max_ses_agr = None
"""Maximum number of sessions aggregated in one subject's visual report."""
output_layout = None
"""Layout of derivatives within output_dir."""
output_spaces = None
Expand Down
30 changes: 30 additions & 0 deletions fmriprep/data/reports-spec-anat.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package: fmriprep
sections:
- name: Summary
reportlets:
- bids: {datatype: figures, desc: summary, suffix: T1w}
- name: Anatomical
reportlets:
- bids:
datatype: figures
desc: conform
extension: [.html]
suffix: T1w
- bids: {datatype: figures, suffix: dseg}
caption: This panel shows the final, preprocessed T1-weighted image,
with contours delineating the detected brain mask and brain tissue segmentations.
subtitle: Brain mask and brain tissue segmentation of the T1w
- bids: {datatype: figures, space: .*, suffix: T1w, regex_search: True}
caption: Spatial normalization of the T1w image to the <code>{space}</code> template.
description: Results of nonlinear alignment of the T1w reference one or more template
space(s). Hover on the panels with the mouse pointer to transition between both
spaces.
static: false
subtitle: Spatial normalization of the anatomical T1w reference
- bids: {datatype: figures, desc: reconall, suffix: T1w}
caption: Surfaces (white and pial) reconstructed with FreeSurfer (<code>recon-all</code>)
overlaid on the participant's T1w template.
subtitle: Surface reconstruction
- name: About
reportlets:
- bids: {datatype: figures, desc: about, suffix: T1w}
157 changes: 157 additions & 0 deletions fmriprep/data/reports-spec-func.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package: fmriprep
sections:
- name: Summary
reportlets:
- bids: {datatype: figures, desc: summary, suffix: T1w}
- name: <em>B<sub>0</sub></em> field mapping
ordering: session,acquisition,run,fmapid
reportlets:
- bids: {datatype: figures, desc: mapped, suffix: fieldmap}
caption: Inhomogeneities of the <em>B<sub>0</sub></em> field introduce (oftentimes severe) spatial distortions
along the phase-encoding direction of the image. Some scanners produce a <em>B<sub>0</sub></em>
mapping of the field, using Spiral Echo Imaging (SEI) or postprocessing a "phase-difference"
acquisition. The plot below shows an anatomical "magnitude" reference and the corresponding
fieldmap.
description: Hover over the panels with the mouse pointer to also visualize the intensity of the
field inhomogeneity in Hertz.
static: false
subtitle: "Preprocessed <em>B<sub>0</sub></em> mapping acquisition"
- bids: {datatype: figures, desc: phasediff, suffix: fieldmap}
caption: Inhomogeneities of the <em>B<sub>0</sub></em> field introduce (oftentimes severe) spatial distortions
along the phase-encoding direction of the image. A Gradient-Recalled Echo (GRE) scheme was included for the
mapping of the <em>B<sub>0</sub></em> inhomogeneities by subtracting the phase maps obtained at
two subsequent echoes. The plot below shows an anatomical "magnitude" reference and the corresponding
fieldmap.
description: Hover over the panels with the mouse pointer to also visualize the intensity of the
field inhomogeneity in Hertz.
static: false
subtitle: "Preprocessed mapping of phase-difference acquisition"
- bids: {datatype: figures, desc: pepolar, suffix: fieldmap}
caption: Inhomogeneities of the <em>B<sub>0</sub></em> field introduce (oftentimes severe) spatial distortions
along the phase-encoding direction of the image. Utilizing two or more images with different
phase-encoding polarities (PEPolar) or directions, it is possible to estimate the inhomogeneity
of the field. The plot below shows a reference EPI (echo-planar imaging) volume generated
using two or more EPI images with varying phase-encoding blips.
description: Hover on the panels with the mouse pointer to also visualize the intensity of the
inhomogeneity of the field in Hertz.
static: false
subtitle: "Preprocessed estimation with varying Phase-Encoding (PE) blips"
- bids: {datatype: figures, desc: anat, suffix: fieldmap}
caption: Inhomogeneities of the <em>B<sub>0</sub></em> field introduce (oftentimes severe) spatial distortions
along the phase-encoding direction of the image. Utilizing an <em>anatomically-correct</em> acquisition
(for instance, T1w or T2w), it is possible to estimate the inhomogeneity of the field by means of nonlinear
registration. The plot below shows a reference EPI (echo-planar imaging) volume generated
using two or more EPI images with the same PE encoding, after alignment to the anatomical scan.
description: Hover on the panels with the mouse pointer to also visualize the intensity of the
inhomogeneity of the field in Hertz.
static: false
subtitle: "Preprocessed estimation by nonlinear registration to an anatomical scan (&ldquo;<em>fieldmap-less</em>&rdquo;)"

- name: Functional
ordering: session,task,acquisition,ceagent,reconstruction,direction,run,echo
reportlets:
- bids: {datatype: figures, desc: summary, suffix: bold}
- bids: {datatype: figures, desc: validation, suffix: bold}
- bids: {datatype: figures, desc: fmapCoreg, suffix: bold}
caption: The estimated fieldmap was aligned to the corresponding EPI reference
with a rigid-registration process of the fieldmap reference image,
using <code>antsRegistration</code>.
Overlaid on top of the co-registration results, the final BOLD mask is represented
with a red contour for reference.
static: false
subtitle: Alignment between the anatomical reference of the fieldmap and the target EPI
- bids: {datatype: figures, desc: fieldmap, suffix: bold}
caption: Estimated fieldmap, as reconstructed on the target BOLD run space to allow
the assessment of its alignment with the distorted data.
The anatomical reference is the fieldmap's reference moved into the target EPI's grid through
the estimated transformation.
In other words, this plot should be equivalent to that of the
<em>Preprocessed estimation with varying Phase-Encoding (PE) blips</em> shown above in the
fieldmap section.
Therefore, the fieldmap should be positioned relative to the anatomical reference exactly
as it is positioned in the reportlet above.
static: false
subtitle: "Reconstructed <em>B<sub>0</sub></em> map in the corresponding run's space (debug mode)"
- bids: {datatype: figures, desc: sdc, suffix: bold}
caption: Results of performing susceptibility distortion correction (SDC) on the
BOLD reference image. The "distorted" image is the image that would be used to
align to the anatomical reference if SDC were not applied. The "corrected"
image is the image that was used.
static: false
subtitle: Susceptibility distortion correction
- bids: {datatype: figures, desc: forcedsyn, suffix: bold}
caption: The dataset contained some fieldmap information, but the argument <code>--force-syn</code>
was used. The higher-priority SDC method was used. Here, we show the results
of performing SyN-based SDC on the EPI for comparison.
static: false
subtitle: Experimental fieldmap-less susceptibility distortion correction
- bids: {datatype: figures, desc: t2scomp, suffix: bold}
caption: A T2* map was calculated from the echos. Here, we show the comparison
of the T2* map and the BOLD reference map used for BOLD-T1w coregistration.
The red contour shows the anatomical gray-matter mask resampled into BOLD space.
static: false
subtitle: T2* map
- bids: {datatype: figures, desc: t2starhist, suffix: bold}
caption: A histogram of estimated T2* values within the anatomically-derived gray-matter mask
shown in the previous plot. Note that values are clipped at 100ms, so any extreme outliers will
appear in the 100ms bin.
static: false
subtitle: T2* gray-matter values
- bids: {datatype: figures, desc: coreg, suffix: bold}
caption: This panel shows the alignment of the reference EPI (BOLD) image to the
anatomical (T1-weighted) image.
The reference EPI has been contrast enhanced and susceptibility-distortion
corrected (if applicable) for improved anatomical fidelity.
The anatomical image has been resampled into EPI space, as well as the
anatomical white matter mask, which appears as a red contour.
static: false
subtitle: Alignment of functional and anatomical MRI data (coregistration)
- 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
confounding components that can be then used as nuisance regressors in analysis.<br />
The <em>anatomical CompCor</em> ROI (magenta contour) is a mask combining
CSF and WM (white-matter), where voxels containing a minimal partial volume
of GM have been removed.<br />
The <em>temporal CompCor</em> ROI (blue contour) contains the top 2% most
variable voxels within the brain mask.<br />
The <em>brain edge</em> (or <em>crown</em>) ROI (green contour) picks signals
outside but close to the brain, which are decomposed into 24 principal components.
subtitle: Brain mask and (anatomical/temporal) CompCor ROIs
- bids:
datatype: figures
desc: '[at]compcor'
extension: [.html]
suffix: bold
- bids: {datatype: figures, desc: 'compcorvar', suffix: bold}
caption: The cumulative variance explained by the first k components of the
<em>t/aCompCor</em> decomposition, plotted for all values of <em>k</em>.
The number of components that must be included in the model in order to
explain some fraction of variance in the decomposition mask can be used
as a feature selection criterion for confound regression.
subtitle: Variance explained by t/aCompCor components
- bids: {datatype: figures, desc: carpetplot, suffix: bold}
caption: Summary statistics are plotted, which may reveal trends or artifacts
in the BOLD data. Global signals calculated within the whole-brain (GS), within
the white-matter (WM) and within cerebro-spinal fluid (CSF) show the mean BOLD
signal in their corresponding masks. DVARS and FD show the standardized DVARS
and framewise-displacement measures for each time point.<br />
A carpet plot shows the time series for all voxels within the brain mask,
or if <code>--cifti-output</code> was enabled, all grayordinates.
See the figure legend for specific color mappings.
"Ctx" = cortex, "Cb" = cerebellum, "WM" = white matter, "CSF" = cerebrospinal fluid.
"d" and "s" prefixes indicate "deep" and "shallow" relative to the cortex.
"Edge" indicates regions just outside the brain.
subtitle: BOLD Summary
- bids: {datatype: figures, desc: 'confoundcorr', suffix: bold}
caption: |
Left: Heatmap summarizing the correlation structure among confound variables.
(Cosine bases and PCA-derived CompCor components are inherently orthogonal.)
Right: magnitude of the correlation between each confound time series and the
mean global signal. Strong correlations might be indicative of partial volume
effects and can inform decisions about feature orthogonalization prior to
confound regression.
subtitle: Correlations among nuisance regressors
- name: About
reportlets:
- bids: {datatype: figures, desc: about, suffix: T1w}
67 changes: 65 additions & 2 deletions fmriprep/reports/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,12 @@
from nireports.assembler.report import Report


def generate_reports(subject_list, output_dir, run_uuid, config=None, work_dir=None):
def generate_reports(
subject_list, output_dir, run_uuid, session_list=None, bootstrap_file=None, work_dir=None
):
"""Generate reports for a list of subjects."""
from .. import config, data

Check warning on line 32 in fmriprep/reports/core.py

View check run for this annotation

Codecov / codecov/patch

fmriprep/reports/core.py#L32

Added line #L32 was not covered by tests
celprov marked this conversation as resolved.
Show resolved Hide resolved

reportlets_dir = None
if work_dir is not None:
reportlets_dir = Path(work_dir) / "reportlets"
Expand All @@ -36,10 +40,27 @@
entities = {}
entities["subject"] = subject_label

# The number of sessions is intentionally not based on session_list but on the total number of sessions, because
# I want the final derivatives folder to be the same whether sessions were ran one at a time or all-together.
n_ses = len(config.execution.layout.get_sessions(subject=subject_label))

Check warning on line 45 in fmriprep/reports/core.py

View check run for this annotation

Codecov / codecov/patch

fmriprep/reports/core.py#L45

Added line #L45 was not covered by tests

if bootstrap_file is not None:

Check warning on line 47 in fmriprep/reports/core.py

View check run for this annotation

Codecov / codecov/patch

fmriprep/reports/core.py#L47

Added line #L47 was not covered by tests
# If a config file is precised, we do not override it
html_report = "report.html"
elif n_ses < config.execution.max_ses_agr:

Check warning on line 50 in fmriprep/reports/core.py

View check run for this annotation

Codecov / codecov/patch

fmriprep/reports/core.py#L49-L50

Added lines #L49 - L50 were not covered by tests
celprov marked this conversation as resolved.
Show resolved Hide resolved
# If there is only a few session for this subject, we aggregate them in a single visual report.
bootstrap_file = data.load("reports-spec.yml")
html_report = "report.html"

Check warning on line 53 in fmriprep/reports/core.py

View check run for this annotation

Codecov / codecov/patch

fmriprep/reports/core.py#L52-L53

Added lines #L52 - L53 were not covered by tests
else:
# Beyond a threshold, we separate the anatomical report from the functional.
bootstrap_file = data.load("reports-spec-anat.yml")
html_report = ''.join([f"sub-{subject_label}", "_anat.html"])

Check warning on line 57 in fmriprep/reports/core.py

View check run for this annotation

Codecov / codecov/patch

fmriprep/reports/core.py#L56-L57

Added lines #L56 - L57 were not covered by tests

robj = Report(
output_dir,
run_uuid,
bootstrap_file=config,
bootstrap_file=bootstrap_file,
out_filename=html_report,
reportlets_dir=reportlets_dir,
plugins=None,
plugin_meta=None,
Expand All @@ -54,10 +75,52 @@
import sys
import traceback

# Store the list of subjects for which report generation failed
errors.append(subject_label)
traceback.print_exception(
*sys.exc_info(),
file=str(Path(output_dir) / "logs" / f"report-{run_uuid}-{subject_label}.err"),
)

if n_ses >= config.execution.max_ses_agr:

Check warning on line 85 in fmriprep/reports/core.py

View check run for this annotation

Codecov / codecov/patch

fmriprep/reports/core.py#L85

Added line #L85 was not covered by tests
celprov marked this conversation as resolved.
Show resolved Hide resolved
# Beyond a certain number of sessions per subject, we separate the functional reports per session
if session_list is None:
session_list = config.execution.layout.get_sessions(subject=subject_label)

Check warning on line 88 in fmriprep/reports/core.py

View check run for this annotation

Codecov / codecov/patch

fmriprep/reports/core.py#L87-L88

Added lines #L87 - L88 were not covered by tests

# Drop ses- prefixes
session_list = [ses[4:] if ses.startswith("ses-") else ses for ses in session_list]

Check warning on line 91 in fmriprep/reports/core.py

View check run for this annotation

Codecov / codecov/patch

fmriprep/reports/core.py#L91

Added line #L91 was not covered by tests

for session_label in session_list:
bootstrap_file = data.load("reports-spec-func.yml")
html_report = ''.join(

Check warning on line 95 in fmriprep/reports/core.py

View check run for this annotation

Codecov / codecov/patch

fmriprep/reports/core.py#L93-L95

Added lines #L93 - L95 were not covered by tests
[f"sub-{subject_label}", f"_ses-{session_label}", "_func.html"]
)
entities["session"] = session_label

Check warning on line 98 in fmriprep/reports/core.py

View check run for this annotation

Codecov / codecov/patch

fmriprep/reports/core.py#L98

Added line #L98 was not covered by tests

robj = Report(

Check warning on line 100 in fmriprep/reports/core.py

View check run for this annotation

Codecov / codecov/patch

fmriprep/reports/core.py#L100

Added line #L100 was not covered by tests
output_dir,
run_uuid,
bootstrap_file=bootstrap_file,
out_filename=html_report,
reportlets_dir=reportlets_dir,
plugins=None,
plugin_meta=None,
metadata=None,
**entities,
)

try:
robj.generate_report()
except:

Check warning on line 114 in fmriprep/reports/core.py

View check run for this annotation

Codecov / codecov/patch

fmriprep/reports/core.py#L112-L114

Added lines #L112 - L114 were not covered by tests
# Store the list of subjects for which report generation failed
errors.append(subject_label)
traceback.print_exception(

Check warning on line 117 in fmriprep/reports/core.py

View check run for this annotation

Codecov / codecov/patch

fmriprep/reports/core.py#L116-L117

Added lines #L116 - L117 were not covered by tests
*sys.exc_info(),
file=str(
Path(output_dir)
/ "logs"
/ f"report-{run_uuid}-{subject_label}-func.err"
),
)
celprov marked this conversation as resolved.
Show resolved Hide resolved

return errors