Skip to content

Commit

Permalink
enh(bids): use ReadSidecarJSON from niworkflows
Browse files Browse the repository at this point in the history
  • Loading branch information
oesteban committed Apr 2, 2019
1 parent 8babfd0 commit 0fdfb1d
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 122 deletions.
20 changes: 19 additions & 1 deletion mriqc/interfaces/__init__.py
Expand Up @@ -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',
]
122 changes: 10 additions & 112 deletions 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
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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('.')
Expand Down
11 changes: 6 additions & 5 deletions mriqc/workflows/anatomical.py
Expand Up @@ -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


Expand Down Expand Up @@ -265,8 +266,8 @@ def _getwm(inlist):

workflow.connect([
(inputnode, meta, [('in_file', 'in_file')]),
(meta, datasink, [('relative_path', 'in_file'),
('subject_id', 'subject_id'),
(inputnode, datasink, [('in_file', 'in_file')]),
(meta, datasink, [('subject_id', 'subject_id'),
('session_id', 'session_id'),
('acq_id', 'acq_id'),
('rec_id', 'rec_id'),
Expand Down
9 changes: 5 additions & 4 deletions mriqc/workflows/functional.py
Expand Up @@ -31,14 +31,15 @@
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
from niworkflows.interfaces.plotting import FMRISummary

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.
Expand Down Expand Up @@ -269,11 +270,11 @@ 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'),
(meta, datasink, [('subject_id', 'subject_id'),
('session_id', 'session_id'),
('task_id', 'task_id'),
('acq_id', 'acq_id'),
Expand Down

0 comments on commit 0fdfb1d

Please sign in to comment.