From 452073d16ddb81fada9e6f310174cd3a6e7d99bd Mon Sep 17 00:00:00 2001 From: Matt Cieslak Date: Thu, 21 Nov 2024 16:07:58 -0500 Subject: [PATCH] seems to work --- qsirecon/interfaces/anatomical.py | 13 +- qsirecon/interfaces/scalar_mapping.py | 10 +- qsirecon/utils/bids.py | 15 +- qsirecon/utils/ingress.py | 151 --------------------- qsirecon/workflows/base.py | 2 +- qsirecon/workflows/recon/anatomical.py | 5 +- qsirecon/workflows/recon/scalar_mapping.py | 5 +- 7 files changed, 33 insertions(+), 168 deletions(-) delete mode 100644 qsirecon/utils/ingress.py diff --git a/qsirecon/interfaces/anatomical.py b/qsirecon/interfaces/anatomical.py index dc0bc8f6..5f49b1fd 100644 --- a/qsirecon/interfaces/anatomical.py +++ b/qsirecon/interfaces/anatomical.py @@ -331,10 +331,15 @@ class GetTemplate(SimpleInterface): def _run_interface(self, runtime): from templateflow.api import get as get_template + template_name = self.inputs.template_name + cohort = None + if "+" in template_name: + template_name, cohort = template_name.split("+") + template_file = str( get_template( - self.inputs.template_name, - cohort=[None, "2"], + template_name, + cohort=cohort, resolution="1", desc=None, suffix="T1w", @@ -343,8 +348,8 @@ def _run_interface(self, runtime): ) mask_file = str( get_template( - self.inputs.template_name, - cohort=[None, "2"], + template_name, + cohort=cohort, resolution="1", desc="brain", suffix="mask", diff --git a/qsirecon/interfaces/scalar_mapping.py b/qsirecon/interfaces/scalar_mapping.py index ddb3da95..4c5b1588 100644 --- a/qsirecon/interfaces/scalar_mapping.py +++ b/qsirecon/interfaces/scalar_mapping.py @@ -23,7 +23,6 @@ ) from nipype.utils.filemanip import fname_presuffix -from .. import config from .bids import get_bids_params LOGGER = logging.getLogger("nipype.interface") @@ -219,6 +218,7 @@ class _TemplateMapperInputSpec(ScalarMapperInputSpec): template_reference_image = File(exists=True, mandatory=True) to_template_transform = File(exists=True, mandatory=True) interpolation = traits.Str("NearestNeighbor", usedefault=True) + template_space = traits.Str(mandatory=True) class _TemplateMapperOutputSpec(ScalarMapperOutputSpec): @@ -263,13 +263,9 @@ def _do_mapping(self, runtime): new_metadata["path"] = output_fname if "bids" not in new_metadata: raise Exception(f"incomplete metadata spec {new_metadata}") - new_metadata["bids"]["space"] = ( - "MNIInfant" if config.workflow.infant else "MNI152NLin2009cAsym" - ) + new_metadata["bids"]["space"] = self.inputs.template_space resampled_image_metadata.append(new_metadata) self._results["template_space_scalars"] = resampled_images self._results["template_space_scalar_info"] = resampled_image_metadata - self._results["template_space"] = ( - "MNIInfant" if config.workflow.infant else "MNI152NLin2009cAsym" - ) + self._results["template_space"] = self.inputs.template_space diff --git a/qsirecon/utils/bids.py b/qsirecon/utils/bids.py index a00b8abd..ec31cd58 100644 --- a/qsirecon/utils/bids.py +++ b/qsirecon/utils/bids.py @@ -184,13 +184,14 @@ def collect_anatomical_data( from qsirecon.data import load as load_data anat_data = {} - status = {} + status = {"template_output_space": None} if session_id is None: return anat_data, {"has_qsiprep_t1w": False, "has_qsiprep_t1w_transforms": False} _spec = yaml.safe_load(load_data.readable("io_spec.yaml").read_text()) queries = _spec["queries"]["anat"] + if config.workflow.infant: queries["acpc_to_template_xfm"]["to"] = "MNIInfant" queries["template_to_acpc_xfm"]["from"] = "MNIInfant" @@ -241,10 +242,20 @@ def collect_anatomical_data( config.loggers.utils.warning("No anat-to-template or template-to-anat transforms found.") status["has_qsiprep_t1w_transforms"] = False - + else: + # Determine the output space from the transform file + status["template_output_space"] = _determine_output_space(anat_data) return anat_data, status +def _determine_output_space(status): + """Determine what output space the transform maps to/from""" + if not status["template_to_acpc_xfm"]: + return None + + return get_entity(status["template_to_acpc_xfm"], "from") + + def write_derivative_description( bids_dir, deriv_dir, diff --git a/qsirecon/utils/ingress.py b/qsirecon/utils/ingress.py deleted file mode 100644 index 0a1ba58e..00000000 --- a/qsirecon/utils/ingress.py +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -""" -Utilities to handle data from other preprocessing pipelines -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -""" -import re -from pathlib import Path - -UKB_DIR_PATTERN = re.compile("(\d+)_(\d+)_(\d+)") - - -def missing_from_ukb_directory(ukb_subject_dir): - """Check for missing files in a ukb subject directory. - - Parameters - ---------- - ukb_subject_dir : :obj:`pathlib.Path` - The path to the ukb subject directory. - - Returns - ------- - missing_files : :obj:`list` of :obj:`str` - A list of missing files. - """ - dmri_dir = ukb_subject_dir / "DTI" / "dMRI" / "dMRI" - - # Files needed to run recon workflows - required_files = [ - dmri_dir / "bvals", - dmri_dir / "bvecs", - dmri_dir / "data_ud.nii.gz", - dmri_dir / "dti_FA.nii.gz", - # The anatomical data is not strictly necessary for recon - # anat_dir / "T1_brain.nii.gz", - # anat_dir / "T1_brain_mask.nii.gz" - ] - - return [str(fpath) for fpath in required_files if not fpath.exists()] - - -def create_ukb_layout(ukb_dir, participant_label=None): - """Find all valid ukb directories under ukb_dir. - - Parameters - ---------- - ukb_dir : :obj:`pathlib.Path` - The path to the ukb directory. - participant_label : :obj:`list` of :obj:`str` - A list of participant labels to search for. - - Returns - ------- - ukb_layout : :obj:`list` of :obj:`dict` - A list of dictionaries containing the subject ID, session ID, path to the ukb directory, - and the path to the fake dwi file. - """ - # find directories starting with a number. These are the candidate directories - ukb_layout = [] - for potential_dir in Path(ukb_dir).iterdir(): - if participant_label and not potential_dir.name.startswith(participant_label): - continue - - match = re.match(UKB_DIR_PATTERN, potential_dir.name) - if not match: - continue - - if missing_from_ukb_directory(potential_dir): - continue - - subject, ses_major, ses_minor = match.groups() - renamed_ses = "%02d%02d" % (int(ses_major), int(ses_minor)) - fake_dwi_file = ( - f"/bids/sub-{subject}/ses-{renamed_ses}/dwi/sub-{subject}_ses-{renamed_ses}_dwi.nii.gz" - ) - ukb_layout.append( - { - "subject": subject, - "session": renamed_ses, - "path": potential_dir, - "bids_dwi_file": fake_dwi_file, - } - ) - - return ukb_layout - - -def ukb_dirname_to_bids(ukb_dir): - """Convert a UKB directory name to a BIDS subject ID. - - Parameters - ---------- - ukb_dir : :obj:`pathlib.Path` - The path to the ukb directory. - - Returns - ------- - bids_subject_id : :obj:`str` - The BIDS subject ID and session ID, combined in a string. - """ - ukb_path = Path(ukb_dir) - match = re.match(UKB_DIR_PATTERN, ukb_path.name) - subject, ses_major, ses_minor = match.groups() - renamed_ses = "%02d%02d" % (int(ses_major), int(ses_minor)) - return f"sub-{subject}_ses-{renamed_ses}" - - -def collect_ukb_participants(ukb_layout, participant_label): - """Collect all valid UKB participants. - - Parameters - ---------- - ukb_layout : :obj:`list` of :obj:`dict` - A list of dictionaries containing the subject ID, session ID, path to the ukb directory, - and the path to the fake dwi file. - participant_label : :obj:`list` of :obj:`str` - A list of participant labels to search for. - - Returns - ------- - participants : :obj:`list` of :obj:`str` - A list of participant labels. - """ - all_participants = set([spec["subject"] for spec in ukb_layout]) - - # No --participant-label was set, return all - if not participant_label: - return sorted(all_participants) - - if isinstance(participant_label, str): - participant_label = [participant_label] - - participant_label = set(participant_label) - - # Remove labels not found - found_labels = sorted(participant_label & all_participants) - requested_but_missing = sorted(participant_label - all_participants) - - if requested_but_missing: - raise Exception( - "Requested subjects [{}] do not have complete UKB directories under".format( - ", ".join(requested_but_missing) - ) - ) - if not found_labels: - raise Exception("No complete UKB directories were found") - - return sorted(found_labels) diff --git a/qsirecon/workflows/base.py b/qsirecon/workflows/base.py index 9d91d008..6e321f12 100644 --- a/qsirecon/workflows/base.py +++ b/qsirecon/workflows/base.py @@ -148,6 +148,7 @@ def init_single_subject_recon_wf(subject_id): needs_t1w_transform=bool(config.execution.atlases), bids_filters=config.execution.bids_filters or {}, ) + config.loggers.workflow.info( f"Anatomical data available for {anat_input_file.path}:\n" f"{yaml.dump(anat_data, default_flow_style=False, indent=4)}" @@ -250,7 +251,6 @@ def init_single_subject_recon_wf(subject_id): name=f"{wf_name}_dwi_specific_anat_wf", **highres_anat_statuses[anat_input.path], ) - inputs_dict = { "dwi_file": dwi_file, "dwi_metadata": config.execution.layout.get_metadata(dwi_file), diff --git a/qsirecon/workflows/recon/anatomical.py b/qsirecon/workflows/recon/anatomical.py index e9861fab..af651903 100644 --- a/qsirecon/workflows/recon/anatomical.py +++ b/qsirecon/workflows/recon/anatomical.py @@ -318,6 +318,7 @@ def init_dwi_recon_anatomical_workflow( has_freesurfer, extras_to_make, name, + template_output_space, prefer_dwi_mask=False, ): """Ensure that anatomical data is available for the reconstruction workflows. @@ -398,12 +399,12 @@ def _get_status(): "has_freesurfer_5tt_hsvs": has_freesurfer_5tt_hsvs, "has_qsiprep_t1w": has_qsiprep_t1w, "has_qsiprep_t1w_transforms": has_qsiprep_t1w_transforms, + "template_output_space": template_output_space, } - # XXX: This is a temporary solution until QSIRecon supports flexible output spaces. get_template = pe.Node( GetTemplate( - template_name="MNI152NLin2009cAsym" if not config.workflow.infant else "MNIInfant", + template_name=template_output_space, ), name="get_template", ) diff --git a/qsirecon/workflows/recon/scalar_mapping.py b/qsirecon/workflows/recon/scalar_mapping.py index 8a0c9c55..bd277696 100644 --- a/qsirecon/workflows/recon/scalar_mapping.py +++ b/qsirecon/workflows/recon/scalar_mapping.py @@ -165,7 +165,10 @@ def init_scalar_to_template_wf( name="outputnode", ) workflow = Workflow(name=name) - template_mapper = pe.Node(TemplateMapper(**params), name="template_mapper") + template_mapper = pe.Node( + TemplateMapper(template_space=inputs_dict["template_output_space"], **params), + name="template_mapper", + ) scalar_output_wf = init_scalar_output_wf() workflow.connect([