diff --git a/mriqc/interfaces/__init__.py b/mriqc/interfaces/__init__.py index 2e334998e..309a30999 100644 --- a/mriqc/interfaces/__init__.py +++ b/mriqc/interfaces/__init__.py @@ -7,7 +7,25 @@ from .anatomical import \ StructuralQC, ArtifactMask, ComputeQI2, Harmonize, RotationMask from .functional import FunctionalQC, Spikes -from .bids import ReadSidecarJSON, IQMFileSink +from .bids import IQMFileSink from .viz import PlotMosaic, PlotContours, PlotSpikes from .common import ConformImage, EnsureSize from .webapi import UploadIQMs + + +__all__ = [ + 'ArtifactMask', + 'ComputeQI2', + 'ConformImage', + 'EnsureSize', + 'FunctionalQC', + 'Harmonize', + 'IQMFileSink', + 'PlotContours', + 'PlotMosaic', + 'PlotSpikes', + 'RotationMask', + 'Spikes', + 'StructuralQC', + 'UploadIQMs', +] diff --git a/mriqc/interfaces/bids.py b/mriqc/interfaces/bids.py index cc4b82269..ee4f791a4 100644 --- a/mriqc/interfaces/bids.py +++ b/mriqc/interfaces/bids.py @@ -1,6 +1,5 @@ # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: -import os.path as op from pathlib import Path import re import simplejson as json @@ -9,64 +8,13 @@ traits, isdefined, TraitedSpec, DynamicTraitedSpec, BaseInterfaceInputSpec, File, Undefined, Str, SimpleInterface ) -from ..utils.misc import BIDS_COMP, BIDS_EXPR +from ..utils.misc import BIDS_COMP IFLOGGER = logging.getLogger('nipype.interface') -class ReadSidecarJSONInputSpec(BaseInterfaceInputSpec): - in_file = File(exists=True, mandatory=True, desc='the input nifti file') - fields = traits.List(Str, desc='get only certain fields') - - -class ReadSidecarJSONOutputSpec(TraitedSpec): - subject_id = Str() - session_id = Str() - task_id = Str() - acq_id = Str() - rec_id = Str() - run_id = Str() - out_dict = traits.Dict() - relative_path = Str() - - -class ReadSidecarJSON(SimpleInterface): - """ - An utility to find and read JSON sidecar files of a BIDS tree - """ - expr = re.compile(BIDS_EXPR) - input_spec = ReadSidecarJSONInputSpec - output_spec = ReadSidecarJSONOutputSpec - - def _run_interface(self, runtime): - metadata = get_metadata_for_nifti(self.inputs.in_file) - output_keys = [key for key in list(self.output_spec().get().keys()) if key.endswith('_id')] - outputs = self.expr.search(op.basename(self.inputs.in_file)).groupdict() - - for key in output_keys: - id_value = outputs.get(key) - if id_value is not None: - self._results[key] = outputs.get(key) - - if isdefined(self.inputs.fields) and self.inputs.fields: - for fname in self.inputs.fields: - self._results[fname] = metadata[fname] - else: - self._results['out_dict'] = metadata - - # Crawl back to the BIDS root - path = Path(self.inputs.in_file) - for i in range(1, 4): - if str(path.parents[i].name).startswith('sub-'): - bids_root = path.parents[i + 1] - break - - self._results['relative_path'] = str(path.relative_to(bids_root)) - return runtime - - class IQMFileSinkInputSpec(DynamicTraitedSpec, BaseInterfaceInputSpec): - in_file = Str(mandatory=True, desc='path of input file relative to BIDS root') + in_file = Str(mandatory=True, desc='path of input file') subject_id = Str(mandatory=True, desc='the subject id') modality = Str(mandatory=True, desc='the qc type') session_id = traits.Either(None, Str, usedefault=True) @@ -129,8 +77,15 @@ def _gen_outfile(self): if isdefined(self.inputs.out_dir): out_dir = Path(self.inputs.out_dir) + # Crawl back to the BIDS root + path = Path(self.inputs.in_file) + for i in range(1, 4): + if str(path.parents[i].name).startswith('sub-'): + bids_root = path.parents[i + 1] + break + in_file = str(path.relative_to(bids_root)) + # Build path and ensure directory exists - in_file = self.inputs.in_file bids_path = out_dir / in_file.replace( ''.join(Path(in_file).suffixes), '.json') bids_path.parent.mkdir(parents=True, exist_ok=True) @@ -199,63 +154,6 @@ def _run_interface(self, runtime): return runtime -def get_metadata_for_nifti(in_file): - """Fetchs metadata for a given nifi file""" - in_file = op.abspath(in_file) - - fname, ext = op.splitext(in_file) - if ext == '.gz': - fname, ext2 = op.splitext(fname) - ext = ext2 + ext - - side_json = fname + '.json' - fname_comps = op.basename(side_json).split("_") - - session_comp_list = [] - subject_comp_list = [] - top_comp_list = [] - ses = None - sub = None - - for comp in fname_comps: - if comp[:3] != "run": - session_comp_list.append(comp) - if comp[:3] == "ses": - ses = comp - else: - subject_comp_list.append(comp) - if comp[:3] == "sub": - sub = comp - else: - top_comp_list.append(comp) - - if any([comp.startswith('ses') for comp in fname_comps]): - bids_dir = '/'.join(op.dirname(in_file).split('/')[:-3]) - else: - bids_dir = '/'.join(op.dirname(in_file).split('/')[:-2]) - - top_json = op.join(bids_dir, "_".join(top_comp_list)) - potential_json = [top_json] - - subject_json = op.join(bids_dir, sub, "_".join(subject_comp_list)) - potential_json.append(subject_json) - - if ses: - session_json = op.join(bids_dir, sub, ses, "_".join(session_comp_list)) - potential_json.append(session_json) - - potential_json.append(side_json) - - merged_param_dict = {} - for json_file_path in potential_json: - if op.isfile(json_file_path): - with open(json_file_path, 'r') as jsonfile: - param_dict = json.load(jsonfile) - merged_param_dict.update(param_dict) - - return merged_param_dict - - def _process_name(name, val): if '.' in name: newkeys = name.split('.') diff --git a/mriqc/workflows/anatomical.py b/mriqc/workflows/anatomical.py index 4de96fd1e..dcf952cc8 100644 --- a/mriqc/workflows/anatomical.py +++ b/mriqc/workflows/anatomical.py @@ -37,12 +37,13 @@ from nipype.interfaces import utility as niu from nipype.interfaces import fsl, ants from templateflow.api import get as get_template -from niworkflows.anat.skullstrip import afni_wf as skullstrip_wf +from niworkflows.interfaces.bids import ReadSidecarJSON from niworkflows.interfaces.registration import RobustMNINormalizationRPT as RobustMNINormalization +from niworkflows.anat.skullstrip import afni_wf as skullstrip_wf from .. import DEFAULTS, logging -from ..interfaces import (StructuralQC, ArtifactMask, ReadSidecarJSON, - ConformImage, ComputeQI2, IQMFileSink, RotationMask) +from ..interfaces import (StructuralQC, ArtifactMask, ConformImage, + ComputeQI2, IQMFileSink, RotationMask) from .utils import get_fwhmx @@ -265,14 +266,14 @@ def _getwm(inlist): workflow.connect([ (inputnode, meta, [('in_file', 'in_file')]), - (meta, datasink, [('relative_path', 'in_file'), - ('subject_id', 'subject_id'), - ('session_id', 'session_id'), - ('acq_id', 'acq_id'), - ('rec_id', 'rec_id'), - ('run_id', 'run_id'), + (inputnode, datasink, [('in_file', 'in_file')]), + (meta, datasink, [('subject', 'subject_id'), + ('session', 'session_id'), + ('task', 'task_id'), + ('acquisition', 'acq_id'), + ('reconstruction', 'rec_id'), + ('run', 'run_id'), ('out_dict', 'metadata')]), - (inputnode, addprov, [('in_file', 'in_file'), ('airmask', 'air_msk'), ('rotmask', 'rot_msk')]), diff --git a/mriqc/workflows/functional.py b/mriqc/workflows/functional.py index 49014b20f..b542c0903 100644 --- a/mriqc/workflows/functional.py +++ b/mriqc/workflows/functional.py @@ -31,6 +31,7 @@ from nipype.interfaces import utility as niu from nipype.interfaces import afni, ants, fsl +from niworkflows.interfaces.bids import ReadSidecarJSON from niworkflows.interfaces import segmentation as nws from niworkflows.interfaces import registration as nwr from niworkflows.interfaces import utils as niutils @@ -38,7 +39,7 @@ from .utils import get_fwhmx from .. import DEFAULTS, logging -from ..interfaces import ReadSidecarJSON, FunctionalQC, Spikes, IQMFileSink +from ..interfaces import FunctionalQC, Spikes, IQMFileSink DEFAULT_FD_RADIUS = 50. @@ -269,16 +270,16 @@ def compute_iqms(settings, name='ComputeIQMs'): name='datasink', run_without_submitting=True) workflow.connect([ - (inputnode, datasink, [('exclude_index', 'dummy_trs')]), + (inputnode, datasink, [('in_file', 'in_file'), + ('exclude_index', 'dummy_trs')]), (inputnode, meta, [('in_file', 'in_file')]), (inputnode, addprov, [('in_file', 'in_file')]), - (meta, datasink, [('relative_path', 'in_file'), - ('subject_id', 'subject_id'), - ('session_id', 'session_id'), - ('task_id', 'task_id'), - ('acq_id', 'acq_id'), - ('rec_id', 'rec_id'), - ('run_id', 'run_id'), + (meta, datasink, [('subject', 'subject_id'), + ('session', 'session_id'), + ('task', 'task_id'), + ('acquisition', 'acq_id'), + ('reconstruction', 'rec_id'), + ('run', 'run_id'), ('out_dict', 'metadata')]), (addprov, datasink, [('out', 'provenance')]), (outliers, datasink, [(('out_file', _parse_tout), 'aor')]),