diff --git a/Dockerfile b/Dockerfile index 4a40ac5..96c9c40 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,9 @@ LABEL org.opencontainers.image.authors="grdryn " \ org.opencontainers.image.description="A ChRIS plugin that..." ADD https://github.com/rordenlab/dcm2niix/releases/download/v1.0.20220720/dcm2niix_lnx.zip /tmp/dcm2niix_lnx.zip -RUN apt-get -y update && apt-get install -y unzip && unzip /tmp/dcm2niix_lnx.zip -d /usr/local/bin/ && apt-get remove -y unzip +RUN apt-get -y update && \ + apt-get install -y unzip git git-annex && \ + unzip /tmp/dcm2niix_lnx.zip -d /usr/local/bin/ WORKDIR /usr/local/src diff --git a/pl_heudiconv/heudiconv.py b/pl_heudiconv/heudiconv.py index c76bc29..8949dfc 100644 --- a/pl_heudiconv/heudiconv.py +++ b/pl_heudiconv/heudiconv.py @@ -8,6 +8,7 @@ # dev@babyMRI.org # +import os import subprocess from chrisapp.base import ChrisApp @@ -26,15 +27,9 @@ Gstr_synopsis = """ -(Edit this in-line help for app specifics. At a minimum, the -flags below are supported -- in the case of DS apps, both -positional arguments and ; for FS and TS apps -only -- and similarly for directories -where necessary.) - NAME - heudiconv + pl-heudiconv SYNOPSIS @@ -53,9 +48,9 @@ * Bare bones execution - docker run --rm -u $(id -u) \ - -v $(pwd)/in:/incoming -v $(pwd)/out:/outgoing \ - rh-impact/pl-heudiconv pl-heudiconv \ + docker run --rm -u $(id -u) \\ + -v $(pwd)/in:/incoming -v $(pwd)/out:/outgoing \\ + quay.io/rh-impact/pl-heudiconv pl-heudiconv \\ /incoming /outgoing DESCRIPTION @@ -89,7 +84,7 @@ class Heudiconv(ChrisApp): """ - An app to ... + An app to organize brain imaging data into structured directory layouts. """ PACKAGE = __package__ TITLE = 'A ChRIS plugin for heudiconv' @@ -122,24 +117,279 @@ def define_parameters(self): Use self.add_argument to specify a new app argument. """ + self.add_argument( + '--inputdir-type', + default='files', + choices=('files', 'dicom_dir_template'), + optional=True, + dest='inputdir_type', + type=str, + help='''For input, heudiconv accepts EITHER a "--files" + argument, or a "--dicom_dir_template" argument. This + option specifies which to use for the positional + "inputdir" argument to this plugin.''') + + self.add_argument( + '-f', '--heuristic', + optional=True, + default='reproin', + dest='heuristic', + type=str, + help='''Name of a known heuristic or path to the Python + script containing heuristic.''') + + self.add_argument( + '-b', '--bids', + optional=True, + default=False, + dest='bids', + type=bool, + help='''Flag for output into BIDS structure. Can also take + BIDS-specific options by using --bids-options, e.g., + --bids-options notop.''') + + self.add_argument( + '--bids-options', + optional=True, + nargs='+', + default=[], + choices=['notop'], + metavar=('BIDSOPTION1', 'BIDSOPTION2'), + dest='bids_options', + type=str, + help='''The only currently supported bids options is + "notop", which skips creation of top-level BIDS + files. This is useful when running in batch mode to + prevent possible race conditions.''') + + self.add_argument( + '--overwrite', + optional=True, + default=False, + dest='overwrite', + type=bool, + help='Overwrite existing converted files.') + + self.add_argument( + '--datalad', + optional=True, + default=False, + dest='datalad', + type=bool, + help='''Store the entire collection as DataLad + dataset(s). Small files will be committed directly to git, + while large to annex. New version (6) of annex + repositories will be used in a "thin" mode so it would + look to mortals as just any other regular directory + (i.e. no symlinks to under .git/annex). For now just for + BIDS mode.''') + + self.add_argument( + '--minmeta', + optional=True, + default=False, + dest='minmeta', + type=bool, + help='Exclude dcmstack meta information in sidecar jsons.') + + self.add_argument( + '--random-seed', + optional=True, + default=[], + dest='random_seed', + type=int, + help='Random seed to initialize RNG.') + + self.add_argument( + '-l', '--locator', + optional=True, + default=[], + dest='locator', + type=str, + help='''Study path under outdir. If provided, it overloads + the value provided by the heuristic. If --datalad is + enabled, every directory within locator becomes a + super-dataset thus establishing a hierarchy. Setting to + "unknown" will skip that dataset.''') + + self.add_argument( + '-ss', '--ses', + optional=True, + default=[], + dest='session', + type=str, + help='''Session for longitudinal study_sessions. Default is + None.''') + + self.add_argument( + '-p', '--with-prov', + optional=True, + default=False, + dest='with_prov', + type=bool, + help='Store additional provenance information.') + + self.add_argument( + '--command', + default=[], + choices=( + 'heuristics', 'heuristic-info', 'ls', 'populate-templates', + 'sanitize-jsons', 'treat-jsons', 'populate-intended-for'), + optional=True, + dest='command', + type=str, + help='''Custom action to be performed on provided files + instead of regular operation.''') + + self.add_argument( + '--anon-cmd', + default=[], + optional=True, + dest='anon_cmd', + type=str, + help='Command to run to convert subject IDs used for DICOMs to ' + 'anonymized IDs. Such command must take a single argument and ' + 'return a single anonymized ID.') + + self.add_argument( + '-s', '--subjects', + dest='subjects', + optional=True, + default=[], + type=str, + nargs='*', + help='''List of subjects - required for dicom template. If + not provided, DICOMS would first be "sorted" and subject + IDs deduced by the heuristic.''') + + self.add_argument( + '-g', '--grouping', + default=[], + choices=('studyUID', 'accession_number', 'all', 'custom'), + optional=True, + dest='grouping', + type=str, + help='How to group dicoms (default: by studyUID)') + + self.add_argument( + '--dcmconfig', + default=[], + optional=True, + dest='dcmconfig', + type=str, + help='JSON file for additional dcm2niix configuration.') + + submission = self.add_argument_group('Conversion submission options') + submission.add_argument( + '-q', '--queue', + choices=("SLURM", None), + default=None, + help='Batch system to submit jobs in parallel.') + + submission.add_argument( + '--queue-args', + dest='queue_args', + default=None, + help='''Additional queue arguments passed as a single + string of space-separated Argument=Value pairs.''') + + gitopts = self.add_argument_group( + 'Git config arguments', + 'Used to set user details for git commits when using the datalad option.') + + gitopts.add_argument( + '--git-user-name', + default='ChRIS HeuDiConv Plugin', + dest='git_user_name', + type=str, + help='''User name to use for Git commits when --datalad is + specified. It will be set as the value of the + "GIT_AUTHOR_NAME" and "GIT_COMMITTER_NAME" environment + variables. Defaults to "ChRIS HeuDiConv Plugin"''') + + gitopts.add_argument( + '--git-user-email', + default='pl-heudiconv@chrisproject.org', + dest='git_user_email', + type=str, + help='''User email to use for Git commits when --datalad + is specified. It will be set as the value of the + "GIT_AUTHOR_EMAIL" and "GIT_COMMITTER_EMAIL" environment + variables. Defaults to "pl-heudiconv@chrisproject.org"''') + def run(self, options): """ Define the code to be run by this plugin app. """ - cmd = ( - '/usr/local/bin/heudiconv', - '--files', options.inputdir, - '-f', 'reproin', - '-o', options.outputdir, - '--bids' - ) + cmd = [ + 'heudiconv', + '--' + options.inputdir_type, options.inputdir, + '--outdir', options.outputdir, + '--heuristic', options.heuristic + ] + + if options.bids: + cmd = cmd + ['--bids'] + if options.bids_options: + print('bids_options: ') + print(options.bids_options) + cmd = cmd + options.bids_options + + if options.overwrite: + cmd = cmd + ['--overwrite'] + + if options.datalad: + cmd = cmd + ['--datalad'] + + if options.minmeta: + cmd = cmd + ['--minmeta'] + + if options.with_prov: + cmd = cmd + ['--with-prov'] + + if options.random_seed: + cmd = cmd + ['--random-seed', str(options.random_seed)] + + if options.command: + cmd = cmd + ['--command', options.command] + + if options.grouping: + cmd = cmd + ['--grouping', options.grouping] + + if options.locator: + cmd = cmd + ['--locator', options.locator] + + if options.session: + cmd = cmd + ['--ses', options.session] + + if options.dcmconfig: + cmd = cmd + ['--dcmconfig', options.dcmconfig] + + if options.queue: + cmd = cmd + ['--queue', options.queue] + + if options.queue_args: + cmd = cmd + ['--queue-args', options.queue_args] + + if options.anon_cmd: + cmd = cmd + ['--anon-cmd', options.anon_cmd] + + if options.subjects: + cmd = cmd + ['--subjects'] + options.subjects + print(Gstr_title) print('Version: %s' % self.get_version()) + env = os.environ.copy() + env["GIT_AUTHOR_NAME"] = options.git_user_name + env["GIT_AUTHOR_EMAIL"] = options.git_user_email + env["GIT_COMMITTER_NAME"] = options.git_user_name + env["GIT_COMMITTER_EMAIL"] = options.git_user_email + print(f'Command: {" ".join(map(str, cmd))}') - subprocess.run(cmd, check=True) + subprocess.run(cmd, check=True, env=env) def show_man_page(self): """ diff --git a/requirements.txt b/requirements.txt index 1758e73..400d36e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ chrisapp~=2.5.3 nose~=1.3.7 heudiconv~=0.11.3 +datalad~=0.17.5 diff --git a/setup.py b/setup.py index 07606aa..34ce8fa 100755 --- a/setup.py +++ b/setup.py @@ -7,13 +7,13 @@ setup( name = 'pl-heudiconv', version = '0.1', - description = 'An app to ...', + description = 'An app to organize brain imaging data into structured directory layouts', long_description = readme, author = 'grdryn', author_email = 'gerard@ryan.lt', - url = 'http://wiki', + url = 'https://github.com/rh-impact/pl-heudiconv', packages = ['pl_heudiconv'], - install_requires = ['chrisapp', 'heudiconv'], + install_requires = ['chrisapp', 'heudiconv', 'datalad'], test_suite = 'nose.collector', tests_require = ['nose'], license = 'MIT',