Add scButterfly method (Multiome GEX↔ATAC) - #20
Conversation
Adds scButterfly (Cao et al., Nat. Commun. 2024, doi:10.1038/s41467-024-47418-x), a dual-VAE + adversarial translator, as a train/predict method scoped to Multiome (GEX<->ATAC). Direction is detected from uns["modality"], covering both swap (ATAC->GEX) and normal (GEX->ATAC) variants. Train serializes the real scButterfly weights; predict reconstructs the model, reruns the (deterministic) preprocessing on the train data, loads the weights and runs inference, so it generalises to unseen test cells. Registered in run_benchmark and given a Multiome-only fixture step in test_resources.sh. Both components pass `viash ns test`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Ran this end-to-end on a de.NBI Tesla T4 (train + predict) to check the Registration: correct. GPU: genuinely used, in both steps. Built each component as an executable, ran on a T4 with (short run -- Output contract: the prediction validates against Worth calling out that the suffix-stripping in method_id = meta["name"]
if method_id.endswith(_suffix): ...is doing real work. #18 and #19 both write I merged current One thing to expect at merge time: all three PRs append to the same |
# Conflicts: # src/workflows/run_benchmark/config.vsh.yaml # src/workflows/run_benchmark/main.nf
Summary
Adds scButterfly (Cao et al., Nat. Commun. 2024, doi:10.1038/s41467-024-47418-x) — a dual-VAE + adversarial translator for single-cell cross-modality prediction — as a train/predict method.
Butterflyclass. Direction is detected fromuns["modality"], covering bothswap(ATAC→GEX) andnormal(GEX→ATAC) variants. CITE-seq/ADT is out of scope and raises a clearValueError.What's included
src/methods/scbutterfly/— wrapper +scbutterfly_train+scbutterfly_predict, plus shared helpers (butterfly_common.py,chrom_utils.py).src/workflows/run_benchmark/{main.nf,config.vsh.yaml}.scripts/create_datasets/test_resources.sh— Multiome-only fixture-generation step (mirrorsnovel/simple_mlp).Design notes
scipy==1.9.3,scvi-tools==0.19.0,episcanpy==0.3.2) that won't build on the sharedopenproblems/base_*Python. The engine usespython:3.9+ torch cu117 wheels (CUDA bundled, so thegpulabel still works), scButterfly--no-deps+ the pinned stack, andopenproblemsfromopenproblems-bio/core(as the base images do).state_dicts (<model>/model/*.pt) +metadata.pkl; predict reconstructs the architecture and loads them, so it generalises to unseentest_mod1.data_preprocessingrefits HVG/peak-filter/TF-IDF on its input), so predict rebuilds the paired object from the train data and re-runs the deterministic preprocessing before loading weights.input_train_mod1is therefore wired into predict.size_averageloss kwarg; clamp BCE input to [0,1] (float32 sigmoid can drift >1.0); make.cuda()a no-op when no GPU is present.Verification
Verified locally:
viash ns test -s src/methods/scbutterfly— passes both components.bmmc_multiome/swapand.../normal: output matchestest_mod2in shape and var order, no NaNs, correctmethod_id/dataset_id.Needs CI / infra validation:
.cuda()shim; the native GPU path was not exercised).run_wfwrapper and fullrun_benchmarkrun (blocked locally by nextflow/JDK version constraints + component build size). The wrapper follows the standard train/predict pattern.test_resources.sh, which regenerates and syncs it).🤖 Generated with Claude Code