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
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ dependencies = [
"acres",
"datalad",
"nipype",
"nireports",
"pandas",
"pybids >= 0.15.6",
"typer",
"toml",
"pyyaml",
]
dynamic = ["version"]
Expand Down Expand Up @@ -187,6 +189,7 @@ ignore = [
"S113",
"S202",
"S602",
"T100",
]

[tool.ruff.lint.flake8-quotes]
Expand Down
7 changes: 7 additions & 0 deletions src/simbids/cli/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,12 @@ def _bids_filter(value, parser):
),
)

parser.add_argument(
'bids_app',
choices=['qsiprep', 'qsirecon', 'xcp_d', 'fmriprep'],
help=('BIDS-App to be simulated'),
)

g_bids = parser.add_argument_group('Options for filtering BIDS queries')
g_bids.add_argument(
'--skip_bids_validation',
Expand Down Expand Up @@ -336,3 +342,4 @@ def parse_args(args=None, namespace=None):
)

config.execution.participant_label = sorted(participant_label)
return opts
10 changes: 5 additions & 5 deletions src/simbids/cli/raw_mri.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
LGR = logging.getLogger(__name__)

# Get all the yaml files using importlib
with resources.path('simbids.data.bids-mri', '') as bids_mri_path:
with resources.path('simbids.data.bids_mri', '') as bids_mri_path:
yaml_files = [f.name for f in bids_mri_path.glob('*.yaml')]


Expand Down Expand Up @@ -83,12 +83,12 @@ def main():
config_path = Path(args.config_file)
if not config_path.exists():
raise FileNotFoundError(f'Config file {args.config_file} not found')
# For custom config files, pass the string path
simulate_dataset(args.bids_dir, str(config_path), args.fill_files, args.datalad_init)
else:
LGR.info(f'Using bundled config file: {args.config_file}')
config_path = resources.path('simbids.data.bids-mri', args.config_file)

# Create the raw MRI BIDS dataset
simulate_dataset(args.bids_dir, config_path, args.fill_files, args.datalad_init)
# For bundled config files, pass the filename directly
simulate_dataset(args.bids_dir, args.config_file, args.fill_files, args.datalad_init)


if __name__ == '__main__':
Expand Down
24 changes: 4 additions & 20 deletions src/simbids/cli/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,12 @@
def main():
"""Entry point."""
import gc
from multiprocessing import Manager, Process

from simbids.cli.parser import parse_args
from simbids.cli.workflow import build_workflow

# Code Carbon
if config.execution.track_carbon:
pass
# Parse arguments
parse_args()

if 'pdb' in config.execution.debug:
from simbids.utils.debug import setup_exceptionhook
Expand All @@ -51,22 +50,7 @@ def main():
config_file.parent.mkdir(exist_ok=True, parents=True)
config.to_filename(config_file)

# 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.
if 'pdb' not in config.execution.debug:
with Manager() as mgr:
retval = mgr.dict()
p = Process(target=build_workflow, args=(str(config_file), retval))
p.start()
p.join()
retval = dict(retval.items()) # Convert to base dictionary

if p.exitcode:
retval['return_code'] = p.exitcode

else:
retval = build_workflow(str(config_file), {})
retval = build_workflow(str(config_file), {})

global EXITCODE
EXITCODE = retval.get('return_code', 0)
Expand Down
23 changes: 1 addition & 22 deletions src/simbids/cli/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ def build_workflow(config_file, retval):
"""Create the Nipype Workflow that supports the whole execution graph."""
from pathlib import Path

from fmriprep.utils.bids import check_pipeline_version
from fmriprep.utils.misc import check_deps
from niworkflows.utils.bids import collect_participants
from pkg_resources import resource_filename as pkgrf

Expand All @@ -48,7 +46,6 @@ def build_workflow(config_file, retval):
config.load(config_file)
build_log = config.loggers.workflow

output_dir = config.execution.output_dir
version = config.environment.version

retval['return_code'] = 1
Expand All @@ -64,15 +61,6 @@ def build_workflow(config_file, retval):
banner += ['#' * len(banner[1])]
build_log.log(25, f'\n{" " * 9}'.join(banner))

# warn if older results exist: check for dataset_description.json in output folder
msg = check_pipeline_version(
'SimBIDS',
version,
output_dir / 'dataset_description.json',
)
if msg is not None:
build_log.warning(msg)

# Please note this is the input folder's dataset_description.json
dset_desc_path = config.execution.bids_dir / 'dataset_description.json'
if dset_desc_path.exists():
Expand Down Expand Up @@ -103,6 +91,7 @@ def build_workflow(config_file, retval):
"Building SimBIDS's workflow:",
f'BIDS dataset path: {config.execution.bids_dir}.',
f'Participant list: {subject_list}.',
f'BIDS-App: {config.workflow.bids_app}.',
f'Run identifier: {config.execution.run_uuid}.',
f'Output spaces: {config.execution.output_spaces}.',
]
Expand All @@ -114,16 +103,6 @@ def build_workflow(config_file, retval):

retval['workflow'] = init_simbids_wf()

# Check workflow for missing commands
missing = check_deps(retval['workflow'])
if missing:
build_log.critical(
'Cannot run SimBIDS. Missing dependencies:%s',
'\n\t* '.join([''] + [f'{cmd} (Interface: {iface})' for iface, cmd in missing]),
)
retval['return_code'] = 127 # 127 == command not found.
return retval

config.to_filename(config_file)
build_log.info(
'SimBIDS workflow graph with %d nodes built successfully.',
Expand Down
26 changes: 4 additions & 22 deletions src/simbids/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@

# Debug modes are names that influence the exposure of internal details to
# the user, either through additional derivatives or increased verbosity
DEBUG_MODES = ('compcor', 'fieldmaps', 'pdb')
DEBUG_MODES = ('pdb',)


class _Config:
Expand Down Expand Up @@ -481,9 +481,7 @@ def init(cls):
'sourcedata',
'models',
re.compile(r'^\.'),
re.compile(
r'sub-[a-zA-Z0-9]+(/ses-[a-zA-Z0-9]+)?/(beh|dwi|eeg|ieeg|meg|perf)'
),
re.compile(r'sub-[a-zA-Z0-9]+(/ses-[a-zA-Z0-9]+)?/(beh|eeg|ieeg|meg|perf)'),
),
)
cls._layout = BIDSLayout(
Expand Down Expand Up @@ -541,15 +539,8 @@ class workflow(_Config):

ignore = None
"""Ignore particular steps for *SimBIDS*."""
cifti_output = None
"""Generate HCP Grayordinates, accepts either ``'91k'`` (default) or ``'170k'``."""
dummy_scans = None
"""Set a number of initial scans to be considered nonsteady states."""
slice_time_ref = 0.5
"""The time of the reference slice to correct BOLD values to, as a fraction
acquisition time. 0 indicates the start, 0.5 the midpoint, and 1 the end
of acquisition. The alias `start` corresponds to 0, and `middle` to 0.5.
The default value is 0.5."""
bids_app = 'qsiprep'
"""The BIDS App to simulate."""


class loggers:
Expand Down Expand Up @@ -738,14 +729,5 @@ def init_spaces(checkpoint=True):
if 'MNI152NLin6Asym' not in spaces.get_spaces(nonstandard=False, dim=(3,)):
spaces.add(Reference('MNI152NLin6Asym', {}))

# Ensure user-defined spatial references for outputs are correctly parsed.
# Certain options require normalization to a space not explicitly defined by users.
# These spaces will not be included in the final outputs.
cifti_output = workflow.cifti_output
if cifti_output:
# CIFTI grayordinates to corresponding FSL-MNI resolutions.
vol_res = '2' if cifti_output == '91k' else '1'
spaces.add(Reference('MNI152NLin6Asym', {'res': vol_res}))

# Make the SpatialReferences object available
workflow.spaces = spaces
Loading
Loading