Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/simbids/cli/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def _bids_filter(value, parser):
)

parser.add_argument(
'bids_app',
'--bids-app',
choices=['qsiprep', 'qsirecon', 'xcp_d', 'fmriprep'],
help=('BIDS-App to be simulated'),
)
Expand Down
69 changes: 69 additions & 0 deletions src/simbids/utils/bids.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,75 @@
from simbids.data import load as load_data


def write_derivative_description(input_dir, output_dir):
"""Write dataset_description.json file for derivatives.

Parameters
----------
input_dir : :obj:`str`
Path to the primary input dataset being ingested.
This may be a raw BIDS dataset (in the case of raw+derivatives workflows)
or a preprocessing derivatives dataset (in the case of derivatives-only workflows).
output_dir : :obj:`str`
Path to the output xcp-d dataset.
dataset_links : :obj:`dict`, optional
Dictionary of dataset links to include in the dataset description.
"""

DOWNLOAD_URL = 'https://github.com/nipreps/simbids/archive/0.1.0.tar.gz'

input_dir = Path(input_dir)
output_dir = Path(output_dir)

orig_dset_description = os.path.join(input_dir, 'dataset_description.json')
if not os.path.isfile(orig_dset_description):
raise FileNotFoundError(f'Dataset description does not exist: {orig_dset_description}')

with open(orig_dset_description) as fobj:
desc = json.load(fobj)

# Update dataset description
desc['Name'] = 'fMRIPost-AROMA- ICA-AROMA Postprocessing Outputs'
desc['BIDSVersion'] = '1.9.0dev'
desc['DatasetType'] = 'derivative'
desc['HowToAcknowledge'] = 'Include the generated boilerplate in the methods section.'

# Start with GeneratedBy from the primary input dataset's dataset_description.json
desc['GeneratedBy'] = desc.get('GeneratedBy', [])

# Add GeneratedBy from fMRIPost-AROMA
desc['GeneratedBy'].insert(
0,
{
'Name': 'SimBIDS',
'Version': '0.1.0',
'CodeURL': DOWNLOAD_URL,
},
)

# Keys that can only be set by environment
if 'SIMBIDS_SINGULARITY_URL' in os.environ:
desc['GeneratedBy'][0]['Container'] = {
'Type': 'singularity',
'URI': os.getenv('SIMBIDS_SINGULARITY_URL'),
}

# Replace local templateflow path with URL
dataset_links = {}
dataset_links['templateflow'] = 'https://github.com/templateflow/templateflow'

# Add DatasetLinks
desc['DatasetLinks'] = desc.get('DatasetLinks', {})
for k, v in dataset_links.items():
if k in desc['DatasetLinks'].keys() and str(desc['DatasetLinks'][k]) != str(v):
print(f'"{k}" is already a dataset link. Overwriting.')

desc['DatasetLinks'][k] = str(v)

out_desc = Path(output_dir / 'dataset_description.json')
out_desc.write_text(json.dumps(desc, indent=4))


def collect_derivatives(
raw_dataset: Path | BIDSLayout | None,
derivatives_dataset: Path | BIDSLayout | None,
Expand Down
14 changes: 12 additions & 2 deletions src/simbids/workflows/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@

from simbids import config

from ..utils.bids import write_derivative_description


def init_simbids_wf():
"""Build *SimBIDS*'s pipeline.
Expand All @@ -61,7 +63,7 @@ def init_simbids_wf():

simbids_wf = Workflow(name=f'simbids_{ver.major}_{ver.minor}_wf')
simbids_wf.base_dir = config.execution.work_dir

write_derivative_description(config.execution.bids_dir, config.execution.output_dir)
for subject_id in config.execution.participant_label:
single_subject_wf = init_single_subject_wf(subject_id)

Expand Down Expand Up @@ -114,5 +116,13 @@ def init_single_subject_wf(subject_id: str):
from simbids.workflows.qsiprep import init_single_subject_qsiprep_wf

return init_single_subject_qsiprep_wf(subject_id)
elif config.workflow.bids_app == 'qsirecon':
from simbids.workflows.qsirecon import (
init_single_subject_qsirecon_wf,
write_root_level_atlases,
)

write_root_level_atlases(config.execution.output_dir)
return init_single_subject_qsirecon_wf(subject_id)
else:
raise ValueError(f'Unknown application: {config.workflow.simulated_app}')
raise ValueError(f'Unknown application: {config.workflow.bids_app}')
218 changes: 102 additions & 116 deletions src/simbids/workflows/qsiprep/qsiprep.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,134 +313,120 @@ def init_single_subject_qsiprep_wf(subject_id: str):

# Create the anatomical datasinks
anatomical_template = 'MNI152NLin6Asym'
for anat_file in subject_data['t1w']:
workflow.add_nodes(
[
pe.Node(
DerivativesDataSink(
compress=True,
in_file=anat_file,
source_file=anat_file,
base_directory=config.execution.output_dir,
space='ACPC',
desc='preproc',
keep_dtype=True,
),
name=_get_wf_name(anat_file, 'ds_t1_preproc'),
run_without_submitting=True,
anat_file = (subject_data['t1w'] + subject_data['t2w'])[0]
workflow.add_nodes(
[
pe.Node(
DerivativesDataSink(
compress=True,
in_file=anat_file,
source_file=anat_file,
base_directory=config.execution.output_dir,
space='ACPC',
desc='preproc',
keep_dtype=True,
),
pe.Node(
DerivativesDataSink(
compress=True,
in_file=anat_file,
source_file=anat_file,
base_directory=config.execution.output_dir,
space='ACPC',
desc='brain',
suffix='mask',
),
name=_get_wf_name(anat_file, 'ds_t1_mask'),
run_without_submitting=True,
name=_get_wf_name(anat_file, 'ds_t1_preproc'),
run_without_submitting=True,
),
pe.Node(
DerivativesDataSink(
compress=True,
in_file=anat_file,
source_file=anat_file,
base_directory=config.execution.output_dir,
space='ACPC',
desc='brain',
suffix='mask',
),
pe.Node(
DerivativesDataSink(
compress=True,
in_file=anat_file,
source_file=anat_file,
base_directory=config.execution.output_dir,
space='ACPC',
suffix='dseg',
),
name=_get_wf_name(anat_file, 'ds_t1_seg'),
run_without_submitting=True,
name=_get_wf_name(anat_file, 'ds_t1_mask'),
run_without_submitting=True,
),
pe.Node(
DerivativesDataSink(
compress=True,
in_file=anat_file,
source_file=anat_file,
base_directory=config.execution.output_dir,
space='ACPC',
suffix='dseg',
),
pe.Node(
DerivativesDataSink(
compress=True,
in_file=anat_file,
source_file=anat_file,
base_directory=config.execution.output_dir,
space='ACPC',
desc='aseg',
suffix='dseg',
),
name=_get_wf_name(anat_file, 'ds_t1_aseg'),
run_without_submitting=True,
name=_get_wf_name(anat_file, 'ds_t1_seg'),
run_without_submitting=True,
),
pe.Node(
DerivativesDataSink(
compress=True,
in_file=anat_file,
source_file=anat_file,
base_directory=config.execution.output_dir,
space='ACPC',
desc='aseg',
suffix='dseg',
),
pe.Node(
DerivativesDataSink(
in_file=anat_file,
source_file=anat_file,
base_directory=config.execution.output_dir,
to='ACPC',
mode='image',
suffix='xfm',
**{'from': anatomical_template},
),
name=_get_wf_name(anat_file, 'ds_t1_mni_inv_warp'),
run_without_submitting=True,
name=_get_wf_name(anat_file, 'ds_t1_aseg'),
run_without_submitting=True,
),
pe.Node(
DerivativesDataSink(
in_file=anat_file,
source_file=anat_file,
base_directory=config.execution.output_dir,
suffix='xfm',
extension='.h5',
**{'from': anatomical_template, 'to': 'ACPC', 'mode': 'image'},
),
pe.Node(
DerivativesDataSink(
source_file=anat_file,
in_file=anat_file,
base_directory=config.execution.output_dir,
to='ACPC',
mode='image',
suffix='xfm',
**{'from': 'anat'},
),
name=_get_wf_name(anat_file, 'ds_t1_template_acpc_transforms'),
run_without_submitting=True,
name=_get_wf_name(anat_file, 'ds_t1_mni_inv_warp'),
run_without_submitting=True,
),
pe.Node(
DerivativesDataSink(
source_file=anat_file,
in_file=text_file,
base_directory=config.execution.output_dir,
to='ACPC',
mode='image',
suffix='xfm',
extension='.mat',
**{'from': 'anat'},
),
pe.Node(
DerivativesDataSink(
in_file=anat_file,
source_file=anat_file,
base_directory=config.execution.output_dir,
to='anat',
mode='image',
suffix='xfm',
**{'from': 'ACPC'},
),
name=_get_wf_name(anat_file, 'ds_t1_template_acpc_inv_transforms'),
run_without_submitting=True,
name=_get_wf_name(anat_file, 'ds_t1_template_acpc_transforms'),
run_without_submitting=True,
),
pe.Node(
DerivativesDataSink(
in_file=text_file,
source_file=anat_file,
base_directory=config.execution.output_dir,
to='anat',
mode='image',
suffix='xfm',
extension='.mat',
**{'from': 'ACPC'},
),
pe.Node(
DerivativesDataSink(
in_file=anat_file,
source_file=anat_file,
base_directory=config.execution.output_dir,
to=anatomical_template,
mode='image',
suffix='xfm',
**{'from': 'ACPC'},
),
name=_get_wf_name(anat_file, 'ds_t1_mni_warp'),
run_without_submitting=True,
name=_get_wf_name(anat_file, 'ds_t1_template_acpc_inv_transforms'),
run_without_submitting=True,
),
pe.Node(
DerivativesDataSink(
in_file=anat_file,
source_file=anat_file,
base_directory=config.execution.output_dir,
to=anatomical_template,
mode='image',
suffix='xfm',
extension='.h5',
**{'from': 'ACPC'},
),
]
)
name=_get_wf_name(anat_file, 'ds_t1_mni_warp'),
run_without_submitting=True,
),
]
)

return clean_datasinks(workflow)


def init_single_dwi_run_wf(dwi_file: str):
"""Set up a single-run workflow for SimBIDS."""
from niworkflows.engine.workflows import LiterateWorkflow as Workflow

workflow = Workflow(name=_get_wf_name(dwi_file, 'single_run'))
workflow.__desc__ = ''

# Fill in datasinks seen so far
for node in workflow.list_node_names():
if node.split('.')[-1].startswith('ds_'):
workflow.get_node(node).inputs.base_directory = config.execution.output_dir
workflow.get_node(node).inputs.source_file = dwi_file

return workflow


def clean_datasinks(workflow: pe.Workflow) -> pe.Workflow:
"""Overwrite ``out_path_base`` of DataSinks."""
for node in workflow.list_node_names():
Expand Down
3 changes: 3 additions & 0 deletions src/simbids/workflows/qsirecon/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .qsirecon import init_single_subject_qsirecon_wf, write_root_level_atlases

__all__ = ['init_single_subject_qsirecon_wf', 'write_root_level_atlases']
Loading
Loading