From d3c7d4df7b075e8fe931e06ea1bd5c8e57db9c40 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Wed, 21 May 2025 15:33:15 -0400 Subject: [PATCH 1/3] Work on group QC-plotting workflow. --- aslprep/cli/aggregate_qc.py | 320 ++++++++++++++++++++++++++++++++++-- aslprep/interfaces/group.py | 244 +++++++++++++++++++++++++++ plot_stuff.ipynb | 147 +++++++++++++++++ 3 files changed, 693 insertions(+), 18 deletions(-) create mode 100644 aslprep/interfaces/group.py create mode 100644 plot_stuff.ipynb diff --git a/aslprep/cli/aggregate_qc.py b/aslprep/cli/aggregate_qc.py index e2e730460..e40af218a 100644 --- a/aslprep/cli/aggregate_qc.py +++ b/aslprep/cli/aggregate_qc.py @@ -7,41 +7,325 @@ import pandas as pd +from aslprep._warnings import logging -def get_parser(): + +def _build_parser(): """Build parser object.""" from argparse import ArgumentParser, RawTextHelpFormatter + from functools import partial + + def _path_exists(path, parser): + """Ensure a given path exists.""" + if path is None or not Path(path).exists(): + raise parser.error(f'Path does not exist: <{path}>.') + return Path(path).absolute() parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter) - parser.add_argument('aslprep_dir', action='store', type=Path, help='aslprep output dir') + PathExists = partial(_path_exists, parser=parser) + + parser.add_argument( + 'aslprep_dir', + action='store', + type=PathExists, + help='Path to ASLPrep derivatives dataset.', + ) + parser.add_argument( + 'output_dir', + action='store', + type=Path, + help='Path to output directory where QC measures will be saved.', + ) + parser.add_argument( + 'analysis_level', + choices=['group'], + help='processing stage to be run, only "group" in the case of this CLI.', + ) + + g_other = parser.add_argument_group('Other options') + g_other.add_argument( + '-w', + '--work-dir', + action='store', + type=Path, + default=Path('work').absolute(), + help='path where intermediate results should be stored', + ) - parser.add_argument('output_prefix', action='store', type=str, help='output prefix for group') return parser +def parse_args(args=None, namespace=None): + """Parse args and run further checks on the command line.""" + parser = _build_parser() + opts = parser.parse_args(args, namespace) + + output_dir = opts.output_dir + input_dir = opts.aslprep_dir + work_dir = opts.work_dir + + output_dir.mkdir(exist_ok=True, parents=True) + work_dir.mkdir(exist_ok=True, parents=True) + + log_dir = output_dir / 'logs' + log_dir.mkdir(exist_ok=True, parents=True) + + build_log = logging.getLogger() + + # Wipe out existing work_dir + if opts.clean_workdir and work_dir.exists(): + from niworkflows.utils.misc import clean_directory + + build_log.info(f'Clearing previous aslprep working directory: {work_dir}') + if not clean_directory(work_dir): + build_log.warning(f'Could not clear all contents of working directory: {work_dir}') + + return opts + + +def build_workflow(opts, retval): + """Build the workflow.""" + from bids.layout import BIDSLayout + from nipype.interfaces import utility as niu + from nipype.pipeline import engine as pe + from niworkflows.engine.workflows import LiterateWorkflow as Workflow + + from aslprep.data import load as load_data + from aslprep.interfaces.bids import DerivativesDataSink + from aslprep.interfaces.group import ( + AggregateCBFQC, + MeanCBF, + PlotAggregatedCBFQC, + StatisticalMapRPT, + ) + + aslprep_config = load_data('aslprep_bids_config.json') + layout = BIDSLayout( + opts.aslprep_dir, + validate=False, + config=['bids', 'derivatives', aslprep_config], + ) + qc_data = collect_aslprep_qc_derivatives(layout) + + workflow = Workflow(name='aslprep_group_wf') + + inputnode = pe.Node( + niu.IdentityInterface( + fields=[ + 'cbf_qc', + 'cbf_img', + ], + ), + name='inputnode', + ) + inputnode.inputs.cbf_qc = qc_data['cbf_qc'] + inputnode.inputs.cbf_img = qc_data['cbf_img'] + + # Aggregate QC metrics + aggregate_cbf_qc = pe.Node( + AggregateCBFQC(), + name='aggregate_cbf_qc', + ) + workflow.connect([(inputnode, aggregate_cbf_qc, [('cbf_qc', 'in_files')])]) + + # Plot aggregated QC metrics + plot_cbf_qc = pe.Node( + PlotAggregatedCBFQC(), + name='plot_cbf_qc', + ) + workflow.connect([(aggregate_cbf_qc, plot_cbf_qc, [('out_file', 'in_file')])]) + + # Save aggregated QC metrics + ds_report_qei = pe.MapNode( + DerivativesDataSink( + base_directory=opts.output_dir, + datatype='figures', + suffix='cbf', + ), + iterfield=['in_file', 'desc'], + name='ds_report_qei', + ) + workflow.connect([ + (plot_cbf_qc, ds_report_qei, [ + ('out_files', 'in_file'), + ('measures', 'desc'), + ]), + ]) # fmt:skip + + # Average CBF maps across subjects + average_cbf_maps = pe.Node( + MeanCBF(), + name='average_cbf_maps', + ) + workflow.connect([(inputnode, average_cbf_maps, [('cbf_img', 'in_files')])]) + + # Plot average and SD CBF maps + plot_mean_cbf = pe.Node( + StatisticalMapRPT(cmap='viridis'), + name='plot_mean_cbf', + ) + workflow.connect([ + (inputnode, plot_mean_cbf, [('template', 'underlay')]), + (average_cbf_maps, plot_mean_cbf, [('mean_map', 'overlay')]), + ]) # fmt:skip + + ds_report_mean_cbf = pe.Node( + DerivativesDataSink( + base_directory=opts.output_dir, + datatype='figures', + desc='mean', + suffix='cbf', + ), + name='ds_report_mean_cbf', + ) + workflow.connect([(plot_mean_cbf, ds_report_mean_cbf, [('out_report', 'in_file')])]) + + plot_sd_cbf = pe.Node( + StatisticalMapRPT(cmap='viridis'), + name='plot_sd_cbf', + ) + workflow.connect([ + (inputnode, plot_sd_cbf, [('template', 'underlay')]), + (average_cbf_maps, plot_sd_cbf, [('sd_map', 'overlay')]), + ]) # fmt:skip + + ds_report_sd_cbf = pe.Node( + DerivativesDataSink( + base_directory=opts.output_dir, + datatype='figures', + desc='sd', + suffix='cbf', + ), + name='ds_report_sd_cbf', + ) + workflow.connect([(plot_sd_cbf, ds_report_sd_cbf, [('out_report', 'in_file')])]) + + return workflow + + +def collect_aslprep_qc_derivatives(layout): + """Collect ASLPrep QC derivatives.""" + qc_data = {} + qc_files = layout.get( + desc='qualitycontrol', + suffix='cbf', + extension='.tsv', + return_type='file', + ) + qc_data['cbf_qc'] = qc_files + for qc_file in qc_files: + # Collect associated files + ... + + return qc_data + + +def build_boilerplate(opts, aslprep_wf): + """Build the boilerplate.""" + pass + + def main(): - """Run the workflow.""" - opts = get_parser().parse_args() + """Entry point.""" + import gc + import sys + from multiprocessing import Manager, Process + from os import EX_SOFTWARE + from pathlib import Path + + from aslprep.utils.bids import write_bidsignore, write_derivative_description + + opts = parse_args() + input_dir = opts.aslprep_dir + output_dir = opts.output_dir + work_dir = opts.work_dir + + logger = logging.getLogger() + + # CRITICAL Call build_workflow(config_file, retval) in a subprocess. + # Because Python on Linux does not ever free virtual memory (VM), running the + # workflow construction jailed within a process preempts excessive VM buildup. + with Manager() as mgr: + retval = mgr.dict() + p = Process(target=build_workflow, args=(opts, retval)) + p.start() + p.join() + retval = dict(retval.items()) # Convert to base dictionary + + if p.exitcode: + retval['return_code'] = p.exitcode + + exitcode = retval.get('return_code', 0) + aslprep_wf = retval.get('workflow', None) + + exitcode = exitcode or (aslprep_wf is None) * EX_SOFTWARE + if exitcode != 0: + sys.exit(exitcode) + + # Generate boilerplate + with Manager() as mgr: + p = Process(target=build_boilerplate, args=(opts, aslprep_wf)) + p.start() + p.join() + + # Clean up master process before running workflow, which may create forks + gc.collect() + + logger.log(25, 'ASLPrep started!') + errno = 1 # Default is error exit unless otherwise set + try: + aslprep_wf.run(**config.nipype.get_plugin()) + except Exception as e: + logger.critical('ASLPrep-Group failed: %s', e) + raise + else: + logger.log(25, 'ASLPrep-Group finished successfully!') + + # Bother users with the boilerplate only iff the workflow went okay. + boiler_file = output_dir / 'logs' / 'CITATION.md' + if boiler_file.exists(): + if config.environment.exec_env in ( + 'singularity', + 'docker', + 'aslprep-docker', + ): + boiler_file = Path('') / boiler_file.relative_to(output_dir) + logger.log( + 25, + 'Works derived from this ASLPrep execution should include the ' + f'boilerplate text found in {boiler_file}.', + ) + errno = 0 + finally: + from aslprep import data + from aslprep.reports.core import generate_reports - allsubj_dir = os.path.abspath(opts.aslprep_dir) - outputfile = os.getcwd() + '/' + str(opts.output_prefix) + '_allsubjects_qc.tsv' + # Generate reports phase + session_list = config.execution.get().get('bids_filters', {}).get('asl', {}).get('session') - qclist = [] - for r, _, f in os.walk(allsubj_dir): - for filex in f: - if filex.endswith('desc-qualitycontrol_cbf.tsv'): - qclist.append(r + '/' + filex) + failed_reports = generate_reports( + config.execution.participant_label, + config.execution.aslprep_dir, + config.execution.run_uuid, + session_list=session_list, + bootstrap_file=data.load('reports-spec.yml'), + ) + write_derivative_description(input_dir, output_dir) + write_bidsignore(output_dir) - datax = pd.read_table(qclist[0]) - for i in range(1, len(qclist)): - dy = pd.read_table(qclist[i]) - datax = pd.concat([datax, dy]) + if failed_reports: + msg = ( + 'Report generation was not successful for the following participants ' + f': {", ".join(failed_reports)}.' + ) + logger.error(msg) - datax.to_csv(outputfile, index=None, sep='\t') + sys.exit(int((errno + len(failed_reports)) > 0)) if __name__ == '__main__': raise RuntimeError( - 'this should be run after running aslprep;\nit required installation of aslprep' + 'aslprep/cli/aggregate_qc.py should not be run directly;\n' + 'Please `pip install` aslprep and use the `aslprep-group` command' ) diff --git a/aslprep/interfaces/group.py b/aslprep/interfaces/group.py new file mode 100644 index 000000000..3419e7113 --- /dev/null +++ b/aslprep/interfaces/group.py @@ -0,0 +1,244 @@ +# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- +# vi: set ft=python sts=4 ts=4 sw=4 et: +"""Interfaces for calculating and collecting confounds.""" + +import os + +import matplotlib.pyplot as plt +import pandas as pd +import seaborn as sns +from nipype.interfaces.base import ( + BaseInterfaceInputSpec, + File, + SimpleInterface, + TraitedSpec, + isdefined, + traits, +) + + +class _AggregateCBFQCInputSpec(BaseInterfaceInputSpec): + in_files = traits.List(File(exists=True), desc='list of QC tsv files') + + +class _AggregateCBFQCOutputSpec(TraitedSpec): + out_file = File(exists=True, desc='aggregated QC file') + + +class AggregateCBFQC(SimpleInterface): + """Aggregate CBF QC metrics across runs, sessions, and subjects.""" + + input_spec = _AggregateCBFQCInputSpec + output_spec = _AggregateCBFQCOutputSpec + + def _run_interface(self, runtime): + in_files = self.inputs.in_files + dfs = [] + for in_file in in_files: + temp_df = pd.read_table(in_file) + temp_df['source_file'] = os.path.basename(in_file) + dfs.append(temp_df) + + df = pd.concat(dfs) + + self._results['out_file'] = os.path.join(runtime.cwd, 'aggregated_qc.tsv') + df.to_csv(self._results['out_file'], index=None, sep='\t') + + return runtime + + +class _PlotAggregatedCBFQCInputSpec(BaseInterfaceInputSpec): + in_file = File(exists=True, desc='aggregated QC file') + + +class _PlotAggregatedCBFQCOutputSpec(TraitedSpec): + out_files = traits.List(File(exists=True), desc='list of output figures') + measures = traits.List(traits.String, desc='list of measures that have been plotted') + + +class PlotAggregatedCBFQC(SimpleInterface): + """Plot aggregated CBF QC metrics across runs, sessions, and subjects.""" + + input_spec = _PlotAggregatedCBFQCInputSpec + output_spec = _PlotAggregatedCBFQCOutputSpec + + def _run_interface(self, runtime): + from bids.layout import parse_file_entities + + df = pd.read_table(self.inputs.in_file) + all_filenames = df['source_file'].unique() + all_entities = [list(parse_file_entities(f).keys()) for f in all_filenames] + # flatten list of lists + all_entities = [item for sublist in all_entities for item in sublist] + all_entities = list(set(all_entities)) + + cols_to_ignore = all_entities + ['source_file'] + measures = [c for c in df.columns if c not in cols_to_ignore] + + entities_to_ignore = ['subject', 'session', 'run'] + entities_to_group = [e for e in all_entities if e not in entities_to_ignore] + + for measure in measures: + figure_file = self.plot_aggregated_qc_metrics( + df, measure, entities_to_group, runtime.cwd + ) + self._results['out_files'].append(figure_file) + self._results['measures'].append(measure) + + return runtime + + def plot_aggregated_qc_metrics(self, df, measure, entity_columns, cwd): + """Plot aggregated QC metrics for a given measure and entity columns.""" + # Identify entities with varying values in dataset + grouping_columns = [] + for col in entity_columns: + if df[col].unique().size > 1: + grouping_columns.append(col) + df[col] = df[col].fillna('NONE') + + # Create a new column that indexes combinations of grouping columns + df['group'] = '' + for col in grouping_columns: + df['group'] += col + '-' + df[col] + '_' + + # Remove trailing underscore + df['group'] = df['group'].str.rstrip('_') + + kwargs = {} + if len(grouping_columns): + kwargs = {'hue': 'group'} + + fig, ax = plt.subplots() + sns.stripplot(data=df, y=measure, ax=ax, figure=fig, **kwargs) + figure_file = os.path.join(cwd, f'{measure}.svg') + fig.savefig(figure_file) + return figure_file + + +class _MeanCBFInputSpec(BaseInterfaceInputSpec): + in_files = traits.List(File(exists=True), desc='list of CBF files') + + +class _MeanCBFOutputSpec(TraitedSpec): + mean_map = File(exists=True, desc='mean CBF map') + sd_map = File(exists=True, desc='SD CBF map') + + +class MeanCBF(SimpleInterface): + """Calculate mean and SD CBF maps dataset.""" + + input_spec = _MeanCBFInputSpec + output_spec = _MeanCBFOutputSpec + + def _run_interface(self, runtime): + from nilearn import image + + mean_map = image.mean_img(self.inputs.in_files) + sd_map = image.math_img('np.std(img, axis=3)', img=self.inputs.in_files) + + mean_map_file = os.path.join(runtime.cwd, 'mean_cbf.nii.gz') + sd_map_file = os.path.join(runtime.cwd, 'sd_cbf.nii.gz') + + mean_map.to_filename(mean_map_file) + sd_map.to_filename(sd_map_file) + + self._results['mean_map'] = mean_map_file + self._results['sd_map'] = sd_map_file + + return runtime + + +class _StatisticalMapInputSpecRPT(BaseInterfaceInputSpec): + overlay = File( + exists=True, + mandatory=True, + desc='FC inflation time series', + ) + underlay = File( + exists=True, + mandatory=True, + desc='Underlay image', + ) + mask = File( + exists=True, + mandatory=False, + desc='Mask image', + ) + cmap = traits.Str( + 'viridis', + desc='Colormap', + usedefault=True, + ) + out_report = File( + 'statistical_map_report.svg', + usedefault=True, + desc='Filename for the visual report generated by Nipype.', + ) + + +class _StatisticalMapOutputSpecRPT(TraitedSpec): + out_report = File( + exists=True, + desc='Filename for the visual report generated by Nipype.', + ) + + +class StatisticalMapRPT(SimpleInterface): + """Create a reportlet for Rapidtide outputs.""" + + input_spec = _StatisticalMapInputSpecRPT + output_spec = _StatisticalMapOutputSpecRPT + + def _run_interface(self, runtime): + from uuid import uuid4 + + from nilearn import image, masking, plotting + from nireports._vendored.svgutils.transform import fromstring + from nireports.reportlets.utils import compose_view, cuts_from_bbox, extract_svg + + out_file = os.path.abspath(self.inputs.out_report) + + if isdefined(self.inputs.mask): + mask_img = image.load_img(self.inputs.mask) + overlay_img = masking.unmask( + masking.apply_mask(self.inputs.overlay, self.inputs.mask), + self.inputs.mask, + ) + # since the moving image is already in the fixed image space we + # should apply the same mask + underlay_img = image.load_img(self.inputs.underlay) + else: + overlay_img = image.load_img(self.inputs.overlay) + underlay_img = image.load_img(self.inputs.underlay) + mask_img = image.threshold_img(overlay_img, 1e-3) + + n_cuts = 7 + cuts = cuts_from_bbox(mask_img, cuts=n_cuts) + order = ('z', 'x', 'y') + out_files = [] + + # Plot each cut axis + plot_params = {} + for mode in list(order): + plot_params['display_mode'] = mode + plot_params['cut_coords'] = cuts[mode] + plot_params['title'] = None + plot_params['cmap'] = self.inputs.cmap + + # Generate nilearn figure + display = plotting.plot_stat_map( + overlay_img, + bg_img=underlay_img, + **plot_params, + ) + + svg = extract_svg(display, compress=False) + display.close() + + # Find and replace the figure_1 id. + svg = svg.replace('figure_1', f'{mode}-{uuid4()}', 1) + out_files.append(fromstring(svg)) + + compose_view(bg_svgs=out_files, fg_svgs=None, out_file=out_file) + self._results['out_report'] = out_file + return runtime diff --git a/plot_stuff.ipynb b/plot_stuff.ipynb new file mode 100644 index 000000000..16dc825cf --- /dev/null +++ b/plot_stuff.ipynb @@ -0,0 +1,147 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 45, + "id": "c99e2941-9aa7-49dc-a81f-e59e8b301d9f", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "import matplotlib.pyplot as plt\n", + "import pandas as pd\n", + "import numpy as np\n", + "import seaborn as sns" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "dc2611a9-3e85-49f7-8b7b-bbe6a474066b", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "data = [\n", + " [1, 2, 3, 4, 5, 6],\n", + " ['pcasl', 'pasl', 'pcasl', 'pasl', 'pcasl', 'pasl'],\n", + " ['AP', 'AP', 'AP', 'AP', 'AP', 'AP'],\n", + " ['moco', 'moco', 'moco', None, None, None],\n", + " [0.5, 0.4, 0.3, 0.4, 0.5, 0.6],\n", + "]\n", + "data = list(map(list, zip(*data)))\n", + "df = pd.DataFrame(columns=['subject', 'acquisition', 'direction', 'reconstruction', 'qei'], data=data)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "e57a99ee-beb8-4cd7-8177-263ce7b8f91e", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "columns_to_group = ['acquisition', 'direction', 'reconstruction']\n", + "grouping_columns = []\n", + "for col in columns_to_group:\n", + " if df[col].unique().size > 1:\n", + " grouping_columns.append(col)\n", + " df[col] = df[col].fillna(\"NONE\")" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "f3a8f28f-c234-40ce-b73b-4a281a7f781c", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "df['group'] = ''\n", + "for col in grouping_columns:\n", + " df['group'] += col + '-' + df[col] + '_'\n", + "\n", + "df['group'] = df['group'].str.rstrip('_')" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "58610ee0-f0fb-40ee-ab06-0ea89bd2508e", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "n_columns = df['group'].unique().size + 1" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "045a6aa1-1ef7-42d0-bfed-99a575d256db", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "sns.stripplot(data=df, y='qei', hue='group')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9e54d996-345b-42a4-9721-f9b4a17c7510", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.17" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 76cd318353f294012fd156690272bc49e96f6106 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Wed, 21 May 2025 15:37:33 -0400 Subject: [PATCH 2/3] Update some stuff. --- aslprep/cli/aggregate_qc.py | 3 - plot_stuff.ipynb | 147 ------------------------------------ 2 files changed, 150 deletions(-) delete mode 100644 plot_stuff.ipynb diff --git a/aslprep/cli/aggregate_qc.py b/aslprep/cli/aggregate_qc.py index e40af218a..b5ff4b3a6 100644 --- a/aslprep/cli/aggregate_qc.py +++ b/aslprep/cli/aggregate_qc.py @@ -2,11 +2,8 @@ # vi: set ft=python sts=4 ts=4 sw=4 et: """Aggregate QC measures across all subjects in dataset.""" -import os from pathlib import Path -import pandas as pd - from aslprep._warnings import logging diff --git a/plot_stuff.ipynb b/plot_stuff.ipynb deleted file mode 100644 index 16dc825cf..000000000 --- a/plot_stuff.ipynb +++ /dev/null @@ -1,147 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 45, - "id": "c99e2941-9aa7-49dc-a81f-e59e8b301d9f", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "import matplotlib.pyplot as plt\n", - "import pandas as pd\n", - "import numpy as np\n", - "import seaborn as sns" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "id": "dc2611a9-3e85-49f7-8b7b-bbe6a474066b", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "data = [\n", - " [1, 2, 3, 4, 5, 6],\n", - " ['pcasl', 'pasl', 'pcasl', 'pasl', 'pcasl', 'pasl'],\n", - " ['AP', 'AP', 'AP', 'AP', 'AP', 'AP'],\n", - " ['moco', 'moco', 'moco', None, None, None],\n", - " [0.5, 0.4, 0.3, 0.4, 0.5, 0.6],\n", - "]\n", - "data = list(map(list, zip(*data)))\n", - "df = pd.DataFrame(columns=['subject', 'acquisition', 'direction', 'reconstruction', 'qei'], data=data)" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "id": "e57a99ee-beb8-4cd7-8177-263ce7b8f91e", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "columns_to_group = ['acquisition', 'direction', 'reconstruction']\n", - "grouping_columns = []\n", - "for col in columns_to_group:\n", - " if df[col].unique().size > 1:\n", - " grouping_columns.append(col)\n", - " df[col] = df[col].fillna(\"NONE\")" - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "id": "f3a8f28f-c234-40ce-b73b-4a281a7f781c", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "df['group'] = ''\n", - "for col in grouping_columns:\n", - " df['group'] += col + '-' + df[col] + '_'\n", - "\n", - "df['group'] = df['group'].str.rstrip('_')" - ] - }, - { - "cell_type": "code", - "execution_count": 44, - "id": "58610ee0-f0fb-40ee-ab06-0ea89bd2508e", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "n_columns = df['group'].unique().size + 1" - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "id": "045a6aa1-1ef7-42d0-bfed-99a575d256db", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 47, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "sns.stripplot(data=df, y='qei', hue='group')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9e54d996-345b-42a4-9721-f9b4a17c7510", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.17" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} From 09890f9c02cf96184b7b736acba3abba8f5a36d5 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Wed, 21 May 2025 15:41:31 -0400 Subject: [PATCH 3/3] Update aggregate_qc.py --- aslprep/cli/aggregate_qc.py | 36 ++++++++---------------------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/aslprep/cli/aggregate_qc.py b/aslprep/cli/aggregate_qc.py index b5ff4b3a6..15679349f 100644 --- a/aslprep/cli/aggregate_qc.py +++ b/aslprep/cli/aggregate_qc.py @@ -213,6 +213,8 @@ def collect_aslprep_qc_derivatives(layout): qc_data['cbf_qc'] = qc_files for qc_file in qc_files: # Collect associated files + # Standard-space CBF map + # We also need the space used so we can grab the right template ... return qc_data @@ -229,14 +231,12 @@ def main(): import sys from multiprocessing import Manager, Process from os import EX_SOFTWARE - from pathlib import Path from aslprep.utils.bids import write_bidsignore, write_derivative_description opts = parse_args() input_dir = opts.aslprep_dir output_dir = opts.output_dir - work_dir = opts.work_dir logger = logging.getLogger() @@ -269,44 +269,24 @@ def main(): # Clean up master process before running workflow, which may create forks gc.collect() - logger.log(25, 'ASLPrep started!') + logger.log(25, 'ASLPrep-Group started!') errno = 1 # Default is error exit unless otherwise set try: - aslprep_wf.run(**config.nipype.get_plugin()) + aslprep_wf.run({'plugin': 'Linear'}) except Exception as e: logger.critical('ASLPrep-Group failed: %s', e) raise else: logger.log(25, 'ASLPrep-Group finished successfully!') - - # Bother users with the boilerplate only iff the workflow went okay. - boiler_file = output_dir / 'logs' / 'CITATION.md' - if boiler_file.exists(): - if config.environment.exec_env in ( - 'singularity', - 'docker', - 'aslprep-docker', - ): - boiler_file = Path('') / boiler_file.relative_to(output_dir) - logger.log( - 25, - 'Works derived from this ASLPrep execution should include the ' - f'boilerplate text found in {boiler_file}.', - ) errno = 0 finally: from aslprep import data - from aslprep.reports.core import generate_reports + from aslprep.reports.core import generate_group_report # Generate reports phase - session_list = config.execution.get().get('bids_filters', {}).get('asl', {}).get('session') - - failed_reports = generate_reports( - config.execution.participant_label, - config.execution.aslprep_dir, - config.execution.run_uuid, - session_list=session_list, - bootstrap_file=data.load('reports-spec.yml'), + failed_reports = generate_group_report( + output_dir, + bootstrap_file=data.load('reports-spec-group.yml'), ) write_derivative_description(input_dir, output_dir) write_bidsignore(output_dir)