20: The command line

Run the whole pipeline from a shell, with no Python, and read the JSON summary it prints.

Introduction

Everything the tutorials did in Python is also a shell command. The evoparam console script has thirteen sub-commands, for a pipeline you can put in a batch script, a cluster job or a Makefile without writing any Python:

  • simulate writes a synthetic BIDS dataset with a known truth;

  • detect-events finds the pulses in a continuous file;

  • clean removes the artifact; test runs the existence test;

  • fit does the whole chain (read, clean, reference, fit, test) and writes BIDS derivatives;

  • report makes an HTML, PDF or Markdown report;

  • validate-bids, reproduce, migrate, info, presets and cite.

Every command takes --json (one JSON summary on stdout), --random-state and --config (a JSON or TOML file of options). The exit code says what happened: 0 success, 2 usage error, 3 partial (some channel failed), 4 a missing optional package, 5 a bad input, 6 a reproduction mismatch. We drive it here with subprocess, exactly as a shell would.

import json
import subprocess
import sys
import tempfile
from pathlib import Path

work = Path(tempfile.mkdtemp(prefix="evoparam-cli-"))


def evoparam(*args):
    """Run one ``evoparam`` command with ``--json`` and return its parsed summary."""
    proc = subprocess.run(
        [sys.executable, "-m", "evoparam", *map(str, args), "--json"],
        capture_output=True, text=True, check=False,
    )  # fmt: skip
    summary = json.loads(proc.stdout.splitlines()[-1])
    cmd = " ".join(map(str, args))
    print(f"$ evoparam {cmd}  ->  {summary['status']} (exit {proc.returncode})")
    return summary

1. Make a dataset

simulate writes a small BIDS dataset with ground truth, so the rest of the tutorial needs no data of your own. In real use you point fit at your own BIDS root instead.

evoparam("simulate", "--modality", "ccep", "--n-trials", "10", "--n-channels", "4",
         "--seed", "0", "--out", work / "bids")  # fmt: skip
$ evoparam simulate --modality ccep --n-trials 10 --n-channels 4 --seed 0 --out /tmp/evoparam-cli-8hsp1487/bids  ->  ok (exit 0)

{'command': 'simulate', 'version': '0.1.0.dev0', 'status': 'ok', 'exit_code': 0, 'outputs': ['/tmp/evoparam-cli-8hsp1487/bids/sub-01/ieeg/sub-01_task-ccep_ieeg.vhdr'], 'n_units': {'ok': 0, 'no_response': 0, 'failed': 0, 'skipped': 0}, 'warnings': [], 'duration_s': 1.026036268000098}

2. Check the stimulation metadata, then fit

validate-bids reports, run by run, which stimulation fields are present, inferred, missing or inconsistent. Then fit runs the whole chain and writes the derivatives.

summary = evoparam("validate-bids", work / "bids")
print("  metadata:", summary["result"]["counts"])

summary = evoparam("fit", work / "bids", "--sub", "01", "--task", "ccep", "--preset", "ccep",
                    "--random-state", "0",
                    "--out", work / "bids/derivatives/evoparam")  # fmt: skip
print("  units:", summary["n_units"])
print("  wrote:", len(summary["outputs"]), "files")
$ evoparam validate-bids /tmp/evoparam-cli-8hsp1487/bids  ->  ok (exit 0)
  metadata: {'present': 6, 'missing': 3, 'inferred': 1}
$ evoparam fit /tmp/evoparam-cli-8hsp1487/bids --sub 01 --task ccep --preset ccep --random-state 0 --out /tmp/evoparam-cli-8hsp1487/bids/derivatives/evoparam  ->  ok (exit 0)
  units: {'ok': 3, 'no_response': 1, 'failed': 0, 'skipped': 0}
  wrote: 15 files

3. Report, and the JSON summary

report turns the derivatives into a Markdown (or HTML, or PDF) report. The --json object is the same on every command: status, exit_code, outputs, n_units (ok, no_response, failed, skipped), warnings and duration_s. A script reads that object to decide what to do next, and the exit code to stop on an error.

summary = evoparam("report", work / "bids/derivatives/evoparam", "--format", "md",
                   "--out", work / "report")  # fmt: skip
print("\nfull JSON summary of the last command:")
keys = ("command", "status", "exit_code", "n_units")
print(json.dumps({key: summary[key] for key in keys}, indent=2))
$ evoparam report /tmp/evoparam-cli-8hsp1487/bids/derivatives/evoparam --format md --out /tmp/evoparam-cli-8hsp1487/report  ->  ok (exit 0)

full JSON summary of the last command:
{
  "command": "report",
  "status": "ok",
  "exit_code": 0,
  "n_units": {
    "ok": 0,
    "no_response": 0,
    "failed": 0,
    "skipped": 0
  }
}

The same four lines, as a shell script, are:

evoparam simulate --modality ccep --n-trials 10 --n-channels 4 --seed 0 --out bids/
evoparam validate-bids bids/
evoparam fit bids/ --sub 01 --task ccep --preset ccep --out bids/derivatives/evoparam
evoparam report bids/derivatives/evoparam --format html

evoparam fit bids/ ... writes exactly the same files as the Python write_bids_derivatives(fit(read_bids(...))), so you can prototype in Python and run in a shell, or the other way round.

Conclusion

The command line is the whole package without Python: simulate for test data, fit for the pipeline, report for the write-up, and the smaller commands for events, cleaning, the existence test, validation, reproduction and citation. --json plus the exit codes make it safe to script over many subjects on a cluster.

Total running time of the script: (0 minutes 5.545 seconds)

Gallery generated by Sphinx-Gallery