Skip to content

Multiple Samples with run_together

When you have several sections that should share one FICTURE model, list them in a sample sheet and run_together will:

  1. ingest every sample,
  2. train a single joint model on all of them together,
  3. package each sample independently against that shared model.

The dependency graph fans in on the joint model, then fans back out per sample — so make -j ingests all samples in parallel, blocks once on the shared model, then packages each sample in parallel:

1
2
3
ingest (sample 1) ─┐
                   ├─► ficture (joint model) ─┬─► cartload 1 ─► images 1 ─► publish 1
ingest (sample 2) ─┘                          └─► cartload 2 ─► images 2 ─► publish 2

When to use a joint model

Sharing one model across sections makes factors directly comparable between samples and is the recommended setup for replicates or a cohort. For unrelated samples, run them independently (see batches). Background: Why multi-sample?


1. List the samples in a TSV sheet

A multi-sample run is just a single-sample run with --samples samples.tsv in place of --in-dir. The sheet is a wide table of input roles; for the common case you only need id + in_dir:

1
2
3
4
id      in_dir
rep1    /data/kidney/rep1/outs
rep2    /data/kidney/rep2/outs
rep3    /data/kidney/rep3/outs

2. Run

1
2
3
cartloader run_together --platform 10x_xenium \
    --samples samples.tsv --out-dir /path/to/output/kidney-cohort \
    --width 18 --n-factor 24 -j 10

All rows share one --out-dir, so they train one joint model. Everything else (FICTURE mode, images, cell analyses) is assembled exactly as in the single-sample run and applied to every sample. Add --dry-run to inspect run_together.mk and run_together.resolved.json first.

With Docker, mount the directories that hold the sheet, the inputs, and the output, and use the container paths both on the command line and inside the sheet:

1
2
3
4
docker run --rm -v /data:/data hyunminkang/cartloader:latest \
    run_together --platform 10x_xenium \
    --samples /data/kidney/samples.tsv --out-dir /data/output/kidney-cohort \
    --width 18 --n-factor 24 -j 10

Output layout

Packaging is delegated to run_cartload2_multi: each sample is written to a self-contained cartl/<multi_id>-<sample_id>/ directory (with <multi_id> defaulting to the --out-dir basename), plus a cartl/multi-catalog.yaml that links every per-sample catalog.yaml and copies the shared factor files (post/rgb/de/info/umap) into the cartl/ root as a unified factors: map. Upload the whole cartl/ directory to S3 as one unit.

Extra role columns

The sheet can also carry per-sample inputs — raw_transcript (a raw CSV to ingest), transcript (pre-converted TSV, skips ingest), xy, boundaries, clusters, mex, cellxgene (a cell×gene matrix CSV, e.g. MERSCOPE cell_by_gene.csv):

1
2
3
4
id    in_dir       transcript                     boundaries               cellxgene
rep1  /data/rep1
rep2               /data/rep2/transcripts.tsv.gz  /data/rep2/bounds.csv.gz
rep3  /data/rep3                                                            /data/rep3/cell_by_gene.csv
See the reference → sample sheet for the full column list, aliases, and the three transcript sources.

When to use JSON instead

Escalate to a --config JSON when samples need different inputs, or to change run-wide settings beyond the CLI flags. JSON augments the CLI: per-sample inputs go in samples entries, and run-wide blocks such as cartload sit at the top level (a joint run packages all samples with one run_cartload2_multi call, so packaging settings apply to every sample):

1
2
3
4
5
6
7
{
  "cartload": { "bin_count": 300 },
  "samples": [
    { "id": "rep3", "in_dir": "/data/kidney/rep3/outs",
      "hne": "/data/kidney/rep3/he.tif" }
  ]
}

Samples from the config and the sheet are combined

samples entries in the JSON are added to the rows of --samples; a sample listed in both appears twice. List each sample in only one place.

Unknown keys abort the run, so a typo is caught instead of silently ignored. See the configuration reference for every key.


Visium HD example

The same shape works for Visium HD; the 10x_visium_hd profile adds the square/cell imports and (with a per-sample hne column) the H&E layer automatically. Add hne as a sheet column:

1
2
3
id      in_dir             hne
38088   /data/SI_38088/outs  /data/HE_38088.tif
39685   /data/SI_39685/outs  /data/HE_39685.tif
1
2
cartloader run_together --platform 10x_visium_hd \
    --samples samples.tsv --out-dir /path/to/output/prostate-cohort -j 10

Publishing (opt-in)

Publishing is CLI-driven, with no config block. Enable AI annotation (--anno, or --anno-deep for one joint LLM call over all factors; both need --tissue/--organism) and/or S3 upload (--s3-upload) for every sample:

1
2
3
4
cartloader run_together --platform 10x_xenium \
    --samples samples.tsv --out-dir OUT --width 18 --n-factor 24 -j 10 \
    --anno --tissue "Kidney" --organism human \
    --s3-upload --collection kidney-cohort

Each sample uploads to s3://cartostore/data/batch=<YYYY_MM>/<collection>/<multi_id>-<sample_id>/, where <YYYY_MM> defaults to the current date (override --batch) and <collection> defaults to the out-dir basename (override --collection). See the reference → Publish for all flags (--s3-prefix, --aws-profile, annotation model/threads, …).


Batches of independent samples

For many unrelated samples (each its own model), use --out-root instead of --out-dir:

1
2
cartloader run_together --platform 10x_xenium \
    --samples samples.tsv --out-root /path/to/output/2026_07 -j 6

Each row becomes its own <out_root>/<id>/ with an independent model. See Specifying Inputs → Multi-sample.


Next steps