Why use the CLI?
Sometimes you want the fmrigds pipeline without opening
an R session for each run. The CLI gives you the same lazy plan grammar
that you use in R, but in a form that fits scripts, shell pipelines, and
scheduled jobs.
The common path is short: inspect a source, save a plan, preview a small block, then execute it. When you need more control, the same CLI also accepts metadata tables, model formulas, reducer options, post-hoc options, and saved plan JSON.
At the terminal, the workflow looks like this:
fmrigds probe --input group.csv
fmrigds plan --input group.csv --reduce fixed --save-plan plan.json
fmrigds preview --load-plan plan.json --n 3
fmrigds run --load-plan plan.json --out results.h5The chunks below call the same CLI parser from inside R so the examples stay runnable in the vignette.
What are we driving?
We’ll use a small long-table input with ROI labels, four subjects,
and two contrasts. The extra time column will become
contrast metadata later.
read.csv(stats_path, stringsAsFactors = FALSE)
#> sample subject contrast time beta var
#> 1 ROI_1 sub-01 baseline 0 0.50 0.04
#> 2 ROI_1 sub-01 task 1 0.80 0.04
#> 3 ROI_1 sub-02 baseline 0 0.40 0.05
#> 4 ROI_1 sub-02 task 1 0.90 0.05
#> 5 ROI_1 sub-03 baseline 0 0.45 0.05
#> 6 ROI_1 sub-03 task 1 1.05 0.05
#> 7 ROI_1 sub-04 baseline 0 0.52 0.05
#> 8 ROI_1 sub-04 task 1 1.12 0.05
#> 9 ROI_2 sub-01 baseline 0 0.60 0.03
#> 10 ROI_2 sub-01 task 1 0.70 0.03
#> 11 ROI_2 sub-02 baseline 0 0.55 0.02
#> 12 ROI_2 sub-02 task 1 0.95 0.02
#> 13 ROI_2 sub-03 baseline 0 0.58 0.02
#> 14 ROI_2 sub-03 task 1 1.10 0.02
#> 15 ROI_2 sub-04 baseline 0 0.62 0.02
#> 16 ROI_2 sub-04 task 1 1.18 0.02How do you get a result quickly?
If you already know the source and the reducer you want,
run is the shortest path from input table to output
file.
cli_show(
"run",
"--input", stats_path,
"--subset", "contrast=task",
"--reduce", "fixed",
"--out", quick_out
)
#> $ fmrigds 'run' '--input' '/tmp/Rtmp17Odxi/file1cec36df0018.csv' '--subset' 'contrast=task' '--reduce' 'fixed' '--out' '/tmp/Rtmp17Odxi/file1cec1ed7fd8f.csv'
#> GDS
#> dims: 2 x 1 x 1 [sample x subject x contrast]
#> space: sample_labels n=2
#> subjects: 1 (meta)
#> contrasts: 1 (task)
#> assays: beta[location], var[variance], beta_g[?], var_g[?], se_g[?], z_g[?], p_g[?], Q[?], I2[?], n_eff[n_eff], z[z], p[p]
head(read.csv(quick_out, stringsAsFactors = FALSE))
#> sample subject contrast beta var beta_g var_g
#> 1 ROI_1 meta task 0.9576471 0.011764706 0.9576471 0.011764706
#> 2 ROI_2 meta task 1.0081818 0.005454545 1.0081818 0.005454545
#> se_g z_g p_g Q I2 n_eff z
#> 1 0.10846523 8.82907 1.055500e-18 1.385529 0.0000000 4 8.82907
#> 2 0.07385489 13.65085 1.995474e-42 5.232727 0.4266852 4 13.65085
#> p
#> 1 1.055500e-18
#> 2 1.995474e-42That is the “easy path”: subset to the contrast you want, reduce across subjects, and write a tidy result. For one-off jobs or shell scripts, that is often enough.
How do you inspect a source before you commit to a model?
probe tells you what the CLI sees before anything is
computed: the adapter, the dimensions, the assays, and whether the
source has enough structure for the next step.
probe <- cli_json(
"probe",
"--input", stats_path,
"--contrast-data-cols", "time",
"--json"
)
data.frame(
adapter = probe$adapter,
samples = probe$dims[1],
subjects = probe$dims[2],
contrasts = probe$dims[3],
assays = paste(probe$assays, collapse = ", "),
has_contrast_data = probe$has_contrast_data,
stringsAsFactors = FALSE
)
#> adapter samples subjects contrasts assays has_contrast_data
#> 1 tabular 2 4 2 beta, var TRUEHere --contrast-data-cols time promotes the
time column into contrast-level metadata. That matters when
you move from a simple group summary to a model that needs contrast
annotations.
How do you separate planning from execution?
plan is useful when you want to describe the workflow
once, save it, review it, and execute it later.
plan_info <- cli_json(
"plan",
"--input", stats_path,
"--contrast-data-cols", "time",
"--subset", "contrast=task",
"--reduce", "fixed",
"--posthoc", "fdr:bh",
"--save-plan", plan_path,
"--json"
)
knitr::kable(node_table(plan_info$nodes), row.names = FALSE)| op | summary |
|---|---|
| subset_axis | subset |
| reduce | reduce:fixed |
| posthoc | posthoc:fdr:bh |
This is the point where the CLI stops being a thin wrapper around
compute() and becomes a real planning tool. You can keep
the saved JSON under version control, review the steps, and reuse the
same plan across runs.
How do you preview a saved plan?
preview runs a small sample block through the current
plan. That is useful when you want to check the output contract without
writing the full result.
preview_out <- cli_json(
"preview",
"--load-plan", plan_path,
"--n", "1",
"--show-assay", "beta,q",
"--json"
)
preview_out$data
#> sample subject contrast beta q
#> 1 ROI_1 meta task 0.9576 1.0555e-18The preview already includes the derived and post-hoc assays from the saved plan. In practice, this is the fastest way to confirm that a workflow is producing the fields you expect before you launch a longer batch run.
How do you reach more complex workflows?
The CLI is not limited to fixed-effects summaries. You can also attach subject-level covariates, specify formulas, and hand reducer-specific options to more complex models.
cli_show(
"preview",
"--input", stats_path,
"--subset", "contrast=task",
"--col-data", cov_path,
"--reduce", "meta:fe_reg",
"--formula", "~ age",
"--show-assay", "coef:age",
"--n", "2"
)
#> $ fmrigds 'preview' '--input' '/tmp/Rtmp17Odxi/file1cec36df0018.csv' '--subset' 'contrast=task' '--col-data' '/tmp/Rtmp17Odxi/file1cec57b20e9d.csv' '--reduce' 'meta:fe_reg' '--formula' '~ age' '--show-assay' 'coef:age' '--n' '2'
#> GDS
#> dims: 2 x 1 x 1 [sample x subject x contrast]
#> space: sample_labels n=2
#> subjects: 1 (meta)
#> contrasts: 1 (task)
#> assays: Q[?], df_res[?], coef:(Intercept)[?], coef:age[?], se_coef:(Intercept)[?], se_coef:age[?], z_coef:(Intercept)[?], z_coef:age[?], p_coef:(Intercept)[?], p_coef:age[?]
#> design[1]: (Intercept), age
#> sample subject contrast coef:age
#> ROI_1 meta task 0.02776596
#> ROI_2 meta task 0.03831522The same pattern extends to other advanced switches:
-
--source-option key=valuepasses adapter-specific arguments throughgds(). -
--reduce-option key=valuepasses reducer options throughreduce(). -
--posthoc-option key=valuepasses method-specific options throughposthoc(). -
--write-option key=valuecontrols exporters when you write to disk. -
--load-planand--save-planlet you mix shell-driven execution with plan-first workflows.
How do you discover available methods?
list exposes the registries that the CLI can target. You
can use it to see which reducers and post-hoc methods are available in
the current installation.
reducers <- cli_json("list", "reducers", "--json")
head(
data.frame(
name = reducers$name,
requires = vapply(reducers$requires, paste, collapse = ", ", character(1)),
provides = vapply(reducers$provides, paste, collapse = ", ", character(1)),
stringsAsFactors = FALSE
),
6
)
#> name requires
#> 1 combine:fisher p
#> 2 combine:lancaster p
#> 3 combine:stouffer z
#> 4 lmm:ri beta
#> 5 lmm:ri_knownvar beta, var
#> 6 lmm:ri_slope1 beta
#> provides
#> 1 p_g, chi2, df
#> 2 p_g, chi2, df
#> 3 z_g, p_g
#> 4 coef, se_coef, t_coef, p_coef, df_coef, sigma2, vc_intercept, vc_resid, df_res, logLik, converged, lambda
#> 5 coef, se_coef, t_coef, p_coef, df_coef, sigma2, vc_intercept, vc_resid, sampling_var_mean, df_res, logLik, converged
#> 6 coef, se_coef, t_coef, p_coef, df_coef, sigma2, vc_intercept, vc_slope, vc_cov_intercept_slope, vc_resid, df_res, logLik, converged, lambda_intercept, lambda_slope, lambda_cov_intercept_slope, corr_intercept_slopeIf you are scripting around the CLI, this registry view is a good way to make your wrapper validate method names before a larger run.
Where should you go next?
Once you are comfortable with the CLI surface, the R vignettes fill in the modeling details:
-
vignette("fmrigds")for the core lazy workflow -
vignette("spatial-operations")for mapping, masking, and alignment -
vignette("as-plan-and-spatial-fdr")for post-hoc and spatial FDR workflows
The CLI is best when you want a reproducible command surface. When
you need custom transforms or deeply programmatic branching, save a plan
from the CLI and continue in R with load_plan(),
explain_plan(), and compute().