From 5950b7fad66435473e7b2c6b7f1df440caede83b Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Wed, 10 Apr 2024 06:53:18 +0200 Subject: [PATCH] fix: revise config save/load and update inputs after dropping Resolves: #1244. --- mriqc/cli/parser.py | 2 +- mriqc/cli/run.py | 15 +++---- mriqc/config.py | 74 +++++++++++++++++++++---------- mriqc/engine/plugin.py | 2 +- mriqc/workflows/diffusion/base.py | 4 ++ 5 files changed, 61 insertions(+), 36 deletions(-) diff --git a/mriqc/cli/parser.py b/mriqc/cli/parser.py index 7792069b..54212019 100644 --- a/mriqc/cli/parser.py +++ b/mriqc/cli/parser.py @@ -482,7 +482,7 @@ def parse_args(args=None, namespace=None): )) config.loggers.default.addHandler(_handler) - extra_messages = [] + extra_messages = [''] if opts.bids_filter_file: extra_messages.insert( diff --git a/mriqc/cli/run.py b/mriqc/cli/run.py index 4e282f21..2ca4afc2 100644 --- a/mriqc/cli/run.py +++ b/mriqc/cli/run.py @@ -53,14 +53,9 @@ def main(): # straightforward way to communicate with the child process is via the filesystem. # The config file name needs to be unique, otherwise multiple mriqc instances # will create write conflicts. - config_file = mkstemp( - dir=config.execution.work_dir, prefix='.mriqc.', suffix='.toml' - )[1] - - config.to_filename(config_file) - config.loggers.cli.info( - f'Saved MRIQC config file: {config_file}.') - config.file_path = config_file + config_file = config.to_filename() + config.loggers.cli.info(f'MRIQC config file: {config_file}.') + exitcode = 0 # Set up participant level if 'participant' in config.workflow.analysis_level: @@ -81,7 +76,7 @@ def main(): _pool = ProcessPoolExecutor( max_workers=config.nipype.nprocs, initializer=config._process_initializer, - initargs=(config.file_path,), + initargs=(config_file,), ) _resmon = None @@ -153,7 +148,7 @@ def main(): if mriqc_wf and config.execution.write_graph: mriqc_wf.write_graph(graph2use='colored', format='svg', simple_form=True) - if not config.execution.dry_run or not config.execution.reports_only: + if not config.execution.dry_run and not config.execution.reports_only: # Warn about submitting measures BEFORE if not config.execution.no_sub: config.loggers.cli.warning(config.DSA_MESSAGE) diff --git a/mriqc/config.py b/mriqc/config.py index 160f80b2..e8b3caa2 100644 --- a/mriqc/config.py +++ b/mriqc/config.py @@ -91,6 +91,7 @@ import sys from contextlib import suppress from pathlib import Path +from tempfile import mkstemp from time import strftime from uuid import uuid4 @@ -205,12 +206,6 @@ _memory_gb = float(_mem_str) / (1024.0**3) -file_path: Path = None -""" -Path to configuration file. -""" - - class _Config: """An abstract class forbidding instantiation.""" @@ -221,9 +216,9 @@ def __init__(self): raise RuntimeError('Configuration type is not instantiable.') @classmethod - def load(cls, settings, init=True): + def load(cls, sections, init=True): """Store settings from a dictionary.""" - for k, v in settings.items(): + for k, v in sections.items(): if v is None: continue if k in cls._paths: @@ -253,6 +248,15 @@ def get(cls): return out +class settings(_Config): + """Settings of this config module.""" + + file_path: Path = None + """Path to this configuration file.""" + + _paths = ('file_path', ) + + class environment(_Config): """ Read-only options regarding the platform and environment. @@ -545,8 +549,11 @@ class workflow(_Config): """Turn on FFT based spike detector (slow).""" inputs = None """List of files to be processed with MRIQC.""" - min_len_dwi = 5 - """Minimum DWI length to be considered a "processable" dataset.""" + min_len_dwi = 7 + """ + Minimum DWI length to be considered a "processable" dataset + (default: 7, assuming one low-b and six gradients for diffusion tensor imaging). + """ species = 'human' """Subject species to choose most appropriate template""" template_id = 'MNI152NLin2009cAsym' @@ -561,7 +568,7 @@ class loggers: default = logging.getLogger() """The root logger.""" - cli = logging.getLogger('cli') + cli = logging.getLogger('mriqc') """Command-line interface logging.""" workflow = None """NiPype's workflow logger.""" @@ -618,11 +625,11 @@ def getLogger(cls, name): return retval -def from_dict(settings): +def from_dict(sections): """Read settings from a flat dictionary.""" - execution.load(settings) - workflow.load(settings) - nipype.load(settings, init=False) + execution.load(sections) + workflow.load(sections) + nipype.load(sections, init=False) def load(filename): @@ -630,27 +637,33 @@ def load(filename): from toml import loads filename = Path(filename) - settings = loads(filename.read_text()) - for sectionname, configs in settings.items(): + sections = loads(filename.read_text()) + for sectionname, configs in sections.items(): if sectionname != 'environment': section = getattr(sys.modules[__name__], sectionname) section.load(configs) + if settings.file_path is None: + settings.file_path = filename + + loggers.cli.debug(f'Loaded MRIQC config file: {settings.file_path}.') + def get(flat=False): """Get config as a dict.""" - settings = { + sections = { 'environment': environment.get(), 'execution': execution.get(), 'workflow': workflow.get(), 'nipype': nipype.get(), + 'settings': settings.get(), } if not flat: - return settings + return sections return { '.'.join((section, k)): v - for section, configs in settings.items() + for section, configs in sections.items() for k, v in configs.items() } @@ -662,11 +675,24 @@ def dumps(): return dumps(get()) -def to_filename(filename): +def to_filename(filename=None): """Write settings to file.""" - filename = Path(filename) - filename.parent.mkdir(exist_ok=True, parents=True) - filename.write_text(dumps()) + + if filename: + settings.file_path = Path(filename) + elif settings.file_path is None: + settings.file_path = Path( + mkstemp( + dir=execution.work_dir, + prefix='.mriqc.', + suffix='.toml' + )[1], + ) + + settings.file_path.parent.mkdir(exist_ok=True, parents=True) + settings.file_path.write_text(dumps()) + loggers.cli.debug(f'Saved MRIQC config file: {settings.file_path}.') + return settings.file_path def _process_initializer(config_file: Path): diff --git a/mriqc/engine/plugin.py b/mriqc/engine/plugin.py index 43c999cc..2de75f80 100644 --- a/mriqc/engine/plugin.py +++ b/mriqc/engine/plugin.py @@ -447,7 +447,7 @@ def __init__(self, pool=None, plugin_args=None): self.pool = pool or ProcessPoolExecutor( max_workers=self.processors, initializer=config._process_initializer, - initargs=(config.file_path,), + initargs=(config.settings.file_path,), mp_context=mp_context, ) diff --git a/mriqc/workflows/diffusion/base.py b/mriqc/workflows/diffusion/base.py index aedb4be0..3699463c 100644 --- a/mriqc/workflows/diffusion/base.py +++ b/mriqc/workflows/diffusion/base.py @@ -108,6 +108,10 @@ def dmri_qc_workflow(name='dwiMRIQC'): 'insufficient in number to execute the workflow.' ) + if set(dataset) - set(full_data): + config.workflow.inputs['dwi'] = full_data + config.to_filename() + message = BUILDING_WORKFLOW.format( modality='diffusion', detail=(