From d4c03f4ca4e2ce69512be9fe8c773f5633bace92 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Sun, 12 Feb 2023 08:58:26 -0500 Subject: [PATCH] Improve executive summary carpet plots (#747) --- .circleci/NiftiWithoutFreeSurferTest.sh | 3 +- docs/api.rst | 4 +- xcp_d/cli/run.py | 1 - xcp_d/interfaces/__init__.py | 10 +- ...layout_builder.py => executive_summary.py} | 6 + xcp_d/interfaces/{qc_plot.py => plotting.py} | 141 +++++- xcp_d/interfaces/regression.py | 2 +- xcp_d/interfaces/report_core.py | 2 +- xcp_d/interfaces/surfplotting.py | 118 ----- xcp_d/tests/test_ALFF.py | 2 +- xcp_d/tests/test_cli.py | 15 +- xcp_d/tests/test_smoothing.py | 2 +- xcp_d/tests/test_utils_plotting.py | 32 ++ xcp_d/tests/test_utils_qcmetrics.py | 13 + xcp_d/utils/__init__.py | 4 +- xcp_d/utils/concatenation.py | 45 +- xcp_d/utils/{plot.py => plotting.py} | 462 +++++++----------- xcp_d/workflows/bold.py | 6 +- xcp_d/workflows/cifti.py | 6 +- xcp_d/workflows/execsummary.py | 4 +- xcp_d/workflows/plotting.py | 8 +- xcp_d/workflows/restingstate.py | 2 +- 22 files changed, 390 insertions(+), 498 deletions(-) rename xcp_d/interfaces/{layout_builder.py => executive_summary.py} (97%) rename xcp_d/interfaces/{qc_plot.py => plotting.py} (77%) delete mode 100644 xcp_d/interfaces/surfplotting.py create mode 100644 xcp_d/tests/test_utils_plotting.py create mode 100644 xcp_d/tests/test_utils_qcmetrics.py rename xcp_d/utils/{plot.py => plotting.py} (74%) diff --git a/.circleci/NiftiWithoutFreeSurferTest.sh b/.circleci/NiftiWithoutFreeSurferTest.sh index 549438256..df49b5c8a 100644 --- a/.circleci/NiftiWithoutFreeSurferTest.sh +++ b/.circleci/NiftiWithoutFreeSurferTest.sh @@ -33,7 +33,8 @@ XCPD_CMD="$BASE_XCPD_CMD \ -vv \ --nuisance-regressors 27P \ --disable-bandpass-filter \ - --dummy-scans 1" + --dummy-scans 1 \ + --dcan_qc" echo $XCPD_CMD diff --git a/docs/api.rst b/docs/api.rst index cf6ca7fc6..ee6aae1a7 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -53,8 +53,8 @@ xcp_d-combineqc xcp_d.interfaces.bids xcp_d.interfaces.c3 xcp_d.interfaces.connectivity + xcp_d.interfaces.executive_summary xcp_d.interfaces.filtering - xcp_d.interfaces.layout_builder xcp_d.interfaces.nilearn xcp_d.interfaces.prepostcleaning xcp_d.interfaces.qc_plot @@ -89,7 +89,7 @@ xcp_d-combineqc xcp_d.utils.fcon xcp_d.utils.filemanip xcp_d.utils.modified_data - xcp_d.utils.plot + xcp_d.utils.plotting xcp_d.utils.qcmetrics xcp_d.utils.sentry xcp_d.utils.utils diff --git a/xcp_d/cli/run.py b/xcp_d/cli/run.py index 743c9abef..996df1e25 100644 --- a/xcp_d/cli/run.py +++ b/xcp_d/cli/run.py @@ -583,7 +583,6 @@ def main(args=None): subjects=subject_list, fmri_dir=str(fmri_dir), output_dir=str(Path(str(output_dir)) / "xcp_d/"), - work_dir=work_dir, cifti=opts.cifti, dcan_qc=opts.dcan_qc, dummy_scans=opts.dummy_scans, diff --git a/xcp_d/interfaces/__init__.py b/xcp_d/interfaces/__init__.py index adfd5e422..426dc833a 100644 --- a/xcp_d/interfaces/__init__.py +++ b/xcp_d/interfaces/__init__.py @@ -6,16 +6,15 @@ bids, c3, connectivity, + executive_summary, filtering, - layout_builder, nilearn, + plotting, prepostcleaning, - qc_plot, regression, report, report_core, resting_state, - surfplotting, workbench, ) @@ -25,14 +24,13 @@ "c3", "connectivity", "filtering", - "layout_builder", + "executive_summary", "nilearn", "prepostcleaning", - "qc_plot", + "plotting", "regression", "report_core", "report", "resting_state", - "surfplotting", "workbench", ] diff --git a/xcp_d/interfaces/layout_builder.py b/xcp_d/interfaces/executive_summary.py similarity index 97% rename from xcp_d/interfaces/layout_builder.py rename to xcp_d/interfaces/executive_summary.py index 5216f51e4..56c8a8084 100644 --- a/xcp_d/interfaces/layout_builder.py +++ b/xcp_d/interfaces/executive_summary.py @@ -178,6 +178,9 @@ def collect_inputs(self): for task_entity_set in task_entity_sets: task_file_figures = task_entity_set.copy() + task_file_figures[ + "key" + ] = f"task-{task_entity_set['task']}_run-{task_entity_set.get('run', 0)}" query = { "subject": self.subject_id, @@ -208,6 +211,9 @@ def collect_inputs(self): task_files.append(task_file_figures) + # Sort the files by the desired key + task_files = sorted(task_files, key=lambda d: d["key"]) + self.task_files_ = task_files def generate_report(self, out_file=None): diff --git a/xcp_d/interfaces/qc_plot.py b/xcp_d/interfaces/plotting.py similarity index 77% rename from xcp_d/interfaces/qc_plot.py rename to xcp_d/interfaces/plotting.py index 397c80e41..5df1a9aa2 100644 --- a/xcp_d/interfaces/qc_plot.py +++ b/xcp_d/interfaces/plotting.py @@ -1,12 +1,13 @@ # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: -"""Quality control plotting interfaces.""" +"""Plotting interfaces.""" import os import matplotlib.pyplot as plt import numpy as np import pandas as pd import seaborn as sns +from nilearn.plotting import plot_anat from nipype import logging from nipype.interfaces.base import ( BaseInterfaceInputSpec, @@ -20,7 +21,7 @@ from xcp_d.utils.confounds import load_confound, load_motion from xcp_d.utils.filemanip import fname_presuffix from xcp_d.utils.modified_data import compute_fd -from xcp_d.utils.plot import FMRIPlot +from xcp_d.utils.plotting import FMRIPlot, plot_fmri_es from xcp_d.utils.qcmetrics import compute_dvars, compute_registration_qc from xcp_d.utils.write_save import read_ndata, write_ndata @@ -183,7 +184,7 @@ def _run_interface(self, runtime): return runtime -class _QCPlotInputSpec(BaseInterfaceInputSpec): +class _QCPlotsInputSpec(BaseInterfaceInputSpec): bold_file = File(exists=True, mandatory=True, desc="Raw bold file from fMRIPrep") dummy_scans = traits.Int(mandatory=True, desc="Dummy time to drop") tmask = File(exists=True, mandatory=True, desc="Temporal mask") @@ -205,14 +206,14 @@ class _QCPlotInputSpec(BaseInterfaceInputSpec): bold2temp_mask = File(exists=True, mandatory=False, desc="Bold mask in T1W") -class _QCPlotOutputSpec(TraitedSpec): +class _QCPlotsOutputSpec(TraitedSpec): qc_file = File(exists=True, mandatory=True, desc="qc file in tsv") raw_qcplot = File(exists=True, mandatory=True, desc="qc plot before regression") clean_qcplot = File(exists=True, mandatory=True, desc="qc plot after regression") -class QCPlot(SimpleInterface): - """Generate a quality control (QC) figure. +class QCPlots(SimpleInterface): + """Generate pre- and post-processing quality control (QC) figures. Examples -------- @@ -221,20 +222,20 @@ class QCPlot(SimpleInterface): >>> tmpdir = TemporaryDirectory() >>> os.chdir(tmpdir.name) .. doctest:: - computeqcwf = QCPlot() - computeqcwf.inputs.cleaned_file = datafile - computeqcwf.inputs.bold_file = rawbold - computeqcwf.inputs.TR = TR - computeqcwf.inputs.tmask = temporalmask - computeqcwf.inputs.mask_file = mask - computeqcwf.inputs.dummy_scans = dummy_scans - computeqcwf.run() + qcplots = QCPlots() + qcplots.inputs.cleaned_file = datafile + qcplots.inputs.bold_file = rawbold + qcplots.inputs.TR = TR + qcplots.inputs.tmask = temporalmask + qcplots.inputs.mask_file = mask + qcplots.inputs.dummy_scans = dummy_scans + qcplots.run() .. testcleanup:: >>> tmpdir.cleanup() """ - input_spec = _QCPlotInputSpec - output_spec = _QCPlotOutputSpec + input_spec = _QCPlotsInputSpec + output_spec = _QCPlotsOutputSpec def _run_interface(self, runtime): # Load confound matrix and load motion with motion filtering @@ -445,3 +446,111 @@ def _run_interface(self, runtime): df.to_csv(self._results["qc_file"], index=False, header=True) return runtime + + +class _QCPlotsESInputSpec(BaseInterfaceInputSpec): + rawdata = File(exists=True, mandatory=True, desc="Raw data") + regressed_data = File( + exists=True, + mandatory=True, + desc="Data after regression and interpolation, but not filtering.", + ) + residual_data = File(exists=True, mandatory=True, desc="Data after filtering") + filtered_motion = File( + exists=True, + mandatory=True, + desc="TSV file with filtered motion parameters.", + ) + TR = traits.Float(default_value=1, desc="Repetition time") + + # Optional inputs + mask = File(exists=True, mandatory=False, desc="Bold mask") + seg_data = File(exists=True, mandatory=False, desc="Segmentation file") + dummy_scans = traits.Int( + 0, + usedefault=True, + desc="Number of dummy volumes to drop from the beginning of the run.", + ) + + +class _QCPlotsESOutputSpec(TraitedSpec): + before_process = File(exists=True, mandatory=True, desc=".SVG file before processing") + after_process = File(exists=True, mandatory=True, desc=".SVG file after processing") + + +class QCPlotsES(SimpleInterface): + """Plot fd, dvars, and carpet plots of the bold data before and after regression/filtering. + + This is essentially equivalent to the QCPlots + (which are paired pre- and post-processing FMRIPlots), but adapted for the executive summary. + + It takes in the data that's regressed, the data that's filtered and regressed, + as well as the segmentation files, TR, FD, bold_mask and unprocessed data. + + It outputs the .SVG files before after processing has taken place. + """ + + input_spec = _QCPlotsESInputSpec + output_spec = _QCPlotsESOutputSpec + + def _run_interface(self, runtime): + before_process_fn = fname_presuffix( + "carpetplot_before_", + suffix="file.svg", + newpath=runtime.cwd, + use_ext=False, + ) + + after_process_fn = fname_presuffix( + "carpetplot_after_", + suffix="file.svg", + newpath=runtime.cwd, + use_ext=False, + ) + + mask_file = self.inputs.mask + mask_file = mask_file if isdefined(mask_file) else None + + segmentation_file = self.inputs.seg_data + segmentation_file = segmentation_file if isdefined(segmentation_file) else None + + self._results["before_process"], self._results["after_process"] = plot_fmri_es( + preprocessed_file=self.inputs.rawdata, + residuals_file=self.inputs.regressed_data, + denoised_file=self.inputs.residual_data, + dummy_scans=self.inputs.dummy_scans, + TR=self.inputs.TR, + mask=mask_file, + filtered_motion=self.inputs.filtered_motion, + seg_data=segmentation_file, + processed_filename=after_process_fn, + unprocessed_filename=before_process_fn, + ) + + return runtime + + +class _AnatomicalPlotInputSpec(BaseInterfaceInputSpec): + in_file = File(exists=True, mandatory=True, desc="plot image") + + +class _AnatomicalPlotOutputSpec(TraitedSpec): + out_file = File(exists=True, desc="out image") + + +class AnatomicalPlot(SimpleInterface): + """Python class to plot x,y, and z of image data.""" + + input_spec = _AnatomicalPlotInputSpec + output_spec = _AnatomicalPlotOutputSpec + + def _run_interface(self, runtime): + self._results["out_file"] = fname_presuffix( + self.inputs.in_file, suffix="_file.svg", newpath=runtime.cwd, use_ext=False + ) + + fig = plt.figure(constrained_layout=False, figsize=(25, 10)) + plot_anat(self.inputs.in_file, draw_cross=False, figure=fig) + fig.savefig(self._results["out_file"], bbox_inches="tight", pad_inches=None) + + return runtime diff --git a/xcp_d/interfaces/regression.py b/xcp_d/interfaces/regression.py index d2cd1a438..9d86c443a 100644 --- a/xcp_d/interfaces/regression.py +++ b/xcp_d/interfaces/regression.py @@ -118,7 +118,7 @@ def _run_interface(self, runtime): standardize=False, sample_mask=None, confounds=confound, - standardize_confounds=False, # do we want to set this to True? + standardize_confounds=True, # do we want to set this to True? filter=None, low_pass=None, high_pass=None, diff --git a/xcp_d/interfaces/report_core.py b/xcp_d/interfaces/report_core.py index ec84004fd..68655e653 100644 --- a/xcp_d/interfaces/report_core.py +++ b/xcp_d/interfaces/report_core.py @@ -11,7 +11,7 @@ from niworkflows.reports.core import Report as _Report -from xcp_d.interfaces.layout_builder import ExecutiveSummary +from xcp_d.interfaces.executive_summary import ExecutiveSummary from xcp_d.utils.bids import get_entity from xcp_d.utils.doc import fill_doc diff --git a/xcp_d/interfaces/surfplotting.py b/xcp_d/interfaces/surfplotting.py deleted file mode 100644 index d044faa24..000000000 --- a/xcp_d/interfaces/surfplotting.py +++ /dev/null @@ -1,118 +0,0 @@ -# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -"""Surface plotting interfaces.""" -from nipype import logging -from nipype.interfaces.base import ( - BaseInterfaceInputSpec, - File, - SimpleInterface, - TraitedSpec, - isdefined, - traits, -) - -from xcp_d.utils.filemanip import fname_presuffix -from xcp_d.utils.plot import plot_svgx, plotimage - -LOGGER = logging.getLogger("nipype.interface") - - -class _PlotImageInputSpec(BaseInterfaceInputSpec): - in_file = File(exists=True, mandatory=True, desc="plot image") - - -class _PlotImageOutputSpec(TraitedSpec): - out_file = File(exists=True, desc="out image") - - -class PlotImage(SimpleInterface): - """Python class to plot x,y, and z of image data.""" - - input_spec = _PlotImageInputSpec - output_spec = _PlotImageOutputSpec - - def _run_interface(self, runtime): - self._results["out_file"] = fname_presuffix( - self.inputs.in_file, suffix="_file.svg", newpath=runtime.cwd, use_ext=False - ) - - self._results["out_file"] = plotimage(self.inputs.in_file, self._results["out_file"]) - - return runtime - - -class _PlotSVGDataInputSpec(BaseInterfaceInputSpec): - rawdata = File(exists=True, mandatory=True, desc="Raw data") - regressed_data = File(exists=True, mandatory=True, desc="Data after regression") - residual_data = File(exists=True, mandatory=True, desc="Data after filtering") - filtered_motion = File( - exists=True, - mandatory=True, - desc="TSV file with filtered motion parameters.", - ) - TR = traits.Float(default_value=1, desc="Repetition time") - - # Optional inputs - mask = File(exists=True, mandatory=False, desc="Bold mask") - tmask = File(exists=True, mandatory=False, desc="Temporal mask") - seg_data = File(exists=True, mandatory=False, desc="Segmentation file") - dummy_scans = traits.Int( - 0, - usedefault=True, - desc="Number of dummy volumes to drop from the beginning of the run.", - ) - - -class _PlotSVGDataOutputSpec(TraitedSpec): - before_process = File(exists=True, mandatory=True, desc=".SVG file before processing") - after_process = File(exists=True, mandatory=True, desc=".SVG file after processing") - - -class PlotSVGData(SimpleInterface): - """Plot fd, dvars, and carpet plots of the bold data before and after regression/filtering. - - It takes in the data that's regressed, the data that's filtered and regressed, - as well as the segmentation files, TR, FD, bold_mask and unprocessed data. - - It outputs the .SVG files before after processing has taken place. - """ - - input_spec = _PlotSVGDataInputSpec - output_spec = _PlotSVGDataOutputSpec - - def _run_interface(self, runtime): - before_process_fn = fname_presuffix( - "carpetplot_before_", - suffix="file.svg", - newpath=runtime.cwd, - use_ext=False, - ) - - after_process_fn = fname_presuffix( - "carpetplot_after_", - suffix="file.svg", - newpath=runtime.cwd, - use_ext=False, - ) - - mask_file = self.inputs.mask - mask_file = mask_file if isdefined(mask_file) else None - - segmentation_file = self.inputs.seg_data - segmentation_file = segmentation_file if isdefined(segmentation_file) else None - - self._results["before_process"], self._results["after_process"] = plot_svgx( - preprocessed_file=self.inputs.rawdata, - residuals_file=self.inputs.regressed_data, - denoised_file=self.inputs.residual_data, - tmask=self.inputs.tmask, - dummy_scans=self.inputs.dummy_scans, - TR=self.inputs.TR, - mask=mask_file, - filtered_motion=self.inputs.filtered_motion, - seg_data=segmentation_file, - processed_filename=after_process_fn, - unprocessed_filename=before_process_fn, - ) - - return runtime diff --git a/xcp_d/tests/test_ALFF.py b/xcp_d/tests/test_ALFF.py index 25d10d0d7..af661c4d0 100644 --- a/xcp_d/tests/test_ALFF.py +++ b/xcp_d/tests/test_ALFF.py @@ -3,8 +3,8 @@ # Necessary imports import os -import nibabel as nb +import nibabel as nb from numpy.fft import fft, ifft from xcp_d.utils.bids import _get_tr diff --git a/xcp_d/tests/test_cli.py b/xcp_d/tests/test_cli.py index 2eb50cab0..909b001a2 100644 --- a/xcp_d/tests/test_cli.py +++ b/xcp_d/tests/test_cli.py @@ -45,9 +45,8 @@ def test_ds001419_nifti(datasets, output_dir, working_dir): "--head_radius=40", "--smoothing=6", "-vvv", - "--motion-filter-type=notch", - "--band-stop-min=12", - "--band-stop-max=18", + "--motion-filter-type=lp", + "--band-stop-min=6", "--min-coverage=1", ] opts = get_parser().parse_args(parameters) @@ -97,18 +96,19 @@ def test_ds001419_cifti(datasets, output_dir, working_dir): "--nthreads=2", "--omp-nthreads=2", f"--bids-filter-file={filter_file}", + "--nuisance-regressors=acompcor_gsr", "--despike", "--head_radius=40", "--smoothing=6", - "-vvv", - "--motion-filter-type=lp", - "--band-stop-min=6", + "--motion-filter-type=notch", + "--band-stop-min=12", + "--band-stop-max=18", "--warp-surfaces-native2std", "--cifti", "--combineruns", "--dcan-qc", "--dummy-scans=auto", - "--fd-thresh=0.04", + "--fd-thresh=0.2", ] opts = get_parser().parse_args(parameters) retval = {} @@ -123,7 +123,6 @@ def test_ds001419_cifti(datasets, output_dir, working_dir): subjects=["01"], fmri_dir=data_dir, output_dir=os.path.join(out_dir, "xcp_d"), - work_dir=work_dir, cifti=opts.cifti, dcan_qc=opts.dcan_qc, dummy_scans=opts.dummy_scans, diff --git a/xcp_d/tests/test_smoothing.py b/xcp_d/tests/test_smoothing.py index 7ef7cac62..00b066be4 100644 --- a/xcp_d/tests/test_smoothing.py +++ b/xcp_d/tests/test_smoothing.py @@ -1,7 +1,7 @@ """Tests for smoothing methods.""" import os -import tempfile import re +import tempfile import numpy as np from nipype.interfaces.workbench import CiftiSmooth diff --git a/xcp_d/tests/test_utils_plotting.py b/xcp_d/tests/test_utils_plotting.py new file mode 100644 index 000000000..0e232eb02 --- /dev/null +++ b/xcp_d/tests/test_utils_plotting.py @@ -0,0 +1,32 @@ +"""Tests for xcp_d.utils.plotting module.""" +import os + +from xcp_d.utils import plotting + + +def test_plot_fmri_es(fmriprep_with_freesurfer_data, tmp_path_factory): + """Run smoke test on xcp_d.utils.plotting.plot_fmri_es.""" + tmpdir = tmp_path_factory.mktemp("test_plot_fmri_es") + + preprocessed_file = fmriprep_with_freesurfer_data["cifti_file"] + residuals_file = fmriprep_with_freesurfer_data["cifti_file"] + denoised_file = fmriprep_with_freesurfer_data["cifti_file"] + dummy_scans = 0 + # Using unfiltered FD instead of calculating filtered version. + filtered_motion = fmriprep_with_freesurfer_data["confounds_file"] + unprocessed_filename = os.path.join(tmpdir, "unprocessed.svg") + processed_filename = os.path.join(tmpdir, "processed.svg") + t_r = 2 + + out_file1, out_file2 = plotting.plot_fmri_es( + preprocessed_file=preprocessed_file, + residuals_file=residuals_file, + denoised_file=denoised_file, + dummy_scans=dummy_scans, + filtered_motion=filtered_motion, + unprocessed_filename=unprocessed_filename, + processed_filename=processed_filename, + TR=t_r, + ) + assert os.path.isfile(out_file1) + assert os.path.isfile(out_file2) diff --git a/xcp_d/tests/test_utils_qcmetrics.py b/xcp_d/tests/test_utils_qcmetrics.py new file mode 100644 index 000000000..87ebb1053 --- /dev/null +++ b/xcp_d/tests/test_utils_qcmetrics.py @@ -0,0 +1,13 @@ +"""Tests for the xcp_d.utils.qcmetrics module.""" +import numpy as np + +from xcp_d.utils import qcmetrics + + +def test_compute_dvars(): + """Run a smoke test for xcp_d.utils.qcmetrics.compute_dvars.""" + n_volumes, n_vertices = 100, 10000 + data = np.random.random((n_vertices, n_volumes)) + + dvars = qcmetrics.compute_dvars(data) + assert dvars.shape == (n_volumes,) diff --git a/xcp_d/utils/__init__.py b/xcp_d/utils/__init__.py index b371d8be7..75e6714f2 100644 --- a/xcp_d/utils/__init__.py +++ b/xcp_d/utils/__init__.py @@ -14,7 +14,7 @@ filemanip, hcp2fmriprep, modified_data, - plot, + plotting, qcmetrics, sentry, utils, @@ -33,7 +33,7 @@ "filemanip", "hcp2fmriprep", "modified_data", - "plot", + "plotting", "qcmetrics", "sentry", "utils", diff --git a/xcp_d/utils/concatenation.py b/xcp_d/utils/concatenation.py index 435a468ad..b7c8947fb 100644 --- a/xcp_d/utils/concatenation.py +++ b/xcp_d/utils/concatenation.py @@ -17,10 +17,10 @@ from xcp_d.utils.confounds import _infer_dummy_scans, get_confounds_tsv from xcp_d.utils.doc import fill_doc from xcp_d.utils.modified_data import _drop_dummy_scans -from xcp_d.utils.plot import plot_svgx +from xcp_d.utils.plotting import plot_fmri_es from xcp_d.utils.qcmetrics import compute_dvars, make_dcan_df from xcp_d.utils.utils import get_segfile -from xcp_d.utils.write_save import read_ndata, write_ndata +from xcp_d.utils.write_save import read_ndata _pybids_spec = loads(Path(_pkgres("xcp_d", "data/nipreps.json")).read_text()) path_patterns = _pybids_spec["default_path_patterns"] @@ -31,7 +31,6 @@ def concatenate_derivatives( fmri_dir, output_dir, - work_dir, subjects, cifti, dcan_qc, @@ -54,8 +53,6 @@ def concatenate_derivatives( Path to preprocessed derivatives (not xcpd post-processed derivatives). output_dir : str Path to location of xcpd derivatives. - work_dir : str - Working directory. subjects : list of str List of subjects to run concatenation on. cifti : bool @@ -321,28 +318,6 @@ def concatenate_derivatives( mask = None segfile = None - # Create a censored version of the denoised file, - # because denoised_file is from before interpolation. - concat_censored_file = os.path.join( - tmpdir, - f"filtereddata{preproc_files[0].extension}", - ) - tmask_df = pd.read_table(concat_outlier_file) - tmask_arr = tmask_df["framewise_displacement"].values - tmask_bool = ~tmask_arr.astype(bool) - temp_data_arr = read_ndata( - datafile=concat_denoised_file, - maskfile=mask, - ) - temp_data_arr = temp_data_arr[:, tmask_bool] - write_ndata( - data_matrix=temp_data_arr, - template=concat_denoised_file, - filename=concat_censored_file, - mask=mask, - TR=TR, - ) - # Calculate DVARS from preprocessed BOLD raw_dvars = [] for i_file, preproc_file in enumerate(preproc_files): @@ -353,9 +328,6 @@ def concatenate_derivatives( raw_dvars = np.concatenate(raw_dvars) - # Censor DVARS - raw_dvars = raw_dvars[tmask_bool] - # Calculate DVARS from denoised BOLD denoised_dvars = [] for denoised_file in denoised_files: @@ -365,9 +337,6 @@ def concatenate_derivatives( denoised_dvars = np.concatenate(denoised_dvars) - # Censor DVARS - denoised_dvars = denoised_dvars[tmask_bool] - # Start on carpet plots LOGGER.debug("Generating carpet plots") carpet_entities = denoised_files[0].get_entities() @@ -392,13 +361,12 @@ def concatenate_derivatives( validate=False, ) - LOGGER.debug("Starting plot_svgx") - plot_svgx( + LOGGER.debug("Starting plot_fmri_es") + plot_fmri_es( preprocessed_file=concat_preproc_file, - residuals_file=concat_censored_file, + residuals_file=concat_denoised_file, denoised_file=concat_denoised_file, dummy_scans=0, - tmask=concat_outlier_file, filtered_motion=concat_motion_file, raw_dvars=raw_dvars, residuals_dvars=denoised_dvars, @@ -408,9 +376,8 @@ def concatenate_derivatives( mask=mask, seg_data=segfile, TR=TR, - work_dir=work_dir, ) - LOGGER.debug("plot_svgx done") + LOGGER.debug("plot_fmri_es done") # Now timeseries files atlases = layout_xcpd.get_atlases( diff --git a/xcp_d/utils/plot.py b/xcp_d/utils/plotting.py similarity index 74% rename from xcp_d/utils/plot.py rename to xcp_d/utils/plotting.py index d05c3271c..5fd8712c8 100644 --- a/xcp_d/utils/plot.py +++ b/xcp_d/utils/plotting.py @@ -45,17 +45,7 @@ def _decimate_data(data, seg_data, size): return data, seg_data -def plotimage(img, out_file): - """Plot anatomical image and save to file.""" - from nilearn.plotting import plot_anat - - fig = plt.figure(constrained_layout=False, figsize=(25, 10)) - plot_anat(img, draw_cross=False, figure=fig) - fig.savefig(out_file, bbox_inches="tight", pad_inches=None) - return out_file - - -def confoundplot( +def plot_confounds( time_series, grid_spec_ts, gs_dist=None, @@ -256,17 +246,17 @@ def confoundplot( return time_series_axis, grid_specification -def confoundplotx( +def plot_confounds_es( time_series, grid_spec_ts, TR=None, hide_x=True, ylims=None, ylabel=None, - FD=False, - work_dir=None, + is_fd=False, + is_whole_brain=False, ): - """Create confounds plot.""" + """Create confounds plot for the executive summary.""" sns.set_style("whitegrid") # Define TR and number of frames @@ -280,7 +270,11 @@ def confoundplotx( # Define nested GridSpec grid_specification = mgs.GridSpecFromSubplotSpec( - 1, 2, subplot_spec=grid_spec_ts, width_ratios=[1, 100], wspace=0.0 + 1, + 2, + subplot_spec=grid_spec_ts, + width_ratios=[1, 100], + wspace=0.0, ) time_series_axis = plt.subplot(grid_specification[1]) @@ -306,129 +300,125 @@ def confoundplotx( # Set y-axis labels if ylabel: time_series_axis.set_ylabel(ylabel) - if work_dir is not None: - time_series.to_csv(f"/{work_dir}/{ylabel}_tseries.npy") + columns = time_series.columns - maximum_value = [] - minimum_value = [] + maximum_values = [] + minimum_values = [] - if FD is True: + if is_fd: for c in columns: time_series_axis.plot(time_series[c], label=c, linewidth=3, color="black") - maximum_value.append(max(time_series[c])) - minimum_value.append(min(time_series[c])) + maximum_values.append(max(time_series[c])) + minimum_values.append(min(time_series[c])) # Threshold fd at 0.1, 0.2 and 0.5 and plot time_series_axis.axhline( y=1, color="lightgray", linestyle="-", linewidth=10, alpha=0.5 ) - fda = time_series[c].copy() - FD_timeseries = time_series[c].copy() - FD_timeseries[FD_timeseries > 0] = 1.05 - time_series_axis.plot(fda, ".", color="gray", markersize=40) - time_series_axis.plot(FD_timeseries, ".", color="gray", markersize=40) - - time_series_axis.axhline(y=0.05, color="gray", linestyle="-", linewidth=10, alpha=0.5) - fda[fda < 0.05] = np.nan - FD_timeseries = time_series[c].copy() - FD_timeseries[FD_timeseries >= 0.05] = 1.05 - FD_timeseries[FD_timeseries < 0.05] = np.nan - time_series_axis.plot(fda, ".", color="gray", markersize=40) - time_series_axis.plot(FD_timeseries, ".", color="gray", markersize=40) - time_series_axis.axhline( - y=0.1, color="#66c2a5", linestyle="-", linewidth=10, alpha=0.5 - ) - fda[fda < 0.1] = np.nan - FD_timeseries = time_series[c].copy() - FD_timeseries[FD_timeseries >= 0.1] = 1.05 - FD_timeseries[FD_timeseries < 0.1] = np.nan - time_series_axis.plot(fda, ".", color="#66c2a5", markersize=40) - time_series_axis.plot(FD_timeseries, ".", color="#66c2a5", markersize=40) + # Plot zero line + fd_dots = time_series[c].copy() + fd_line = time_series[c].copy() + + fd_dots[fd_dots < 0] = np.nan + fd_line[fd_line > 0] = 1.05 + time_series_axis.plot(fd_dots, ".", color="gray", markersize=40) + time_series_axis.plot(fd_line, ".", color="gray", markersize=40) + + THRESHOLDS = [0.05, 0.1, 0.2, 0.5] + COLORS = ["gray", "#66c2a5", "#fc8d62", "#8da0cb"] + for i_thresh, threshold in enumerate(THRESHOLDS): + color = COLORS[i_thresh] + + time_series_axis.axhline( + y=threshold, + color=color, + linestyle="-", + linewidth=10, + alpha=0.5, + ) + + fd_dots[fd_dots < threshold] = np.nan + time_series_axis.plot(fd_dots, ".", color=color, markersize=40) + + fd_line = time_series[c].copy() + fd_line[fd_line >= threshold] = 1.05 + fd_line[fd_line < threshold] = np.nan + time_series_axis.plot(fd_line, ".", color=color, markersize=40) + + # Plot the good volumes, i.e: thresholded at 0.1, 0.2, 0.5 + good_vols = len(time_series[c][time_series[c] < threshold]) + time_series_axis.text( + 1.01, + threshold, + good_vols, + c=color, + verticalalignment="top", + horizontalalignment="left", + transform=time_series_axis.transAxes, + fontsize=30, + ) + + elif is_whole_brain: + # Plot the whole brain mean and std. + # Mean scale on the left, std scale on the right. + mean_line = time_series_axis.plot( + time_series["Mean"], + label="Mean", + linewidth=10, + alpha=0.5, + ) + maximum_values.append(max(time_series["Mean"])) + minimum_values.append(min(time_series["Mean"])) + ax_right = time_series_axis.twinx() + ax_right.set_ylabel("Standard Deviation") + std_line = ax_right.plot( + time_series["Std"], + label="Std", + color="orange", + linewidth=10, + alpha=0.5, + ) - time_series_axis.axhline( - y=0.2, color="#fc8d62", linestyle="-", linewidth=10, alpha=0.5 - ) - fda[fda < 0.2] = np.nan - FD_timeseries = time_series[c].copy() - FD_timeseries[FD_timeseries >= 0.2] = 1.05 - FD_timeseries[FD_timeseries < 0.2] = np.nan - time_series_axis.plot(fda, ".", color="#fc8d62", markersize=40) - time_series_axis.plot(FD_timeseries, ".", color="#fc8d62", markersize=40) + std_mean = np.mean(time_series["Std"]) + ax_right.set_ylim( + (1.5 * np.min(time_series["Std"] - std_mean)) + std_mean, + (1.5 * np.max(time_series["Std"] - std_mean)) + std_mean, + ) + ax_right.yaxis.label.set_fontsize(30) + for item in ax_right.get_yticklabels(): + item.set_fontsize(30) + + lines = mean_line + std_line + line_labels = [line.get_label() for line in lines] + time_series_axis.legend(lines, line_labels, fontsize=40) - time_series_axis.axhline( - y=0.5, color="#8da0cb", linestyle="-", linewidth=10, alpha=0.5 - ) - fda[fda < 0.5] = np.nan - FD_timeseries = time_series[c].copy() - FD_timeseries[FD_timeseries >= 0.5] = 1.05 - FD_timeseries[FD_timeseries < 0.5] = np.nan - time_series_axis.plot(fda, ".", color="#8da0cb", markersize=40) - time_series_axis.plot(FD_timeseries, ".", color="#8da0cb", markersize=40) - - # Plot the good volumes, i.e: thresholded at 0.1, 0.2, 0.5 - good_vols = len(time_series[c][time_series[c] < 0.1]) - time_series_axis.text( - 1.01, - 0.1, - good_vols, - c="#66c2a5", - verticalalignment="top", - horizontalalignment="left", - transform=time_series_axis.transAxes, - fontsize=30, - ) - good_vols = len(time_series[c][time_series[c] < 0.2]) - time_series_axis.text( - 1.01, - 0.2, - good_vols, - c="#fc8d62", - verticalalignment="top", - horizontalalignment="left", - transform=time_series_axis.transAxes, - fontsize=30, - ) - good_vols = len(time_series[c][time_series[c] < 0.5]) - time_series_axis.text( - 1.01, - 0.5, - good_vols, - c="#8da0cb", - verticalalignment="top", - horizontalalignment="left", - transform=time_series_axis.transAxes, - fontsize=30, - ) - good_vols = len(time_series[c][time_series[c] < 0.05]) - time_series_axis.text( - 1.01, - 0.05, - good_vols, - c="grey", - verticalalignment="top", - horizontalalignment="left", - transform=time_series_axis.transAxes, - fontsize=30, - ) else: # If no thresholding for c in columns: time_series_axis.plot(time_series[c], label=c, linewidth=10, alpha=0.5) - maximum_value.append(max(time_series[c])) - minimum_value.append(min(time_series[c])) + maximum_values.append(max(time_series[c])) + minimum_values.append(min(time_series[c])) # Set limits and format - minimum_x_value = [abs(x) for x in minimum_value] + minimum_x_value = [abs(x) for x in minimum_values] time_series_axis.set_xlim((0, ntsteps - 1)) - time_series_axis.legend(fontsize=40) - if FD is True: + if is_fd is True: + time_series_axis.legend(fontsize=40) time_series_axis.set_ylim(0, 1.1) time_series_axis.set_yticks([0, 0.05, 0.1, 0.2, 0.5, 1]) elif ylims: + time_series_axis.legend(fontsize=40) time_series_axis.set_ylim(ylims) + elif is_whole_brain: + mean_mean = np.mean(time_series["Mean"]) + time_series_axis.set_ylim( + (1.5 * np.min(time_series["Mean"] - mean_mean)) + mean_mean, + (1.5 * np.max(time_series["Mean"] - mean_mean)) + mean_mean, + ) else: - time_series_axis.set_ylim([-1.5 * max(minimum_x_value), 1.5 * max(maximum_value)]) + time_series_axis.legend(fontsize=40) + time_series_axis.set_ylim([-1.5 * max(minimum_x_value), 1.5 * max(maximum_values)]) for item in ( [time_series_axis.title, time_series_axis.xaxis.label, time_series_axis.yaxis.label] @@ -444,11 +434,10 @@ def confoundplotx( @fill_doc -def plot_svgx( +def plot_fmri_es( preprocessed_file, residuals_file, denoised_file, - tmask, dummy_scans, filtered_motion, unprocessed_filename, @@ -459,9 +448,8 @@ def plot_svgx( raw_dvars=None, residuals_dvars=None, denoised_dvars=None, - work_dir=None, ): - """Generate carpet plot with DVARS, FD, and WB. + """Generate carpet plot with DVARS, FD, and WB for the executive summary. Parameters ---------- @@ -473,8 +461,6 @@ def plot_svgx( nifti or cifti after regression, filtering, and interpolation mask : mask for nifti if available - tmask : - temporal censoring mask %(dummy_scans)s seg_data : 3 tissues seg_data files @@ -491,19 +477,11 @@ def plot_svgx( raw_data_arr = read_ndata(datafile=preprocessed_file, maskfile=mask) residuals_data_arr = read_ndata(datafile=residuals_file, maskfile=mask) denoised_data_arr = read_ndata(datafile=denoised_file, maskfile=mask) - tmask_df = pd.read_table(tmask) - tmask_arr = tmask_df["framewise_displacement"].values - tmask_bool = ~tmask_arr.astype(bool) - # Let's remove dummy time from the raw_data_arr if needed + # Remove dummy time from the raw_data_arr if needed if dummy_scans > 0: raw_data_arr = raw_data_arr[:, dummy_scans:] - # Let's censor the interpolated data and raw_data_arr: - if sum(tmask_arr) > 0: - raw_data_arr = raw_data_arr[:, tmask_bool] - denoised_data_arr = denoised_data_arr[:, tmask_bool] - if not isinstance(raw_dvars, np.ndarray): raw_dvars = compute_dvars(raw_data_arr) @@ -521,11 +499,19 @@ def plot_svgx( f"\t{denoised_file}: {denoised_data_arr.shape}\n\n" ) + if not (raw_data_arr.shape == residuals_data_arr.shape == denoised_data_arr.shape): + raise ValueError( + "Shapes do not match:\n" + f"\t{preprocessed_file}: {raw_data_arr.shape}\n" + f"\t{residuals_file}: {residuals_data_arr.shape}\n" + f"\t{denoised_file}: {denoised_data_arr.shape}\n\n" + ) + # Formatting & setting of files sns.set_style("whitegrid") # Create dataframes for the bold_data DVARS, FD - DVARS_timeseries = pd.DataFrame( + dvars_regressors = pd.DataFrame( { "Pre regression": raw_dvars, "Post regression": residuals_dvars, @@ -533,7 +519,7 @@ def plot_svgx( } ) - FD_timeseries = pd.DataFrame( + fd_regressor = pd.DataFrame( { "FD": pd.read_table(filtered_motion)["framewise_displacement"].values, } @@ -541,7 +527,10 @@ def plot_svgx( # The mean and standard deviation of raw data unprocessed_data_timeseries = pd.DataFrame( - {"Mean": np.nanmean(raw_data_arr, axis=0), "Std": np.nanstd(raw_data_arr, axis=0)} + { + "Mean": np.nanmean(raw_data_arr, axis=0), + "Std": np.nanstd(raw_data_arr, axis=0), + } ) # The mean and standard deviation of filtered data @@ -558,10 +547,8 @@ def plot_svgx( atlaslabels = None # The plot going to carpet plot will be rescaled to [-600,600] - raw_data = read_ndata(datafile=preprocessed_file, maskfile=mask) - denoised_data = read_ndata(datafile=denoised_file, maskfile=mask) - scaled_raw_data = scale_to_min_max(raw_data, -600, 600) - scaled_denoised_data = scale_to_min_max(denoised_data, -600, 600) + scaled_raw_data = scale_to_min_max(raw_data_arr, -600, 600) + scaled_denoised_data = scale_to_min_max(denoised_data_arr, -600, 600) # Make a temporary file for niftis and ciftis if preprocessed_file.endswith(".nii.gz"): @@ -574,99 +561,68 @@ def plot_svgx( # Write out the scaled data scaled_raw_file = write_ndata( data_matrix=scaled_raw_data, - template=preprocessed_file, + template=residuals_file, # residuals file is censored, so length matches filename=scaled_raw_file, mask=mask, TR=TR, ) scaled_denoised_file = write_ndata( data_matrix=scaled_denoised_data, - template=denoised_file, + template=residuals_file, # residuals file is censored, so length matches filename=scaled_denoised_file, mask=mask, TR=TR, ) - # Plot the data and confounds, plus the carpet plot - plt.cla() - plt.clf() - unprocessed_figure = plt.figure(constrained_layout=True, figsize=(22.5, 30)) - grid = mgs.GridSpec(5, 1, wspace=0.0, hspace=0.05, height_ratios=[1, 1, 0.2, 2.5, 1]) - confoundplotx( - time_series=DVARS_timeseries, - grid_spec_ts=grid[0], - TR=TR, - ylabel="DVARS", - hide_x=True, - ) - confoundplotx( - time_series=unprocessed_data_timeseries, - grid_spec_ts=grid[1], - TR=TR, - hide_x=True, - ylabel="WB", - ) - plot_carpet( - func=scaled_raw_file, - atlaslabels=atlaslabels, - TR=TR, - subplot=grid[3], - legend=False, - ) - confoundplotx( - time_series=FD_timeseries, - grid_spec_ts=grid[4], - TR=TR, - hide_x=False, - ylims=[0, 1], - ylabel="FD[mm]", - FD=True, - ) - - # Save out the before processing file - unprocessed_figure.savefig(unprocessed_filename, bbox_inches="tight", pad_inches=None, dpi=300) - plt.cla() - plt.clf() - - # Plot the data and confounds, plus the carpet plot - processed_figure = plt.figure(constrained_layout=True, figsize=(22.5, 30)) - grid = mgs.GridSpec(5, 1, wspace=0.0, hspace=0.05, height_ratios=[1, 1, 0.2, 2.5, 1]) + files_for_carpet = [scaled_raw_file, scaled_denoised_file] + figure_names = [unprocessed_filename, processed_filename] + data_arrays = [unprocessed_data_timeseries, processed_data_timeseries] + for i_fig, figure_name in enumerate(figure_names): + file_for_carpet = files_for_carpet[i_fig] + data_arr = data_arrays[i_fig] + + # Plot the data and confounds, plus the carpet plot + plt.cla() + plt.clf() + + fig = plt.figure(constrained_layout=True, figsize=(22.5, 30)) + grid = mgs.GridSpec(5, 1, wspace=0.0, hspace=0.05, height_ratios=[1, 1, 0.2, 2.5, 1]) + + plot_confounds_es( + time_series=dvars_regressors, + grid_spec_ts=grid[0], + TR=TR, + ylabel="DVARS", + hide_x=True, + ) + plot_confounds_es( + time_series=data_arr, + grid_spec_ts=grid[1], + TR=TR, + hide_x=True, + ylabel="WB", + is_whole_brain=True, + ) + plot_carpet( + func=file_for_carpet, + atlaslabels=atlaslabels, + TR=TR, + subplot=grid[3], + legend=False, + ) + plot_confounds_es( + time_series=fd_regressor, + grid_spec_ts=grid[4], + TR=TR, + hide_x=False, + ylims=[0, 1], + ylabel="FD[mm]", + is_fd=True, + ) - confoundplotx( - time_series=DVARS_timeseries, - grid_spec_ts=grid[0], - TR=TR, - ylabel="DVARS", - hide_x=True, - work_dir=work_dir, - ) - confoundplotx( - time_series=processed_data_timeseries, - grid_spec_ts=grid[1], - TR=TR, - hide_x=True, - ylabel="WB", - work_dir=work_dir, - ) + # Save out the before processing file + fig.savefig(figure_name, bbox_inches="tight", pad_inches=None, dpi=300) - plot_carpet( - func=scaled_denoised_file, - atlaslabels=atlaslabels, - TR=TR, - subplot=grid[3], - legend=True, - ) - confoundplotx( - time_series=FD_timeseries, - grid_spec_ts=grid[4], - TR=TR, - hide_x=False, - ylims=[0, 1], - ylabel="FD[mm]", - FD=True, - work_dir=work_dir, - ) - processed_figure.savefig(processed_filename, bbox_inches="tight", pad_inches=None, dpi=300) # Save out the after processing file return unprocessed_filename, processed_filename @@ -761,7 +717,7 @@ def plot(self, labelsize, figure=None): for i, (name, kwargs) in enumerate(self.confounds.items()): time_series = kwargs.pop("values") - confoundplot( + plot_confounds( time_series, grid[grid_id], TR=self.TR, color=palette[i], name=name, **kwargs ) grid_id += 1 @@ -1111,7 +1067,7 @@ def plot_alff_reho_surface(output_path, filename, bold_file): from nilearn import plotting as plott from templateflow.api import get as get_template - from xcp_d.utils.plot import surf_data_from_cifti + from xcp_d.utils.plotting import surf_data_from_cifti density = parse_file_entities(bold_file).get("den", "32k") if density == "91k": @@ -1225,71 +1181,3 @@ def plot_design_matrix(design_matrix): plotting.plot_design_matrix(design_matrix_df, output_file=design_matrix_figure) return design_matrix_figure - - -def plot_ribbon_svg(template, in_file): - """Plot the anatomical template and ribbon in a static SVG file.""" - import os - - import matplotlib.pyplot as plt - import nibabel as nib - import numpy as np - from matplotlib.patches import Rectangle - from nilearn.image import math_img - from nilearn.plotting import plot_anat - - plot_file = os.path.abspath("ribbon.svg") - - # Set vmax to 95-th percentile of T1w image - t1w_img = nib.load(template) - vmax = np.percentile(t1w_img.get_fdata(), 95) - - # Coerce to ints - img = math_img("img.astype(int)", img=in_file) - white_img = math_img("img == 1", img=img) - pial_img = math_img("img == 2", img=img) - both_img = math_img("img == 3", img=img) - - fig = plt.figure( - figsize=(25, 10), - ) - display = plot_anat( - template, - draw_cross=False, - vmax=vmax, - figure=fig, - ) - display.add_contours( - white_img, - contours=1, - antialiased=False, - linewidths=1.0, - levels=[0], - colors=["purple"], - ) - display.add_contours( - pial_img, - contours=1, - antialiased=False, - linewidths=1.0, - levels=[0], - colors=["green"], - ) - display.add_contours( - both_img, - contours=1, - antialiased=False, - linewidths=1.0, - levels=[0], - colors=["red"], - ) - # We generate a legend using the trick described on - # http://matplotlib.sourceforge.net/users/legend_guide.httpml#using-proxy-artist - p1 = Rectangle((0, 0), 1, 1, fc="purple", label="White Matter") - p2 = Rectangle((0, 0), 1, 1, fc="green", label="Pial") - p3 = Rectangle((0, 0), 1, 1, fc="red", label="Both Somehow") - plt.legend([p1, p2, p3], ["White Matter", "Pial", "Both Somehow"], fontsize=18) - - fig.savefig(plot_file, bbox_inches="tight", pad_inches=None) - # return fig - return plot_file diff --git a/xcp_d/workflows/bold.py b/xcp_d/workflows/bold.py index ee59ec971..a89e04a2d 100644 --- a/xcp_d/workflows/bold.py +++ b/xcp_d/workflows/bold.py @@ -30,7 +30,7 @@ ) from xcp_d.utils.doc import fill_doc from xcp_d.utils.filemanip import check_binary_mask -from xcp_d.utils.plot import plot_design_matrix +from xcp_d.utils.plotting import plot_design_matrix from xcp_d.utils.utils import estimate_brain_radius from xcp_d.workflows.connectivity import init_nifti_functional_connectivity_wf from xcp_d.workflows.execsummary import init_execsummary_wf @@ -481,8 +481,8 @@ def init_boldpostprocess_wf( (determine_head_radius, qc_report_wf, [ ("head_radius", "inputnode.head_radius"), ]), - (regression_wf, qc_report_wf, [ - ("res_file", "inputnode.cleaned_unfiltered_file"), + (interpolate_wf, qc_report_wf, [ + ("bold_interpolated", "inputnode.cleaned_unfiltered_file"), ]), ]) # fmt:on diff --git a/xcp_d/workflows/cifti.py b/xcp_d/workflows/cifti.py index 0ecb767fe..6efcd71a7 100644 --- a/xcp_d/workflows/cifti.py +++ b/xcp_d/workflows/cifti.py @@ -30,7 +30,7 @@ get_customfile, ) from xcp_d.utils.doc import fill_doc -from xcp_d.utils.plot import plot_design_matrix +from xcp_d.utils.plotting import plot_design_matrix from xcp_d.utils.utils import estimate_brain_radius from xcp_d.workflows.connectivity import init_cifti_functional_connectivity_wf from xcp_d.workflows.execsummary import init_execsummary_wf @@ -448,8 +448,8 @@ def init_ciftipostprocess_wf( (determine_head_radius, qc_report_wf, [ ("head_radius", "inputnode.head_radius"), ]), - (regression_wf, qc_report_wf, [ - ("res_file", "inputnode.cleaned_unfiltered_file"), + (interpolate_wf, qc_report_wf, [ + ("bold_interpolated", "inputnode.cleaned_unfiltered_file"), ]), ]) # fmt:on diff --git a/xcp_d/workflows/execsummary.py b/xcp_d/workflows/execsummary.py index 20702e93a..7487c791f 100644 --- a/xcp_d/workflows/execsummary.py +++ b/xcp_d/workflows/execsummary.py @@ -11,7 +11,7 @@ from pkg_resources import resource_filename as pkgrf from xcp_d.interfaces.bids import DerivativesDataSink -from xcp_d.interfaces.surfplotting import PlotImage +from xcp_d.interfaces.plotting import AnatomicalPlot from xcp_d.interfaces.workbench import ShowScene from xcp_d.utils.doc import fill_doc from xcp_d.utils.execsummary import ( @@ -353,7 +353,7 @@ def init_execsummary_wf( )[0] # Plot the reference bold image - plot_boldref = pe.Node(PlotImage(), name="plot_boldref") + plot_boldref = pe.Node(AnatomicalPlot(), name="plot_boldref") # fmt:off workflow.connect([ diff --git a/xcp_d/workflows/plotting.py b/xcp_d/workflows/plotting.py index ab3f0bc6d..296cbbd49 100644 --- a/xcp_d/workflows/plotting.py +++ b/xcp_d/workflows/plotting.py @@ -7,9 +7,8 @@ from xcp_d.interfaces.ants import ApplyTransforms from xcp_d.interfaces.bids import DerivativesDataSink -from xcp_d.interfaces.qc_plot import CensoringPlot, QCPlot +from xcp_d.interfaces.plotting import CensoringPlot, QCPlots, QCPlotsES from xcp_d.interfaces.report import FunctionalSummary -from xcp_d.interfaces.surfplotting import PlotSVGData from xcp_d.utils.doc import fill_doc from xcp_d.utils.qcmetrics import _make_dcan_qc_file from xcp_d.utils.utils import get_bold2std_and_t1w_xforms, get_std2bold_xforms @@ -317,7 +316,7 @@ def init_qc_report_wf( # fmt:on qcreport = pe.Node( - QCPlot( + QCPlots( TR=TR, template_mask=nlin2009casym_brain_mask, ), @@ -379,7 +378,7 @@ def init_qc_report_wf( # Generate preprocessing and postprocessing carpet plots. plot_executive_summary_carpets = pe.Node( - PlotSVGData(TR=TR), + QCPlotsES(TR=TR), name="plot_executive_summary_carpets", mem_gb=mem_gb, n_procs=omp_nthreads, @@ -392,7 +391,6 @@ def init_qc_report_wf( ("cleaned_unfiltered_file", "regressed_data"), ("cleaned_file", "residual_data"), ("filtered_motion", "filtered_motion"), - ("tmask", "tmask"), ("dummy_scans", "dummy_scans"), ]), ]) diff --git a/xcp_d/workflows/restingstate.py b/xcp_d/workflows/restingstate.py index 223d30b28..ae74aaae7 100644 --- a/xcp_d/workflows/restingstate.py +++ b/xcp_d/workflows/restingstate.py @@ -17,7 +17,7 @@ FixCiftiIntent, ) from xcp_d.utils.doc import fill_doc -from xcp_d.utils.plot import plot_alff_reho_surface, plot_alff_reho_volumetric +from xcp_d.utils.plotting import plot_alff_reho_surface, plot_alff_reho_volumetric from xcp_d.utils.utils import fwhm2sigma