Skip to content

feat(merxen_stub): stubbed MerXen pipeline for Flow validation (FLOW-700) - #2

Open
mhusbynflow wants to merge 5 commits into
mainfrom
mhusbynflow/flow-700-pipeline-file-compat
Open

feat(merxen_stub): stubbed MerXen pipeline for Flow validation (FLOW-700)#2
mhusbynflow wants to merge 5 commits into
mainfrom
mhusbynflow/flow-700-pipeline-file-compat

Conversation

@mhusbynflow

Copy link
Copy Markdown
Contributor

What

Adds merxen_stub/, a structural stub of bourdenxlab/MerXen for FLOW-700. It reproduces MerXen's process DAG, directory-typed inputs, and directory-typed outputs while doing zero real computation — so we can iterate on Flow's handling of directory inputs/outputs without GPUs, conda, or reference data.

Approach

  • Verbatim from MerXen: workflows/main.nf, nextflow.config, conf/dwight.config, samplesheet.example.csv (byte-identical).
  • Stubbed: only each module's process script: body — replaced with mkdir/echo that create exactly the declared outputs (.zarr → directory + dummy .zattrs; *_out/ → directory + placeholder). All tag/publishDir/input/output signatures unchanged.
  • Flow adaptation (schema only): flow/schema/merxen.json exposes raw MERSCOPE/Xenium directory inputs + MerXen's hardcoded reference paths as data inputs, and declares all 18 curated outputs as directories ("filetype": ""). Per FLOW-700 the pipeline code is not modified to accommodate Flow.
  • test/: local smoke test with fake inputs, dummy references, and an execution-only override config (disables conda/containers/GPU-lock).

Verification

merxen_stub/test/run_smoke.sh runs the full DAG in two configs — paired defaults (→ clustering, 27 processes) and all opt-in stages (→ mapmycells, 36 processes) — 21/21 output-directory assertions pass.

Findings (documented in README)

  1. Requires Nextflow ≥ 26. MerXen's main.nf compiles a >64 KB constant that the legacy Groovy parser rejects (String too long) on 24.x/25.x.
  2. Only the first segmentation branch runs. With default analysis_segmentation=both, main.nf uses .join() (1:1) between a one-per-pair_id|platform zarr channel and a two-per-key segmentation gate, so original_seg is silently dropped. Reproduced faithfully, not patched — worth confirming against production usage.

Not done (deliberate)

  • MerXen's side-channel writes to hardcoded outdir paths (latest/latest_spatialdata.zarr, durable segmentation/*.npy) are not reproduced — they're written outside publishDir, so Flow can't discover them anyway.

🤖 Generated with Claude Code

mhusbynflow and others added 5 commits August 4, 2026 14:08
Adds a structural stub of bourdenxlab/MerXen (FLOW-700) so we can iterate on
Flow's handling of directory-typed inputs and outputs without running any real
compute, GPUs, conda, or reference data.

workflows/main.nf, nextflow.config, conf/dwight.config and
samplesheet.example.csv are copied verbatim from MerXen. Every module keeps its
process signatures, publishDir targets and input/output tuple shapes unchanged;
only the script bodies are stubbed to create the declared outputs with
placeholder content (.zarr as a directory with a dummy .zattrs, *_out as a
directory with a placeholder file).

flow/schema/merxen.json is the only Flow-specific addition: it exposes the raw
MERSCOPE/Xenium directory inputs and MerXen's hardcoded reference paths as data
inputs, and declares every curated output as a directory. Per FLOW-700 the
pipeline code is not modified to accommodate Flow.

test/ contains a local smoke test (fake inputs, dummy references, and an
execution-only override config) that runs the full DAG in two configurations
and asserts the expected output-directory tree.

Two behaviours surfaced while running the verbatim pipeline and documented in
the README:
- main.nf only compiles on Nextflow >= 26 (the workflow body exceeds Groovy's
  64 KB constant limit on older parsers).
- With analysis_segmentation=both, main.nf joins a one-per-platform zarr channel
  against a two-per-key segmentation gate with .join() (1:1), so only the reseg
  branch runs downstream and original_seg is silently dropped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…dings

Adds flow/flow.config for a Flow docker+slurm environment: runs the pure-shell
stub bodies in a stock ubuntu image instead of MerXen's GPU containers/conda
envs, and drops the workstation GPU queues, flock GPU locks, and oversized
resource requests from conf/dwight.config. It only sets keys the deployment
leaves unset, since Flow applies repo config_paths at the lowest precedence.

Documents the two Flow-backend behaviours that decide FLOW-700, confirmed from
flow-api and by direct Nextflow experiments:

- Flow's default Nextflow version (23.04.3) cannot compile main.nf; the pipeline
  version must allow a 26.x release.
- Flow appends a generated publish.config last that forces every process's
  publishDir to a flat flow-results/<process>/, and a withName publishDir in a
  later config replaces a module's body publishDir. This flattens MerXen's
  nested <pair_id>/<platform>/<stage>/ output tree and collides parallel tasks
  of the same process. Registering the pipeline with "imports samples" yields an
  empty publish.config (native publishing), which is the path to preserving the
  nested structure and is the recommended next experiment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
merxen.json uses takes_samples=true, which forces a Flow sample per row.
merxen-nosamples.json lets a dummy run pick directory data items directly and
type a pair_id, with defaults (stop_stage=enrich, mecr_enabled=false) that need
no reference data.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The stub previously passed merscope_dir/xenium_dir through as opaque value
paths and never opened them, so on Flow it would report success even if the
selected data path was not mounted into the process container or had the wrong
internal structure — a false green for the exact thing FLOW-700 needs to test.

BUILD_SPATIALDATA now reproduces the real input resolution from
io/builders/pipeline.py: it reads input_path from the build config and fails the
task if the path is not an accessible directory, or (for a raw export folder)
if it lacks the files the builder reads — MERSCOPE: images/,
micron_to_mosaic_pixel_transform.csv, cell_boundaries.parquet,
detected_transcripts.parquet|csv; XENIUM: experiment.xenium/specs.json and
transcripts.parquet. A reusable .zarr cache path only needs to be a directory.

Test input directories are corrected to this real structure (they previously
used the wrong filenames). Documented the remaining same-class gap: the value
paths consumed by later stages (MECR/clustering/MapMyCells references,
cortical-depth/distance GeoJSONs) are not yet access-checked inside the
container.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant