Skip to content

CLI Reference

Complete reference for the leech command-line interface.

Command overview

The CLI is organized into workflow-based command groups:

Group Commands Purpose
leech data prepare, merge Extract features, merge and split datasets
leech model train, train-crf, optimize, benchmark, bundle, bundle-info, calibrate, export, release, list, fetch Train, tune, calibrate, package, and publish models
leech eval test Evaluate models
leech predict (top-level) Run inference on new data
leech --help       # Show all commands
leech --version    # Show version

Data commands

leech data prepare

Extract training chunks from POD5 and BAM files centered on a sequence motif.

leech data prepare --pod5 FILE --bam FILE --output-dir DIR [OPTIONS]

Required:

Option Description
--pod5 FILE POD5 file with raw nanopore signal
--bam FILE BAM file with alignments and move tables (mv, ns tags)
--output-dir DIR Directory to save training chunks

Feature extraction:

Option Default Description
--signal-context LEFT RIGHT (symmetric 225/225) Asymmetric signal window as two ints (e.g. --signal-context 90 450). Mutually exclusive with --signal-context-bases
--signal-context-bases L R (disabled) Base-defined signal window: cuts at the base-to-signal map positions of offsets -L/+R around the focus base (e.g. --signal-context-bases 8 24), instead of a fixed sample count, so two reads at different translocation speeds read the same bases of context. Padded or centre-cropped to --signal-len. Mutually exclusive with --signal-context. Implemented in both prepare backends and in the Rust predict inference pipeline
--signal-len INT (L+R+1)*36 Fixed emitted signal length for --signal-context-bases (default is a conservative slow-read rate, so a typical read pads rather than centre-crops). Ignored without --signal-context-bases
--feature-start INT -5 Feature window start offset from focus base (negative = upstream, into the body of the read)
--feature-end INT 5 Feature window end offset from focus base (positive = toward adaptor)
--feature-channels LIST (historical twelve) Comma-separated feature-channel names selecting and ordering the corpus's feature rows, e.g. dwell,dwell_log,level_mean,level_skew -- or the single word all for every known channel (the historical twelve plus level_skew/level_kurtosis), so a corpus can be prepared once with everything and model train --feature-channels can pick a subset per run later, without re-preparing per ablation arm. Default (unset): dwell,dwell_log,dwell_mean,dwell_std,dwell_ratio,level_mean,level_median,level_std,level_range,kmer_expected,kmer_residual,kmer_residual_abs -- so omitting this flag reproduces every corpus prepared before it existed byte-identically. level_skew/level_kurtosis are also available individually (population skewness / Fisher excess kurtosis) but not in the default list. Row order is the model's input channel order: recorded in prepare_config.json as the expanded list (never the literal word all), carried into the trained model's config, and extracted exactly by leech predict (never re-chosen at predict time). data merge refuses to combine corpora whose lists differ. Both extraction backends (Rust and Python) honor it. Unknown names are refused before any read is touched.

Motif search:

Option Default Description
--motif STR -- Sequence motif to center on
--motif-offset INT 0 Focus base within motif (0-indexed)
--motif-reference STR fasta Search in fasta (reference) or bam (basecalled sequence)
--reference-fasta FILE -- Reference FASTA if not in BAM header
--skip-motif-indels False Skip motif sites with indels in alignment. Class-selective: removes far more charged than uncharged reads; see per-class attrition in the log and prepare_config.json.
--require-query-mapping / --no-require-query-mapping enabled Require a reference motif to also map cleanly to query coordinates (--motif-reference fasta, --anchor reference). See note below.

When to use --no-require-query-mapping

By default a motif found in the reference must also map cleanly to query coordinates. That mapping is only a quality gate -- its result is discarded, because the chunk is positioned from the reference through CIGAR interpolation -- so turning it off keeps reads whose motif basecalled badly without moving any chunk.

Use it when the modification under study mis-calls the motif itself. Otherwise those reads are dropped for carrying the very signal being measured.

The setting is recorded in prepare_config.json, carried into the model config by leech model train, and applied again by leech predict, so a model is scored on the same read population it was trained on.

Every chunk also records junction_indel (the CIGAR-measured indel length at the motif junction, 0 when exact) and junction_mapped (whether the junction mapped at all) -- automatically, with no flag to enable them. Use leech model train --sample-weight-field junction_indel to over-sample disrupted-junction chunks, and leech predict --abstain-on-junction-indel to apply --min-margin only to reads whose junction is disrupted. leech eval test reports metrics split by junction_indel == 0 vs. != 0 automatically whenever the test corpus carries the field.

Signal handling:

Option Default Description
--base-justify STR center Where to center signal chunk within the focus base: start, center, or end
--no-reverse-signal (off) Do NOT reverse raw signal. By default signal is reversed for direct RNA (POD5 stores 3'->5'). Use this flag for DNA data.
--mask-seq-side [left\|right] (none) Blank (N) sequence-branch bases strictly on that side (5'/left or 3'/right) of the focus base, in both sequence (base_onehot) and sequence_with_kmer_context (signal_kmer) -- the focus base itself is never masked. Prevents a feature+signal model from reading identity through bases upstream/downstream of a motif that would otherwise leak the label (e.g., tRNA-body identity leaking through acceptor-stem bases upstream of a 3'-end motif). Baked into the corpus, recorded in prepare_config.json, carried into the trained model's config, and reapplied automatically to live chunks by leech predict. Forces the Python extraction path (unsupported in Rust) at both prepare and predict time. data merge warns if the corpora being merged disagree on it.

Signal map refinement:

Option Default Description
--refine-signal-map / --no-refine-signal-map enabled Refine move-table base boundaries against expected k-mer levels. Use --no-refine-signal-map for DNA.
--kmer-table FILE (bundled) K-mer level table; defaults to the table shipped with leech
--scale-iters INT 2 -1 = no refinement; 0 = one banded-DP pass without rescaling; N = N passes with Theil-Sen rescaling between them

Refinement changes chunk contents

Refinement is on by default and moves base boundaries, so it changes every dwell and every level-derived feature. A corpus prepared with it on is not interchangeable with one prepared with it off.

The refined boundaries are taken; the affine (scale, shift, drift) fit that escapepod returns alongside them is deliberately discarded. Applying it would replace one median-MAD transform shared by every read with a per-read fit estimated on a near-constant 3' adapter, where it is weakly identified -- and cross-read comparability is what k-mer residual features depend on.

Splitting and parallelism:

Option Default Description
--label STR -- Label identifier for this sample (e.g. Ala, charged); numeric labels are assigned during merge
--train-split FLOAT 0.7 Fraction for training
--val-split FLOAT 0.15 Fraction for validation
--no-split False Save all chunks to all.npz (for later merge)
--workers INT 8 Parallel workers
--chunk-size INT 100 Reads per batch
--min-mapq INT 0 Minimum mapping quality
--seed INT (none) Random seed

Examples:

# Basic: prepare training data
leech data prepare \
  --pod5 reads.pod5 --bam alignments.bam \
  --output-dir chunks/ --label 1

# Parallel with reference-based motif search
leech data prepare \
  --pod5 reads.pod5 --bam alignments.bam \
  --output-dir chunks/ --workers 8 \
  --motif CCAGGC --motif-reference fasta --skip-motif-indels

# Prepare without splitting (for multi-sample merge later)
leech data prepare \
  --pod5 reads.pod5 --bam alignments.bam \
  --output-dir chunks/ --no-split --label 1

leech data merge

Merge chunk files from multiple samples and split at the read level to prevent data leakage.

leech data merge -i LABEL=FILE -i LABEL=FILE -o DIR [OPTIONS]
Option Default Description
-i, --input-chunks (required) Input chunks with labels (label=file.npz)
-o, --output-dir (required) Output directory for split chunks
--train-split 0.7 Fraction for training
--val-split 0.15 Fraction for validation
--seed 42 Random seed
--k-fold 1 Number of cross-validation folds. When > 1, creates k-fold splits (must be >= 3)
--comparison-spec -- TSV file with batch comparison specs
--split-by -- NPZ field to split by group instead of by read (e.g. reference_names); reads sharing a value stay in the same split

Examples:

# Pairwise comparison (e.g., amino acid identity)
leech data merge \
  -i Ala=ala.npz -i Gly=gly.npz \
  -o merged/

# Group comparison (chemical properties)
leech data merge \
  -i basic=lys.npz -i basic=arg.npz \
  -i acidic=asp.npz -i acidic=glu.npz \
  -o merged/

# 5-fold cross-validation
leech data merge \
  -i Ala=ala.npz -i Gly=gly.npz \
  -o kfold/ --k-fold 5

Model commands

leech model train

Train a model on prepared training data.

leech model train --train-data FILES --val-data FILES --model MODEL --output-dir DIR [OPTIONS]

Required:

Option Description
--train-data FILES Training data JSON files (glob patterns supported)
--val-data FILES Validation data JSON files
--model MODEL Architecture name (see below)
--output-dir DIR Directory for model checkpoints

Available architectures (26 total; production and experimental tiers — see leech.models.model_tier). ConvLSTMRemora/ConvLSTMRemoraBase exist for comparison against the Remora paper's architecture, and ConvOnly cannot reach ONNX export (--format onnx) and is deprecated (get_model("ConvOnly", ...) raises DeprecationWarning -- zero references in escapepod-models and no fix planned, see leech.models.model_tier); neither is listed here — see leech model train --help for the full registry including those.

Family Models
ConvLSTM ConvLSTMDwell (production), ConvLSTMBase (production, ablation arm), +BN, +Attn, +BNAttn, +GNAttn, +LNAttn variants
Transformer TransformerDwell, TransformerDwellResidual (2-channel signal)
TCN TCNDwell, TCNDwellGN, TCNDwellLN, TCNDwellResidual (production), TCNDwellResidualGN, TCNDwellResidualLN (production), TCNDwellResidualMotor, TCNDwellResidualLNMotor (+ motor-region pooling), TCNDwellResidualDwellAttn, TCNDwellResidualLNDwellAttn (+ dwell-only cross-attention), TCNDwellSplitResidual, TCNDwellSplitResidualLN
Other ResNetDwell, SignalCNN (signal-only ablation, ignores sequence/features)

Core training options:

Option Default Description
--epochs INT 50 Training epochs
--batch-size INT 128 Batch size
--learning-rate FLOAT 0.001 Learning rate
--device STR cuda cuda or cpu
--seed INT (none) Random seed
--early-stopping INT 10 Stop after N epochs without improvement (0 = disable)
--resume FILE -- Resume from a checkpoint file

Class balancing:

Option Default Description
--use-class-weights / --no-class-weights enabled Auto-compute class weights for imbalanced data
--pos-weight FLOAT -- Manual positive class weight (overrides auto)
--balance-groups / --no-balance-groups disabled Balance sampling across source_group values so each group contributes equally per epoch
--oversample-minority / --no-oversample-minority disabled Oversample minority classes so each class contributes equally per epoch (mutually exclusive with --balance-groups)
--sample-weight-field STR -- Generalizes --balance-groups to any chunk metadata field: inverse-frequency weight sampling by that field's distinct values, one group per value (mutually exclusive with --balance-groups/--oversample-minority). E.g. --sample-weight-field junction_indel to over-sample chunks whose motif junction mapped with an indel

Regularization and optimization:

Option Default Description
--weight-decay FLOAT 0 L2 weight decay
--max-grad-norm FLOAT 0 Gradient clipping (0 = disabled)
--scheduler STR none LR scheduler: none, reduce_on_plateau, or cosine
--scheduler-patience INT 5 Epochs before reducing LR
--scheduler-factor FLOAT 0.5 Factor to reduce LR by
--warmup-epochs INT 0 Linear warmup epochs

With --scheduler cosine, warmup is part of the schedule itself: the LR ramps linearly for --warmup-epochs and then follows a cosine decay down to a floor of 1e-6. Past the epoch budget the LR holds at the floor rather than cycling back up. reduce_on_plateau keeps its own warmup handling and is driven by --checkpoint-metric, not validation loss.

Optimizer loop:

Option Default Description
--grad-accum-split INT 1 Split each batch into N sub-batches, accumulating gradients before stepping once (1 = disabled)
--quantile-grad-clip / --no-quantile-grad-clip disabled Clip at 2x the median of the last 100 gradient norms instead of a fixed threshold
--save-optim-every INT 1 Write optimizer state only every N epochs (weights are still written on every save)

Use --grad-accum-split to raise the effective batch size when a real batch will not fit in GPU memory: --batch-size 64 --grad-accum-split 4 costs the memory of 16 samples but takes the optimizer step of 64. Keep --batch-size divisible by N -- on a ragged final sub-batch, per-sample weighting is slightly off.

--quantile-grad-clip adapts the clipping threshold to the gradient scale the run is actually seeing, which is useful when a fixed --max-grad-norm is hard to pick. It takes precedence over --max-grad-norm if both are given.

--save-optim-every N trades resumability for checkpoint size. Note that it applies by epoch number, so model_last.pt carries optimizer state only if the final epoch is a multiple of N; resuming from a checkpoint without it starts from a fresh optimizer and logs a warning. It has no effect on model_resume.pt, which always carries optimizer state -- see below.

Loss function:

Option Default Description
--loss STR bce Loss function: bce, focal, cross_entropy, or noise_corrected_bce
--focal-gamma FLOAT 2.0 Focal loss gamma (only with --loss focal)
--label-noise-rate STR -- group=rate[,group=rate,...] per-source_group label-flip probability for --loss noise_corrected_bce (e.g. gold=0.09,enzymatic=0.17). Unmapped groups get rate 0. See Label-noise-aware loss.
--focal-neg-gamma FLOAT none (symmetric) Separate gamma for negative-labeled examples, making the focal loss asymmetric (only with --loss focal). Larger than --focal-gamma down-weights easy negatives harder, shaping the loss for a low-FPR operating regime

Data augmentation:

Option Default Description
--augment-jitter FLOAT 0 Signal jitter noise std dev (0 = disabled). Per-channel via key=val,..., e.g. signal=0.02,signal_residual=0.001
--augment-scale-min FLOAT 1.0 Min random scale factor
--augment-scale-max FLOAT 1.0 Max random scale factor
--augment-shift-max-bases FLOAT 0 Max cross-layer shift in bases; float for sub-base resolution (0 = disabled)
--augment-time-mask-bases INT 0 Max width in bases for time masking (0 = disabled)
--augment-time-mask-count INT 1 Number of time masks to apply
--augment-feature-noise-scale FLOAT 0 Per-channel Gaussian noise scale for features (0 = disabled)
--augment-time-stretch MIN,MAX 1,1 (disabled) Resample each chunk's signal window by a per-sample factor drawn uniformly from [MIN, MAX] about the focus position, for speed invariance. Scales the base-to-signal map to match (signal_kmer stays aligned) and the dwell/dwell_mean/dwell_log feature channels (dwell_ratio, dwell_std and level channels are unchanged). Batched __getitems__ fetch path only; never applied to validation; incompatible with --augment-shift-max-bases and --augment-time-mask-bases
--label-smoothing FLOAT 0 Label smoothing factor (0 = disabled; e.g. 0.05 softens 0/1 targets)
--mixed-precision / --no-mixed-precision disabled Mixed precision training (CUDA only)

Provenance (recorded in config for inference):

Option Default Description
--motif STR -- Motif used for chunk extraction
--motif-offset INT 0 Focus base within motif (0-indexed)
--base-justify STR center Signal justification
--model-config FILE -- JSON file with model architecture overrides (e.g. {"causal": false} for a TCN family's padding, or precomputed feature_mean/feature_std)
--seq-encoding STR signal_kmer Sequence encoding: base_onehot or signal_kmer
--encoding-fallback / --no-encoding-fallback auto Allow signal_kmer to fall back to base_onehot when the corpus has no base-to-signal maps. Auto = allowed only when --seq-encoding was left at its default
--num-workers INT 0 DataLoader workers (0=auto)
--standardize-features / --no-standardize-features disabled Compute per-channel feature mean/std once over the training corpus and freeze them into the feature branch as an affine layer. Only models with a feature_branch (the ConvLSTM and TCN families) accept this
--strict-window / --no-strict-window disabled Raise instead of zero-padding when the signal crop window (left_context/right_context, e.g. via --model-config) extends outside the stored chunk. Default logs one warning per dataset the first time it happens rather than silently training on zero-padded samples
--feature-channels LIST (every corpus row) Comma-separated feature-channel names selecting and ordering a SUBSET of the training corpus's recorded feature_channels (channel ablation) -- e.g. dwell,dwell_log,level_mean. Lets a corpus prepared once with data prepare --feature-channels all be trained on different channel subsets without re-preparing. Every requested name must be in the corpus's recorded list (prepare_config.json, or the historical twelve if absent); an unknown or absent one is refused before training starts, naming the missing channels and the corpus's list. Applied identically to --val-data. Default (unset): use every row the corpus has, exactly as before this flag existed. The checkpoint's config.json records the SELECTED list as feature_channels (what leech predict extracts) and the corpus's full list separately as corpus_feature_channels, for provenance

Output files:

  • model_best.pt -- best checkpoint by --checkpoint-metric (default auto: val_auc for binary, val_f1 for multiclass). Also accepts tpr_at_fpr:<f> (TPR at a fixed FPR) and callable_at_precision:<p> (fraction of validation reads callable at a precision floor) for a low-FPR operating regime -- binary tasks only; leech model optimize's --selection-metric takes the same names.
  • model_last.pt -- final epoch checkpoint, written once after training ends (early stopping or completion)
  • model_resume.pt -- rolling checkpoint, written atomically after every epoch and deleted on clean exit. This is the file that makes --resume recover a run an external kill (SLURM walltime, OOM, scancel) cut off mid-loop: model_last.pt never appears in that case, since it is only written when the epoch loop actually finishes. Point --resume at model_resume.pt in anything that retries a job -- a Snakemake restart-times, a resubmitted sbatch script. Refuses to resume (raises, does not warn) if the optimizer/scheduler/loss/auxiliary-head recipe or either corpus file changed since it was written; delete it to start over instead of warm-starting a different run under it. The check is scoped to what Trainer itself can vary -- a checkpoint's --motif, --batch-size, --seq-encoding etc. are provenance recorded in config.json, not enforced here, since a Trainer built directly (bypassing leech model train) never sets them. Do not add model_resume.pt to output: in a Snakemake rule -- Snakemake deletes a failed job's declared outputs, which would erase the one file the retry needs.
  • config.json -- full training configuration (needed for inference)
  • metrics.json -- per-epoch metrics

Examples:

# Basic training
leech model train \
  --train-data chunks/train.json --val-data chunks/val.json \
  --model ConvLSTMDwell --output-dir models/

# With focal loss, gradient clipping, and LR scheduling
leech model train \
  --train-data chunks/train.json --val-data chunks/val.json \
  --model ConvLSTMDwell --output-dir models/ \
  --loss focal --focal-gamma 2.0 \
  --max-grad-norm 1.0 \
  --scheduler reduce_on_plateau --scheduler-patience 5

# Recover an interrupted run (SLURM walltime, OOM, scancel, ...) -- safe to
# pass unconditionally in a retry, since --resume is ignored when the file
# doesn't exist (e.g. the first attempt)
leech model train \
  --train-data chunks/train.json --val-data chunks/val.json \
  --model ConvLSTMDwell --output-dir models/ \
  --resume models/model_resume.pt

# Extend a run that finished cleanly to more epochs
leech model train \
  --train-data chunks/train.json --val-data chunks/val.json \
  --model ConvLSTMDwell --output-dir models/ --epochs 80 \
  --resume models/model_last.pt

# Large effective batch on a small GPU, with adaptive clipping
leech model train \
  --train-data chunks/train.json --val-data chunks/val.json \
  --model ConvLSTMDwell --output-dir models/ \
  --batch-size 128 --grad-accum-split 4 \
  --quantile-grad-clip \
  --scheduler cosine --warmup-epochs 2

leech model optimize

Grid search over signal context windows and dwell offsets to find the best configuration.

leech model optimize --train-data FILE --output-dir DIR --context-grid VALUES [OPTIONS]

Required:

Option Description
--train-data FILE Training dataset (.npz)
-o, --output-dir DIR Output directory
--context-grid VALUES Comma-separated context values or start:stop:step range

Grid options:

Option Default Description
--val-data FILE -- Validation dataset
--left-contexts VALUES uses --context-grid Override left context grid
--right-contexts VALUES uses --context-grid Override right context grid
--dwell-offsets VALUES -- Dwell offset values to search (comma-separated or start:stop:step)
--kmer-context INT 5 K-mer context
--base-justify STR center Signal chunk centering
--parallel INT 1 Grid points to train concurrently

Training options (--model, --epochs, --batch-size, --learning-rate, --device, --seed, --early-stopping) work the same as in model train. So do the loss and augmentation options above --early-stopping in that reference (--loss, --label-noise-rate, --focal-gamma/--focal-neg-gamma, --scheduler*, --warmup-epochs, --weight-decay, --max-grad-norm, every --augment-* flag, --label-smoothing, --mixed-precision) and --motif/--motif-offset from provenance -- both decorators (training_hyperparams, model_provenance) are shared between train and optimize. --selection-metric accepts the same names as model train's --checkpoint-metric (see the "Output files" note under model train above: auto/val_acc/val_f1/val_auc, plus the parametric tpr_at_fpr:<f> and callable_at_precision:<p>). --feature-channels is also passed through unchanged to every grid point -- see model train's flag of the same name.

Output:

  • grid_summary.csv -- results for all grid points
  • best_params.json -- best configuration (for use with model train --model-config)
  • Per-grid-point subdirectories with model checkpoints

Examples:

# Coarse symmetric grid
leech model optimize \
  --train-data chunks/train.npz --val-data chunks/val.npz \
  --context-grid 200,500,1000,2000,5000 \
  --output-dir grid_results/

# Asymmetric fine grid with range syntax
leech model optimize \
  --train-data chunks/train.npz --val-data chunks/val.npz \
  --left-contexts 8000:10000:500 \
  --right-contexts 0:2000:500 \
  --output-dir grid_results/

# With dwell offset search, parallel on CPU
leech model optimize \
  --train-data chunks/train.npz --val-data chunks/val.npz \
  --context-grid 500,1000 \
  --dwell-offsets -5:5:1 \
  --device cpu --parallel 8 \
  --output-dir grid_results/

leech model benchmark

Time one training step end to end -- per-phase timing plus GPU utilization -- to find out which phase is the bottleneck before changing anything.

leech model benchmark --train-data FILE --output-dir DIR [OPTIONS]
Option Default Description
--train-data PATH (required) Training dataset (.npz or JSON chunks config) to benchmark against
-o, --output-dir DIR (required) Directory for benchmark.json (and trace.json with --trace)
--model STR ConvLSTMDwell Architecture to benchmark
--batch-size INT 128 Batch size
--device STR cuda cuda or cpu
--num-steps INT 100 Timed training steps
--warmup-steps INT 10 Warmup steps (cuDNN benchmark, torch.compile, prefetch queue)
--num-workers INT 8 DataLoader workers (0 disables multiprocessing)
--prefetch-factor INT 4 DataLoader prefetch_factor
--mixed-precision / --no-mixed-precision enabled torch.amp autocast + GradScaler
--non-blocking / --blocking blocking .to(device, non_blocking=True) for host-to-device copies
--signal-len INT 400 Signal length
--kmer-len INT 11 K-mer length
--seq-encoding STR signal_kmer base_onehot or signal_kmer
--signal-mode STR both both, residual, or signal
--trace / --no-trace off Also collect a torch.profiler Chrome trace
--trace-active-steps INT 10 Active steps captured in the trace
--model-config FILE -- JSON file with model architecture overrides, same as model train
--standardize-features / --no-standardize-features disabled Compute per-channel feature mean/std over --train-data and freeze them into the feature branch, same as model train's flag

Example:

leech model benchmark \
  --train-data chunks/train.npz \
  --model TCNDwellResidual \
  --num-workers 8 \
  -o bench/

leech model bundle

Package multiple trained pairwise models into a single versioned bundle file for deployment.

leech model bundle --model-dir DIR --output FILE --version VERSION [OPTIONS]
Option Default Description
--model-dir DIR (required) Root directory containing pair subdirectories (each with model_best.pt + config.json)
-o, --output FILE (required) Output .pt bundle file
-v, --version STR (required) Semantic version (e.g., "0.1.0-alpha.1")
--comparison-type STR pairwise pairwise, one_vs_all, group, or multiclass

The command auto-discovers all subdirectories containing model_best.pt and config.json.

Example:

# Bundle all pairwise models from a training run
leech model bundle \
  --model-dir results/models/pairwise/ \
  --output bundles/aa_classifier_v0.1.0.pt \
  --version 0.1.0

# One-vs-all bundle
leech model bundle \
  --model-dir results/models/one_vs_all/ \
  --output bundles/ova_v0.1.0.pt \
  --version 0.1.0 \
  --comparison-type one_vs_all

leech model bundle-info

Display metadata from a model bundle.

leech model bundle-info --bundle FILE

Shows architecture, version, comparison type, included pairs, and file size.

leech model calibrate

Learn post-hoc calibration on the validation set. Binary models use Platt scaling (fits a, b so sigmoid(a*logit + b) is better calibrated). Multiclass models use temperature (default), matrix, or Dirichlet scaling.

leech model calibrate --model-dir DIR --val-data FILE [OPTIONS]
Option Default Description
--model-dir DIR (required) Model directory or parent with pair subdirs
--val-data FILE (required) Validation data (.npz)
--device STR cpu Device for inference
--batch-size INT 1024 Batch size for validation pass
--num-workers INT 0 DataLoader workers
--method STR temperature Multiclass method: temperature, matrix, or dirichlet (binary always uses Platt)
--reg-lambda FLOAT 0.01 L2 regularization toward identity for matrix/dirichlet
-o, --output FILE -- Output calibration JSON path (single model only)

Binary models write platt.json; multiclass models write calibration.json to the model directory. For a parent directory with pair subdirs, calibrates each pair independently.

Examples:

# Calibrate a single model
leech model calibrate --model-dir models/Ala_vs_Gly/ --val-data val.npz

# Calibrate all pairs in a directory
leech model calibrate --model-dir models/one_vs_all/ --val-data val.npz

leech model export

Export a trained model as a standalone .pt file, loadable with torch.export.load() without the leech codebase.

leech model export --model-dir DIR --output FILE
Option Default Description
--model-dir DIR (required) Model checkpoint directory
-o, --output FILE (required) Output .pt file

Example:

leech model export --model-dir models/best/ -o exported_model.pt

leech model release

Publish a model bundle to GitHub Releases from a YAML spec. Requires the GitHub CLI (gh), authenticated (gh auth login) unless --dry-run.

leech model release --spec FILE [OPTIONS]
Option Default Description
--spec FILE (required) YAML release spec
--dry-run off Render the release notes and preview without publishing
--overwrite off Delete an existing release with the same tag first
--repo STR origin GitHub repo as owner/repo

The spec requires name, version, description and bundle (path to the .pt), and accepts organism, experiment, data_version, training, metrics, prerelease and extra_assets. The release tag is model-{name}-v{version}.

Example:

leech model release --spec release/ivc-pairwise.yaml --dry-run

leech model list

Browse published model releases on GitHub.

leech model list [OPTIONS]
Option Default Description
--repo STR origin GitHub repo as owner/repo
--limit INT 30 Maximum releases to show

leech model fetch

Download a published bundle from GitHub Releases.

leech model fetch --name NAME --version VERSION [OPTIONS]
Option Default Description
--name STR -- Model release name (e.g. ivc-pairwise-v1)
--version STR -- Model version
--tag STR -- Full release tag, as an alternative to --name + --version
-o, --output-dir DIR . Directory to download into
--repo STR origin GitHub repo as owner/repo

Example:

leech model fetch --name ivc-pairwise --version 1.0.0 -o models/

Evaluation commands

leech eval test

Evaluate a trained model on a held-out test set.

leech eval test --model FILE --test-data FILES --output FILE [OPTIONS]
Option Default Description
--model FILE (required) Trained model checkpoint (.pt)
--test-data FILES (required) Test data JSON files
--output FILE (required) Output metrics JSON
--device STR auto cuda or cpu
--num-workers INT 0 (auto) DataLoader workers; auto is 8 on GPU (capped by the job's CPU allocation) and 0 on CPU
--emit-scores PATH (off) Also write per-chunk read_ids, labels, probs to an .npz

Why --num-workers matters on a GPU

Collate, the host-to-device copy and the forward pass all run in whichever process owns the loader. With no workers that is one core feeding an accelerator that then waits: a 7.8M-chunk test set evaluated at 8% GPU on an A5000, while training the same corpus on the same card ran at 98%. The default now resolves to workers on CUDA, capped by the CPUs the job is actually allowed to use, so a 2-core allocation gets 1 worker rather than 8 thrashing ones. Raise it when the eval job has cores to spare.

Why --emit-scores exists

The metrics JSON is a summary at one threshold. The per-chunk scores behind it are computed either way; this flag only decides whether they survive the run. Without them, AUPRC for the minority class, per-group error breakdowns (per barcode, per isotype), paired model comparison (McNemar), calibration, and any operating point other than the reported one cannot be answered without re-running inference.

The join to read ids is positional and is verified: if the test .npz holds a different number of read_ids than there are scored chunks, it raises rather than writing a well-formed file with every score attached to the wrong read. Multiclass emits the full (N, C) softmax.

The metrics JSON also carries a threshold_sweep with the best operating points (at_youden, at_mcc, at_f1) alongside prevalence. Prefer at_youden when the deployment class ratio is unknown or varies per sample -- it is the only one of the three that is prevalence-invariant.


Inference command

leech predict

Run inference on new data. Supports single models, single pairs from a bundle, or all-model aggregation from a bundle.

leech predict --pod5 FILE --bam FILE --output FILE (--model DIR | --bundle FILE (--pair NAME | --all)) [OPTIONS]

Required:

Option Description
--pod5 FILE POD5 file with raw signal
--bam FILE BAM file with alignments
-o, --output FILE Output BAM with predictions

Model selection (mutually exclusive):

Option Description
--model DIR Single model checkpoint directory
--bundle FILE Model bundle .pt file (requires --pair or --all)

Bundle options (require --bundle):

Option Description
--pair NAME Run a single pair's model from the bundle
--all Run every model in the bundle, aggregate to a single multi-class prediction (e.g., amino acid identity for a one-vs-all bundle)
--raw Write full-float probabilities instead of the compact uint8 encoding (ac/pp tags)

Signal handling:

Option Default Description
--device STR auto cuda or cpu
--base-justify STR center Signal chunk centering
--no-reverse-signal (off) Disable signal reversal (use for DNA data)

Confidence and abstention:

Option Default Description
--min-confidence INT 0 Confidence threshold in uint8 space (0-255). Reads below threshold are called unc (uncharged/unclassified). 0 = all reads called
--min-margin INT 0 Margin threshold in uint8 space (0-255); margin = top probability minus runner-up. Reads below threshold are called unc. 0 = no margin filter
--abstain-on-junction-indel / --no-abstain-on-junction-indel disabled Multiclass only: enforce --min-margin only on reads whose motif junction is disrupted (unmapped, or a nonzero CIGAR indel), calling them unc when the margin is also below threshold; reads with an intact junction bypass the margin check entirely. junction_indel/junction_mapped are always recorded (BAM ji/jm tags, or a TSV column) so the rule can be re-applied offline

Backend:

Option Default Description
--backend STR auto Chunk-extraction backend: auto (Rust when available), rust (force Rust, error if unavailable), python (force Python). Useful for comparing the two paths.

When auto falls back

Some options cannot be honored by the Rust pipeline -- non-median-MAD --signal-norm, softclip signal recovery, and a model trained with --mask-seq-side (focus-relative sequence masking is Python-only) among them. Under auto those fall back to Python with a warning; under --backend rust they raise rather than silently producing different chunks.

Examples:

# Single model inference
leech predict \
  --model models/model_best.pt \
  --pod5 reads.pod5 --bam alignments.bam \
  --output predictions.bam

# Run one pair from a bundle
leech predict \
  --bundle bundles/aa_classifier.pt --pair Ala_vs_Gly \
  --pod5 reads.pod5 --bam alignments.bam \
  --output predictions.bam

# Run all models in bundle (aggregated multi-class prediction)
leech predict \
  --bundle bundles/aa_classifier.pt --all \
  --pod5 reads.pod5 --bam alignments.bam \
  --output predictions.bam

# Same but also write per-pair raw probabilities
leech predict \
  --bundle bundles/aa_classifier.pt --all --raw \
  --pod5 reads.pod5 --bam alignments.bam \
  --output predictions.bam

Typical workflows

Single-sample classification

# 1. Prepare data
leech data prepare \
  --pod5 reads.pod5 --bam alignments.bam \
  --output-dir chunks/ --label 1

# 2. Train
leech model train \
  --train-data chunks/train.json --val-data chunks/val.json \
  --model ConvLSTMDwell --output-dir models/

# 3. Evaluate
leech eval test \
  --model models/model_best.pt \
  --test-data chunks/test.json --output metrics.json

# 4. Predict
leech predict \
  --model models/ \
  --pod5 new_reads.pod5 --bam new_alignments.bam \
  --output predictions.bam

Multi-sample pairwise comparison

# 1. Prepare each sample without splitting
leech data prepare --pod5 ala.pod5 --bam ala.bam --output-dir chunks/ala/ --no-split --label 0
leech data prepare --pod5 gly.pod5 --bam gly.bam --output-dir chunks/gly/ --no-split --label 1

# 2. Merge and split at read level
leech data merge -i Ala=chunks/ala/all.npz -i Gly=chunks/gly/all.npz -o merged/

# 3. Train, test, predict as above

Bundle workflow (multi-pair deployment)

# 1. Train pairwise models (one per class pair, e.g. amino acid pair)
leech model train --train-data ala_gly/train.json ... --output-dir models/Ala_vs_Gly/
leech model train --train-data ala_val/train.json ... --output-dir models/Ala_vs_Val/
# ... repeat for each pair

# 2. Bundle all models
leech model bundle \
  --model-dir models/ --output bundle.pt --version 1.0.0

# 3. Inspect the bundle
leech model bundle-info --bundle bundle.pt

# 4. Run aggregated inference
leech predict \
  --bundle bundle.pt --all \
  --pod5 reads.pod5 --bam alignments.bam \
  --output predictions.bam

Hyperparameter optimization

# 1. Grid search for optimal context
leech model optimize \
  --train-data chunks/train.npz --val-data chunks/val.npz \
  --context-grid 200,500,1000,2000,5000 \
  --output-dir grid_results/

# 2. Train final model with best params
leech model train \
  --train-data chunks/train.json --val-data chunks/val.json \
  --model ConvLSTMDwell --output-dir models/ \
  --model-config grid_results/best_params.json