From f0d18ec5089968db60e4c115c9b2019f84d06232 Mon Sep 17 00:00:00 2001 From: Robert Luke <748691+rob-luke@users.noreply.github.com> Date: Sun, 20 Jun 2021 22:08:04 +1000 Subject: [PATCH] Implement BIDS Exec (#9) --- .github/workflows/boutiques.yml | 58 ++++++++++++++++++ .github/workflows/tests.yml | 6 +- Dockerfile | 3 +- example_invocation.json | 13 +++++ fnirsapp_qr.json | 100 ++++++++++++++++++++++++++++++++ run.py => fnirsapp_qr.py | 36 ++++++------ 6 files changed, 195 insertions(+), 21 deletions(-) create mode 100644 .github/workflows/boutiques.yml create mode 100644 example_invocation.json create mode 100644 fnirsapp_qr.json rename run.py => fnirsapp_qr.py (88%) diff --git a/.github/workflows/boutiques.yml b/.github/workflows/boutiques.yml new file mode 100644 index 0000000..a53257f --- /dev/null +++ b/.github/workflows/boutiques.yml @@ -0,0 +1,58 @@ +name: "Tests - Boutique" +on: + # Trigger the workflow on push or pull request, but only for the main branch + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + docker: + runs-on: ubuntu-latest + steps: + + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v1 + + - uses: BSFishy/pip-action@v1 + with: + packages: | + boutiques + + - name: Download test data + shell: bash -el {0} + run: | + curl -L https://github.com/rob-luke/BIDS-NIRS-Tapping/archive/master.zip --output data.zip + pwd + unzip data.zip -d ./example_data + ls + + - name: Where are we and whats around + run: | + pwd + ls + + - name: Run bosh + run: | + bosh exec launch fnirsapp_qr.json example_invocation.json --debug + + - name: Where are we and whats around + run: | + pwd + ls + ls example_data + ls example_data/BIDS-NIRS-Tapping-master + ls example_data/BIDS-NIRS-Tapping-master/derivatives + ls example_data/BIDS-NIRS-Tapping-master/derivatives/fnirs-app-quality-reports + + - uses: actions/upload-artifact@v2 + with: + name: Bosh output + path: | + /home/runner/work/fnirs-apps-quality-reports/fnirs-apps-quality-reports/*bosh* + example_data/BIDS-NIRS-Tapping-master/derivatives/fnirs-app-quality-reports/* \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a77e97a..5bcef7a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -41,10 +41,10 @@ jobs: run: docker build --progress=plain -t test . - name: Run docker image - run: docker run -v /home/runner/example_data/BIDS-NIRS-Tapping-master/:/bids_dataset test --participant_label 03 + run: docker run -v /home/runner/example_data/BIDS-NIRS-Tapping-master/:/bids_dataset test --subject-label 03 - name: Run docker image - run: docker run -v /home/runner/example_data/BIDS-NIRS-Tapping-master/:/bids_dataset test --participant_label 03 --pp_threshold 0.6 + run: docker run -v /home/runner/example_data/BIDS-NIRS-Tapping-master/:/bids_dataset test --subject-label 03 --pp-threshold 0.6 - name: Run docker image - run: docker run -v /home/runner/example_data/BIDS-NIRS-Tapping-master/:/bids_dataset test --participant_label 03 --pp_threshold 0.6 --sci_threshold 0.8 + run: docker run -v /home/runner/example_data/BIDS-NIRS-Tapping-master/:/bids_dataset test --subject-label 03 --pp-threshold 0.6 --sci-threshold 0.8 diff --git a/Dockerfile b/Dockerfile index 5402c4b..95c4885 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,6 +14,7 @@ RUN pip install https://github.com/nilearn/nilearn/archive/main.zip RUN pip install mne-nirs RUN pip install h5py -COPY run.py /run.py +COPY fnirsapp_qr.py /run.py +RUN chmod +x /run.py ENTRYPOINT ["/run.py"] \ No newline at end of file diff --git a/example_invocation.json b/example_invocation.json new file mode 100644 index 0000000..33e2aa2 --- /dev/null +++ b/example_invocation.json @@ -0,0 +1,13 @@ +{ + "input_datasets": "/home/runner/work/fnirs-apps-quality-reports/fnirs-apps-quality-reports/example_data/BIDS-NIRS-Tapping-master", + "output_location": "/home/runner/work/fnirs-apps-quality-reports/fnirs-apps-quality-reports/example_data/BIDS-NIRS-Tapping-master/derivatives/fnirs-app-quality-reports", + "subject_label": [ + "01", + "02" + ], + "task_label": [ + "tapping" + ], + "pp_threshold": 0.6, + "sci_threshold": 0.7 +} \ No newline at end of file diff --git a/fnirsapp_qr.json b/fnirsapp_qr.json new file mode 100644 index 0000000..aec68c1 --- /dev/null +++ b/fnirsapp_qr.json @@ -0,0 +1,100 @@ +{ + "name": "fNIRS Apps: Quality Reports", + "description": "A GLM pipeline for quality reports", + "author": "Robert Luke", + "tool-version": "v0.1.0", + "schema-version": "0.5", + "command-line": "/run.py [InputDataset] [OutputLocation] [SubjectLabel] [TaskLabel] [SCIThreshold] [PeakPowerThreshold]", + "container-image": { + "image": "ghcr.io/rob-luke/fnirs-apps-quality-reports/app", + "index": "ghcr.io", + "type": "docker", + "entrypoint": true + }, + "inputs": [ + { + "command-line-flag": "--input-datasets", + "id": "input_datasets", + "description": "The directory with the input dataset formatted according to the BIDS standard.", + "name": "input-datasets", + "optional": true, + "type": "File", + "value-key": "[InputDataset]" + }, + { + "command-line-flag": "--output-location", + "id": "output_location", + "description": "The directory where the output files should be stored.", + "name": "output-location", + "optional": true, + "type": "File", + "value-key": "[OutputLocation]" + }, + { + "command-line-flag": "--subject-label", + "description": "The label(s) of the subjects(s) that should be analyzed. The label corresponds to sub- from the BIDS spec (so it does not include \"sub-\"). If this parameter is not provided all subjects should be analyzed. Multiple participants can be specified with a space separated list.", + "id": "subject_label", + "name": "subject-label", + "optional": true, + "type": "String", + "list": true, + "value-key": "[SubjectLabel]" + }, + { + "command-line-flag": "--task-label", + "description": "The label(s) of the tasks(s) that should be analyzed. The label corresponds to task- from the BIDS spec. If this parameter is not provided all tasks should be analyzed. Multiple tasks can be specified with a space separated list.", + "id": "task_label", + "name": "task-label", + "optional": true, + "type": "String", + "list": true, + "value-key": "[TaskLabel]" + }, + { + "command-line-flag": "--sci-threshold", + "id": "sci_threshold", + "name": "sci-threshold", + "optional": true, + "type": "Number", + "value-key": "[SCIThreshold]" + }, + { + "command-line-flag": "--pp-threshold", + "id": "pp_threshold", + "name": "pp-threshold", + "optional": true, + "type": "Number", + "value-key": "[PeakPowerThreshold]" + } + ], + "output-files": [ + { + "id": "output_directory", + "name": "BIDS derivatives directory", + "optional": false, + "path-template": "[OutputLocation]" + } + ], + "suggested-resources": { + "cpu-cores": 1, + "ram": 1, + "walltime-estimate": 60 + }, + "tags": { + "domain": [ + "neuroimaging", + "fnirs", + "quality", + "bids" + ] + }, + "error-codes": [ + { + "code": 1, + "description": "Crashed" + } + ], + "custom": { + "BIDSAppSpecVersion": "Draft" + } +} \ No newline at end of file diff --git a/run.py b/fnirsapp_qr.py similarity index 88% rename from run.py rename to fnirsapp_qr.py index 176edf4..5ceed73 100755 --- a/run.py +++ b/fnirsapp_qr.py @@ -15,10 +15,10 @@ import os import subprocess -__version__ = "v0.0.13" +__version__ = "v0.1.0" -def run(command, env={}): +def fnirsapp_qr(command, env={}): merged_env = os.environ merged_env.update(env) process = subprocess.Popen(command, stdout=subprocess.PIPE, @@ -35,27 +35,29 @@ def run(command, env={}): parser = argparse.ArgumentParser(description='Quality Reports') -parser.add_argument('--bids_dir', default="/bids_dataset", type=str, +parser.add_argument('--input-datasets', default="/bids_dataset", type=str, help='The directory with the input dataset ' 'formatted according to the BIDS standard.') -parser.add_argument('--sci_threshold', type=float, default=0.0, - help='Threshold below which a channel is marked as bad.') -parser.add_argument('--pp_threshold', type=float, default=0.0, - help='Threshold below which a channel is marked as bad.') -parser.add_argument('--participant_label', +parser.add_argument('--output-location', default="/bids_dataset/derivatives/fnirs-apps-quality-reports", + type=str, help='The directory where the output files should be stored.') +parser.add_argument('--subject-label', help='The label(s) of the participant(s) that should be ' 'analyzed. The label corresponds to ' - 'sub- from the BIDS spec (so it does ' + 'sub- from the BIDS spec (so it does ' 'not include "sub-"). If this parameter is not provided ' 'all subjects should be analyzed. Multiple participants ' 'can be specified with a space separated list.', nargs="+") -parser.add_argument('--task_label', +parser.add_argument('--task-label', help='The label(s) of the tasks(s) that should be ' 'analyzed. If this parameter is not provided ' 'all tasks should be analyzed. Multiple tasks ' 'can be specified with a space separated list.', nargs="+") +parser.add_argument('--sci-threshold', type=float, default=0.0, + help='Threshold below which a channel is marked as bad.') +parser.add_argument('--pp-threshold', type=float, default=0.0, + help='Threshold below which a channel is marked as bad.') parser.add_argument('-v', '--version', action='version', version='BIDS-App Scalp Coupling Index version ' f'{__version__}') @@ -69,11 +71,11 @@ def run(command, env={}): ids = [] # only for a subset of subjects -if args.participant_label: - ids = args.participant_label +if args.subject_label: + ids = args.subject_label # for all subjects else: - subject_dirs = glob(op.join(args.bids_dir, "sub-*")) + subject_dirs = glob(op.join(args.input_datasets, "sub-*")) ids = [subject_dir.split("-")[-1] for subject_dir in subject_dirs] print(f"No participants specified, processing {ids}") @@ -83,7 +85,7 @@ def run(command, env={}): if args.task_label: tasks = args.task_label else: - all_snirfs = glob(f"{args.bids_dir}/**/*_nirs.snirf", recursive=True) + all_snirfs = glob(f"{args.input_datasets}/**/*_nirs.snirf", recursive=True) for a in all_snirfs: s = a.split("_task-")[1] s = s.split("_nirs.snirf")[0] @@ -198,11 +200,11 @@ def summarise_odpsd(raw, report): ######################################## print(" ") -Path(f"{args.bids_dir}/derivatives/fnirs-apps-quality-reports/").\ +Path(f"{args.output_location}/").\ mkdir(parents=True, exist_ok=True) for id in ids: report = mne.Report(verbose=True, raw_psd=True) - report.parse_folder(f"{args.bids_dir}/sub-{id}", render_bem=False) + report.parse_folder(f"{args.input_datasets}/sub-{id}", render_bem=False) for idx, fname in enumerate(report.fnames): if mne.report._endswith(fname, 'nirs'): raw = mne.io.read_raw_snirf(fname) @@ -215,7 +217,7 @@ def summarise_odpsd(raw, report): raw, report = summarise_sci(raw, report, threshold=args.sci_threshold) raw, report = summarise_montage(raw, report) - report.save(f"{args.bids_dir}/derivatives/fnirs-apps-quality-reports/" + report.save(f"{args.output_location}/" f"report_basic_{id}.html", overwrite=True, open_browser=False)