diff --git a/fmriprep/cli/workflow.py b/fmriprep/cli/workflow.py index 310e7fd..5fd1917 100644 --- a/fmriprep/cli/workflow.py +++ b/fmriprep/cli/workflow.py @@ -86,7 +86,7 @@ def build_workflow(config_file, retval): if config.execution.reports_only: build_log.log(25, 'Running --reports-only on participants %s', ', '.join(subject_list)) session_list = ( - config.execution.bids_filters.get('bold', {}).get('session') + config.execution.bids_filters.get('pet', config.execution.bids_filters.get('bold', {})).get('session') if config.execution.bids_filters else None ) diff --git a/fmriprep/data/reports-spec-pet.yml b/fmriprep/data/reports-spec-pet.yml new file mode 100644 index 0000000..6ca78cf --- /dev/null +++ b/fmriprep/data/reports-spec-pet.yml @@ -0,0 +1,14 @@ +package: fmriprep +title: PET report for participant '{subject}', session '{session}' - fMRIPrep +sections: +- name: PET + ordering: session + reportlets: + - bids: {datatype: figures, desc: summary, suffix: pet} + - bids: {datatype: figures, desc: validation, suffix: pet} + - bids: {datatype: figures, desc: carpetplot, suffix: pet} + - bids: {datatype: figures, desc: confoundcorr, suffix: pet} + - bids: {datatype: figures, desc: coreg, suffix: pet} +- name: About + reportlets: + - bids: {datatype: figures, desc: about, suffix: T1w} diff --git a/fmriprep/data/reports-spec.yml b/fmriprep/data/reports-spec.yml index 89d854b..4713372 100644 --- a/fmriprep/data/reports-spec.yml +++ b/fmriprep/data/reports-spec.yml @@ -1,5 +1,5 @@ package: fmriprep -title: Visual report for participant '{subject}' - fMRIPrep +title: Visual report for participant '{subject}' - PETPrep sections: - name: Summary reportlets: @@ -106,6 +106,38 @@ sections: effects and can inform decisions about feature orthogonalization prior to confound regression. subtitle: Correlations among nuisance regressors +- name: PET + ordering: session,task,acquisition,ceagent,reconstruction,direction,run + reportlets: + - bids: {datatype: figures, desc: summary, suffix: pet} + caption: Summary of PET data acquisition parameters and processing workflow overview, including details such as injected dose, radiotracer used, and scan duration. + static: true + subtitle: PET Acquisition and Workflow Summary + + - bids: {datatype: figures, desc: validation, suffix: pet} + caption: Validation of PET images against BIDS specifications and initial quality assessment including checks for missing slices, artifacts, and alignment issues. + static: true + subtitle: PET Data Validation + + - bids: {datatype: figures, desc: carpetplot, suffix: pet} + caption: | + Summary statistics and global PET signal measures are presented. + A carpet plot displays voxel-level PET tracer uptake over time within the brain mask. Global signals calculated across the whole-brain (GS), white matter (WM), and cerebrospinal fluid (CSF) regions are plotted, along with DVARS and framewise displacement (FD) to visualize potential motion or acquisition artifacts. + "Ctx" = cortex, "Cb" = cerebellum, "WM" = white matter, "CSF" = cerebrospinal fluid. + static: false + subtitle: PET Summary and Carpet Plot + + - bids: {datatype: figures, desc: confoundcorr, suffix: pet} + caption: | + Left: Correlation heatmap illustrating relationships among PET-derived confound variables (e.g., motion parameters, global signal). + Right: Magnitude of correlation between each PET confound time series and the global PET signal. High correlations suggest potential partial volume effects or motion-induced artifacts, informing subsequent confound regression strategies. + static: false + subtitle: PET Confound Correlation + + - bids: {datatype: figures, desc: coreg, suffix: pet} + caption: PET to anatomical alignment check + static: false + subtitle: Additional PET Visualizations - name: About nested: true reportlets: diff --git a/fmriprep/reports/core.py b/fmriprep/reports/core.py index a7b0419..6898f19 100644 --- a/fmriprep/reports/core.py +++ b/fmriprep/reports/core.py @@ -119,7 +119,7 @@ def generate_reports( # we separate the functional reports per session if session_list is None: all_filters = config.execution.bids_filters or {} - filters = all_filters.get('bold', {}) + filters = all_filters.get("pet", all_filters.get("bold", {})) session_list = config.execution.layout.get_sessions( subject=subject_label, **filters ) @@ -145,4 +145,21 @@ def generate_reports( if report_error is not None: errors.append(report_error) + bootstrap_file = data.load('reports-spec-pet.yml') + html_report = f'sub-{subject_label}_ses-{session_label}_pet.html' + + report_error = run_reports( + output_dir, + subject_label, + run_uuid, + bootstrap_file=bootstrap_file, + out_filename=html_report, + reportlets_dir=reportlets_dir, + errorname=f'report-{run_uuid}-{subject_label}-pet.err', + subject=subject_label, + session=session_label, + ) + if report_error is not None: + errors.append(report_error) + return errors diff --git a/fmriprep/reports/tests/test_reports.py b/fmriprep/reports/tests/test_reports.py index 8489c28..2d4dc41 100644 --- a/fmriprep/reports/tests/test_reports.py +++ b/fmriprep/reports/tests/test_reports.py @@ -23,6 +23,10 @@ 'sub-001_ses-003_func.html', 'sub-001_ses-004_func.html', 'sub-001_ses-005_func.html', + 'sub-001_ses-001_pet.html', + 'sub-001_ses-003_pet.html', + 'sub-001_ses-004_pet.html', + 'sub-001_ses-005_pet.html', ], ), (4, ['sub-001.html']), @@ -109,3 +113,30 @@ def mock_session_list(*args, **kwargs): assert 'One or more execution steps failed' in html_content, ( f'The file {expected_files[0]} did not contain the reported error.' ) + + +def test_pet_report(tmp_path, monkeypatch): + fake_uuid = 'fake_uuid' + + pet_source = data_dir / 'work/reportlets/fmriprep' + sub_dir = tmp_path / 'sub-01' / 'figures' + sub_dir.mkdir(parents=True) + + shutil.copy2(pet_source / 'sub-001/figures/sub-001_desc-about_T1w.html', sub_dir / 'sub-01_desc-about_T1w.html') + shutil.copy2(pet_source / 'sub-001/figures/sub-001_ses-001_task-qct_dir-LR_part-mag_desc-summary_bold.html', sub_dir / 'sub-01_ses-baseline_desc-summary_pet.html') + shutil.copy2(pet_source / 'sub-001/figures/sub-001_ses-001_task-qct_dir-LR_part-mag_desc-validation_bold.html', sub_dir / 'sub-01_ses-baseline_desc-validation_pet.html') + shutil.copy2(pet_source / 'sub-001/figures/sub-001_ses-001_task-qct_dir-LR_part-mag_desc-carpetplot_bold.svg', sub_dir / 'sub-01_ses-baseline_desc-carpetplot_pet.svg') + shutil.copy2(pet_source / 'sub-001/figures/sub-001_ses-001_task-qct_dir-LR_part-mag_desc-confoundcorr_bold.svg', sub_dir / 'sub-01_ses-baseline_desc-confoundcorr_pet.svg') + shutil.copy2(pet_source / 'sub-01/func/sub-01_task-mixedgamblestask_run-01_bold_bbr.svg', sub_dir / 'sub-01_ses-baseline_pet.svg') + + config.execution.aggr_ses_reports = 4 + config.execution.layout = BIDSLayout(data_dir / 'pet') + monkeypatch.setattr(config.execution, 'bids_filters', {'pet': {'session': ['baseline']}}) + + failed_reports = generate_reports(['01'], tmp_path, fake_uuid) + + assert not failed_reports + html_file = tmp_path / 'sub-01.html' + assert html_file.is_file() + html_content = html_file.read_text() + assert '