def init(cls): """Set NiPype configurations.""" from nipype import config as ncfg # Configure resource_monitor if cls.resource_monitor: ncfg.update_config({ "monitoring": { "enabled": cls.resource_monitor, "sample_frequency": "0.5", "summary_append": True, } }) ncfg.enable_resource_monitor() # Nipype config (logs and execution) ncfg.update_config({ "execution": { "crashdump_dir": str(execution.log_dir), "crashfile_format": cls.crashfile_format, "get_linked_libs": cls.get_linked_libs, "stop_on_first_crash": cls.stop_on_first_crash, "check_version": False, # disable future telemetry } }) if cls.omp_nthreads is None: cls.omp_nthreads = min( cls.nprocs - 1 if cls.nprocs > 1 else os.cpu_count(), 8)
def init(cls): """Set NiPype configurations.""" from nipype import config as ncfg # Configure resource_monitor if cls.resource_monitor: ncfg.update_config({ 'monitoring': { 'enabled': cls.resource_monitor, 'sample_frequency': '0.5', 'summary_append': True, } }) ncfg.enable_resource_monitor() # Nipype config (logs and execution) ncfg.update_config({ 'execution': { 'crashdump_dir': str(execution.log_dir), 'crashfile_format': cls.crashfile_format, 'get_linked_libs': cls.get_linked_libs, 'stop_on_first_crash': cls.stop_on_first_crash, } }) if cls.omp_nthreads is None: cls.omp_nthreads = min( cls.nprocs - 1 if cls.nprocs > 1 else os.cpu_count(), 8)
def init(cls): """Set NiPype configurations.""" from nipype import config as ncfg # Configure resource_monitor if cls.resource_monitor: ncfg.update_config( { "monitoring": { "enabled": cls.resource_monitor, "sample_frequency": "0.5", "summary_append": True, } } ) ncfg.enable_resource_monitor() # Nipype config (logs and execution) ncfg.update_config( { "execution": { "crashdump_dir": str(execution.log_dir), "crashfile_format": cls.crashfile_format, "get_linked_libs": cls.get_linked_libs, "stop_on_first_crash": cls.stop_on_first_crash, "parameterize_dirs": cls.parameterize_dirs, } } )
def build_workflow(opts, retval): """ Create the Nipype Workflow that supports the whole execution graph, given the inputs. All the checks and the construction of the workflow are done inside this function that has pickleable inputs and output dictionary (``retval``) to allow isolation using a ``multiprocessing.Process`` that allows fmriprep to enforce a hard-limited memory-scope. """ from nipype import logging, config as ncfg from ..info import __version__ from ..workflows.base import init_fmriprep_wf from ..utils.bids import collect_participants from ..viz.reports import generate_reports logger = logging.getLogger('nipype.workflow') INIT_MSG = """ Running fMRIPREP version {version}: * BIDS dataset path: {bids_dir}. * Participant list: {subject_list}. * Run identifier: {uuid}. """.format output_spaces = opts.output_space or [] # Validity of some inputs # ERROR check if use_aroma was specified, but the correct template was not if opts.use_aroma and (opts.template != 'MNI152NLin2009cAsym' or 'template' not in output_spaces): output_spaces.append('template') logger.warning( 'Option "--use-aroma" requires functional images to be resampled to MNI space. ' 'The argument "template" has been automatically added to the list of output ' 'spaces (option "--output-space").') # Check output_space if 'template' not in output_spaces and (opts.use_syn_sdc or opts.force_syn): msg = [ 'SyN SDC correction requires T1 to MNI registration, but ' '"template" is not specified in "--output-space" arguments.', 'Option --use-syn will be cowardly dismissed.' ] if opts.force_syn: output_spaces.append('template') msg[1] = ( ' Since --force-syn has been requested, "template" has been added to' ' the "--output-space" list.') logger.warning(' '.join(msg)) # Set up some instrumental utilities run_uuid = '%s_%s' % (strftime('%Y%m%d-%H%M%S'), uuid.uuid4()) # First check that bids_dir looks like a BIDS folder bids_dir = op.abspath(opts.bids_dir) subject_list = collect_participants( bids_dir, participant_label=opts.participant_label) # Setting up MultiProc nthreads = opts.nthreads if nthreads < 1: nthreads = cpu_count() plugin_settings = { 'plugin': 'MultiProc', 'plugin_args': { 'n_procs': nthreads, 'raise_insufficient': False, 'maxtasksperchild': 1, } } if opts.mem_mb: plugin_settings['plugin_args']['memory_gb'] = opts.mem_mb / 1024 # Overload plugin_settings if --use-plugin if opts.use_plugin is not None: from yaml import load as loadyml with open(opts.use_plugin) as f: plugin_settings = loadyml(f) omp_nthreads = opts.omp_nthreads if omp_nthreads == 0: omp_nthreads = min(nthreads - 1 if nthreads > 1 else cpu_count(), 8) if 1 < nthreads < omp_nthreads: logger.warning( 'Per-process threads (--omp-nthreads=%d) exceed total ' 'threads (--nthreads/--n_cpus=%d)', omp_nthreads, nthreads) # Set up directories output_dir = op.abspath(opts.output_dir) log_dir = op.join(output_dir, 'fmriprep', 'logs') work_dir = op.abspath(opts.work_dir or 'work') # Set work/ as default # Check and create output and working directories os.makedirs(output_dir, exist_ok=True) os.makedirs(log_dir, exist_ok=True) os.makedirs(work_dir, exist_ok=True) # Nipype config (logs and execution) ncfg.update_config({ 'logging': { 'log_directory': log_dir, 'log_to_file': True }, 'execution': { 'crashdump_dir': log_dir, 'crashfile_format': 'txt', 'get_linked_libs': False, 'stop_on_first_crash': opts.stop_on_first_crash or opts.work_dir is None, }, 'monitoring': { 'enabled': opts.resource_monitor, 'sample_frequency': '0.5', 'summary_append': True, } }) if opts.resource_monitor: ncfg.enable_resource_monitor() retval['return_code'] = 0 retval['plugin_settings'] = plugin_settings retval['output_dir'] = output_dir retval['work_dir'] = work_dir retval['subject_list'] = subject_list retval['run_uuid'] = run_uuid retval['workflow'] = None # Called with reports only if opts.reports_only: logger.log(25, 'Running --reports-only on participants %s', ', '.join(subject_list)) if opts.run_uuid is not None: run_uuid = opts.run_uuid retval['return_code'] = generate_reports(subject_list, output_dir, work_dir, run_uuid) return retval # Build main workflow logger.log( 25, INIT_MSG(version=__version__, bids_dir=bids_dir, subject_list=subject_list, uuid=run_uuid)) template_out_grid = opts.template_resampling_grid if opts.output_grid_reference is not None: logger.warning( 'Option --output-grid-reference is deprecated, please use ' '--template-resampling-grid') template_out_grid = template_out_grid or opts.output_grid_reference retval['workflow'] = init_fmriprep_wf( subject_list=subject_list, task_id=opts.task_id, run_uuid=run_uuid, ignore=opts.ignore, debug=opts.debug, low_mem=opts.low_mem, anat_only=opts.anat_only, longitudinal=opts.longitudinal, t2s_coreg=opts.t2s_coreg, omp_nthreads=omp_nthreads, skull_strip_template=opts.skull_strip_template, work_dir=work_dir, output_dir=output_dir, bids_dir=bids_dir, freesurfer=opts.run_reconall, skull_kernel=opts.skull_kernel, output_spaces=output_spaces, template=opts.template, medial_surface_nan=opts.medial_surface_nan, cifti_output=opts.cifti_output, template_out_grid=template_out_grid, hires=opts.hires, use_bbr=opts.use_bbr, bold2t1w_dof=opts.bold2t1w_dof, fmap_bspline=opts.fmap_bspline, fmap_demean=opts.fmap_no_demean, use_syn=opts.use_syn_sdc, force_syn=opts.force_syn, use_aroma=opts.use_aroma, aroma_melodic_dim=opts.aroma_melodic_dimensionality, ignore_aroma_err=opts.ignore_aroma_denoising_errors, ) retval['return_code'] = 0 return retval
def build_collect_workflow(args, retval): import os import glob import warnings warnings.filterwarnings("ignore") import ast import pkg_resources from pathlib import Path import yaml import uuid from time import strftime import shutil try: import pynets print(f"\n\nPyNets Version:\n{pynets.__version__}\n\n") except ImportError: print("PyNets not installed! Ensure that you are using the correct" " python version.") # Set Arguments to global variables resources = args.pm if resources == "auto": from multiprocessing import cpu_count import psutil nthreads = cpu_count() - 1 procmem = [ int(nthreads), int(list(psutil.virtual_memory())[4] / 1000000000) ] else: procmem = list(eval(str(resources))) plugin_type = args.plug if isinstance(plugin_type, list): plugin_type = plugin_type[0] verbose = args.v working_path = args.basedir work_dir = args.work modality = args.modality drop_cols = args.dc if isinstance(modality, list): modality = modality[0] if os.path.isdir(work_dir): shutil.rmtree(work_dir) os.makedirs(f"{str(Path(working_path))}/{modality}_group_topology_auc", exist_ok=True) wf = collect_all(working_path, modality, drop_cols) with open(pkg_resources.resource_filename("pynets", "runconfig.yaml"), "r") as stream: try: hardcoded_params = yaml.load(stream) runtime_dict = {} execution_dict = {} for i in range(len(hardcoded_params["resource_dict"])): runtime_dict[list(hardcoded_params["resource_dict"][i].keys( ))[0]] = ast.literal_eval( list(hardcoded_params["resource_dict"][i].values())[0][0]) for i in range(len(hardcoded_params["execution_dict"])): execution_dict[list( hardcoded_params["execution_dict"][i].keys())[0]] = list( hardcoded_params["execution_dict"][i].values())[0][0] except FileNotFoundError: print("Failed to parse runconfig.yaml") run_uuid = f"{strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4()}" os.makedirs(f"{work_dir}/pynets_out_collection{run_uuid}", exist_ok=True) wf.base_dir = f"{work_dir}/pynets_out_collection{run_uuid}" if verbose is True: from nipype import config, logging cfg_v = dict( logging={ "workflow_level": "DEBUG", "utils_level": "DEBUG", "interface_level": "DEBUG", "filemanip_level": "DEBUG", "log_directory": str(wf.base_dir), "log_to_file": True, }, monitoring={ "enabled": True, "sample_frequency": "0.1", "summary_append": True, "summary_file": str(wf.base_dir), }, ) logging.update_logging(config) config.update_config(cfg_v) config.enable_debug_mode() config.enable_resource_monitor() import logging callback_log_path = f"{wf.base_dir}{'/run_stats.log'}" logger = logging.getLogger("callback") logger.setLevel(logging.DEBUG) handler = logging.FileHandler(callback_log_path) logger.addHandler(handler) execution_dict["crashdump_dir"] = str(wf.base_dir) execution_dict["plugin"] = str(plugin_type) cfg = dict(execution=execution_dict) for key in cfg.keys(): for setting, value in cfg[key].items(): wf.config[key][setting] = value try: wf.write_graph(graph2use="colored", format="png") except BaseException: pass if verbose is True: from nipype.utils.profiler import log_nodes_cb plugin_args = { "n_procs": int(procmem[0]), "memory_gb": int(procmem[1]), "status_callback": log_nodes_cb, "scheduler": "mem_thread", } else: plugin_args = { "n_procs": int(procmem[0]), "memory_gb": int(procmem[1]), "scheduler": "mem_thread", } print("%s%s%s" % ("\nRunning with ", str(plugin_args), "\n")) wf.run(plugin=plugin_type, plugin_args=plugin_args) if verbose is True: from nipype.utils.draw_gantt_chart import generate_gantt_chart print("Plotting resource profile from run...") generate_gantt_chart(callback_log_path, cores=int(procmem[0])) handler.close() logger.removeHandler(handler) return
def build_recon_workflow(opts, retval): """ Create the Nipype Workflow that supports the whole execution graph, given the inputs. All the checks and the construction of the workflow are done inside this function that has pickleable inputs and output dictionary (``retval``) to allow isolation using a ``multiprocessing.Process`` that allows qsiprep to enforce a hard-limited memory-scope. """ from subprocess import check_call, CalledProcessError, TimeoutExpired from pkg_resources import resource_filename as pkgrf from nipype import logging, config as ncfg from ..__about__ import __version__ from ..workflows.recon import init_qsirecon_wf from ..utils.bids import collect_participants logger = logging.getLogger('nipype.workflow') INIT_MSG = """ Running qsirecon version {version}: * BIDS dataset path: {bids_dir}. * Participant list: {subject_list}. * Run identifier: {uuid}. """.format # Set up some instrumental utilities run_uuid = '%s_%s' % (strftime('%Y%m%d-%H%M%S'), uuid.uuid4()) # First check that bids_dir looks like a BIDS folder bids_dir = os.path.abspath(opts.bids_dir) subject_list = collect_participants( bids_dir, participant_label=opts.participant_label, bids_validate=False) # Load base plugin_settings from file if --use-plugin if opts.use_plugin is not None: from yaml import load as loadyml with open(opts.use_plugin) as f: plugin_settings = loadyml(f) plugin_settings.setdefault('plugin_args', {}) else: # Defaults plugin_settings = { 'plugin': 'MultiProc', 'plugin_args': { 'raise_insufficient': False, 'maxtasksperchild': 1, } } # Resource management options # Note that we're making strong assumptions about valid plugin args # This may need to be revisited if people try to use batch plugins nthreads = plugin_settings['plugin_args'].get('n_procs') # Permit overriding plugin config with specific CLI options if nthreads is None or opts.nthreads is not None: nthreads = opts.nthreads if nthreads is None or nthreads < 1: nthreads = cpu_count() plugin_settings['plugin_args']['n_procs'] = nthreads if opts.mem_mb: plugin_settings['plugin_args']['memory_gb'] = opts.mem_mb / 1024 omp_nthreads = opts.omp_nthreads if omp_nthreads == 0: omp_nthreads = min(nthreads - 1 if nthreads > 1 else cpu_count(), 8) if 1 < nthreads < omp_nthreads: logger.warning( 'Per-process threads (--omp-nthreads=%d) exceed total ' 'threads (--nthreads/--n_cpus=%d)', omp_nthreads, nthreads) # Set up directories output_dir = op.abspath(opts.output_dir) log_dir = op.join(output_dir, 'qsirecon', 'logs') work_dir = op.abspath(opts.work_dir or 'work') # Set work/ as default # Check and create output and working directories os.makedirs(output_dir, exist_ok=True) os.makedirs(log_dir, exist_ok=True) os.makedirs(work_dir, exist_ok=True) # Nipype config (logs and execution) ncfg.update_config({ 'logging': { 'log_directory': log_dir, 'log_to_file': True }, 'execution': { 'crashdump_dir': log_dir, 'crashfile_format': 'txt', 'get_linked_libs': False, 'stop_on_first_crash': opts.stop_on_first_crash or opts.work_dir is None, }, 'monitoring': { 'enabled': opts.resource_monitor, 'sample_frequency': '0.5', 'summary_append': True, } }) if opts.resource_monitor: ncfg.enable_resource_monitor() retval['return_code'] = 0 retval['plugin_settings'] = plugin_settings retval['bids_dir'] = bids_dir retval['output_dir'] = output_dir retval['work_dir'] = work_dir retval['subject_list'] = subject_list retval['run_uuid'] = run_uuid retval['workflow'] = None # Build main workflow logger.log( 25, INIT_MSG( version=__version__, bids_dir=bids_dir, subject_list=subject_list, uuid=run_uuid)) retval['workflow'] = init_qsirecon_wf( subject_list=subject_list, run_uuid=run_uuid, work_dir=work_dir, output_dir=output_dir, recon_input=opts.recon_input, recon_spec=opts.recon_spec, low_mem=opts.low_mem, omp_nthreads=omp_nthreads, bids_dir=bids_dir, sloppy=opts.sloppy ) retval['return_code'] = 0 logs_path = Path(output_dir) / 'qsirecon' / 'logs' boilerplate = retval['workflow'].visit_desc() (logs_path / 'CITATION.md').write_text(boilerplate) logger.log( 25, 'Works derived from this qsiprep execution should ' 'include the following boilerplate:\n\n%s', boilerplate) # Generate HTML file resolving citations cmd = [ 'pandoc', '-s', '--bibliography', pkgrf('qsiprep', 'data/boilerplate.bib'), '--filter', 'pandoc-citeproc', str(logs_path / 'CITATION.md'), '-o', str(logs_path / 'CITATION.html') ] try: check_call(cmd, timeout=10) except (FileNotFoundError, CalledProcessError, TimeoutExpired): logger.warning('Could not generate CITATION.html file:\n%s', ' '.join(cmd)) # Generate LaTex file resolving citations cmd = [ 'pandoc', '-s', '--bibliography', pkgrf('qsiprep', 'data/boilerplate.bib'), '--natbib', str(logs_path / 'CITATION.md'), '-o', str(logs_path / 'CITATION.tex') ] try: check_call(cmd, timeout=10) except (FileNotFoundError, CalledProcessError, TimeoutExpired): logger.warning('Could not generate CITATION.tex file:\n%s', ' '.join(cmd)) return retval
def build_workflow(opts, retval): """ Create the Nipype Workflow that supports the whole execution graph, given the inputs. All the checks and the construction of the workflow are done inside this function that has pickleable inputs and output dictionary (``retval``) to allow isolation using a ``multiprocessing.Process`` that allows smriprep to enforce a hard-limited memory-scope. """ from shutil import copyfile from os import cpu_count import uuid from time import strftime from subprocess import check_call, CalledProcessError, TimeoutExpired from pkg_resources import resource_filename as pkgrf import json from bids import BIDSLayout from nipype import logging, config as ncfg from niworkflows.utils.bids import collect_participants from ..__about__ import __version__ from ..workflows.base import init_smriprep_wf logger = logging.getLogger("nipype.workflow") INIT_MSG = """ Running sMRIPrep version {version}: * BIDS dataset path: {bids_dir}. * Participant list: {subject_list}. * Run identifier: {uuid}. {spaces} """.format # Set up some instrumental utilities run_uuid = "%s_%s" % (strftime("%Y%m%d-%H%M%S"), uuid.uuid4()) # First check that bids_dir looks like a BIDS folder bids_dir = opts.bids_dir.resolve() layout = BIDSLayout(str(bids_dir), validate=False) subject_list = collect_participants( layout, participant_label=opts.participant_label) bids_filters = (json.loads(opts.bids_filter_file.read_text()) if opts.bids_filter_file else None) # Load base plugin_settings from file if --use-plugin if opts.use_plugin is not None: from yaml import load as loadyml with open(opts.use_plugin) as f: plugin_settings = loadyml(f) plugin_settings.setdefault("plugin_args", {}) else: # Defaults plugin_settings = { "plugin": "MultiProc", "plugin_args": { "raise_insufficient": False, "maxtasksperchild": 1, }, } # Resource management options # Note that we're making strong assumptions about valid plugin args # This may need to be revisited if people try to use batch plugins nprocs = plugin_settings["plugin_args"].get("n_procs") # Permit overriding plugin config with specific CLI options if nprocs is None or opts.nprocs is not None: nprocs = opts.nprocs if nprocs is None or nprocs < 1: nprocs = cpu_count() plugin_settings["plugin_args"]["n_procs"] = nprocs if opts.mem_gb: plugin_settings["plugin_args"]["memory_gb"] = opts.mem_gb omp_nthreads = opts.omp_nthreads if omp_nthreads == 0: omp_nthreads = min(nprocs - 1 if nprocs > 1 else cpu_count(), 8) if 1 < nprocs < omp_nthreads: logger.warning( "Per-process threads (--omp-nthreads=%d) exceed total " "available CPUs (--nprocs/--ncpus=%d)", omp_nthreads, nprocs, ) # Set up directories output_dir = opts.output_dir.resolve() log_dir = output_dir / "smriprep" / "logs" work_dir = opts.work_dir.resolve() # Check and create output and working directories log_dir.mkdir(parents=True, exist_ok=True) work_dir.mkdir(parents=True, exist_ok=True) # Nipype config (logs and execution) ncfg.update_config({ "logging": { "log_directory": str(log_dir), "log_to_file": True }, "execution": { "crashdump_dir": str(log_dir), "crashfile_format": "txt", "get_linked_libs": False, "stop_on_first_crash": opts.stop_on_first_crash, }, "monitoring": { "enabled": opts.resource_monitor, "sample_frequency": "0.5", "summary_append": True, }, }) if opts.resource_monitor: ncfg.enable_resource_monitor() retval["return_code"] = 0 retval["plugin_settings"] = plugin_settings retval["bids_dir"] = str(bids_dir) retval["output_dir"] = str(output_dir) retval["work_dir"] = str(work_dir) retval["subject_list"] = subject_list retval["run_uuid"] = run_uuid retval["workflow"] = None # Called with reports only if opts.reports_only: from niworkflows.reports import generate_reports logger.log(25, "Running --reports-only on participants %s", ", ".join(subject_list)) if opts.run_uuid is not None: run_uuid = opts.run_uuid retval["return_code"] = generate_reports(subject_list, str(output_dir), run_uuid, packagename="smriprep") return retval output_spaces = opts.output_spaces if not output_spaces.is_cached(): output_spaces.checkpoint() logger.log( 25, INIT_MSG( version=__version__, bids_dir=bids_dir, subject_list=subject_list, uuid=run_uuid, spaces=output_spaces, ), ) # Build main workflow retval["workflow"] = init_smriprep_wf( debug=opts.sloppy, fast_track=opts.fast_track, freesurfer=opts.run_reconall, fs_subjects_dir=opts.fs_subjects_dir, hires=opts.hires, layout=layout, longitudinal=opts.longitudinal, low_mem=opts.low_mem, omp_nthreads=omp_nthreads, output_dir=str(output_dir), run_uuid=run_uuid, skull_strip_fixed_seed=opts.skull_strip_fixed_seed, skull_strip_mode=opts.skull_strip_mode, skull_strip_template=opts.skull_strip_template[0], spaces=output_spaces, subject_list=subject_list, work_dir=str(work_dir), bids_filters=bids_filters, ) retval["return_code"] = 0 boilerplate = retval["workflow"].visit_desc() (log_dir / "CITATION.md").write_text(boilerplate) logger.log( 25, "Works derived from this sMRIPrep execution should " "include the following boilerplate:\n\n%s", boilerplate, ) # Generate HTML file resolving citations cmd = [ "pandoc", "-s", "--bibliography", pkgrf("smriprep", "data/boilerplate.bib"), "--citeproc", "--metadata", 'pagetitle="sMRIPrep citation boilerplate"', str(log_dir / "CITATION.md"), "-o", str(log_dir / "CITATION.html"), ] try: check_call(cmd, timeout=10) except (FileNotFoundError, CalledProcessError, TimeoutExpired): logger.warning("Could not generate CITATION.html file:\n%s", " ".join(cmd)) # Generate LaTex file resolving citations cmd = [ "pandoc", "-s", "--bibliography", pkgrf("smriprep", "data/boilerplate.bib"), "--natbib", str(log_dir / "CITATION.md"), "-o", str(log_dir / "CITATION.tex"), ] try: check_call(cmd, timeout=10) except (FileNotFoundError, CalledProcessError, TimeoutExpired): logger.warning("Could not generate CITATION.tex file:\n%s", " ".join(cmd)) else: copyfile(pkgrf("smriprep", "data/boilerplate.bib"), str(log_dir / "CITATION.bib")) return retval
""" Module to unit test the resource_monitor in nipype """ from __future__ import print_function, division, unicode_literals, absolute_import import os import pytest # Import packages from nipype import config from nipype.utils.profiler import _use_resources from nipype.interfaces.base import traits, CommandLine, CommandLineInputSpec from nipype.interfaces import utility as niu # Try to enable the resource monitor config.enable_resource_monitor() run_profile = config.resource_monitor class UseResourcesInputSpec(CommandLineInputSpec): mem_gb = traits.Float(desc='Number of GB of RAM to use', argstr='-g %f', mandatory=True) n_procs = traits.Int(desc='Number of threads to use', argstr='-p %d', mandatory=True) class UseResources(CommandLine): """ use_resources cmd interface """ from nipype import __path__
def build_workflow(opts, retval): """ Create the Nipype Workflow that supports the whole execution graph, given the inputs. All the checks and the construction of the workflow are done inside this function that has pickleable inputs and output dictionary (``retval``) to allow isolation using a ``multiprocessing.Process`` that allows smriprep to enforce a hard-limited memory-scope. """ from shutil import copyfile from os import cpu_count import uuid from time import strftime from subprocess import check_call, CalledProcessError, TimeoutExpired from pkg_resources import resource_filename as pkgrf import json from bids import BIDSLayout from nipype import logging, config as ncfg from niworkflows.utils.bids import collect_participants from ..__about__ import __version__ from ..workflows.base import init_smriprep_wf logger = logging.getLogger('nipype.workflow') INIT_MSG = """ Running sMRIPrep version {version}: * BIDS dataset path: {bids_dir}. * Participant list: {subject_list}. * Run identifier: {uuid}. {spaces} """.format # Set up some instrumental utilities run_uuid = '%s_%s' % (strftime('%Y%m%d-%H%M%S'), uuid.uuid4()) # First check that bids_dir looks like a BIDS folder bids_dir = opts.bids_dir.resolve() layout = BIDSLayout(str(bids_dir), validate=False) subject_list = collect_participants( layout, participant_label=opts.participant_label) bids_filters = json.loads( opts.bids_filter_file.read_text()) if opts.bids_filter_file else None # Load base plugin_settings from file if --use-plugin if opts.use_plugin is not None: from yaml import load as loadyml with open(opts.use_plugin) as f: plugin_settings = loadyml(f) plugin_settings.setdefault('plugin_args', {}) else: # Defaults plugin_settings = { 'plugin': 'MultiProc', 'plugin_args': { 'raise_insufficient': False, 'maxtasksperchild': 1, } } # Resource management options # Note that we're making strong assumptions about valid plugin args # This may need to be revisited if people try to use batch plugins nprocs = plugin_settings['plugin_args'].get('n_procs') # Permit overriding plugin config with specific CLI options if nprocs is None or opts.nprocs is not None: nprocs = opts.nprocs if nprocs is None or nprocs < 1: nprocs = cpu_count() plugin_settings['plugin_args']['n_procs'] = nprocs if opts.mem_gb: plugin_settings['plugin_args']['memory_gb'] = opts.mem_gb omp_nthreads = opts.omp_nthreads if omp_nthreads == 0: omp_nthreads = min(nprocs - 1 if nprocs > 1 else cpu_count(), 8) if 1 < nprocs < omp_nthreads: logger.warning( 'Per-process threads (--omp-nthreads=%d) exceed total ' 'available CPUs (--nprocs/--ncpus=%d)', omp_nthreads, nprocs) # Set up directories output_dir = opts.output_dir.resolve() log_dir = output_dir / 'smriprep' / 'logs' work_dir = opts.work_dir.resolve() # Check and create output and working directories log_dir.mkdir(parents=True, exist_ok=True) work_dir.mkdir(parents=True, exist_ok=True) # Nipype config (logs and execution) ncfg.update_config({ 'logging': { 'log_directory': str(log_dir), 'log_to_file': True }, 'execution': { 'crashdump_dir': str(log_dir), 'crashfile_format': 'txt', 'get_linked_libs': False, 'stop_on_first_crash': opts.stop_on_first_crash, }, 'monitoring': { 'enabled': opts.resource_monitor, 'sample_frequency': '0.5', 'summary_append': True, } }) if opts.resource_monitor: ncfg.enable_resource_monitor() retval['return_code'] = 0 retval['plugin_settings'] = plugin_settings retval['bids_dir'] = str(bids_dir) retval['output_dir'] = str(output_dir) retval['work_dir'] = str(work_dir) retval['subject_list'] = subject_list retval['run_uuid'] = run_uuid retval['workflow'] = None # Called with reports only if opts.reports_only: from niworkflows.reports import generate_reports logger.log(25, 'Running --reports-only on participants %s', ', '.join(subject_list)) if opts.run_uuid is not None: run_uuid = opts.run_uuid retval['return_code'] = generate_reports(subject_list, str(output_dir), run_uuid, packagename="smriprep") return retval logger.log( 25, INIT_MSG(version=__version__, bids_dir=bids_dir, subject_list=subject_list, uuid=run_uuid, spaces=opts.output_spaces)) # Build main workflow retval['workflow'] = init_smriprep_wf( debug=opts.sloppy, fast_track=opts.fast_track, freesurfer=opts.run_reconall, fs_subjects_dir=opts.fs_subjects_dir, hires=opts.hires, layout=layout, longitudinal=opts.longitudinal, low_mem=opts.low_mem, omp_nthreads=omp_nthreads, output_dir=str(output_dir), run_uuid=run_uuid, skull_strip_fixed_seed=opts.skull_strip_fixed_seed, skull_strip_mode=opts.skull_strip_mode, skull_strip_template=opts.skull_strip_template[0], spaces=opts.output_spaces, subject_list=subject_list, work_dir=str(work_dir), bids_filters=bids_filters, ) retval['return_code'] = 0 boilerplate = retval['workflow'].visit_desc() (log_dir / 'CITATION.md').write_text(boilerplate) logger.log( 25, 'Works derived from this sMRIPrep execution should ' 'include the following boilerplate:\n\n%s', boilerplate) # Generate HTML file resolving citations cmd = [ 'pandoc', '-s', '--bibliography', pkgrf('smriprep', 'data/boilerplate.bib'), '--filter', 'pandoc-citeproc', '--metadata', 'pagetitle="sMRIPrep citation boilerplate"', str(log_dir / 'CITATION.md'), '-o', str(log_dir / 'CITATION.html') ] try: check_call(cmd, timeout=10) except (FileNotFoundError, CalledProcessError, TimeoutExpired): logger.warning('Could not generate CITATION.html file:\n%s', ' '.join(cmd)) # Generate LaTex file resolving citations cmd = [ 'pandoc', '-s', '--bibliography', pkgrf('smriprep', 'data/boilerplate.bib'), '--natbib', str(log_dir / 'CITATION.md'), '-o', str(log_dir / 'CITATION.tex') ] try: check_call(cmd, timeout=10) except (FileNotFoundError, CalledProcessError, TimeoutExpired): logger.warning('Could not generate CITATION.tex file:\n%s', ' '.join(cmd)) else: copyfile(pkgrf('smriprep', 'data/boilerplate.bib'), str(log_dir / 'CITATION.bib')) return retval
def run(opts, should_run): # print info from .. import __version__ logger.info(f"Halfpipe version {__version__}") if not opts.verbose: logger.log( 25, 'Option "--verbose" was not specified. Will not print detailed logs to the terminal. \n' 'Detailed logs information will only be available in the "log.txt" file in the working directory. ' ) logger.debug(f"debug={opts.debug}") workdir = opts.workdir if workdir is not None: workdir = Path(workdir) workdir.mkdir(exist_ok=True, parents=True) logger.debug(f'should_run["spec-ui"]={should_run["spec-ui"]}') if should_run["spec-ui"]: logger.info("Stage: spec-ui") from ..ui import init_spec_ui from calamities.config import config as calamities_config calamities_config.fs_root = opts.fs_root workdir = init_spec_ui(workdir=workdir, debug=opts.debug) assert workdir is not None, "Missing working directory" assert Path(workdir).is_dir(), "Working directory does not exist" if opts.watchdog is True: from ..watchdog import init_watchdog init_watchdog() execgraphs = None logger.debug(f'should_run["workflow"]={should_run["workflow"]}') if should_run["workflow"]: logger.info("Stage: workflow") from fmriprep import config if opts.nipype_omp_nthreads is not None and opts.nipype_omp_nthreads > 0: config.nipype.omp_nthreads = opts.nipype_omp_nthreads omp_nthreads_origin = "command line arguments" elif opts.use_cluster: config.nipype.omp_nthreads = 2 omp_nthreads_origin = "from --use-cluster" else: omp_nthreads = opts.nipype_n_procs // 4 if omp_nthreads < 1: omp_nthreads = 1 if omp_nthreads > 8: omp_nthreads = 8 config.nipype.omp_nthreads = omp_nthreads omp_nthreads_origin = "inferred" logger.info( f"config.nipype.omp_nthreads={config.nipype.omp_nthreads} ({omp_nthreads_origin})" ) from ..workflow import init_workflow, init_execgraph workflow = init_workflow(workdir) execgraphs = init_execgraph(workdir, workflow, n_chunks=opts.n_chunks, subject_chunks=opts.subject_chunks or opts.use_cluster) if opts.use_cluster: from ..cluster import create_example_script create_example_script(workdir, execgraphs) logger.debug(f'should_run["run"]={should_run["run"]}') logger.debug(f"opts.use_cluster={opts.use_cluster}") if should_run["run"] and not opts.use_cluster: logger.info("Stage: run") if execgraphs is None: from ..io import loadpicklelzma assert (opts.execgraph_file is not None ), "Missing required --execgraph-file input for step run" execgraphs = loadpicklelzma(opts.execgraph_file) if not isinstance(execgraphs, list): execgraphs = [execgraphs] logger.info( f'Using execgraphs defined in file "{opts.execgraph_file}"') else: logger.info("Using execgraphs from previous step") if opts.nipype_resource_monitor is True: from nipype import config as nipypeconfig nipypeconfig.enable_resource_monitor() import nipype.pipeline.plugins as nip import halfpipe.plugins as ppp plugin_args = { "workdir": workdir, "watchdog": opts.watchdog, "stop_on_first_crash": opts.debug, "raise_insufficient": False, "keep": opts.keep, } if opts.nipype_n_procs is not None: plugin_args["n_procs"] = opts.nipype_n_procs if opts.nipype_memory_gb is not None: plugin_args["memory_gb"] = opts.nipype_memory_gb else: from ..memory import memorylimit memory_gb = memorylimit() if memory_gb is not None: plugin_args["memory_gb"] = memory_gb runnername = f"{opts.nipype_run_plugin}Plugin" if hasattr(ppp, runnername): logger.info( f'Using a patched version of nipype_run_plugin "{runnername}"') runnercls = getattr(ppp, runnername) elif hasattr(nip, runnername): logger.warning( f'Using unsupported nipype_run_plugin "{runnername}"') runnercls = getattr(nip, runnername) else: raise ValueError(f'Unknown nipype_run_plugin "{runnername}"') logger.debug(f'Using plugin arguments\n{pformat(plugin_args)}') execgraphstorun = [] if len(execgraphs) > 1: n_subjectlevel_chunks = len(execgraphs) - 1 if opts.only_model_chunk: logger.info("Will not run subject level chunks") logger.info("Will run model chunk") execgraphstorun.append(execgraphs[-1]) elif opts.only_chunk_index is not None: zerobasedchunkindex = opts.only_chunk_index - 1 assert zerobasedchunkindex < n_subjectlevel_chunks logger.info( f"Will run subject level chunk {opts.only_chunk_index} of {n_subjectlevel_chunks}" ) logger.info("Will not run model chunk") execgraphstorun.append(execgraphs[zerobasedchunkindex]) else: logger.info( f"Will run all {n_subjectlevel_chunks} subject level chunks" ) logger.info("Will run model chunk") execgraphstorun.extend(execgraphs) elif len(execgraphs) == 1: execgraphstorun.append(execgraphs[0]) else: raise ValueError("No execgraphs") n_execgraphstorun = len(execgraphstorun) for i, execgraph in enumerate(execgraphstorun): from ..utils import first if len(execgraphs) > 1: logger.info(f"Running chunk {i+1} of {n_execgraphstorun}") try: runner = runnercls(plugin_args=plugin_args) firstnode = first(execgraph.nodes()) if firstnode is not None: runner.run(execgraph, updatehash=False, config=firstnode.config) except Exception as e: if opts.debug: raise e else: logger.warning(f"Ignoring exception in chunk {i+1}", exc_info=True) if len(execgraphs) > 1: logger.info(f"Completed chunk {i+1} of {n_execgraphstorun}")
def build_collect_workflow(args, retval): import re import glob import warnings warnings.filterwarnings("ignore") import ast import pkg_resources from pathlib import Path import yaml try: import pynets print(f"\n\nPyNets Version:\n{pynets.__version__}\n\n") except ImportError: print( "PyNets not installed! Ensure that you are using the correct python version." ) # Set Arguments to global variables resources = args.pm if resources: procmem = list(eval(str(resources))) else: from multiprocessing import cpu_count nthreads = cpu_count() procmem = [int(nthreads), int(float(nthreads) * 2)] plugin_type = args.plug if isinstance(plugin_type, list): plugin_type = plugin_type[0] verbose = args.v working_path = args.basedir work_dir = args.work modality = args.modality os.makedirs(f"{str(Path(working_path).parent)}/all_visits_netmets_auc", exist_ok=True) wf = collect_all(working_path, modality) with open(pkg_resources.resource_filename("pynets", "runconfig.yaml"), "r") as stream: try: hardcoded_params = yaml.load(stream) runtime_dict = {} execution_dict = {} for i in range(len(hardcoded_params["resource_dict"])): runtime_dict[list(hardcoded_params["resource_dict"][i].keys( ))[0]] = ast.literal_eval( list(hardcoded_params["resource_dict"][i].values())[0][0]) for i in range(len(hardcoded_params["execution_dict"])): execution_dict[list( hardcoded_params["execution_dict"][i].keys())[0]] = list( hardcoded_params["execution_dict"][i].values())[0][0] except FileNotFoundError: print("Failed to parse runconfig.yaml") os.makedirs(f"{work_dir}{'/pynets_out_collection'}", exist_ok=True) wf.base_dir = f"{work_dir}{'/pynets_out_collection'}" if verbose is True: from nipype import config, logging cfg_v = dict( logging={ "workflow_level": "DEBUG", "utils_level": "DEBUG", "interface_level": "DEBUG", "filemanip_level": "DEBUG", "log_directory": str(wf.base_dir), "log_to_file": True, }, monitoring={ "enabled": True, "sample_frequency": "0.1", "summary_append": True, "summary_file": str(wf.base_dir), }, ) logging.update_logging(config) config.update_config(cfg_v) config.enable_debug_mode() config.enable_resource_monitor() import logging callback_log_path = f"{wf.base_dir}{'/run_stats.log'}" logger = logging.getLogger("callback") logger.setLevel(logging.DEBUG) handler = logging.FileHandler(callback_log_path) logger.addHandler(handler) execution_dict["crashdump_dir"] = str(wf.base_dir) execution_dict["plugin"] = str(plugin_type) cfg = dict(execution=execution_dict) for key in cfg.keys(): for setting, value in cfg[key].items(): wf.config[key][setting] = value try: wf.write_graph(graph2use="colored", format="png") except BaseException: pass if verbose is True: from nipype.utils.profiler import log_nodes_cb plugin_args = { "n_procs": int(procmem[0]), "memory_gb": int(procmem[1]), "status_callback": log_nodes_cb, "scheduler": "mem_thread", } else: plugin_args = { "n_procs": int(procmem[0]), "memory_gb": int(procmem[1]), "scheduler": "mem_thread", } print("%s%s%s" % ("\nRunning with ", str(plugin_args), "\n")) wf.run(plugin=plugin_type, plugin_args=plugin_args) if verbose is True: from nipype.utils.draw_gantt_chart import generate_gantt_chart print("Plotting resource profile from run...") generate_gantt_chart(callback_log_path, cores=int(procmem[0])) handler.close() logger.removeHandler(handler) files_ = glob.glob( f"{str(Path(working_path).parent)}{'/all_visits_netmets_auc/*clean.csv'}" ) print("Aggregating dataframes...") dfs = [] for file_ in files_: df = pd.read_csv(file_, chunksize=100000).read() try: df.drop(df.filter(regex="Unname"), axis=1, inplace=True) except BaseException: pass dfs.append(df) del df df_concat(dfs, working_path) return
def create_workflow(self, save_profiler_log=False): """Create the Niype workflow of the super-resolution pipeline. It is composed of a succession of Nodes and their corresponding parameters, where the output of node i goes to the input of node i+1. Parameters ---------- save_profiler_log : bool If `True`, save node runtime statistics to a JSON-style log file. """ sub_ses = self.subject if self.session is not None: sub_ses = ''.join([sub_ses, '_', self.session]) if self.session is None: wf_base_dir = os.path.join( self.output_dir, '-'.join(["nipype", __nipype_version__]), self.subject, "rec-{}".format(self.sr_id)) final_res_dir = os.path.join(self.output_dir, '-'.join(["pymialsrtk", __version__]), self.subject) else: wf_base_dir = os.path.join( self.output_dir, '-'.join(["nipype", __nipype_version__]), self.subject, self.session, "rec-{}".format(self.sr_id)) final_res_dir = os.path.join(self.output_dir, '-'.join(["pymialsrtk", __version__]), self.subject, self.session) if not os.path.exists(wf_base_dir): os.makedirs(wf_base_dir) print("Process directory: {}".format(wf_base_dir)) # Initialization (Not sure we can control the name of nipype log) if os.path.isfile(os.path.join(wf_base_dir, "pypeline.log")): os.unlink(os.path.join(wf_base_dir, "pypeline.log")) if save_profiler_log: log_filename = os.path.join(wf_base_dir, 'pypeline_stats.log') if os.path.isfile(log_filename): os.unlink(log_filename) self.wf = Workflow(name=self.pipeline_name, base_dir=wf_base_dir) config.update_config({ 'logging': { 'log_directory': os.path.join(wf_base_dir), 'log_to_file': True }, 'execution': { 'remove_unnecessary_outputs': False, 'stop_on_first_crash': True, 'stop_on_first_rerun': False, 'crashfile_format': "txt", 'use_relative_paths': True, 'write_provenance': False } }) if save_profiler_log: config.update_config({ 'monitoring': { 'enabled': save_profiler_log, 'sample_frequency': "1.0", 'summary_append': True } }) config.enable_resource_monitor() # Update logging with config logging.update_logging(config) # config.enable_provenance() if self.use_manual_masks: dg = Node(interface=DataGrabber(outfields=['T2ws', 'masks']), name='data_grabber') dg.inputs.base_directory = self.bids_dir dg.inputs.template = '*' dg.inputs.raise_on_empty = False dg.inputs.sort_filelist = True if self.session is not None: t2ws_template = os.path.join( self.subject, self.session, 'anat', '_'.join([sub_ses, '*run-*', '*T2w.nii.gz'])) if self.m_masks_desc is not None: masks_template = os.path.join( 'derivatives', self.m_masks_derivatives_dir, self.subject, self.session, 'anat', '_'.join([ sub_ses, '*_run-*', '_desc-' + self.m_masks_desc, '*mask.nii.gz' ])) else: masks_template = os.path.join( 'derivatives', self.m_masks_derivatives_dir, self.subject, self.session, 'anat', '_'.join([sub_ses, '*run-*', '*mask.nii.gz'])) else: t2ws_template = os.path.join(self.subject, 'anat', sub_ses + '*_run-*_T2w.nii.gz') if self.m_masks_desc is not None: masks_template = os.path.join( 'derivatives', self.m_masks_derivatives_dir, self.subject, self.session, 'anat', '_'.join([ sub_ses, '*_run-*', '_desc-' + self.m_masks_desc, '*mask.nii.gz' ])) else: masks_template = os.path.join( 'derivatives', self.m_masks_derivatives_dir, self.subject, 'anat', sub_ses + '*_run-*_*mask.nii.gz') dg.inputs.field_template = dict(T2ws=t2ws_template, masks=masks_template) brainMask = MapNode( interface=IdentityInterface(fields=['out_file']), name='brain_masks_bypass', iterfield=['out_file']) else: dg = Node(interface=DataGrabber(outfields=['T2ws']), name='data_grabber') dg.inputs.base_directory = self.bids_dir dg.inputs.template = '*' dg.inputs.raise_on_empty = False dg.inputs.sort_filelist = True dg.inputs.field_template = dict( T2ws=os.path.join(self.subject, 'anat', sub_ses + '*_run-*_T2w.nii.gz')) if self.session is not None: dg.inputs.field_template = dict(T2ws=os.path.join( self.subject, self.session, 'anat', '_'.join( [sub_ses, '*run-*', '*T2w.nii.gz']))) if self.m_stacks is not None: t2ws_filter_prior_masks = Node( interface=preprocess.FilteringByRunid(), name='t2ws_filter_prior_masks') t2ws_filter_prior_masks.inputs.stacks_id = self.m_stacks brainMask = MapNode(interface=preprocess.BrainExtraction(), name='brainExtraction', iterfield=['in_file']) brainMask.inputs.bids_dir = self.bids_dir brainMask.inputs.in_ckpt_loc = pkg_resources.resource_filename( "pymialsrtk", os.path.join("data", "Network_checkpoints", "Network_checkpoints_localization", "Unet.ckpt-88000.index")).split('.index')[0] brainMask.inputs.threshold_loc = 0.49 brainMask.inputs.in_ckpt_seg = pkg_resources.resource_filename( "pymialsrtk", os.path.join("data", "Network_checkpoints", "Network_checkpoints_segmentation", "Unet.ckpt-20000.index")).split('.index')[0] brainMask.inputs.threshold_seg = 0.5 t2ws_filtered = Node(interface=preprocess.FilteringByRunid(), name='t2ws_filtered') masks_filtered = Node(interface=preprocess.FilteringByRunid(), name='masks_filtered') if not self.m_skip_stacks_ordering: stacksOrdering = Node(interface=preprocess.StacksOrdering(), name='stackOrdering') else: stacksOrdering = Node( interface=IdentityInterface(fields=['stacks_order']), name='stackOrdering') stacksOrdering.inputs.stacks_order = self.m_stacks if not self.m_skip_nlm_denoising: nlmDenoise = MapNode(interface=preprocess.BtkNLMDenoising(), name='nlmDenoise', iterfield=['in_file', 'in_mask']) nlmDenoise.inputs.bids_dir = self.bids_dir # Sans le mask le premier correct slice intensity... srtkCorrectSliceIntensity01_nlm = MapNode( interface=preprocess.MialsrtkCorrectSliceIntensity(), name='srtkCorrectSliceIntensity01_nlm', iterfield=['in_file', 'in_mask']) srtkCorrectSliceIntensity01_nlm.inputs.bids_dir = self.bids_dir srtkCorrectSliceIntensity01_nlm.inputs.out_postfix = '_uni' srtkCorrectSliceIntensity01 = MapNode( interface=preprocess.MialsrtkCorrectSliceIntensity(), name='srtkCorrectSliceIntensity01', iterfield=['in_file', 'in_mask']) srtkCorrectSliceIntensity01.inputs.bids_dir = self.bids_dir srtkCorrectSliceIntensity01.inputs.out_postfix = '_uni' srtkSliceBySliceN4BiasFieldCorrection = MapNode( interface=preprocess.MialsrtkSliceBySliceN4BiasFieldCorrection(), name='srtkSliceBySliceN4BiasFieldCorrection', iterfield=['in_file', 'in_mask']) srtkSliceBySliceN4BiasFieldCorrection.inputs.bids_dir = self.bids_dir srtkSliceBySliceCorrectBiasField = MapNode( interface=preprocess.MialsrtkSliceBySliceCorrectBiasField(), name='srtkSliceBySliceCorrectBiasField', iterfield=['in_file', 'in_mask', 'in_field']) srtkSliceBySliceCorrectBiasField.inputs.bids_dir = self.bids_dir # 4-modules sequence to be defined as a stage. if not self.m_skip_nlm_denoising: srtkCorrectSliceIntensity02_nlm = MapNode( interface=preprocess.MialsrtkCorrectSliceIntensity(), name='srtkCorrectSliceIntensity02_nlm', iterfield=['in_file', 'in_mask']) srtkCorrectSliceIntensity02_nlm.inputs.bids_dir = self.bids_dir srtkIntensityStandardization01_nlm = Node( interface=preprocess.MialsrtkIntensityStandardization(), name='srtkIntensityStandardization01_nlm') srtkIntensityStandardization01_nlm.inputs.bids_dir = self.bids_dir srtkHistogramNormalization_nlm = Node( interface=preprocess.MialsrtkHistogramNormalization(), name='srtkHistogramNormalization_nlm') srtkHistogramNormalization_nlm.inputs.bids_dir = self.bids_dir srtkIntensityStandardization02_nlm = Node( interface=preprocess.MialsrtkIntensityStandardization(), name='srtkIntensityStandardization02_nlm') srtkIntensityStandardization02_nlm.inputs.bids_dir = self.bids_dir # 4-modules sequence to be defined as a stage. srtkCorrectSliceIntensity02 = MapNode( interface=preprocess.MialsrtkCorrectSliceIntensity(), name='srtkCorrectSliceIntensity02', iterfield=['in_file', 'in_mask']) srtkCorrectSliceIntensity02.inputs.bids_dir = self.bids_dir srtkIntensityStandardization01 = Node( interface=preprocess.MialsrtkIntensityStandardization(), name='srtkIntensityStandardization01') srtkIntensityStandardization01.inputs.bids_dir = self.bids_dir srtkHistogramNormalization = Node( interface=preprocess.MialsrtkHistogramNormalization(), name='srtkHistogramNormalization') srtkHistogramNormalization.inputs.bids_dir = self.bids_dir srtkIntensityStandardization02 = Node( interface=preprocess.MialsrtkIntensityStandardization(), name='srtkIntensityStandardization02') srtkIntensityStandardization02.inputs.bids_dir = self.bids_dir srtkMaskImage01 = MapNode(interface=preprocess.MialsrtkMaskImage(), name='srtkMaskImage01', iterfield=['in_file', 'in_mask']) srtkMaskImage01.inputs.bids_dir = self.bids_dir srtkImageReconstruction = Node( interface=reconstruction.MialsrtkImageReconstruction(), name='srtkImageReconstruction') srtkImageReconstruction.inputs.bids_dir = self.bids_dir srtkImageReconstruction.inputs.sub_ses = sub_ses srtkImageReconstruction.inputs.no_reg = self.m_skip_svr srtkTVSuperResolution = Node( interface=reconstruction.MialsrtkTVSuperResolution(), name='srtkTVSuperResolution') srtkTVSuperResolution.inputs.bids_dir = self.bids_dir srtkTVSuperResolution.inputs.sub_ses = sub_ses srtkTVSuperResolution.inputs.in_loop = self.primal_dual_loops srtkTVSuperResolution.inputs.in_deltat = self.deltatTV srtkTVSuperResolution.inputs.in_lambda = self.lambdaTV srtkTVSuperResolution.inputs.use_manual_masks = self.use_manual_masks srtkN4BiasFieldCorrection = Node( interface=postprocess.MialsrtkN4BiasFieldCorrection(), name='srtkN4BiasFieldCorrection') srtkN4BiasFieldCorrection.inputs.bids_dir = self.bids_dir if self.m_do_refine_hr_mask: srtkHRMask = Node( interface=postprocess.MialsrtkRefineHRMaskByIntersection(), name='srtkHRMask') srtkHRMask.inputs.bids_dir = self.bids_dir else: srtkHRMask = Node(interface=Function( input_names=["input_image"], output_names=["output_srmask"], function=postprocess.binarize_image), name='srtkHRMask') srtkMaskImage02 = Node(interface=preprocess.MialsrtkMaskImage(), name='srtkMaskImage02') srtkMaskImage02.inputs.bids_dir = self.bids_dir # Build workflow : connections of the nodes # Nodes ready : Linking now if self.use_manual_masks: self.wf.connect(dg, "masks", brainMask, "out_file") else: if self.m_stacks is not None: self.wf.connect(dg, "T2ws", t2ws_filter_prior_masks, "input_files") self.wf.connect(t2ws_filter_prior_masks, "output_files", brainMask, "in_file") else: self.wf.connect(dg, "T2ws", brainMask, "in_file") if not self.m_skip_stacks_ordering: self.wf.connect(brainMask, "out_file", stacksOrdering, "input_masks") self.wf.connect(stacksOrdering, "stacks_order", t2ws_filtered, "stacks_id") self.wf.connect(dg, "T2ws", t2ws_filtered, "input_files") self.wf.connect(stacksOrdering, "stacks_order", masks_filtered, "stacks_id") self.wf.connect(brainMask, "out_file", masks_filtered, "input_files") self.wf.connect(t2ws_filtered, ("output_files", utils.sort_ascending), nlmDenoise, "in_file") self.wf.connect(masks_filtered, ("output_files", utils.sort_ascending), nlmDenoise, "in_mask") ## Comment to match docker process if not self.m_skip_nlm_denoising: self.wf.connect(nlmDenoise, ("out_file", utils.sort_ascending), srtkCorrectSliceIntensity01_nlm, "in_file") self.wf.connect(masks_filtered, ("output_files", utils.sort_ascending), srtkCorrectSliceIntensity01_nlm, "in_mask") self.wf.connect(t2ws_filtered, ("output_files", utils.sort_ascending), srtkCorrectSliceIntensity01, "in_file") self.wf.connect(masks_filtered, ("output_files", utils.sort_ascending), srtkCorrectSliceIntensity01, "in_mask") if not self.m_skip_nlm_denoising: self.wf.connect(srtkCorrectSliceIntensity01_nlm, ("out_file", utils.sort_ascending), srtkSliceBySliceN4BiasFieldCorrection, "in_file") else: self.wf.connect(srtkCorrectSliceIntensity01, ("out_file", utils.sort_ascending), srtkSliceBySliceN4BiasFieldCorrection, "in_file") self.wf.connect(masks_filtered, ("output_files", utils.sort_ascending), srtkSliceBySliceN4BiasFieldCorrection, "in_mask") self.wf.connect(srtkCorrectSliceIntensity01, ("out_file", utils.sort_ascending), srtkSliceBySliceCorrectBiasField, "in_file") self.wf.connect(srtkSliceBySliceN4BiasFieldCorrection, ("out_fld_file", utils.sort_ascending), srtkSliceBySliceCorrectBiasField, "in_field") self.wf.connect(masks_filtered, ("output_files", utils.sort_ascending), srtkSliceBySliceCorrectBiasField, "in_mask") if not self.m_skip_nlm_denoising: self.wf.connect(srtkSliceBySliceN4BiasFieldCorrection, ("out_im_file", utils.sort_ascending), srtkCorrectSliceIntensity02_nlm, "in_file") self.wf.connect(masks_filtered, ("output_files", utils.sort_ascending), srtkCorrectSliceIntensity02_nlm, "in_mask") self.wf.connect(srtkCorrectSliceIntensity02_nlm, ("out_file", utils.sort_ascending), srtkIntensityStandardization01_nlm, "input_images") self.wf.connect(srtkIntensityStandardization01_nlm, ("output_images", utils.sort_ascending), srtkHistogramNormalization_nlm, "input_images") self.wf.connect(masks_filtered, ("output_files", utils.sort_ascending), srtkHistogramNormalization_nlm, "input_masks") self.wf.connect(srtkHistogramNormalization_nlm, ("output_images", utils.sort_ascending), srtkIntensityStandardization02_nlm, "input_images") self.wf.connect(srtkSliceBySliceCorrectBiasField, ("out_im_file", utils.sort_ascending), srtkCorrectSliceIntensity02, "in_file") self.wf.connect(masks_filtered, ("output_files", utils.sort_ascending), srtkCorrectSliceIntensity02, "in_mask") self.wf.connect(srtkCorrectSliceIntensity02, ("out_file", utils.sort_ascending), srtkIntensityStandardization01, "input_images") self.wf.connect(srtkIntensityStandardization01, ("output_images", utils.sort_ascending), srtkHistogramNormalization, "input_images") self.wf.connect(masks_filtered, ("output_files", utils.sort_ascending), srtkHistogramNormalization, "input_masks") self.wf.connect(srtkHistogramNormalization, ("output_images", utils.sort_ascending), srtkIntensityStandardization02, "input_images") if not self.m_skip_nlm_denoising: self.wf.connect(srtkIntensityStandardization02_nlm, ("output_images", utils.sort_ascending), srtkMaskImage01, "in_file") self.wf.connect(masks_filtered, ("output_files", utils.sort_ascending), srtkMaskImage01, "in_mask") else: self.wf.connect(srtkIntensityStandardization02, ("output_images", utils.sort_ascending), srtkMaskImage01, "in_file") self.wf.connect(masks_filtered, ("output_files", utils.sort_ascending), srtkMaskImage01, "in_mask") self.wf.connect(srtkMaskImage01, "out_im_file", srtkImageReconstruction, "input_images") self.wf.connect(masks_filtered, "output_files", srtkImageReconstruction, "input_masks") self.wf.connect(stacksOrdering, "stacks_order", srtkImageReconstruction, "stacks_order") self.wf.connect(srtkIntensityStandardization02, "output_images", srtkTVSuperResolution, "input_images") self.wf.connect(srtkImageReconstruction, ("output_transforms", utils.sort_ascending), srtkTVSuperResolution, "input_transforms") self.wf.connect(masks_filtered, ("output_files", utils.sort_ascending), srtkTVSuperResolution, "input_masks") self.wf.connect(stacksOrdering, "stacks_order", srtkTVSuperResolution, "stacks_order") self.wf.connect(srtkImageReconstruction, "output_sdi", srtkTVSuperResolution, "input_sdi") if self.m_do_refine_hr_mask: self.wf.connect(srtkIntensityStandardization02, ("output_images", utils.sort_ascending), srtkHRMask, "input_images") self.wf.connect(masks_filtered, ("output_files", utils.sort_ascending), srtkHRMask, "input_masks") self.wf.connect(srtkImageReconstruction, ("output_transforms", utils.sort_ascending), srtkHRMask, "input_transforms") self.wf.connect(srtkTVSuperResolution, "output_sr", srtkHRMask, "input_sr") else: self.wf.connect(srtkTVSuperResolution, "output_sr", srtkHRMask, "input_image") self.wf.connect(srtkTVSuperResolution, "output_sr", srtkMaskImage02, "in_file") self.wf.connect(srtkHRMask, "output_srmask", srtkMaskImage02, "in_mask") self.wf.connect(srtkTVSuperResolution, "output_sr", srtkN4BiasFieldCorrection, "input_image") self.wf.connect(srtkHRMask, "output_srmask", srtkN4BiasFieldCorrection, "input_mask") # Datasinker finalFilenamesGeneration = Node( interface=postprocess.FilenamesGeneration(), name='filenames_gen') finalFilenamesGeneration.inputs.sub_ses = sub_ses finalFilenamesGeneration.inputs.sr_id = self.sr_id finalFilenamesGeneration.inputs.use_manual_masks = self.use_manual_masks self.wf.connect(stacksOrdering, "stacks_order", finalFilenamesGeneration, "stacks_order") datasink = Node(interface=DataSink(), name='data_sinker') datasink.inputs.base_directory = final_res_dir if not self.m_skip_stacks_ordering: self.wf.connect(stacksOrdering, "report_image", datasink, 'figures.@stackOrderingQC') self.wf.connect(stacksOrdering, "motion_tsv", datasink, 'anat.@motionTSV') self.wf.connect(masks_filtered, ("output_files", utils.sort_ascending), datasink, 'anat.@LRmasks') self.wf.connect(srtkIntensityStandardization02, ("output_images", utils.sort_ascending), datasink, 'anat.@LRsPreproc') self.wf.connect(srtkImageReconstruction, ("output_transforms", utils.sort_ascending), datasink, 'xfm.@transforms') self.wf.connect(finalFilenamesGeneration, "substitutions", datasink, "substitutions") self.wf.connect(srtkMaskImage01, ("out_im_file", utils.sort_ascending), datasink, 'anat.@LRsDenoised') self.wf.connect(srtkImageReconstruction, "output_sdi", datasink, 'anat.@SDI') self.wf.connect(srtkN4BiasFieldCorrection, "output_image", datasink, 'anat.@SR') self.wf.connect(srtkTVSuperResolution, "output_json_path", datasink, 'anat.@SRjson') self.wf.connect(srtkTVSuperResolution, "output_sr_png", datasink, 'figures.@SRpng') self.wf.connect(srtkHRMask, "output_srmask", datasink, 'anat.@SRmask')
def build_workflow(opts, retval): """ Create the Nipype Workflow that supports the whole execution graph, given the inputs. All the checks and the construction of the workflow are done inside this function that has pickleable inputs and output dictionary (``retval``) to allow isolation using a ``multiprocessing.Process`` that allows fmriprep to enforce a hard-limited memory-scope. """ from subprocess import check_call, CalledProcessError, TimeoutExpired from pkg_resources import resource_filename as pkgrf from shutil import copyfile from nipype import logging, config as ncfg from niworkflows.utils.bids import collect_participants from ..__about__ import __version__ from ..workflows.base import init_fmriprep_wf from ..viz.reports import generate_reports logger = logging.getLogger('nipype.workflow') INIT_MSG = """ Running fMRIPREP version {version}: * BIDS dataset path: {bids_dir}. * Participant list: {subject_list}. * Run identifier: {uuid}. """.format output_spaces = opts.output_space or [] # Validity of some inputs # ERROR check if use_aroma was specified, but the correct template was not if opts.use_aroma and (opts.template != 'MNI152NLin2009cAsym' or 'template' not in output_spaces): output_spaces.append('template') logger.warning( 'Option "--use-aroma" requires functional images to be resampled to MNI space. ' 'The argument "template" has been automatically added to the list of output ' 'spaces (option "--output-space").' ) if opts.cifti_output and (opts.template != 'MNI152NLin2009cAsym' or 'template' not in output_spaces): output_spaces.append('template') logger.warning( 'Option "--cifti-output" requires functional images to be resampled to MNI space. ' 'The argument "template" has been automatically added to the list of output ' 'spaces (option "--output-space").' ) # Check output_space if 'template' not in output_spaces and (opts.use_syn_sdc or opts.force_syn): msg = ['SyN SDC correction requires T1 to MNI registration, but ' '"template" is not specified in "--output-space" arguments.', 'Option --use-syn will be cowardly dismissed.'] if opts.force_syn: output_spaces.append('template') msg[1] = (' Since --force-syn has been requested, "template" has been added to' ' the "--output-space" list.') logger.warning(' '.join(msg)) # Set up some instrumental utilities run_uuid = '%s_%s' % (strftime('%Y%m%d-%H%M%S'), uuid.uuid4()) # First check that bids_dir looks like a BIDS folder bids_dir = os.path.abspath(opts.bids_dir) subject_list = collect_participants( bids_dir, participant_label=opts.participant_label) # Load base plugin_settings from file if --use-plugin if opts.use_plugin is not None: from yaml import load as loadyml with open(opts.use_plugin) as f: plugin_settings = loadyml(f) plugin_settings.setdefault('plugin_args', {}) else: # Defaults plugin_settings = { 'plugin': 'MultiProc', 'plugin_args': { 'raise_insufficient': False, 'maxtasksperchild': 1, } } # Resource management options # Note that we're making strong assumptions about valid plugin args # This may need to be revisited if people try to use batch plugins nthreads = plugin_settings['plugin_args'].get('n_procs') # Permit overriding plugin config with specific CLI options if nthreads is None or opts.nthreads is not None: nthreads = opts.nthreads if nthreads is None or nthreads < 1: nthreads = cpu_count() plugin_settings['plugin_args']['n_procs'] = nthreads if opts.mem_mb: plugin_settings['plugin_args']['memory_gb'] = opts.mem_mb / 1024 omp_nthreads = opts.omp_nthreads if omp_nthreads == 0: omp_nthreads = min(nthreads - 1 if nthreads > 1 else cpu_count(), 8) if 1 < nthreads < omp_nthreads: logger.warning( 'Per-process threads (--omp-nthreads=%d) exceed total ' 'threads (--nthreads/--n_cpus=%d)', omp_nthreads, nthreads) # Set up directories output_dir = op.abspath(opts.output_dir) log_dir = op.join(output_dir, 'fmriprep', 'logs') work_dir = op.abspath(opts.work_dir or 'work') # Set work/ as default # Check and create output and working directories os.makedirs(output_dir, exist_ok=True) os.makedirs(log_dir, exist_ok=True) os.makedirs(work_dir, exist_ok=True) # Nipype config (logs and execution) ncfg.update_config({ 'logging': { 'log_directory': log_dir, 'log_to_file': True }, 'execution': { 'crashdump_dir': log_dir, 'crashfile_format': 'txt', 'get_linked_libs': False, 'stop_on_first_crash': opts.stop_on_first_crash or opts.work_dir is None, }, 'monitoring': { 'enabled': opts.resource_monitor, 'sample_frequency': '0.5', 'summary_append': True, } }) if opts.resource_monitor: ncfg.enable_resource_monitor() retval['return_code'] = 0 retval['plugin_settings'] = plugin_settings retval['bids_dir'] = bids_dir retval['output_dir'] = output_dir retval['work_dir'] = work_dir retval['subject_list'] = subject_list retval['run_uuid'] = run_uuid retval['workflow'] = None # Called with reports only if opts.reports_only: logger.log(25, 'Running --reports-only on participants %s', ', '.join(subject_list)) if opts.run_uuid is not None: run_uuid = opts.run_uuid retval['return_code'] = generate_reports(subject_list, output_dir, work_dir, run_uuid) return retval # Build main workflow logger.log(25, INIT_MSG( version=__version__, bids_dir=bids_dir, subject_list=subject_list, uuid=run_uuid) ) template_out_grid = opts.template_resampling_grid if opts.output_grid_reference is not None: logger.warning( 'Option --output-grid-reference is deprecated, please use ' '--template-resampling-grid') template_out_grid = template_out_grid or opts.output_grid_reference if opts.debug: logger.warning('Option --debug is deprecated and has no effect') retval['workflow'] = init_fmriprep_wf( subject_list=subject_list, task_id=opts.task_id, echo_idx=opts.echo_idx, run_uuid=run_uuid, ignore=opts.ignore, debug=opts.sloppy, low_mem=opts.low_mem, anat_only=opts.anat_only, longitudinal=opts.longitudinal, t2s_coreg=opts.t2s_coreg, omp_nthreads=omp_nthreads, skull_strip_template=opts.skull_strip_template, skull_strip_fixed_seed=opts.skull_strip_fixed_seed, work_dir=work_dir, output_dir=output_dir, bids_dir=bids_dir, freesurfer=opts.run_reconall, output_spaces=output_spaces, template=opts.template, medial_surface_nan=opts.medial_surface_nan, cifti_output=opts.cifti_output, template_out_grid=template_out_grid, hires=opts.hires, use_bbr=opts.use_bbr, bold2t1w_dof=opts.bold2t1w_dof, fmap_bspline=opts.fmap_bspline, fmap_demean=opts.fmap_no_demean, use_syn=opts.use_syn_sdc, force_syn=opts.force_syn, use_aroma=opts.use_aroma, aroma_melodic_dim=opts.aroma_melodic_dimensionality, ignore_aroma_err=opts.ignore_aroma_denoising_errors, ) retval['return_code'] = 0 logs_path = Path(output_dir) / 'fmriprep' / 'logs' boilerplate = retval['workflow'].visit_desc() if boilerplate: (logs_path / 'CITATION.md').write_text(boilerplate) logger.log(25, 'Works derived from this fMRIPrep execution should ' 'include the following boilerplate:\n\n%s', boilerplate) # Generate HTML file resolving citations cmd = ['pandoc', '-s', '--bibliography', pkgrf('fmriprep', 'data/boilerplate.bib'), '--filter', 'pandoc-citeproc', str(logs_path / 'CITATION.md'), '-o', str(logs_path / 'CITATION.html')] try: check_call(cmd, timeout=10) except (FileNotFoundError, CalledProcessError, TimeoutExpired): logger.warning('Could not generate CITATION.html file:\n%s', ' '.join(cmd)) # Generate LaTex file resolving citations cmd = ['pandoc', '-s', '--bibliography', pkgrf('fmriprep', 'data/boilerplate.bib'), '--natbib', str(logs_path / 'CITATION.md'), '-o', str(logs_path / 'CITATION.tex')] try: check_call(cmd, timeout=10) except (FileNotFoundError, CalledProcessError, TimeoutExpired): logger.warning('Could not generate CITATION.tex file:\n%s', ' '.join(cmd)) else: copyfile(pkgrf('fmriprep', 'data/boilerplate.bib'), (logs_path / 'CITATION.bib')) return retval
def run_workflow(sub_dict, c, run, pipeline_timing_info=None, p_name=None, plugin='MultiProc', plugin_args=None, test_config=False): ''' Function to prepare and, optionally, run the C-PAC workflow Parameters ---------- sub_dict : dictionary subject dictionary with anatomical and functional image paths c : Configuration object CPAC pipeline configuration dictionary object run : boolean flag to indicate whether to run the prepared workflow pipeline_timing_info : list (optional); default=None list of pipeline info for reporting timing information p_name : string (optional); default=None name of pipeline plugin : string (optional); defaule='MultiProc' nipype plugin to utilize when the workflow is ran plugin_args : dictionary (optional); default=None plugin-specific arguments for the workflow plugin Returns ------- workflow : nipype workflow the prepared nipype workflow object containing the parameters specified in the config ''' # Assure that changes on config will not affect other parts c = copy.copy(c) subject_id = sub_dict['subject_id'] if sub_dict['unique_id']: subject_id += "_" + sub_dict['unique_id'] log_dir = os.path.join(c.pipeline_setup['log_directory']['path'], f'pipeline_{c.pipeline_setup["pipeline_name"]}', subject_id) if not os.path.exists(log_dir): os.makedirs(os.path.join(log_dir)) # TODO ASH Enforce c.run_logging to be boolean # TODO ASH Schema validation config.update_config({ 'logging': { 'log_directory': log_dir, 'log_to_file': bool(getattr(c.pipeline_setup['log_directory'], 'run_logging', True)) }, 'execution': { 'crashfile_format': 'txt' } }) config.enable_resource_monitor() logging.update_logging(config) # Start timing here pipeline_start_time = time.time() # at end of workflow, take timestamp again, take time elapsed and check # tempfile add time to time data structure inside tempfile, and increment # number of subjects # Check pipeline config resources sub_mem_gb, num_cores_per_sub, num_ants_cores, num_omp_cores = check_config_resources( c) if not plugin: plugin = 'MultiProc' if plugin_args: plugin_args['memory_gb'] = sub_mem_gb plugin_args['n_procs'] = num_cores_per_sub else: plugin_args = {'memory_gb': sub_mem_gb, 'n_procs': num_cores_per_sub} # perhaps in future allow user to set threads maximum # this is for centrality mostly # import mkl os.environ['OMP_NUM_THREADS'] = str(num_omp_cores) os.environ['MKL_NUM_THREADS'] = '1' # str(num_cores_per_sub) os.environ['ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS'] = str(num_ants_cores) # TODO: TEMPORARY # TODO: solve the UNet model hanging issue during MultiProc if "UNet" in c.anatomical_preproc['brain_extraction']['using']: c.pipeline_setup['system_config']['max_cores_per_participant'] = 1 logger.info("\n\n[!] LOCKING CPUs PER PARTICIPANT TO 1 FOR U-NET " "MODEL.\n\nThis is a temporary measure due to a known " "issue preventing Nipype's parallelization from running " "U-Net properly.\n\n") # calculate maximum potential use of cores according to current pipeline # configuration max_core_usage = int( c.pipeline_setup['system_config']['max_cores_per_participant']) * \ int(c.pipeline_setup['system_config'][ 'num_participants_at_once']) try: creds_path = sub_dict['creds_path'] if creds_path and 'none' not in creds_path.lower(): if os.path.exists(creds_path): input_creds_path = os.path.abspath(creds_path) else: err_msg = 'Credentials path: "%s" for subject "%s" was not ' \ 'found. Check this path and try again.' % ( creds_path, subject_id) raise Exception(err_msg) else: input_creds_path = None except KeyError: input_creds_path = None # TODO enforce value with schema validation try: encrypt_data = bool( config.pipeline_setup['Amazon-AWS']['s3_encryption']) except: encrypt_data = False information = """ C-PAC version: {cpac_version} Setting maximum number of cores per participant to {cores} Setting number of participants at once to {participants} Setting OMP_NUM_THREADS to {omp_threads} Setting MKL_NUM_THREADS to 1 Setting ANTS/ITK thread usage to {ants_threads} Maximum potential number of cores that might be used during this run: {max_cores} """ execution_info = """ End of subject workflow {workflow} CPAC run complete: Pipeline configuration: {pipeline} Subject workflow: {workflow} Elapsed run time (minutes): {elapsed} Timing information saved in {log_dir}/cpac_individual_timing_{pipeline}.csv System time of start: {run_start} System time of completion: {run_finish} """ logger.info(information.format( cpac_version=CPAC.__version__, cores=c.pipeline_setup['system_config']['max_cores_per_participant'], participants=c.pipeline_setup['system_config'][ 'num_participants_at_once'], omp_threads=c.pipeline_setup['system_config']['num_OMP_threads'], ants_threads=c.pipeline_setup['system_config']['num_ants_threads'], max_cores=max_core_usage )) subject_info = {} subject_info['subject_id'] = subject_id subject_info['start_time'] = pipeline_start_time check_centrality_degree = c.network_centrality['run'] and \ (len(c.network_centrality['degree_centrality'][ 'weight_options']) != 0 or \ len(c.network_centrality[ 'eigenvector_centrality'][ 'weight_options']) != 0) check_centrality_lfcd = c.network_centrality['run'] and \ len(c.network_centrality[ 'local_functional_connectivity_density'][ 'weight_options']) != 0 # Check system dependencies check_ica_aroma = c.nuisance_corrections['1-ICA-AROMA']['run'] if isinstance(check_ica_aroma, list): check_ica_aroma = True in check_ica_aroma check_system_deps(check_ants='ANTS' in c.registration_workflows[ 'anatomical_registration']['registration']['using'], check_ica_aroma=check_ica_aroma, check_centrality_degree=check_centrality_degree, check_centrality_lfcd=check_centrality_lfcd) # absolute paths of the dirs c.pipeline_setup['working_directory']['path'] = os.path.abspath( c.pipeline_setup['working_directory']['path']) if 's3://' not in c.pipeline_setup['output_directory']['path']: c.pipeline_setup['output_directory']['path'] = os.path.abspath( c.pipeline_setup['output_directory']['path']) workflow = build_workflow( subject_id, sub_dict, c, p_name, num_ants_cores ) if test_config: logger.info('This has been a test of the pipeline configuration ' 'file, the pipeline was built successfully, but was ' 'not run') else: working_dir = os.path.join( c.pipeline_setup['working_directory']['path'], workflow.name) # if c.write_debugging_outputs: # with open(os.path.join(working_dir, 'resource_pool.pkl'), 'wb') as f: # pickle.dump(strat_list, f) # if c.pipeline_setup['working_directory']['regenerate_outputs'] is True: # erasable = list(find_files(working_dir, '*sink*')) + \ # list(find_files(working_dir, '*link*')) + \ # list(find_files(working_dir, '*log*')) # for f in erasable: # if os.path.isfile(f): # os.remove(f) # else: # shutil.rmtree(f) if hasattr(c, 'trim') and c.trim: logger.warn(""" Trimming is an experimental feature, and if used wrongly, it can lead to unreproducible results. It is useful for performance optimization, but only if used correctly. Please, make yourself aware of how it works and its assumptions: - The pipeline configuration has not changed; - The data configuration / BIDS directory has not changed; - The files from the output directory has not changed; - Your softwares versions has not changed; - Your C-PAC version has not changed; - You do not have access to the working directory. """) workflow, _ = the_trimmer( workflow, output_dir=c.pipeline_setup['output_directory']['path'], s3_creds_path=input_creds_path, ) pipeline_start_datetime = strftime("%Y-%m-%d %H:%M:%S") try: subject_info['resource_pool'] = [] # for strat_no, strat in enumerate(strat_list): # strat_label = 'strat_%d' % strat_no # subject_info[strat_label] = strat.get_name() # subject_info['resource_pool'].append(strat.get_resource_pool()) subject_info['status'] = 'Running' # Create callback logger cb_log_filename = os.path.join(log_dir, 'callback.log') try: if not os.path.exists(os.path.dirname(cb_log_filename)): os.makedirs(os.path.dirname(cb_log_filename)) except IOError: pass # Add handler to callback log file cb_logger = cb_logging.getLogger('callback') cb_logger.setLevel(cb_logging.DEBUG) handler = cb_logging.FileHandler(cb_log_filename) cb_logger.addHandler(handler) # Log initial information from all the nodes log_nodes_initial(workflow) # Add status callback function that writes in callback log if nipype.__version__ not in ('1.5.1'): err_msg = "This version of Nipype may not be compatible with " \ "CPAC v%s, please install Nipype version 1.5.1\n" \ % (CPAC.__version__) logger.error(err_msg) else: plugin_args['status_callback'] = log_nodes_cb if plugin_args['n_procs'] == 1: plugin = 'Linear' try: # Actually run the pipeline now, for the current subject workflow.run(plugin=plugin, plugin_args=plugin_args) except UnicodeDecodeError: raise EnvironmentError( "C-PAC migrated from Python 2 to Python 3 in v1.6.2 (see " "release notes). Your working directory contains Python 2 " "pickles, probably from an older version of C-PAC. If you " "want to continue to use this working directory, run\n\n" "docker run -i --rm --user $(id -u):$(id -g) " "-v /path/to/working_dir:/working " "fcpindi/c-pac:latest /bids_dir /outputs cli -- " "utils repickle /working\n" "\nor\n\n" "singularity run " "C-PAC_latest.sif /bids_dir /outputs cli -- " "utils repickle /path/to/working_dir\n\n" "before running C-PAC >=v1.6.2" ) # PyPEER kick-off # if c.PyPEER['run']: # from CPAC.pypeer.peer import prep_for_pypeer # prep_for_pypeer(c.PyPEER['eye_scan_names'], c.PyPEER['data_scan_names'], # c.PyPEER['eye_mask_path'], c.pipeline_setup['output_directory']['path'], subject_id, # pipeline_ids, c.PyPEER['stimulus_path'], c.PyPEER['minimal_nuisance_correction']['peer_gsr'], # c.PyPEER['minimal_nuisance_correction']['peer_scrub'], c.PyPEER['minimal_nuisance_correction']['scrub_thresh']) # Dump subject info pickle file to subject log dir subject_info['status'] = 'Completed' subject_info_file = os.path.join( log_dir, 'subject_info_%s.pkl' % subject_id ) with open(subject_info_file, 'wb') as info: pickle.dump(list(subject_info), info) # have this check in case the user runs cpac_runner from terminal and # the timing parameter list is not supplied as usual by the GUI if pipeline_timing_info != None: # pipeline_timing_info list: # [0] - unique pipeline ID # [1] - pipeline start time stamp (first click of 'run' from GUI) # [2] - number of subjects in subject list unique_pipeline_id = pipeline_timing_info[0] pipeline_start_stamp = pipeline_timing_info[1] num_subjects = pipeline_timing_info[2] # elapsed time data list: # [0] - elapsed time in minutes elapsed_time_data = [] elapsed_time_data.append( int(((time.time() - pipeline_start_time) / 60))) # elapsedTimeBin list: # [0] - cumulative elapsed time (minutes) across all subjects # [1] - number of times the elapsed time has been appended # (effectively a measure of how many subjects have run) # TODO # write more doc for all this # warning in .csv that some runs may be partial # code to delete .tmp file timing_temp_file_path = os.path.join( c.pipeline_setup['log_directory']['path'], '%s_pipeline_timing.tmp' % unique_pipeline_id) if not os.path.isfile(timing_temp_file_path): elapsedTimeBin = [] elapsedTimeBin.append(0) elapsedTimeBin.append(0) with open(timing_temp_file_path, 'wb') as handle: pickle.dump(elapsedTimeBin, handle) with open(timing_temp_file_path, 'rb') as handle: elapsedTimeBin = pickle.loads(handle.read()) elapsedTimeBin[0] = elapsedTimeBin[0] + elapsed_time_data[0] elapsedTimeBin[1] = elapsedTimeBin[1] + 1 with open(timing_temp_file_path, 'wb') as handle: pickle.dump(elapsedTimeBin, handle) # this happens once the last subject has finished running! if elapsedTimeBin[1] == num_subjects: pipelineTimeDict = {} pipelineTimeDict['Pipeline'] = c.pipeline_setup[ 'pipeline_name'] pipelineTimeDict['Cores_Per_Subject'] = \ c.pipeline_setup['system_config'][ 'max_cores_per_participant'] pipelineTimeDict['Simultaneous_Subjects'] = \ c.pipeline_setup['system_config'][ 'num_participants_at_once'] pipelineTimeDict['Number_of_Subjects'] = num_subjects pipelineTimeDict['Start_Time'] = pipeline_start_stamp pipelineTimeDict['End_Time'] = strftime( "%Y-%m-%d_%H:%M:%S") pipelineTimeDict['Elapsed_Time_(minutes)'] = \ elapsedTimeBin[0] pipelineTimeDict['Status'] = 'Complete' gpaTimeFields = [ 'Pipeline', 'Cores_Per_Subject', 'Simultaneous_Subjects', 'Number_of_Subjects', 'Start_Time', 'End_Time', 'Elapsed_Time_(minutes)', 'Status' ] timeHeader = dict(zip(gpaTimeFields, gpaTimeFields)) with open(os.path.join( c.pipeline_setup['log_directory']['path'], 'cpac_individual_timing_%s.csv' % c.pipeline_setup['pipeline_name'] ), 'a') as timeCSV, open(os.path.join( c.pipeline_setup['log_directory']['path'], 'cpac_individual_timing_%s.csv' % c.pipeline_setup['pipeline_name'] ), 'r') as readTimeCSV: timeWriter = csv.DictWriter(timeCSV, fieldnames=gpaTimeFields) timeReader = csv.DictReader(readTimeCSV) headerExists = False for line in timeReader: if 'Start_Time' in line: headerExists = True if headerExists == False: timeWriter.writerow(timeHeader) timeWriter.writerow(pipelineTimeDict) # remove the temp timing file now that it is no longer needed os.remove(timing_temp_file_path) # Upload logs to s3 if s3_str in output directory if c.pipeline_setup['output_directory'][ 'path'].lower().startswith('s3://'): try: # Store logs in s3 output director/logs/... s3_log_dir = os.path.join( c.pipeline_setup['output_directory']['path'], 'logs', os.path.basename(log_dir) ) bucket_name = \ c.pipeline_setup['output_directory']['path'].split('/')[2] bucket = fetch_creds.return_bucket(creds_path, bucket_name) # Collect local log files local_log_files = [] for root, _, files in os.walk(log_dir): local_log_files.extend([os.path.join(root, fil) for fil in files]) # Form destination keys s3_log_files = [loc.replace(log_dir, s3_log_dir) for loc in local_log_files] # Upload logs aws_utils.s3_upload(bucket, (local_log_files, s3_log_files), encrypt=encrypt_data) # Delete local log files for log_f in local_log_files: os.remove(log_f) except Exception as exc: err_msg = 'Unable to upload CPAC log files in: %s.\nError: %s' logger.error(err_msg, log_dir, exc) except Exception as e: import traceback; traceback.print_exc() execution_info = """ Error of subject workflow {workflow} CPAC run error: Pipeline configuration: {pipeline} Subject workflow: {workflow} Elapsed run time (minutes): {elapsed} Timing information saved in {log_dir}/cpac_individual_timing_{pipeline}.csv System time of start: {run_start} """ finally: if workflow: resource_report(cb_log_filename, num_cores_per_sub, logger) logger.info(execution_info.format( workflow=workflow.name, pipeline=c.pipeline_setup['pipeline_name'], log_dir=c.pipeline_setup['log_directory']['path'], elapsed=(time.time() - pipeline_start_time) / 60, run_start=pipeline_start_datetime, run_finish=strftime("%Y-%m-%d %H:%M:%S") )) # Remove working directory when done if c.pipeline_setup['working_directory'][ 'remove_working_dir']: try: if os.path.exists(working_dir): logger.info("Removing working dir: %s", working_dir) shutil.rmtree(working_dir) except (FileNotFoundError, PermissionError): logger.warn('Could not remove working directory %s', working_dir)
def build_workflow(opts, retval): """ Create the Nipype Workflow that supports the whole execution graph, given the inputs. All the checks and the construction of the workflow are done inside this function that has pickleable inputs and output dictionary (``retval``) to allow isolation using a ``multiprocessing.Process`` that allows fmriprep to enforce a hard-limited memory-scope. """ from subprocess import check_call, CalledProcessError, TimeoutExpired from pkg_resources import resource_filename as pkgrf from shutil import copyfile from nipype import logging, config as ncfg from niworkflows.utils.bids import collect_participants from ..__about__ import __version__ from ..workflows.base import init_fmriprep_wf from ..viz.reports import generate_reports logger = logging.getLogger('nipype.workflow') INIT_MSG = """ Running fMRIPREP version {version}: * BIDS dataset path: {bids_dir}. * Participant list: {subject_list}. * Run identifier: {uuid}. """.format output_spaces = opts.output_space or [] # Validity of some inputs # ERROR check if use_aroma was specified, but the correct template was not if opts.use_aroma and (opts.template != 'MNI152NLin2009cAsym' or 'template' not in output_spaces): output_spaces.append('template') logger.warning( 'Option "--use-aroma" requires functional images to be resampled to MNI space. ' 'The argument "template" has been automatically added to the list of output ' 'spaces (option "--output-space").') if opts.cifti_output and (opts.template != 'MNI152NLin2009cAsym' or 'template' not in output_spaces): output_spaces.append('template') logger.warning( 'Option "--cifti-output" requires functional images to be resampled to MNI space. ' 'The argument "template" has been automatically added to the list of output ' 'spaces (option "--output-space").') # Check output_space if 'template' not in output_spaces and (opts.use_syn_sdc or opts.force_syn): msg = [ 'SyN SDC correction requires T1 to MNI registration, but ' '"template" is not specified in "--output-space" arguments.', 'Option --use-syn will be cowardly dismissed.' ] if opts.force_syn: output_spaces.append('template') msg[1] = ( ' Since --force-syn has been requested, "template" has been added to' ' the "--output-space" list.') logger.warning(' '.join(msg)) # Set up some instrumental utilities run_uuid = '%s_%s' % (strftime('%Y%m%d-%H%M%S'), uuid.uuid4()) # First check that bids_dir looks like a BIDS folder bids_dir = os.path.abspath(opts.bids_dir) subject_list = collect_participants( bids_dir, participant_label=opts.participant_label) # Load base plugin_settings from file if --use-plugin if opts.use_plugin is not None: from yaml import load as loadyml with open(opts.use_plugin) as f: plugin_settings = loadyml(f) plugin_settings.setdefault('plugin_args', {}) else: # Defaults plugin_settings = { 'plugin': 'MultiProc', 'plugin_args': { 'raise_insufficient': False, 'maxtasksperchild': 1, } } # Resource management options # Note that we're making strong assumptions about valid plugin args # This may need to be revisited if people try to use batch plugins nthreads = plugin_settings['plugin_args'].get('n_procs') # Permit overriding plugin config with specific CLI options if nthreads is None or opts.nthreads is not None: nthreads = opts.nthreads if nthreads is None or nthreads < 1: nthreads = cpu_count() plugin_settings['plugin_args']['n_procs'] = nthreads if opts.mem_mb: plugin_settings['plugin_args']['memory_gb'] = opts.mem_mb / 1024 omp_nthreads = opts.omp_nthreads if omp_nthreads == 0: omp_nthreads = min(nthreads - 1 if nthreads > 1 else cpu_count(), 8) if 1 < nthreads < omp_nthreads: logger.warning( 'Per-process threads (--omp-nthreads=%d) exceed total ' 'threads (--nthreads/--n_cpus=%d)', omp_nthreads, nthreads) # Set up directories output_dir = op.abspath(opts.output_dir) log_dir = op.join(output_dir, 'fmriprep', 'logs') work_dir = op.abspath(opts.work_dir or 'work') # Set work/ as default # Check and create output and working directories os.makedirs(output_dir, exist_ok=True) os.makedirs(log_dir, exist_ok=True) os.makedirs(work_dir, exist_ok=True) # Nipype config (logs and execution) ncfg.update_config({ 'logging': { 'log_directory': log_dir, 'log_to_file': True }, 'execution': { 'crashdump_dir': log_dir, 'crashfile_format': 'txt', 'get_linked_libs': False, 'stop_on_first_crash': opts.stop_on_first_crash or opts.work_dir is None, }, 'monitoring': { 'enabled': opts.resource_monitor, 'sample_frequency': '0.5', 'summary_append': True, } }) if opts.resource_monitor: ncfg.enable_resource_monitor() retval['return_code'] = 0 retval['plugin_settings'] = plugin_settings retval['bids_dir'] = bids_dir retval['output_dir'] = output_dir retval['work_dir'] = work_dir retval['subject_list'] = subject_list retval['run_uuid'] = run_uuid retval['workflow'] = None # Called with reports only if opts.reports_only: logger.log(25, 'Running --reports-only on participants %s', ', '.join(subject_list)) if opts.run_uuid is not None: run_uuid = opts.run_uuid retval['return_code'] = generate_reports(subject_list, output_dir, work_dir, run_uuid) return retval # Build main workflow logger.log( 25, INIT_MSG(version=__version__, bids_dir=bids_dir, subject_list=subject_list, uuid=run_uuid)) template_out_grid = opts.template_resampling_grid if opts.output_grid_reference is not None: logger.warning( 'Option --output-grid-reference is deprecated, please use ' '--template-resampling-grid') template_out_grid = template_out_grid or opts.output_grid_reference if opts.debug: logger.warning('Option --debug is deprecated and has no effect') retval['workflow'] = init_fmriprep_wf( subject_list=subject_list, task_id=opts.task_id, echo_idx=opts.echo_idx, run_uuid=run_uuid, ignore=opts.ignore, debug=opts.sloppy, low_mem=opts.low_mem, anat_only=opts.anat_only, longitudinal=opts.longitudinal, t2s_coreg=opts.t2s_coreg, omp_nthreads=omp_nthreads, skull_strip_template=opts.skull_strip_template, skull_strip_fixed_seed=opts.skull_strip_fixed_seed, work_dir=work_dir, output_dir=output_dir, bids_dir=bids_dir, freesurfer=opts.run_reconall, output_spaces=output_spaces, template=opts.template, medial_surface_nan=opts.medial_surface_nan, cifti_output=opts.cifti_output, template_out_grid=template_out_grid, hires=opts.hires, use_bbr=opts.use_bbr, bold2t1w_dof=opts.bold2t1w_dof, fmap_bspline=opts.fmap_bspline, fmap_demean=opts.fmap_no_demean, use_syn=opts.use_syn_sdc, force_syn=opts.force_syn, use_aroma=opts.use_aroma, aroma_melodic_dim=opts.aroma_melodic_dimensionality, ignore_aroma_err=opts.ignore_aroma_denoising_errors, ) retval['return_code'] = 0 logs_path = Path(output_dir) / 'fmriprep' / 'logs' boilerplate = retval['workflow'].visit_desc() if boilerplate: (logs_path / 'CITATION.md').write_text(boilerplate) logger.log( 25, 'Works derived from this fMRIPrep execution should ' 'include the following boilerplate:\n\n%s', boilerplate) # Generate HTML file resolving citations cmd = [ 'pandoc', '-s', '--bibliography', pkgrf('fmriprep', 'data/boilerplate.bib'), '--filter', 'pandoc-citeproc', '--metadata', 'pagetitle="fMRIPrep citation boilerplate"', str(logs_path / 'CITATION.md'), '-o', str(logs_path / 'CITATION.html') ] try: check_call(cmd, timeout=10) except (FileNotFoundError, CalledProcessError, TimeoutExpired): logger.warning('Could not generate CITATION.html file:\n%s', ' '.join(cmd)) # Generate LaTex file resolving citations cmd = [ 'pandoc', '-s', '--bibliography', pkgrf('fmriprep', 'data/boilerplate.bib'), '--natbib', str(logs_path / 'CITATION.md'), '-o', str(logs_path / 'CITATION.tex') ] try: check_call(cmd, timeout=10) except (FileNotFoundError, CalledProcessError, TimeoutExpired): logger.warning('Could not generate CITATION.tex file:\n%s', ' '.join(cmd)) else: copyfile(pkgrf('fmriprep', 'data/boilerplate.bib'), (logs_path / 'CITATION.bib')) return retval
def compare(args: argparse.Namespace) -> None: workflow_args = dict() # bids dir if str(args.bids_dir).startswith("./"): input_dir = join(os.getcwd(), args.bids_dir[2:]) else: input_dir = args.bids_dir # debug if args.debug: logs_dir = join(dirname(__file__), "logs") config.set_log_dir(logs_dir) config.enable_resource_monitor() config.enable_debug_mode() # profiler if args.profiler is not None: profiler_path = abspath(args.profiler) workflow_args['status_callback'] = profiler_callback if exists(profiler_path): if not isfile(profiler_path): raise OSError("Logs path is a directory.") else: os.makedirs(dirname(profiler_path), exist_ok=True) logger = logging.getLogger('callback') logger.setLevel(logging.DEBUG) handler = logging.FileHandler(profiler_path) logger.addHandler(handler) config.enable_resource_monitor() # derivatives derivatives = args.derivatives if type( args.derivatives) in (list, bool) else [args.derivatives] derivatives = list( map(lambda x: join(input_dir, 'derivatives', x), derivatives)) # pipelines pipelines = parse_pipelines(args.pipelines) temps.base_dir = args.workdir # creating workflow workflow = init_fmridenoise_wf(input_dir, derivatives=derivatives, subject=list(map(str, args.subjects)), session=list(map(str, args.sessions)), task=list(map(str, args.tasks)), runs=list(map(int, args.runs)), pipelines_paths=pipelines, high_pass=args.high_pass, low_pass=args.low_pass, base_dir=args.workdir) # creating graph from workflow if args.graph is not None: try: # TODO: Look for pydot/dot and add to requirements if not os.path.isabs(args.graph): workflow.write_graph(join(os.getcwd(), args.graph), graph2use='flat') else: workflow.write_graph(args.graph, graph2use='flat') except OSError as err: print('OSError: ' + err.args[0]) print(" Graph file was not generated.") # dry if not args.dry: # linear/multiproc if args.MultiProc: workflow_args['maxtasksperchild'] = 1 workflow.run(plugin="MultiProc", plugin_args=workflow_args) else: workflow.run() # write dataset_description.json after successful workflow execution with open( join(input_dir, "derivatives", "fmridenoise", "dataset_description.json"), 'w') as f: f.write(create_dataset_description_json_content()) return 0
def build_workflow(opts, retval): """ Create the Nipype Workflow for a graph given the inputs. All the checks and the construction of the workflow are done inside this function that has pickleable inputs and output dictionary (``retval``) to allow isolation using a ``multiprocessing.Process`` that allows funcworks to enforce a hard-limited memory-scope. """ from bids import BIDSLayout from nipype import logging as nlogging, config as ncfg from ..workflows.base import init_funcworks_wf from .. import __version__ build_log = nlogging.getLogger("nipype.workflow") output_dir = opts.output_dir.resolve() bids_dir = opts.bids_dir.resolve() work_dir = mkdtemp() if opts.work_dir is None else opts.work_dir.resolve() retval["return_code"] = 1 retval["workflow"] = None retval["bids_dir"] = bids_dir retval["output_dir"] = output_dir retval["work_dir"] = work_dir if not opts.database_path: database_path = str(opts.work_dir.resolve() / "dbcache") layout = BIDSLayout( bids_dir, derivatives=opts.derivatives, validate=True, database_file=database_path, reset_database=True, ) else: database_path = opts.database_path layout = BIDSLayout.load(database_path) if output_dir == bids_dir: build_log.error( "The selected output folder is the same as the input BIDS folder. " "Please modify the output path (suggestion: %s).", (bids_dir / "derivatives" / ("funcworks-%s" % __version__.split("+")[0])), ) retval["return_code"] = 1 return retval if bids_dir in opts.work_dir.parents: build_log.error("The selected working directory is a subdirectory " "of the input BIDS folder. " "Please modify the output path.") retval["return_code"] = 1 return retval # Set up some instrumental utilities runtime_uuid = "%s_%s" % (strftime("%Y%m%d-%H%M%S"), uuid.uuid4()) retval["runtime_uuid"] = runtime_uuid if opts.participant_label: retval["participant_label"] = opts.participant_label else: retval["participant_label"] = layout.get_subjects() # Load base plugin_settings from file if --use-plugin plugin_settings = { "plugin": "MultiProc", "plugin_args": { "raise_insufficient": False, "maxtasksperchild": 1 }, } if opts.use_plugin is not None: with open(opts.use_plugin) as f: plugin_settings = json.load(f) # Resource management options # Note that we're making strong assumptions about valid plugin args # This may need to be revisited if people try to use batch plugins # nthreads = plugin_settings['plugin_args'].get('n_procs') # Permit overriding plugin config with specific CLI options # if nthreads is None or opts.nthreads is not None: # nthreads = opts.nthreads # if nthreads is None or nthreads < 1: # nthreads = cpu_count() # plugin_settings['plugin_args']['n_procs'] = nthreads # if opts.mem_mb: # plugin_settings['plugin_args']['memory_gb'] = opts.mem_mb / 1024 # omp_nthreads = opts.omp_nthreads # if omp_nthreads == 0: # omp_nthreads = min(nthreads - 1 if nthreads > 1 else cpu_count(), 8) # if 1 < nthreads < omp_nthreads: # build_log.warning( # 'Per-process threads (--omp-nthreads=%d) exceed total ' # 'threads (--nthreads/--n_cpus=%d)', omp_nthreads, nthreads) retval["plugin_settings"] = plugin_settings # Set up directories # Check and create output and working directories output_dir.mkdir(exist_ok=True, parents=True) work_dir.mkdir(exist_ok=True, parents=True) # Nipype config (logs and execution) ncfg.update_config({ "logging": { "log_to_file": True }, "execution": { "crashfile_format": "txt", "get_linked_libs": False, # 'stop_on_first_crash': opts.stop_on_first_crash, }, "monitoring": { "enabled": opts.resource_monitor, "sample_frequency": "0.5", "summary_append": True, }, }) if opts.resource_monitor: ncfg.enable_resource_monitor() # Called with reports only # if opts.reports_only: # build_log.log(25, 'Running --reports-only on participants %s', # ', '.join(opts.participant_label)) # if opts.runtime_uuid is not None: # runtime_uuid = opts.runtime_uuid # retval['runtime_uuid'] = runtime_uuid # retval['return_code'] = generate_reports( # opts.participant_label, output_dir, work_dir, runtime_uuid, # packagename='funcworks') # return retval # Build main workflow build_log.log( 25, (f""" Running FUNCWORKS version {__version__}: * BIDS dataset path: {bids_dir}. * Participant list: {retval['participant_label']}. * Run identifier: {runtime_uuid}. """), ) if not opts.model_file: model_file = Path(bids_dir) / "models" / "model-default_smdl.json" if not model_file.exists(): raise ValueError("Default Model File not Found") else: model_file = opts.model_file retval["workflow"] = init_funcworks_wf( model_file=model_file, bids_dir=opts.bids_dir, output_dir=opts.output_dir, work_dir=opts.work_dir, database_path=str(database_path), participants=retval["participant_label"], analysis_level=opts.analysis_level, smoothing=opts.smoothing, runtime_uuid=runtime_uuid, use_rapidart=opts.use_rapidart, detrend_poly=opts.detrend_poly, align_volumes=opts.align_volumes, smooth_autocorrelations=opts.smooth_autocorrelations, despike=opts.despike, ) retval["return_code"] = 0 """ logs_path = Path(output_dir) / 'funcworks' / 'logs' boilerplate = retval['workflow'].visit_desc() if boilerplate: citation_files = { ext: logs_path / ('CITATION.%s' % ext) for ext in ('bib', 'tex', 'md', 'html') } # To please git-annex users and also to guarantee consistency # among different renderings of the same file, first remove any # existing one for citation_file in citation_files.values(): try: citation_file.unlink() except FileNotFoundError: pass citation_files['md'].write_text(boilerplate) build_log.log(25, 'Works derived from this FUNCWorks execution should ' 'include the following boilerplate:\n\n%s', boilerplate) """ return retval
def build_qsiprep_workflow(opts, retval): """ Create the Nipype Workflow that supports the whole execution graph, given the inputs. All the checks and the construction of the workflow are done inside this function that has pickleable inputs and output dictionary (``retval``) to allow isolation using a ``multiprocessing.Process`` that allows qsiprep to enforce a hard-limited memory-scope. """ from subprocess import check_call, CalledProcessError, TimeoutExpired from pkg_resources import resource_filename as pkgrf from bids import BIDSLayout from nipype import logging, config as ncfg from ..__about__ import __version__ from ..workflows.base import init_qsiprep_wf from ..utils.bids import collect_participants from ..viz.reports import generate_reports logger = logging.getLogger('nipype.workflow') INIT_MSG = """ Running qsiprep version {version}: * BIDS dataset path: {bids_dir}. * Participant list: {subject_list}. * Run identifier: {uuid}. """.format bids_dir = opts.bids_dir.resolve() output_dir = opts.output_dir.resolve() work_dir = opts.work_dir.resolve() retval['return_code'] = 1 retval['workflow'] = None retval['bids_dir'] = str(bids_dir) retval['work_dir'] = str(work_dir) retval['output_dir'] = str(output_dir) if output_dir == bids_dir: logger.error( 'The selected output folder is the same as the input BIDS folder. ' 'Please modify the output path (suggestion: %s).', bids_dir / 'derivatives' / ('qsiprep-%s' % __version__.split('+')[0])) retval['return_code'] = 1 return retval # Set up some instrumental utilities run_uuid = '%s_%s' % (strftime('%Y%m%d-%H%M%S'), uuid.uuid4()) retval['run_uuid'] = run_uuid # First check that bids_dir looks like a BIDS folder layout = BIDSLayout(str(bids_dir), validate=False) subject_list = collect_participants( layout, participant_label=opts.participant_label) retval['subject_list'] = subject_list output_spaces = opts.output_space or [] force_spatial_normalization = opts.force_spatial_normalization or \ 'template' in output_spaces if 'template' in output_spaces: logger.warning("Using 'template' as an output space is no longer supported.") output_spaces = ["T1w"] # Check output_space if not force_spatial_normalization and (opts.use_syn_sdc or opts.force_syn): msg = [ 'SyN SDC correction requires T1 to MNI registration.', 'Adding T1w-based normalization' ] force_spatial_normalization = True logger.warning(' '.join(msg)) # Load base plugin_settings from file if --use-plugin if opts.use_plugin is not None: from yaml import load as loadyml with open(opts.use_plugin) as f: plugin_settings = loadyml(f) plugin_settings.setdefault('plugin_args', {}) else: # Defaults plugin_settings = { 'plugin': 'MultiProc', 'plugin_args': { 'raise_insufficient': False, 'maxtasksperchild': 1, } } # Resource management options # Note that we're making strong assumptions about valid plugin args # This may need to be revisited if people try to use batch plugins nthreads = plugin_settings['plugin_args'].get('n_procs') # Permit overriding plugin config with specific CLI options if nthreads is None or opts.nthreads is not None: nthreads = opts.nthreads if nthreads is None or nthreads < 1: nthreads = cpu_count() plugin_settings['plugin_args']['n_procs'] = nthreads if opts.mem_mb: plugin_settings['plugin_args']['memory_gb'] = opts.mem_mb / 1024 omp_nthreads = opts.omp_nthreads if omp_nthreads == 0: omp_nthreads = min(nthreads - 1 if nthreads > 1 else cpu_count(), 8) if 1 < nthreads < omp_nthreads: logger.warning( 'Per-process threads (--omp-nthreads=%d) exceed total ' 'threads (--nthreads/--n_cpus=%d)', omp_nthreads, nthreads) retval['plugin_settings'] = plugin_settings logger.info('Running with omp_nthreads=%d, nthreads=%d', omp_nthreads, nthreads) # Set up directories log_dir = output_dir / 'qsiprep' / 'logs' # Check and create output and working directories output_dir.mkdir(exist_ok=True, parents=True) log_dir.mkdir(exist_ok=True, parents=True) work_dir.mkdir(exist_ok=True, parents=True) # Nipype config (logs and execution) ncfg.update_config({ 'logging': { 'log_directory': str(log_dir), 'log_to_file': True }, 'execution': { 'crashdump_dir': str(log_dir), 'crashfile_format': 'txt', 'get_linked_libs': False, 'stop_on_first_crash': opts.stop_on_first_crash or opts.work_dir is None, }, 'monitoring': { 'enabled': opts.resource_monitor, 'sample_frequency': '0.5', 'summary_append': True, } }) if opts.resource_monitor: ncfg.enable_resource_monitor() # Called with reports only if opts.reports_only: logger.log(25, 'Running --reports-only on participants %s', ', '.join(subject_list)) if opts.run_uuid is not None: run_uuid = opts.run_uuid retval['run_uuid'] = run_uuid retval['return_code'] = generate_reports(subject_list, output_dir, work_dir, run_uuid) return retval # Build main workflow logger.log( 25, INIT_MSG( version=__version__, bids_dir=bids_dir, subject_list=subject_list, uuid=run_uuid)) retval['workflow'] = init_qsiprep_wf( subject_list=subject_list, run_uuid=run_uuid, work_dir=work_dir, output_dir=str(output_dir), ignore=opts.ignore, hires=False, freesurfer=opts.do_reconall, debug=opts.sloppy, low_mem=opts.low_mem, anat_only=opts.anat_only, longitudinal=opts.longitudinal, b0_threshold=opts.b0_threshold, combine_all_dwis=opts.combine_all_dwis, distortion_group_merge=opts.distortion_group_merge, dwi_denoise_window=opts.dwi_denoise_window, unringing_method=opts.unringing_method, dwi_no_biascorr=opts.dwi_no_biascorr, no_b0_harmonization=opts.no_b0_harmonization, denoise_before_combining=opts.denoise_before_combining, write_local_bvecs=opts.write_local_bvecs, omp_nthreads=omp_nthreads, skull_strip_template=opts.skull_strip_template, skull_strip_fixed_seed=opts.skull_strip_fixed_seed, force_spatial_normalization=force_spatial_normalization, output_spaces=output_spaces, output_resolution=opts.output_resolution, template=opts.template, bids_dir=bids_dir, motion_corr_to=opts.b0_motion_corr_to, hmc_transform=opts.hmc_transform, hmc_model=opts.hmc_model, eddy_config=opts.eddy_config, shoreline_iters=opts.shoreline_iters, impute_slice_threshold=opts.impute_slice_threshold, b0_to_t1w_transform=opts.b0_to_t1w_transform, intramodal_template_iters=opts.intramodal_template_iters, intramodal_template_transform=opts.intramodal_template_transform, prefer_dedicated_fmaps=opts.prefer_dedicated_fmaps, fmap_bspline=opts.fmap_bspline, fmap_demean=opts.fmap_no_demean, use_syn=opts.use_syn_sdc, force_syn=opts.force_syn ) retval['return_code'] = 0 logs_path = Path(output_dir) / 'qsiprep' / 'logs' boilerplate = retval['workflow'].visit_desc() (logs_path / 'CITATION.md').write_text(boilerplate) logger.log( 25, 'Works derived from this qsiprep execution should ' 'include the following boilerplate:\n\n%s', boilerplate) # Generate HTML file resolving citations cmd = [ 'pandoc', '-s', '--bibliography', pkgrf('qsiprep', 'data/boilerplate.bib'), '--filter', 'pandoc-citeproc', str(logs_path / 'CITATION.md'), '-o', str(logs_path / 'CITATION.html') ] try: check_call(cmd, timeout=10) except (FileNotFoundError, CalledProcessError, TimeoutExpired): logger.warning('Could not generate CITATION.html file:\n%s', ' '.join(cmd)) # Generate LaTex file resolving citations cmd = [ 'pandoc', '-s', '--bibliography', pkgrf('qsiprep', 'data/boilerplate.bib'), '--natbib', str(logs_path / 'CITATION.md'), '-o', str(logs_path / 'CITATION.tex') ] try: check_call(cmd, timeout=10) except (FileNotFoundError, CalledProcessError, TimeoutExpired): logger.warning('Could not generate CITATION.tex file:\n%s', ' '.join(cmd)) return retval
def build_workflow(opts, retval): """ Create the Nipype Workflow that supports the whole execution graph, given the inputs. All the checks and the construction of the workflow are done inside this function that has pickleable inputs and output dictionary (``retval``) to allow isolation using a ``multiprocessing.Process`` that allows fmriprep to enforce a hard-limited memory-scope. """ from bids import BIDSLayout from nipype import logging as nlogging, config as ncfg from niworkflows.utils.bids import collect_participants from niworkflows.reports import generate_reports from ..__about__ import __version__ from ..workflows.base import init_fmriprep_wf build_log = nlogging.getLogger('nipype.workflow') INIT_MSG = """ Running fMRIPREP version {version}: * BIDS dataset path: {bids_dir}. * Participant list: {subject_list}. * Run identifier: {uuid}. """.format bids_dir = opts.bids_dir.resolve() output_dir = opts.output_dir.resolve() work_dir = opts.work_dir.resolve() retval['return_code'] = 1 retval['workflow'] = None retval['bids_dir'] = str(bids_dir) retval['output_dir'] = str(output_dir) retval['work_dir'] = str(work_dir) if output_dir == bids_dir: build_log.error( 'The selected output folder is the same as the input BIDS folder. ' 'Please modify the output path (suggestion: %s).', bids_dir / 'derivatives' / ('fmriprep-%s' % __version__.split('+')[0])) retval['return_code'] = 1 return retval output_spaces = parse_spaces(opts) # Set up some instrumental utilities run_uuid = '%s_%s' % (strftime('%Y%m%d-%H%M%S'), uuid.uuid4()) retval['run_uuid'] = run_uuid # First check that bids_dir looks like a BIDS folder layout = BIDSLayout(str(bids_dir), validate=False, ignore=("code", "stimuli", "sourcedata", "models", "derivatives", re.compile(r'^\.'))) subject_list = collect_participants( layout, participant_label=opts.participant_label) retval['subject_list'] = subject_list # Load base plugin_settings from file if --use-plugin if opts.use_plugin is not None: from yaml import load as loadyml with open(opts.use_plugin) as f: plugin_settings = loadyml(f) plugin_settings.setdefault('plugin_args', {}) else: # Defaults plugin_settings = { 'plugin': 'MultiProc', 'plugin_args': { 'raise_insufficient': False, 'maxtasksperchild': 1, } } # Resource management options # Note that we're making strong assumptions about valid plugin args # This may need to be revisited if people try to use batch plugins nthreads = plugin_settings['plugin_args'].get('n_procs') # Permit overriding plugin config with specific CLI options if nthreads is None or opts.nthreads is not None: nthreads = opts.nthreads if nthreads is None or nthreads < 1: nthreads = cpu_count() plugin_settings['plugin_args']['n_procs'] = nthreads if opts.mem_mb: plugin_settings['plugin_args']['memory_gb'] = opts.mem_mb / 1024 omp_nthreads = opts.omp_nthreads if omp_nthreads == 0: omp_nthreads = min(nthreads - 1 if nthreads > 1 else cpu_count(), 8) if 1 < nthreads < omp_nthreads: build_log.warning( 'Per-process threads (--omp-nthreads=%d) exceed total ' 'threads (--nthreads/--n_cpus=%d)', omp_nthreads, nthreads) retval['plugin_settings'] = plugin_settings # Set up directories log_dir = output_dir / 'fmriprep' / 'logs' # Check and create output and working directories output_dir.mkdir(exist_ok=True, parents=True) log_dir.mkdir(exist_ok=True, parents=True) work_dir.mkdir(exist_ok=True, parents=True) # Nipype config (logs and execution) ncfg.update_config({ 'logging': { 'log_directory': str(log_dir), 'log_to_file': True }, 'execution': { 'crashdump_dir': str(log_dir), 'crashfile_format': 'txt', 'get_linked_libs': False, 'stop_on_first_crash': opts.stop_on_first_crash, }, 'monitoring': { 'enabled': opts.resource_monitor, 'sample_frequency': '0.5', 'summary_append': True, } }) if opts.resource_monitor: ncfg.enable_resource_monitor() # Called with reports only if opts.reports_only: build_log.log(25, 'Running --reports-only on participants %s', ', '.join(subject_list)) if opts.run_uuid is not None: run_uuid = opts.run_uuid retval['run_uuid'] = run_uuid retval['return_code'] = generate_reports(subject_list, output_dir, work_dir, run_uuid, packagename='fmriprep') return retval # Build main workflow build_log.log( 25, INIT_MSG(version=__version__, bids_dir=bids_dir, subject_list=subject_list, uuid=run_uuid)) retval['workflow'] = init_fmriprep_wf( anat_only=opts.anat_only, aroma_melodic_dim=opts.aroma_melodic_dimensionality, bold2t1w_dof=opts.bold2t1w_dof, cifti_output=opts.cifti_output, debug=opts.sloppy, dummy_scans=opts.dummy_scans, echo_idx=opts.echo_idx, err_on_aroma_warn=opts.error_on_aroma_warnings, fmap_bspline=opts.fmap_bspline, fmap_demean=opts.fmap_no_demean, force_syn=opts.force_syn, freesurfer=opts.run_reconall, hires=opts.hires, ignore=opts.ignore, layout=layout, longitudinal=opts.longitudinal, low_mem=opts.low_mem, medial_surface_nan=opts.medial_surface_nan, omp_nthreads=omp_nthreads, output_dir=str(output_dir), output_spaces=output_spaces, run_uuid=run_uuid, regressors_all_comps=opts.return_all_components, regressors_fd_th=opts.fd_spike_threshold, regressors_dvars_th=opts.dvars_spike_threshold, skull_strip_fixed_seed=opts.skull_strip_fixed_seed, skull_strip_template=opts.skull_strip_template, subject_list=subject_list, t2s_coreg=opts.t2s_coreg, task_id=opts.task_id, use_aroma=opts.use_aroma, use_bbr=opts.use_bbr, use_syn=opts.use_syn_sdc, work_dir=str(work_dir), ) retval['return_code'] = 0 logs_path = Path(output_dir) / 'fmriprep' / 'logs' boilerplate = retval['workflow'].visit_desc() if boilerplate: citation_files = { ext: logs_path / ('CITATION.%s' % ext) for ext in ('bib', 'tex', 'md', 'html') } # To please git-annex users and also to guarantee consistency # among different renderings of the same file, first remove any # existing one for citation_file in citation_files.values(): try: citation_file.unlink() except FileNotFoundError: pass citation_files['md'].write_text(boilerplate) build_log.log( 25, 'Works derived from this fMRIPrep execution should ' 'include the following boilerplate:\n\n%s', boilerplate) return retval
def build_workflow(opts, retval): from nipype import logging as nlogging, config as ncfg from niworkflows.utils.bids import collect_participants from niworkflows.reports import generate_reports from ..__about__ import __version__ from time import strftime import uuid from ..workflows.base import init_base_wf build_log = nlogging.getLogger('nipype.workflow') INIT_MSG = """ # Running atlasTransform version {version}: # * BIDS dataset path: {bids_dir}. # * Participant list: {subject_list}. # * Run identifier: {uuid}. # """.format pass bids_dir = opts.bids_dir.resolve() output_dir = opts.output_dir.resolve() work_dir = opts.work_dir.resolve() retval['return_code'] = 1 retval['workflow'] = None retval['bids_dir'] = str(bids_dir) retval['output_dir'] = str(output_dir) retval['work_dir'] = str(work_dir) if output_dir == bids_dir: build_log.error( 'The selected output folder is the same as the input BIDS folder. ' 'Please modify the output path (suggestion: %s).', bids_dir / 'derivatives' / ('atlasTransform-%s' % __version__.split('+')[0])) retval['return_code'] = 1 return retval # Set up some instrumental utilities run_uuid = '%s_%s' % (strftime('%Y%m%d-%H%M%S'), uuid.uuid4()) retval['run_uuid'] = run_uuid # First check that bids_dir looks like a BIDS folder layout = BIDSLayout(str(bids_dir), validate=False, ignore=("code", "stimuli", "sourcedata", "models", "derivatives", re.compile(r'^\.'))) subject_list = collect_participants( layout, participant_label=opts.participant_label) retval['subject_list'] = subject_list # Load base plugin_settings from file if --use-plugin if opts.use_plugin is not None: from yaml import load as loadyml with open(opts.use_plugin) as f: plugin_settings = loadyml(f) plugin_settings.setdefault('plugin_args', {}) else: # Defaults plugin_settings = { 'plugin': 'MultiProc', 'plugin_args': { 'raise_insufficient': False, 'maxtasksperchild': 1, } } # Resource management options # Note that we're making strong assumptions about valid plugin args # This may need to be revisited if people try to use batch plugins nthreads = plugin_settings['plugin_args'].get('n_procs') # Permit overriding plugin config with specific CLI options if nthreads is None or opts.nthreads is not None: nthreads = opts.nthreads if nthreads is None or nthreads < 1: nthreads = cpu_count() plugin_settings['plugin_args']['n_procs'] = nthreads if opts.mem_mb: plugin_settings['plugin_args']['memory_gb'] = opts.mem_mb / 1024 omp_nthreads = opts.omp_nthreads if omp_nthreads == 0: omp_nthreads = min(nthreads - 1 if nthreads > 1 else cpu_count(), 8) if 1 < nthreads < omp_nthreads: build_log.warning( 'Per-process threads (--omp-nthreads=%d) exceed total ' 'threads (--nthreads/--n_cpus=%d)', omp_nthreads, nthreads) retval['plugin_settings'] = plugin_settings # Set up directories log_dir = output_dir / 'atlasTransform' / 'logs' # Check and create output and working directories output_dir.mkdir(exist_ok=True, parents=True) log_dir.mkdir(exist_ok=True, parents=True) work_dir.mkdir(exist_ok=True, parents=True) # Nipype config (logs and execution) ncfg.update_config({ 'logging': { 'log_directory': str(log_dir), 'log_to_file': True }, 'execution': { 'crashdump_dir': str(log_dir), 'crashfile_format': 'txt', 'get_linked_libs': False, 'stop_on_first_crash': opts.stop_on_first_crash, }, 'monitoring': { 'enabled': opts.resource_monitor, 'sample_frequency': '0.5', 'summary_append': True, } }) if opts.resource_monitor: ncfg.enable_resource_monitor() # Called with reports only if opts.reports_only: build_log.log(25, 'Running --reports-only on participants %s', ', '.join(subject_list)) if opts.run_uuid is not None: run_uuid = opts.run_uuid retval['run_uuid'] = run_uuid retval['return_code'] = generate_reports(subject_list, output_dir, work_dir, run_uuid, packagename='atlasTransform') return retval # Build main workflow build_log.log( 25, INIT_MSG(version=__version__, bids_dir=bids_dir, subject_list=subject_list, uuid=run_uuid)) retval['workflow'] = init_base_wf( opts=opts, layout=layout, run_uuid=run_uuid, subject_list=subject_list, work_dir=str(work_dir), output_dir=str(output_dir), ) retval['return_code'] = 0 logs_path = Path(output_dir) / 'atlasTransform' / 'logs' boilerplate = retval['workflow'].visit_desc() if boilerplate: citation_files = { ext: logs_path / ('CITATION.%s' % ext) for ext in ('bib', 'tex', 'md', 'html') } # To please git-annex users and also to guarantee consistency # among different renderings of the same file, first remove any # existing one for citation_file in citation_files.values(): try: citation_file.unlink() except FileNotFoundError: pass citation_files['md'].write_text(boilerplate) build_log.log( 25, 'Works derived from this atlasTransform execution should ' 'include the following boilerplate:\n\n%s', boilerplate) return retval