migec 2.2.0__tar.gz → 2.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {migec-2.2.0 → migec-2.2.1}/CHANGELOG.md +82 -0
- {migec-2.2.0 → migec-2.2.1}/CLAUDE.md +25 -2
- {migec-2.2.0 → migec-2.2.1}/PKG-INFO +123 -17
- {migec-2.2.0 → migec-2.2.1}/README.md +122 -16
- {migec-2.2.0 → migec-2.2.1}/ROADMAP.md +15 -9
- {migec-2.2.0 → migec-2.2.1}/SOURCES.md +47 -0
- migec-2.2.1/assets/ctdna_titration.tsv +35 -0
- migec-2.2.1/assets/ctdna_titration_runs.tsv +101 -0
- {migec-2.2.0 → migec-2.2.1}/docs/downstream.rst +4 -0
- {migec-2.2.0 → migec-2.2.1}/docs/examples.rst +24 -6
- {migec-2.2.0 → migec-2.2.1}/docs/index.rst +4 -0
- migec-2.2.1/docs/nextflow.rst +204 -0
- {migec-2.2.0 → migec-2.2.1}/docs/reference.rst +3 -2
- migec-2.2.1/docs/variants.rst +316 -0
- {migec-2.2.0 → migec-2.2.1}/include/migec/version.hpp +1 -1
- migec-2.2.1/integrations/nextflow/README.md +89 -0
- migec-2.2.1/integrations/nextflow/main.nf +63 -0
- migec-2.2.1/integrations/nextflow/modules/downstream/align/environment.yml +8 -0
- migec-2.2.1/integrations/nextflow/modules/downstream/align/main.nf +74 -0
- migec-2.2.1/integrations/nextflow/modules/downstream/arda/environment.yml +8 -0
- migec-2.2.1/integrations/nextflow/modules/downstream/arda/main.nf +56 -0
- migec-2.2.1/integrations/nextflow/modules/downstream/callvariants/environment.yml +7 -0
- migec-2.2.1/integrations/nextflow/modules/downstream/callvariants/main.nf +81 -0
- {migec-2.2.0 → migec-2.2.1}/integrations/nextflow/nextflow.config +25 -0
- migec-2.2.1/integrations/nextflow/subworkflows/migec_airr/main.nf +35 -0
- migec-2.2.1/integrations/nextflow/subworkflows/migec_ctdna/main.nf +46 -0
- migec-2.2.1/integrations/slurm/README.md +88 -0
- migec-2.2.1/integrations/slurm/migec_array.sbatch +69 -0
- migec-2.2.1/integrations/slurm/migec_sample.sbatch +81 -0
- migec-2.2.1/integrations/slurm/samples.tsv +6 -0
- migec-2.2.1/notebooks/README.md +63 -0
- migec-2.2.1/notebooks/airr_repertoire.py +221 -0
- {migec-2.2.0 → migec-2.2.1}/notebooks/barcode_space.py +1 -1
- migec-2.2.1/notebooks/ctdna_variants.py +274 -0
- migec-2.2.1/notebooks/exome_capture.py +189 -0
- {migec-2.2.0 → migec-2.2.1}/notebooks/platforms.py +1 -1
- {migec-2.2.0 → migec-2.2.1}/notebooks/refine_diagnostics.py +1 -1
- {migec-2.2.0 → migec-2.2.1}/pyproject.toml +1 -1
- {migec-2.2.0 → migec-2.2.1}/python/migec/__init__.py +1 -1
- migec-2.2.1/scripts/ctdna_titration.py +366 -0
- migec-2.2.1/scripts/sra_fetch.py +307 -0
- {migec-2.2.0 → migec-2.2.1}/skills/migec/SKILL.md +70 -1
- migec-2.2.1/tests/unit/__init__.py +0 -0
- migec-2.2.1/tests/unit/test_ctdna_tools.py +194 -0
- migec-2.2.0/docs/nextflow.rst +0 -116
- migec-2.2.0/integrations/nextflow/README.md +0 -36
- {migec-2.2.0 → migec-2.2.1}/.github/workflows/ci.yml +0 -0
- {migec-2.2.0 → migec-2.2.1}/.github/workflows/docs.yml +0 -0
- {migec-2.2.0 → migec-2.2.1}/.github/workflows/publish.yml +0 -0
- {migec-2.2.0 → migec-2.2.1}/.github/workflows/testpypi.yml +0 -0
- {migec-2.2.0 → migec-2.2.1}/.gitignore +0 -0
- {migec-2.2.0 → migec-2.2.1}/CMakeLists.txt +0 -0
- {migec-2.2.0 → migec-2.2.1}/LICENSE +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/PBMC.cell_rank.tsv +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/PBMC.sizes.tsv +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/PBMC.umi_errors.tsv +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/SRR1763769.mig.tsv +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/assemble.coverage.tsv +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/assemble.quality_by_depth.tsv +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/assemble_coverage.gp +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/assemble_coverage.svg +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/benchmark_threads.gp +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/benchmark_threads.json +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/benchmark_threads.svg +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/benchmark_threads.tsv +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/cell_rank.gp +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/cell_rank.svg +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/consensus_error.gp +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/consensus_error.svg +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/consensus_layout.gp +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/consensus_layout.svg +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/consensus_quality.gp +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/consensus_quality.svg +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/mig_size_spectrum.gp +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/mig_size_spectrum.svg +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/mig_size_zipf.gp +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/mig_size_zipf.svg +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/pipeline.dot +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/pipeline.svg +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/umi_error_children.gp +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/umi_error_children.svg +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/umi_error_rate.gp +0 -0
- {migec-2.2.0 → migec-2.2.1}/assets/umi_error_rate.svg +0 -0
- {migec-2.2.0 → migec-2.2.1}/docs/Makefile +0 -0
- {migec-2.2.0 → migec-2.2.1}/docs/assemble.rst +0 -0
- {migec-2.2.0 → migec-2.2.1}/docs/barcode_space.rst +0 -0
- {migec-2.2.0 → migec-2.2.1}/docs/checkout.rst +0 -0
- {migec-2.2.0 → migec-2.2.1}/docs/commands.rst +0 -0
- {migec-2.2.0 → migec-2.2.1}/docs/conf.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/docs/formats.rst +0 -0
- {migec-2.2.0 → migec-2.2.1}/docs/fragmented.rst +0 -0
- {migec-2.2.0 → migec-2.2.1}/docs/grouping.rst +0 -0
- {migec-2.2.0 → migec-2.2.1}/docs/installation.rst +0 -0
- {migec-2.2.0 → migec-2.2.1}/docs/layouts.rst +0 -0
- {migec-2.2.0 → migec-2.2.1}/docs/method.rst +0 -0
- {migec-2.2.0 → migec-2.2.1}/docs/nulls.rst +0 -0
- {migec-2.2.0 → migec-2.2.1}/docs/performance.rst +0 -0
- {migec-2.2.0 → migec-2.2.1}/docs/plots.rst +0 -0
- {migec-2.2.0 → migec-2.2.1}/docs/quality_floor.rst +0 -0
- {migec-2.2.0 → migec-2.2.1}/docs/refine.rst +0 -0
- {migec-2.2.0 → migec-2.2.1}/docs/roadmap.rst +0 -0
- {migec-2.2.0 → migec-2.2.1}/docs/subsample.rst +0 -0
- {migec-2.2.0 → migec-2.2.1}/docs/suggest.rst +0 -0
- {migec-2.2.0 → migec-2.2.1}/docs/umi_errors.rst +0 -0
- {migec-2.2.0 → migec-2.2.1}/docs/umi_statistics.rst +0 -0
- {migec-2.2.0 → migec-2.2.1}/docs/validation.rst +0 -0
- {migec-2.2.0 → migec-2.2.1}/include/migec/assemble.hpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/include/migec/checkout.hpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/include/migec/consensus.hpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/include/migec/fastq.hpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/include/migec/mig_record.hpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/include/migec/parallel.hpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/include/migec/pattern.hpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/include/migec/refine.hpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/include/migec/resource.hpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/include/migec/subsample.hpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/include/migec/suggest.hpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/include/migec/types.hpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/include/migec/umi_stats.hpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/include/migec/whitelist.hpp +0 -0
- /migec-2.2.0/tests/__init__.py → /migec-2.2.1/integrations/nextflow/assets/NO_FILE +0 -0
- {migec-2.2.0 → migec-2.2.1}/integrations/nextflow/modules/migec/assemble/main.nf +0 -0
- {migec-2.2.0 → migec-2.2.1}/integrations/nextflow/modules/migec/assemble/meta.yml +0 -0
- {migec-2.2.0 → migec-2.2.1}/integrations/nextflow/modules/migec/checkout/main.nf +0 -0
- {migec-2.2.0 → migec-2.2.1}/integrations/nextflow/modules/migec/checkout/meta.yml +0 -0
- {migec-2.2.0 → migec-2.2.1}/integrations/nextflow/modules/migec/environment.yml +0 -0
- {migec-2.2.0 → migec-2.2.1}/integrations/nextflow/modules/migec/plot/main.nf +0 -0
- {migec-2.2.0 → migec-2.2.1}/integrations/nextflow/modules/migec/refine/main.nf +0 -0
- {migec-2.2.0 → migec-2.2.1}/integrations/nextflow/modules/migec/refine/meta.yml +0 -0
- {migec-2.2.0 → migec-2.2.1}/integrations/nextflow/subworkflows/migec/main.nf +0 -0
- {migec-2.2.0 → migec-2.2.1}/project/README.md +0 -0
- {migec-2.2.0 → migec-2.2.1}/project/design-assemble.md +0 -0
- {migec-2.2.0 → migec-2.2.1}/project/design-benchmarks.md +0 -0
- {migec-2.2.0 → migec-2.2.1}/project/design-checkout.md +0 -0
- {migec-2.2.0 → migec-2.2.1}/project/design-io-interop.md +0 -0
- {migec-2.2.0 → migec-2.2.1}/project/design-refine.md +0 -0
- {migec-2.2.0 → migec-2.2.1}/project/design-repo.md +0 -0
- {migec-2.2.0 → migec-2.2.1}/project/review-algorithms.md +0 -0
- {migec-2.2.0 → migec-2.2.1}/project/review-scope.md +0 -0
- {migec-2.2.0 → migec-2.2.1}/python/migec/assemble.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/python/migec/checkout.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/python/migec/cli.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/python/migec/plot.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/python/migec/refine.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/python/migec/sheet.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/python/migec/subsample.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/python/migec/suggest.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/scripts/benchmark_threads.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/scripts/collision_check.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/scripts/compare_calib.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/scripts/correction_accuracy.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/scripts/diagnose.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/scripts/example_figures.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/scripts/permutation_nulls.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/scripts/quality_floor.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/scripts/read_start_dispersion.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/scripts/spikein_ratio.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/setup.sh +0 -0
- {migec-2.2.0 → migec-2.2.1}/src/_bindings.cpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/src/assemble.cpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/src/checkout.cpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/src/consensus.cpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/src/fastq.cpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/src/mig_record.cpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/src/pattern.cpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/src/refine.cpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/src/resource.cpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/src/subsample.cpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/src/suggest.cpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/src/types.cpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/src/umi_stats.cpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/src/whitelist.cpp +0 -0
- {migec-2.2.0/tests/benchmark → migec-2.2.1/tests}/__init__.py +0 -0
- {migec-2.2.0/tests/realworld → migec-2.2.1/tests/benchmark}/__init__.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/benchmark/test_assemble_speed.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/benchmark/test_checkout_speed.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/benchmark/test_refine_speed.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/conftest.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/cpp/doctest.h +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/cpp/test_checkout.cpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/cpp/test_checkout_run.cpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/cpp/test_consensus.cpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/cpp/test_fastq.cpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/cpp/test_mig_record.cpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/cpp/test_parallel_stages.cpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/cpp/test_pattern.cpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/cpp/test_types.cpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/cpp/test_umi_stats.cpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/cpp/test_whitelist.cpp +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/data/migec_spikein_truth.tsv +0 -0
- {migec-2.2.0/tests/synthetic → migec-2.2.1/tests/realworld}/__init__.py +0 -0
- {migec-2.2.0/tests/unit → migec-2.2.1/tests/synthetic}/__init__.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/synthetic/_sim.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/synthetic/test_assemble.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/synthetic/test_barcode_space.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/synthetic/test_checkout_end_to_end.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/synthetic/test_dual_end.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/synthetic/test_grouping_accuracy.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/synthetic/test_nulls.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/synthetic/test_quality_floor.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/synthetic/test_refine.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/synthetic/test_sim.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/synthetic/test_subsample.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/synthetic/test_suggest.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/synthetic/test_thread_invariance.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/synthetic/test_umi_errors.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/unit/test_bindings.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/unit/test_compare_calib.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/unit/test_core.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/unit/test_downstream.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/unit/test_plot.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/unit/test_sheet_and_cli.py +0 -0
- {migec-2.2.0 → migec-2.2.1}/tests/unit/test_spikein_ratio.py +0 -0
|
@@ -4,6 +4,88 @@ Hand-written and prose-heavy: each entry says what changed and, where it matters
|
|
|
4
4
|
prevents. Releases before 2.0.0 are the Groovy MIGEC and are described by their git tags on the
|
|
5
5
|
`legacy-v1` branch.
|
|
6
6
|
|
|
7
|
+
## 2.2.1 — 2026-08-14
|
|
8
|
+
|
|
9
|
+
Documentation, pipelines and tooling. Note: **the wheel is unchanged from 2.2.0** — no command,
|
|
10
|
+
flag, output file or number that migec itself produces is different. Everything below is in the
|
|
11
|
+
repository: the SLURM templates, the Nextflow downstream layer, two more notebooks, a new docs
|
|
12
|
+
page, and the two scripts behind it.
|
|
13
|
+
|
|
14
|
+
### Pipelines you can actually run
|
|
15
|
+
|
|
16
|
+
`integrations/slurm/` is new: `migec_sample.sbatch` (one sample, environment-driven) and
|
|
17
|
+
`migec_array.sbatch` (one array task per sample-sheet row), with an example sheet and a README that
|
|
18
|
+
sizes the request per stage. Both run as **ordinary bash without SLURM** — every SLURM variable has
|
|
19
|
+
a fallback — which is how they are tested here and how a layout should be checked before a cohort
|
|
20
|
+
is queued. Note: array task 1 is the first *data* row; the header is skipped rather than counted,
|
|
21
|
+
so the range is `1-(rows - 1)`, and an `--array=0-N` runs a task that reads the header as a sample.
|
|
22
|
+
|
|
23
|
+
`integrations/nextflow/` gained a runnable entry point (`--mode consensus | ctdna | airr`) and a
|
|
24
|
+
downstream layer: `modules/downstream/{align,callvariants,arda}` plus `subworkflows/migec_ctdna`
|
|
25
|
+
and `subworkflows/migec_airr`. The align module **checks its own output** — if no `MI:Z:` tag
|
|
26
|
+
reaches the BAM it exits non-zero and names the flag each aligner needs, because that failure is
|
|
27
|
+
otherwise silent and surfaces much later as an inexplicably untagged BAM.
|
|
28
|
+
|
|
29
|
+
### Notebooks
|
|
30
|
+
|
|
31
|
+
`notebooks/README.md`, plus `exome_capture.py` (why coordinate deduplication undercounts a capture
|
|
32
|
+
panel) and `airr_repertoire.py` (clonotype counts from reads against molecules). Both are
|
|
33
|
+
simulated, so both run offline against a known answer. Never: the PEP 723 headers were
|
|
34
|
+
**under-declared** — altair renders a polars frame through pyarrow and then pandas, so a clean
|
|
35
|
+
machine failed three imports deep with an error naming neither. Every header is now what the
|
|
36
|
+
notebook actually needs, and all of them were executed to prove it.
|
|
37
|
+
|
|
38
|
+
### Which variant caller, and the count that decides it
|
|
39
|
+
|
|
40
|
+
`docs/variants.rst` answers the question people arrive with — *rare variants out of ctDNA, what do
|
|
41
|
+
I run after `assemble`?* — and extends `docs/downstream.rst`'s transport-vs-deduplicate rule one
|
|
42
|
+
level: `Mutect2`, `LoFreq`, `FreeBayes`, `VarDict` and `bcftools` read a BAM and ignore `RX`, so
|
|
43
|
+
they **compose**; `UMI-VarCal`, `UMIErrorCorrect`, `DREAMS-vc` and `Shearwater` group and consense
|
|
44
|
+
themselves, so they **replace** a stage. Never: do not apply a UMI-aware caller's family-size
|
|
45
|
+
filter to a consensus — after `assemble` every family has size 1 by construction, so
|
|
46
|
+
`--min-family-size 3` discards the entire library and reports zero variants without erroring.
|
|
47
|
+
|
|
48
|
+
The caller ranking is quoted from Maruzani et al. 2024 (BMC Genomics,
|
|
49
|
+
[doi:10.1186/s12864-024-10737-w](https://doi.org/10.1186/s12864-024-10737-w)), which is the only
|
|
50
|
+
independent comparison of the six. The page says which half of it transfers and why.
|
|
51
|
+
|
|
52
|
+
### The ctDNA ground truth was found, not built
|
|
53
|
+
|
|
54
|
+
`ROADMAP.md` item 7 said this had to be constructed because Maruzani's deposited runs carry no UMI.
|
|
55
|
+
That is true of those two runs and not of the public record. Screening SRA read structure instead
|
|
56
|
+
turned up **`PRJNA788522`** (72 runs, cell-free DNA reference material at certified 0 / 0.125 /
|
|
57
|
+
0.25 / 1% VAF, crossed with 5/20/80 ng input and 3.3/10/30x depth, three replicates) and
|
|
58
|
+
**`PRJNA507366`** (28 runs, six polymerases on the same material plus 0.031% and 0.0625% VAF).
|
|
59
|
+
Both kept a real 12 nt inline UMI, which `migec suggest` recovers from base composition alone.
|
|
60
|
+
|
|
61
|
+
Note: `PRJNA507366`'s design is in `library_name`, not `sample_alias` — every alias there reads
|
|
62
|
+
`SeraCare_Reference_Material`, so reading the alias alone would have called the study undesigned.
|
|
63
|
+
|
|
64
|
+
### `scripts/sra_fetch.py` — fetch on demand instead of mirroring
|
|
65
|
+
|
|
66
|
+
`probe` reports reads-per-spot and their lengths from metadata alone, which is the cheap "did the
|
|
67
|
+
UMI survive deposition" test; `url` gives the direct S3/HTTPS links with md5 and size; `peek`
|
|
68
|
+
streams the first N spots for `migec suggest`; `get` downloads and converts. Never: the
|
|
69
|
+
obvious-looking source is the slow one — ENA serves ready-made FASTQ and skips `fasterq-dump`, but
|
|
70
|
+
caps at one connection. Measured on `SRR17220895`: **ENA 200 kB/s against NCBI S3 at 6.7 MB/s over
|
|
71
|
+
8 connections**, so S3 is the default and `--prefer ena` is there for the studies that deposited a
|
|
72
|
+
third file the `.sra` object folds away.
|
|
73
|
+
|
|
74
|
+
This is what keeps `isalgo/umi_data` small: anything with a public accession is regenerated by a
|
|
75
|
+
one-line command recorded in `SOURCES.md`, and only the CI fixtures and the semi-internal data that
|
|
76
|
+
has nowhere else to live are mirrored.
|
|
77
|
+
|
|
78
|
+
### `scripts/ctdna_titration.py` and `notebooks/ctdna_variants.py`
|
|
79
|
+
|
|
80
|
+
The three stages over the titration, reporting molecules per site against the certified frequency.
|
|
81
|
+
Never: **the molecule total of a multiplex panel is not the count at any one site** — a variant sits
|
|
82
|
+
on one amplicon, so the total overstates the evidence by exactly the panel size. The amplicon count
|
|
83
|
+
is measured from consensus prefixes rather than assumed, and its share threshold has to sit in the
|
|
84
|
+
gap below the smallest real amplicon: at 1% the count moved with depth (5 amplicons on the deepest
|
|
85
|
+
run, 10 on the shallowest) because a shallow consensus carries more payload error, which deflated
|
|
86
|
+
the per-amplicon count on exactly the runs where the evidence was thinnest. The script warns when
|
|
87
|
+
the count is not constant across runs, which is the check that the threshold is in the gap.
|
|
88
|
+
|
|
7
89
|
## 2.2.0 — 2026-08-13
|
|
8
90
|
|
|
9
91
|
Throughput and one new measurement. No breaking change: every 2.1.0 command, flag and output file
|
|
@@ -465,12 +465,35 @@ correction is written up in `project/review-algorithms.md`.
|
|
|
465
465
|
5. **i7xi5 contingency table** — needs nothing that is not already in the headers.
|
|
466
466
|
6. **`2026-migec-benchmark`** and the published comparisons. This is what the version number is
|
|
467
467
|
waiting on, not the code.
|
|
468
|
-
7. **
|
|
469
|
-
|
|
468
|
+
7. **Run the callers themselves** against the ctDNA ground truth below. It no longer has to be
|
|
469
|
+
built -- only the call sets are missing.
|
|
470
470
|
8. **R1/R2 overlap merge**, as a case of `--contig`'s placement and never a second matcher.
|
|
471
471
|
9. **Bit-parallel matcher**, last and deliberately: the scan is not the bottleneck.
|
|
472
|
+
- **The ctDNA ground truth was FOUND, not built (2026-08-13).** `PRJNA788522` (72 runs, cfDNA
|
|
473
|
+
reference material at certified 0/0.125/0.25/1% VAF x 5/20/80 ng x 3.3/10/30x, 3 reps) and
|
|
474
|
+
`PRJNA507366` (28 runs, six polymerases plus 0.031%/0.0625% VAF) both kept a **real 12 nt inline
|
|
475
|
+
UMI**, and `migec suggest` recovers it from base composition alone. Never: "no public ctDNA data
|
|
476
|
+
has usable UMIs" is a statement about the two runs Maruzani picked, not about the archive --
|
|
477
|
+
check read structure (`scripts/sra_fetch.py probe`) before believing it. Note: `PRJNA507366`'s
|
|
478
|
+
design is in **`library_name`**, not `sample_alias`; every alias there is
|
|
479
|
+
`SeraCare_Reference_Material`, so the alias alone makes a designed study look undesigned.
|
|
480
|
+
- **Never: the molecule total of a multiplex panel is not the count at a site.** A variant sits on
|
|
481
|
+
one amplicon, so the evidence a caller gets is `molecules / amplicons`. The amplicon count is
|
|
482
|
+
measured from consensus prefixes, and its share threshold must sit in the **gap** below the
|
|
483
|
+
smallest real amplicon (5%, where the gap is 7.6% to 1.1%): at 1% the count moved with depth --
|
|
484
|
+
5 on the deepest run, 10 on the shallowest, because a shallow consensus carries more payload
|
|
485
|
+
error -- which deflates the per-amplicon count on exactly the runs where evidence is thinnest.
|
|
486
|
+
- **Note: `scripts/sra_fetch.py` is the fetch-on-demand path**, so `isalgo/umi_data` holds only CI
|
|
487
|
+
fixtures and semi-internal data. Never: ENA's ready-made FASTQ looks like the lazy source and is
|
|
488
|
+
33x slower -- **200 kB/s against NCBI S3's 6.7 MB/s** on 8 connections, measured on SRR17220895.
|
|
489
|
+
S3 plus `fasterq-dump` is the default; `--prefer ena` exists for studies that deposited a third
|
|
490
|
+
file the `.sra` folds away.
|
|
472
491
|
- **Note: Britanova et al aging (bulk TCR, shallow) lives on aldan3** and is the real dataset for the
|
|
473
492
|
1-3 read regime. Not pulled yet. aldan3 compute goes through SLURM, never the frontend.
|
|
493
|
+
- **Note: the ctDNA titration runs on aldan3** (`~/migec-ctdna`, `migec_ctdna.sbatch`, partition
|
|
494
|
+
`medium`, 16 cores / 32 GB). The job builds its own micromamba env because the cluster's system
|
|
495
|
+
python is **3.8** and migec needs >= 3.10, and it installs from PyPI rather than from source,
|
|
496
|
+
which doubles as a check that the published linux wheels work.
|
|
474
497
|
- The archive is pushed: `legacy-v1` + tag `v1-final`, and master is the rewrite. Recovery point
|
|
475
498
|
`~/backup/migec-local-mirror-2026-08-13.git`. Note: The canonical repo is **antigenomics/migec**;
|
|
476
499
|
`mikessh/migec` is a redirect, and `gh` commands must use the former.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: migec
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.1
|
|
4
4
|
Summary: UMI barcode extraction, correction and consensus assembly for barcoded sequencing
|
|
5
5
|
Keywords: umi,barcode,consensus,airr,repseq,single-cell,sequencing
|
|
6
6
|
Author-Email: Mikhail Shugay <mikhail.shugay@gmail.com>
|
|
@@ -51,10 +51,10 @@ Computational Biology* 2017).
|
|
|
51
51
|
|
|
52
52
|
> **Version 2 is under construction.** All three stages work today — `checkout`, `refine` and
|
|
53
53
|
> `assemble` — with cell barcodes, whitelists, dual-end and positional (10x) layouts, cell calling,
|
|
54
|
-
> QC figures, and `suggest`/`subsample`/`plot`. Index hopping, `.mig` bucket output and the
|
|
55
|
-
> comparisons are what remain; see [`ROADMAP.md`](ROADMAP.md). The Groovy
|
|
56
|
-
> branch [`legacy-v1`](../../tree/legacy-v1) and at tag `v1-final` —
|
|
57
|
-
> [1.2.9 release](../../releases/tag/1.2.9).
|
|
54
|
+
> QC figures, and `suggest`/`subsample`/`plot`. Index hopping, `.mig` bucket output and the
|
|
55
|
+
> published benchmark comparisons are what remain; see [`ROADMAP.md`](ROADMAP.md). The Groovy
|
|
56
|
+
> MIGEC 1.2.9 is archived on branch [`legacy-v1`](../../tree/legacy-v1) and at tag `v1-final` —
|
|
57
|
+
> Java users want the jars on the [1.2.9 release](../../releases/tag/1.2.9).
|
|
58
58
|
|
|
59
59
|
## Why
|
|
60
60
|
|
|
@@ -105,13 +105,49 @@ one buys.
|
|
|
105
105
|
> **Never** run alevin, bustools or STARsolo on a consensus FASTQ. They read the barcode out of a
|
|
106
106
|
> *raw* barcode read and deduplicate themselves; migec already did, and that read no longer exists.
|
|
107
107
|
|
|
108
|
+
### Calling rare variants
|
|
109
|
+
|
|
110
|
+
The same line decides which variant caller to use. `Mutect2`, `LoFreq`, `FreeBayes` and `VarDict`
|
|
111
|
+
read a BAM and never look at `RX`, so they **compose**: one consensus is one molecule, so their
|
|
112
|
+
depth already is a molecule count. `UMI-VarCal` and `UMIErrorCorrect` do their own grouping and
|
|
113
|
+
consensus, so they **replace** `assemble` rather than following it — and a `--min-family-size 3`
|
|
114
|
+
filter after `assemble` discards the whole library, because every family already has size 1.
|
|
115
|
+
|
|
116
|
+
Which caller matters less than how many molecules it is handed, and that is fixed before any
|
|
117
|
+
software runs: sequencing deeper buys more reads per molecule, only more input DNA buys molecules.
|
|
118
|
+
[docs/variants.rst](docs/variants.rst) has the caller table, the published comparison, and the run
|
|
119
|
+
over cell-free DNA reference material at known allele frequency that puts numbers on it.
|
|
120
|
+
|
|
108
121
|
## Install
|
|
109
122
|
|
|
110
123
|
```bash
|
|
111
|
-
pip install migec
|
|
124
|
+
uv tool install migec # or: uv pip install migec, or: pip install migec
|
|
125
|
+
migec info # prints the three version strings; they must agree
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Wheels for CPython 3.10–3.13 on Linux x86-64 and macOS arm64, so nothing compiles. Python 3.10 or
|
|
129
|
+
newer is required — on a cluster whose system Python is older, bring your own:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
uv venv --python 3.12 ~/envs/migec && source ~/envs/migec/bin/activate && uv pip install migec
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**From source**, for development:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
git clone https://github.com/antigenomics/migec && cd migec
|
|
139
|
+
bash setup.sh # uv venv, editable install, and asserts the C++ extension imports
|
|
112
140
|
```
|
|
113
141
|
|
|
114
|
-
|
|
142
|
+
`setup.sh` needs [uv](https://docs.astral.sh/uv/getting-started/installation/) and a C++20
|
|
143
|
+
compiler. It deliberately asserts that `migec._core` imports rather than only that the package
|
|
144
|
+
does — without that check a failed C++ build looks like a successful install and only fails much
|
|
145
|
+
later. Then:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
cmake -S . -B build -DMIGEC_TESTS=ON && cmake --build build -j && ctest --test-dir build
|
|
149
|
+
python -m pytest tests/unit tests/synthetic -q
|
|
150
|
+
```
|
|
115
151
|
|
|
116
152
|
## Usage
|
|
117
153
|
|
|
@@ -535,6 +571,30 @@ It is also the memory-hostile case, because distinct barcodes are what everythin
|
|
|
535
571
|
it is what the benchmarks use: 1,179,549 reads/s at 1.02 reads/UMI, 282 B resident per distinct
|
|
536
572
|
barcode, still bounded by the bucket rather than the library.
|
|
537
573
|
|
|
574
|
+
### For rare variants, the molecule count decides — not the caller
|
|
575
|
+
|
|
576
|
+
Run over 100 runs of two SiMSen-Seq studies on cell-free DNA reference material at *certified*
|
|
577
|
+
allele frequencies (`PRJNA788522`, `PRJNA507366`; `scripts/ctdna_titration.py`). At 0.125% VAF the
|
|
578
|
+
DNA input alone settles the outcome:
|
|
579
|
+
|
|
580
|
+
| input | depth | molecules at the site | variant molecules | P(at least 3 copies) |
|
|
581
|
+
|---|---|---|---|---|
|
|
582
|
+
| 5 ng | 3.3× | 2,699 | 3.4 | **0.65** |
|
|
583
|
+
| 5 ng | 10× | 4,530 | 5.7 | **0.89** |
|
|
584
|
+
| 20 ng | 3.3× | 7,162 | 9.0 | 0.99 |
|
|
585
|
+
| 20 ng | 10× | 11,961 | 15.0 | 1.00 |
|
|
586
|
+
|
|
587
|
+
At 5 ng a 0.125% variant is a coin flip and **no caller fixes it** — a third of replicates simply do
|
|
588
|
+
not contain three copies. The decision that mattered was made at extraction.
|
|
589
|
+
[docs/variants.rst](docs/variants.rst) has the caller table (standard callers *compose* with migec,
|
|
590
|
+
UMI-aware ones *replace* a stage), the 33-run true-negative arm, and the rest of the numbers.
|
|
591
|
+
|
|
592
|
+
The same data gives an independent check on the barcode-error estimate: `PRJNA507366` varies the
|
|
593
|
+
polymerase while holding template and protocol fixed, and `refine` separates the high-fidelity
|
|
594
|
+
enzymes from the standard ones by **3 Phred, a factor of two** — Accuprime/Platinum/Phusion at
|
|
595
|
+
Q30.8–31.4 against Platinum HiFi at Q34.0 and Platinum SuperFi at Q34.8–35.1 — from the barcodes
|
|
596
|
+
alone, with no reference, no alignment, and no knowledge of which enzyme it was looking at.
|
|
597
|
+
|
|
538
598
|
### Speed and memory are reported, not assumed
|
|
539
599
|
|
|
540
600
|
`--threads` defaults to one per core on **all three stages**, and **the output is byte-identical
|
|
@@ -646,21 +706,67 @@ distinct molecules. Both are reported; only the collision form feeds any decisio
|
|
|
646
706
|
|
|
647
707
|
## Pipelines
|
|
648
708
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
709
|
+
Two, for two situations ([docs/nextflow.rst](docs/nextflow.rst) has both):
|
|
710
|
+
|
|
711
|
+
**Nextflow** — `integrations/nextflow/` runs the three stages and then keeps going:
|
|
712
|
+
|
|
713
|
+
```bash
|
|
714
|
+
nextflow run integrations/nextflow --mode ctdna --input 'd/*_R{1,2}.fq.gz' --preset tso500 --fasta ref.fa
|
|
715
|
+
nextflow run integrations/nextflow --mode airr --input 'd/*_R{1,2}.fq.gz' --preset migec
|
|
716
|
+
nextflow run integrations/nextflow --mode consensus --input 'd/*_R{1,2}.fq.gz' --preset 10x-v2
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
`ctdna` aligns and calls variants, `airr` calls clonotypes with arda, `consensus` stops at the
|
|
720
|
+
consensus. It drops into [nf-core/airrflow](https://nf-co.re/airrflow) or anything else that hands
|
|
721
|
+
you FASTQ pairs. The align module fails loudly if the `MI:Z:` tag does not reach the BAM, because
|
|
722
|
+
that failure is otherwise silent.
|
|
723
|
+
|
|
724
|
+
**SLURM** — `integrations/slurm/` is two sbatch templates and a sample sheet, for a cohort where
|
|
725
|
+
the consensus *is* the deliverable:
|
|
726
|
+
|
|
727
|
+
```bash
|
|
728
|
+
sbatch --array=1-$(($(wc -l < samples.tsv) - 1)) integrations/slurm/migec_array.sbatch samples.tsv
|
|
729
|
+
R1=s1.fq.gz SAMPLE=s1 BC_PATTERN='0:12' bash integrations/slurm/migec_sample.sbatch # no SLURM
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
Both run as ordinary bash without SLURM — that is how they are tested, and how a layout should be
|
|
733
|
+
checked before a cohort is queued.
|
|
653
734
|
|
|
654
735
|
All three stages thread, and each is byte-identical at any `-t`, so a retry with different cores
|
|
655
|
-
cannot change a result — which is what makes an escalating `errorStrategy 'retry'`
|
|
656
|
-
Per-sample keys in `meta` beat the `params
|
|
657
|
-
|
|
658
|
-
|
|
736
|
+
cannot change a result — which is what makes an escalating `errorStrategy 'retry'` or `--requeue`
|
|
737
|
+
safe. Per-sample keys in `meta` beat the `params.*` defaults, so one run can mix chemistries.
|
|
738
|
+
|
|
739
|
+
> The SLURM templates have been run end to end here. Nextflow has not — it is not installed on this
|
|
740
|
+
> machine, so the modules are reviewed against the nf-core spec rather than verified by a run.
|
|
741
|
+
|
|
742
|
+
## Notebooks
|
|
743
|
+
|
|
744
|
+
Six [marimo](https://marimo.io) notebooks, each a plain Python file with its own dependency header,
|
|
745
|
+
so `uv` builds the environment and nothing needs installing first:
|
|
746
|
+
|
|
747
|
+
```bash
|
|
748
|
+
uv run marimo edit notebooks/ctdna_variants.py
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
`platforms.py` (every layout), `barcode_space.py` (collisions and the error budget),
|
|
752
|
+
`refine_diagnostics.py` (where the molecules went), `exome_capture.py` (why coordinate
|
|
753
|
+
deduplication undercounts), `airr_repertoire.py` (reads against molecules for clonotype counts),
|
|
754
|
+
`ctdna_variants.py` (how many molecules a caller actually gets). See
|
|
755
|
+
[notebooks/README.md](notebooks/README.md); four of the six need no network at all.
|
|
659
756
|
|
|
660
757
|
## Documentation
|
|
661
758
|
|
|
662
|
-
<https://antigenomics.github.io/migec
|
|
663
|
-
|
|
759
|
+
<https://antigenomics.github.io/migec/>. The pages worth knowing about:
|
|
760
|
+
|
|
761
|
+
| | |
|
|
762
|
+
|---|---|
|
|
763
|
+
| [`docs/layouts.rst`](docs/layouts.rst) | where the barcode is: the grammar, every preset, fgbio read structures |
|
|
764
|
+
| [`docs/variants.rst`](docs/variants.rst) | which variant caller, and the molecule count that decides whether any can see it |
|
|
765
|
+
| [`docs/downstream.rst`](docs/downstream.rst) | what consumes the consensus, measured tool by tool |
|
|
766
|
+
| [`docs/nextflow.rst`](docs/nextflow.rst) | the Nextflow modules and the SLURM templates |
|
|
767
|
+
| [`docs/formats.rst`](docs/formats.rst) | the inter-stage contract: tags, `.mig` buckets, every TSV column |
|
|
768
|
+
| [`SOURCES.md`](SOURCES.md) | every dataset, where it came from, and the command that re-fetches it |
|
|
769
|
+
| [`ROADMAP.md`](ROADMAP.md) | what is implemented and what is not |
|
|
664
770
|
|
|
665
771
|
## Citing
|
|
666
772
|
|
|
@@ -17,10 +17,10 @@ Computational Biology* 2017).
|
|
|
17
17
|
|
|
18
18
|
> **Version 2 is under construction.** All three stages work today — `checkout`, `refine` and
|
|
19
19
|
> `assemble` — with cell barcodes, whitelists, dual-end and positional (10x) layouts, cell calling,
|
|
20
|
-
> QC figures, and `suggest`/`subsample`/`plot`. Index hopping, `.mig` bucket output and the
|
|
21
|
-
> comparisons are what remain; see [`ROADMAP.md`](ROADMAP.md). The Groovy
|
|
22
|
-
> branch [`legacy-v1`](../../tree/legacy-v1) and at tag `v1-final` —
|
|
23
|
-
> [1.2.9 release](../../releases/tag/1.2.9).
|
|
20
|
+
> QC figures, and `suggest`/`subsample`/`plot`. Index hopping, `.mig` bucket output and the
|
|
21
|
+
> published benchmark comparisons are what remain; see [`ROADMAP.md`](ROADMAP.md). The Groovy
|
|
22
|
+
> MIGEC 1.2.9 is archived on branch [`legacy-v1`](../../tree/legacy-v1) and at tag `v1-final` —
|
|
23
|
+
> Java users want the jars on the [1.2.9 release](../../releases/tag/1.2.9).
|
|
24
24
|
|
|
25
25
|
## Why
|
|
26
26
|
|
|
@@ -71,13 +71,49 @@ one buys.
|
|
|
71
71
|
> **Never** run alevin, bustools or STARsolo on a consensus FASTQ. They read the barcode out of a
|
|
72
72
|
> *raw* barcode read and deduplicate themselves; migec already did, and that read no longer exists.
|
|
73
73
|
|
|
74
|
+
### Calling rare variants
|
|
75
|
+
|
|
76
|
+
The same line decides which variant caller to use. `Mutect2`, `LoFreq`, `FreeBayes` and `VarDict`
|
|
77
|
+
read a BAM and never look at `RX`, so they **compose**: one consensus is one molecule, so their
|
|
78
|
+
depth already is a molecule count. `UMI-VarCal` and `UMIErrorCorrect` do their own grouping and
|
|
79
|
+
consensus, so they **replace** `assemble` rather than following it — and a `--min-family-size 3`
|
|
80
|
+
filter after `assemble` discards the whole library, because every family already has size 1.
|
|
81
|
+
|
|
82
|
+
Which caller matters less than how many molecules it is handed, and that is fixed before any
|
|
83
|
+
software runs: sequencing deeper buys more reads per molecule, only more input DNA buys molecules.
|
|
84
|
+
[docs/variants.rst](docs/variants.rst) has the caller table, the published comparison, and the run
|
|
85
|
+
over cell-free DNA reference material at known allele frequency that puts numbers on it.
|
|
86
|
+
|
|
74
87
|
## Install
|
|
75
88
|
|
|
76
89
|
```bash
|
|
77
|
-
pip install migec
|
|
90
|
+
uv tool install migec # or: uv pip install migec, or: pip install migec
|
|
91
|
+
migec info # prints the three version strings; they must agree
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Wheels for CPython 3.10–3.13 on Linux x86-64 and macOS arm64, so nothing compiles. Python 3.10 or
|
|
95
|
+
newer is required — on a cluster whose system Python is older, bring your own:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
uv venv --python 3.12 ~/envs/migec && source ~/envs/migec/bin/activate && uv pip install migec
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**From source**, for development:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
git clone https://github.com/antigenomics/migec && cd migec
|
|
105
|
+
bash setup.sh # uv venv, editable install, and asserts the C++ extension imports
|
|
78
106
|
```
|
|
79
107
|
|
|
80
|
-
|
|
108
|
+
`setup.sh` needs [uv](https://docs.astral.sh/uv/getting-started/installation/) and a C++20
|
|
109
|
+
compiler. It deliberately asserts that `migec._core` imports rather than only that the package
|
|
110
|
+
does — without that check a failed C++ build looks like a successful install and only fails much
|
|
111
|
+
later. Then:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
cmake -S . -B build -DMIGEC_TESTS=ON && cmake --build build -j && ctest --test-dir build
|
|
115
|
+
python -m pytest tests/unit tests/synthetic -q
|
|
116
|
+
```
|
|
81
117
|
|
|
82
118
|
## Usage
|
|
83
119
|
|
|
@@ -501,6 +537,30 @@ It is also the memory-hostile case, because distinct barcodes are what everythin
|
|
|
501
537
|
it is what the benchmarks use: 1,179,549 reads/s at 1.02 reads/UMI, 282 B resident per distinct
|
|
502
538
|
barcode, still bounded by the bucket rather than the library.
|
|
503
539
|
|
|
540
|
+
### For rare variants, the molecule count decides — not the caller
|
|
541
|
+
|
|
542
|
+
Run over 100 runs of two SiMSen-Seq studies on cell-free DNA reference material at *certified*
|
|
543
|
+
allele frequencies (`PRJNA788522`, `PRJNA507366`; `scripts/ctdna_titration.py`). At 0.125% VAF the
|
|
544
|
+
DNA input alone settles the outcome:
|
|
545
|
+
|
|
546
|
+
| input | depth | molecules at the site | variant molecules | P(at least 3 copies) |
|
|
547
|
+
|---|---|---|---|---|
|
|
548
|
+
| 5 ng | 3.3× | 2,699 | 3.4 | **0.65** |
|
|
549
|
+
| 5 ng | 10× | 4,530 | 5.7 | **0.89** |
|
|
550
|
+
| 20 ng | 3.3× | 7,162 | 9.0 | 0.99 |
|
|
551
|
+
| 20 ng | 10× | 11,961 | 15.0 | 1.00 |
|
|
552
|
+
|
|
553
|
+
At 5 ng a 0.125% variant is a coin flip and **no caller fixes it** — a third of replicates simply do
|
|
554
|
+
not contain three copies. The decision that mattered was made at extraction.
|
|
555
|
+
[docs/variants.rst](docs/variants.rst) has the caller table (standard callers *compose* with migec,
|
|
556
|
+
UMI-aware ones *replace* a stage), the 33-run true-negative arm, and the rest of the numbers.
|
|
557
|
+
|
|
558
|
+
The same data gives an independent check on the barcode-error estimate: `PRJNA507366` varies the
|
|
559
|
+
polymerase while holding template and protocol fixed, and `refine` separates the high-fidelity
|
|
560
|
+
enzymes from the standard ones by **3 Phred, a factor of two** — Accuprime/Platinum/Phusion at
|
|
561
|
+
Q30.8–31.4 against Platinum HiFi at Q34.0 and Platinum SuperFi at Q34.8–35.1 — from the barcodes
|
|
562
|
+
alone, with no reference, no alignment, and no knowledge of which enzyme it was looking at.
|
|
563
|
+
|
|
504
564
|
### Speed and memory are reported, not assumed
|
|
505
565
|
|
|
506
566
|
`--threads` defaults to one per core on **all three stages**, and **the output is byte-identical
|
|
@@ -612,21 +672,67 @@ distinct molecules. Both are reported; only the collision form feeds any decisio
|
|
|
612
672
|
|
|
613
673
|
## Pipelines
|
|
614
674
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
675
|
+
Two, for two situations ([docs/nextflow.rst](docs/nextflow.rst) has both):
|
|
676
|
+
|
|
677
|
+
**Nextflow** — `integrations/nextflow/` runs the three stages and then keeps going:
|
|
678
|
+
|
|
679
|
+
```bash
|
|
680
|
+
nextflow run integrations/nextflow --mode ctdna --input 'd/*_R{1,2}.fq.gz' --preset tso500 --fasta ref.fa
|
|
681
|
+
nextflow run integrations/nextflow --mode airr --input 'd/*_R{1,2}.fq.gz' --preset migec
|
|
682
|
+
nextflow run integrations/nextflow --mode consensus --input 'd/*_R{1,2}.fq.gz' --preset 10x-v2
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
`ctdna` aligns and calls variants, `airr` calls clonotypes with arda, `consensus` stops at the
|
|
686
|
+
consensus. It drops into [nf-core/airrflow](https://nf-co.re/airrflow) or anything else that hands
|
|
687
|
+
you FASTQ pairs. The align module fails loudly if the `MI:Z:` tag does not reach the BAM, because
|
|
688
|
+
that failure is otherwise silent.
|
|
689
|
+
|
|
690
|
+
**SLURM** — `integrations/slurm/` is two sbatch templates and a sample sheet, for a cohort where
|
|
691
|
+
the consensus *is* the deliverable:
|
|
692
|
+
|
|
693
|
+
```bash
|
|
694
|
+
sbatch --array=1-$(($(wc -l < samples.tsv) - 1)) integrations/slurm/migec_array.sbatch samples.tsv
|
|
695
|
+
R1=s1.fq.gz SAMPLE=s1 BC_PATTERN='0:12' bash integrations/slurm/migec_sample.sbatch # no SLURM
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
Both run as ordinary bash without SLURM — that is how they are tested, and how a layout should be
|
|
699
|
+
checked before a cohort is queued.
|
|
619
700
|
|
|
620
701
|
All three stages thread, and each is byte-identical at any `-t`, so a retry with different cores
|
|
621
|
-
cannot change a result — which is what makes an escalating `errorStrategy 'retry'`
|
|
622
|
-
Per-sample keys in `meta` beat the `params
|
|
623
|
-
|
|
624
|
-
|
|
702
|
+
cannot change a result — which is what makes an escalating `errorStrategy 'retry'` or `--requeue`
|
|
703
|
+
safe. Per-sample keys in `meta` beat the `params.*` defaults, so one run can mix chemistries.
|
|
704
|
+
|
|
705
|
+
> The SLURM templates have been run end to end here. Nextflow has not — it is not installed on this
|
|
706
|
+
> machine, so the modules are reviewed against the nf-core spec rather than verified by a run.
|
|
707
|
+
|
|
708
|
+
## Notebooks
|
|
709
|
+
|
|
710
|
+
Six [marimo](https://marimo.io) notebooks, each a plain Python file with its own dependency header,
|
|
711
|
+
so `uv` builds the environment and nothing needs installing first:
|
|
712
|
+
|
|
713
|
+
```bash
|
|
714
|
+
uv run marimo edit notebooks/ctdna_variants.py
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
`platforms.py` (every layout), `barcode_space.py` (collisions and the error budget),
|
|
718
|
+
`refine_diagnostics.py` (where the molecules went), `exome_capture.py` (why coordinate
|
|
719
|
+
deduplication undercounts), `airr_repertoire.py` (reads against molecules for clonotype counts),
|
|
720
|
+
`ctdna_variants.py` (how many molecules a caller actually gets). See
|
|
721
|
+
[notebooks/README.md](notebooks/README.md); four of the six need no network at all.
|
|
625
722
|
|
|
626
723
|
## Documentation
|
|
627
724
|
|
|
628
|
-
<https://antigenomics.github.io/migec
|
|
629
|
-
|
|
725
|
+
<https://antigenomics.github.io/migec/>. The pages worth knowing about:
|
|
726
|
+
|
|
727
|
+
| | |
|
|
728
|
+
|---|---|
|
|
729
|
+
| [`docs/layouts.rst`](docs/layouts.rst) | where the barcode is: the grammar, every preset, fgbio read structures |
|
|
730
|
+
| [`docs/variants.rst`](docs/variants.rst) | which variant caller, and the molecule count that decides whether any can see it |
|
|
731
|
+
| [`docs/downstream.rst`](docs/downstream.rst) | what consumes the consensus, measured tool by tool |
|
|
732
|
+
| [`docs/nextflow.rst`](docs/nextflow.rst) | the Nextflow modules and the SLURM templates |
|
|
733
|
+
| [`docs/formats.rst`](docs/formats.rst) | the inter-stage contract: tags, `.mig` buckets, every TSV column |
|
|
734
|
+
| [`SOURCES.md`](SOURCES.md) | every dataset, where it came from, and the command that re-fetches it |
|
|
735
|
+
| [`ROADMAP.md`](ROADMAP.md) | what is implemented and what is not |
|
|
630
736
|
|
|
631
737
|
## Citing
|
|
632
738
|
|
|
@@ -43,13 +43,14 @@ Everything below is either open or half-open. Nothing else on this page is.
|
|
|
43
43
|
| 4 | `--rt-error auto` | M1 | falls straight out of (3). The floor is a property of the enzyme and the cycle count, so fitting it per dataset is the honest default once there is something to fit against |
|
|
44
44
|
| 5 | i7xi5 contingency table | M2 | the only way index hopping is actually estimable, and it needs nothing that is not already in the headers |
|
|
45
45
|
| 6 | `2026-migec-benchmark`, the published comparisons | M5 | MIGEC v1, MAGERI, UMI-tools, Calib, fgbio, Cell Ranger, UMI-VarCal, UMIErrorCorrect. This is what the version number is waiting on, not the code |
|
|
46
|
-
| 7 | ctDNA ground truth
|
|
46
|
+
| 7 | ctDNA ground truth | M5 | **no longer has to be built.** `PRJNA788522` and `PRJNA507366` are cfDNA reference material at certified VAF (0 / 0.031 / 0.0625 / 0.125 / 0.25 / 1%) with a **real 12 nt inline UMI that survived deposition**, and a true-negative `WT` arm. Maruzani's two runs carry no UMI and had to be simulated; these do not. `SOURCES.md`, `docs/variants.rst` |
|
|
47
47
|
| 8 | R1/R2 overlap merge | M1 | a special case of `--contig`'s placement, never a second matcher in `checkout` |
|
|
48
48
|
| 9 | Bit-parallel matcher | M2 | last, deliberately: the scan is O(offsets x pattern) and is **not** the bottleneck. It goes in when a benchmark says so |
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
the real 1-3 reads/UMI dataset) lives on aldan3 and has not been pulled
|
|
52
|
-
|
|
50
|
+
One thing is still blocked on data rather than on work: **Britanova et al aging** (bulk TCR,
|
|
51
|
+
shallow -- the real 1-3 reads/UMI dataset) lives on aldan3 and has not been pulled. The ctDNA
|
|
52
|
+
ground truth is no longer on that list: it was found rather than built, by screening SRA read
|
|
53
|
+
structure instead of trusting the published claim that none exists (`scripts/sra_fetch.py probe`).
|
|
53
54
|
|
|
54
55
|
## M0 — skeleton, format, simulator
|
|
55
56
|
|
|
@@ -299,11 +300,16 @@ model has to use the evidence that survives at one read:
|
|
|
299
300
|
- [ ] `2026-migec-benchmark` repo, `isalgo/umi_data`, comparisons against MIGEC v1, MAGERI,
|
|
300
301
|
UMI-tools, Calib, fgbio, Cell Ranger, **UMI-VarCal and UMIErrorCorrect** (the two UMI-aware
|
|
301
302
|
callers benchmarked by Maruzani et al. 2024 for low-frequency ctDNA)
|
|
302
|
-
- [
|
|
303
|
-
|
|
304
|
-
(
|
|
305
|
-
|
|
306
|
-
|
|
303
|
+
- [x] ctDNA ground truth **located, not simulated** (2026-08-13): `PRJNA788522` (72 runs, cfDNA
|
|
304
|
+
reference material at 0 / 0.125 / 0.25 / 1% VAF x 5/20/80 ng x 3.3/10/30x, three replicates)
|
|
305
|
+
and `PRJNA507366` (28 runs, six polymerases plus 0.031% / 0.0625% VAF). Both carry a real
|
|
306
|
+
12 nt inline UMI. Never: Maruzani's runs carry **no** UMI (aligned BAM submissions, `suggest`
|
|
307
|
+
finds no pattern, `CMP_LINKAGE_GROUP` empty), so theirs had to be simulated — 9 nt, Phred
|
|
308
|
+
fixed at 37, assigned to reads sharing start *and* end, which is the co-terminal assumption
|
|
309
|
+
X1 falsified. That is a property of the two runs they picked, not of the public record
|
|
310
|
+
- [ ] Variant calls on top of it: run the callers themselves (LoFreq, Mutect2, UMI-VarCal,
|
|
311
|
+
UMIErrorCorrect) against the `WT` true-negative arm and the certified dilutions. `docs/variants.rst`
|
|
312
|
+
reports the molecule counts and the detectability arithmetic; the call sets are the next step
|
|
307
313
|
- Gate: grouping ARI ≥0.99; residual error ≤1e-5 on a clonal control; ≥3× MIGEC v1 wall-clock
|
|
308
314
|
|
|
309
315
|
## Deliberately not doing
|
|
@@ -16,6 +16,7 @@ are never conflated in a table row.
|
|
|
16
16
|
| `assets/benchmark_threads.tsv` + `.json` | this machine, 2026-08-13 | **experimental** (a timing measurement) | `python scripts/benchmark_threads.py --reads 2000000 -o assets/` |
|
|
17
17
|
| `assets/SRR1763769.mig.tsv`, `assets/assemble.coverage.tsv` | `isalgo/umi_data` CI fixture | derived | `migec refine ci/SRR1763769_umi0.5pct.fq.gz -o r/ && migec assemble r/CTRL.fq.gz -o a/` |
|
|
18
18
|
| `assets/*.svg`, `assets/*.gp` | the tables beside them | derived | `migec plot assets/ -o assets/` |
|
|
19
|
+
| `assets/ctdna_titration.tsv` (+ `_runs.tsv`) | 100 runs of `PRJNA788522` + `PRJNA507366` | derived (migec over experimental data) | `python scripts/sra_fetch.py get <runs> -o simsen/` then `python scripts/ctdna_titration.py --reads simsen/ --out ctdna/ --design design.tsv` |
|
|
19
20
|
|
|
20
21
|
Result tables and figures are output, not data, so they live here next to the script that made
|
|
21
22
|
them rather than in `isalgo/umi_data`. Test corpora go the other way: HuggingFace, never git.
|
|
@@ -158,6 +159,52 @@ numbers inherits it.
|
|
|
158
159
|
background at controlled depth is the ground truth a consensus-quality claim wants — and two more
|
|
159
160
|
comparators for M5, `UMI-VarCal` and `UMIErrorCorrect`.
|
|
160
161
|
|
|
162
|
+
### SiMSen-Seq — the ctDNA data that DID keep its UMIs (fetched 2026-08-13)
|
|
163
|
+
|
|
164
|
+
Found by screening SRA rather than by assuming, after the Maruzani entry above established that
|
|
165
|
+
their two accessions had not. Both studies are the Gothenburg SiMSen-Seq protocol on **commercial
|
|
166
|
+
cell-free DNA reference material with certified mutant allele frequencies**, and both deposited
|
|
167
|
+
the reads untrimmed, so the **12 nt inline UMI is still there**. `migec suggest` recovers it from
|
|
168
|
+
base composition alone with no knowledge of the protocol — a 12 nt near-uniform run at cycles
|
|
169
|
+
0-11, then the constant 16 nt spacer (`ATGGGAAAGAGTGTCC`), which is exactly UMIErrorCorrect's
|
|
170
|
+
`-ul 12 -sl 16`.
|
|
171
|
+
|
|
172
|
+
| Item | Value |
|
|
173
|
+
|---|---|
|
|
174
|
+
| Pattern | `--bc-pattern '0:12'`, single-end, positional (no `--max-offset`) |
|
|
175
|
+
| Panel | multiplex amplicon, measured from the consensus prefixes: **5 amplicons** for `PRJNA788522`, **3** for `PRJNA507366` (whose own labels say `3plx`) |
|
|
176
|
+
| Cluster copy | `/projects/tcr_bcr_rnaseq/migec_ctdna/fastq` on aldan3, 100 runs, 6.9 GB (2026-08-13). A convenience cache — the fetch command above regenerates it |
|
|
177
|
+
| Provenance | experimental (SRA), commercial reference material; the certified VAFs are the vendor's |
|
|
178
|
+
| Fetch | `python scripts/sra_fetch.py get <runs> -o data/` (NCBI S3, ~7 MB/s) |
|
|
179
|
+
| Analyse | `python scripts/ctdna_titration.py --reads data/ --out out/ --design design.tsv` |
|
|
180
|
+
| Designs | `curl -sG 'https://www.ebi.ac.uk/ena/portal/api/search' -d result=read_run -d query=study_accession=<PRJ> -d fields=run_accession,sample_alias -d format=tsv -d limit=0` |
|
|
181
|
+
|
|
182
|
+
**`PRJNA788522` — the titration.** Österlund T, Filges S, Johansson G, Ståhlberg A.
|
|
183
|
+
*UMIErrorCorrect and UMIAnalyzer: Software for Consensus Read Generation, Error Correction, and
|
|
184
|
+
Visualization Using Unique Molecular Identifiers.* **Clin Chem** 2022;68(11):1425–1435.
|
|
185
|
+
[doi:10.1093/clinchem/hvac136](https://doi.org/10.1093/clinchem/hvac136), PMID 36031761.
|
|
186
|
+
|
|
187
|
+
72 runs, 41.8 M reads, Illumina MiniSeq, single-end 151 nt. The design is in `sample_alias` as
|
|
188
|
+
`<input>ng_<vaf>_<depth>x_rep_<n>`: mutant allele frequency **0% (`WT`), 0.125%, 0.25%, 1%** plus
|
|
189
|
+
an undiluted `cell_line` arm, crossed with DNA input **5 / 20 / 80 ng** and depth **3.3 / 10 / 30x
|
|
190
|
+
reads per UMI**, three replicates each. Note: the `WT` arm is a **true negative** — its variant
|
|
191
|
+
frequency is zero by construction, so anything a caller reports there is its own false-positive
|
|
192
|
+
rate on real chemistry rather than on a simulation.
|
|
193
|
+
|
|
194
|
+
**`PRJNA507366` — the polymerase panel.** Filges S, Yamada E, Ståhlberg A, Godfrey TE. *Impact of
|
|
195
|
+
Polymerase Fidelity on Background Error Rates in Next-Generation Sequencing with Unique Molecular
|
|
196
|
+
Identifiers/Barcodes.* **Sci Rep** 2019;9(1):3503.
|
|
197
|
+
[doi:10.1038/s41598-019-39762-6](https://doi.org/10.1038/s41598-019-39762-6), PMID 30837525.
|
|
198
|
+
|
|
199
|
+
28 runs, 81.4 M reads. Note: the design is **not** in `sample_alias` (every row reads
|
|
200
|
+
`SeraCare_Reference_Material`) — it is in **`library_name`** (the enzyme: Phusion, Accuprime,
|
|
201
|
+
Accuprime_hifi, Platinum, Platinum_Hifi, Platinum superfi, three replicates each) and in
|
|
202
|
+
`experiment_title` (`Wildtype` against `0.031% VAF` / `0.0625% VAF` / `0.125% VAF`). Reading the
|
|
203
|
+
alias alone would have called this study undesigned. It extends the frequency range **4x below**
|
|
204
|
+
PRJNA788522's floor, and it is the only public dataset here that varies the polymerase while
|
|
205
|
+
holding template and protocol fixed — which is the comparison `--rt-error auto` needs and the one
|
|
206
|
+
McInerney 2014's published fidelities can be checked against.
|
|
207
|
+
|
|
161
208
|
### Calib (github.com/vpc-ccg/calib)
|
|
162
209
|
|
|
163
210
|
| Item | Value |
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
study enzyme input_ng arm depth n amplicons reads molecules_per_amplicon reads_per_molecule barcode_phred variant_molecules p_enough
|
|
2
|
+
PRJNA507366 Accuprime WT 3 3 1.95274e+06 79751.7 8.09 30.8233 0 0
|
|
3
|
+
PRJNA507366 Accuprime_hifi WT 3 3 2.25763e+06 93253.7 8.07 30.8367 0 0
|
|
4
|
+
PRJNA507366 Phusion WT 3 3 1.84092e+06 95455.7 6.43667 31.3967 0 0
|
|
5
|
+
PRJNA507366 PlatRegular 80 WT 3 3 4.22022e+06 57728 24.3433 28.8667 0 0
|
|
6
|
+
PRJNA507366 PlatSuperfi 80 WT 3 3 3.91793e+06 134430 9.73667 29.37 0 0
|
|
7
|
+
PRJNA507366 Platinum WT 3 3 2.15418e+06 86146.7 8.35667 31.0067 0 0
|
|
8
|
+
PRJNA507366 Platinum superfi 50 0.125% 1 3 3.8665e+06 73286 17.59 34.78 91.61 1
|
|
9
|
+
PRJNA507366 Platinum superfi 70 0.031% 3 3 4.07284e+06 89542 15.1367 35.1067 27.7567 1
|
|
10
|
+
PRJNA507366 Platinum superfi 70 0.0625% 3 3 3.60725e+06 86139 13.89 34.9567 53.8367 1
|
|
11
|
+
PRJNA507366 Platinum_Hifi WT 3 3 1.81798e+06 80859.3 7.54 33.9867 0 0
|
|
12
|
+
PRJNA788522 20 0.125 10.0 3 5 1.06487e+06 11960.7 17.7367 28.6467 14.9533 0.999933
|
|
13
|
+
PRJNA788522 20 0.125 3.3 3 5 321953 7162.33 8.98667 27.65 8.95333 0.9935
|
|
14
|
+
PRJNA788522 20 0.25 10.0 3 5 1.15595e+06 13349.7 17.1167 29.02 33.3733 1
|
|
15
|
+
PRJNA788522 20 0.25 3.3 3 5 289023 8048.67 7.14333 27.7033 20.12 1
|
|
16
|
+
PRJNA788522 20 1 10.0 3 5 445961 8630 10.3233 27.27 86.3 1
|
|
17
|
+
PRJNA788522 20 1 3.3 3 5 131423 5970.67 4.40667 25.4833 59.7067 1
|
|
18
|
+
PRJNA788522 20 WT 10.0 3 5 1.07623e+06 13988.7 15.3567 29.6467 0 0
|
|
19
|
+
PRJNA788522 20 WT 3.3 3 5 334784 8479 7.89 27.3833 0 0
|
|
20
|
+
PRJNA788522 20 cell_line 10.0 3 5 813932 10299.3 15.7467 29.9133
|
|
21
|
+
PRJNA788522 20 cell_line 3.3 3 5 232375 6310 7.39333 26.27
|
|
22
|
+
PRJNA788522 20 cell_line 30.0 3 5 1.9048e+06 16808.7 22.6567 30.9867
|
|
23
|
+
PRJNA788522 5 0.125 10.0 3 5 434820 4530 20.02 29.03 5.66333 0.894
|
|
24
|
+
PRJNA788522 5 0.125 3.3 3 5 182150 2699 13.47 28.1 3.37 0.654933
|
|
25
|
+
PRJNA788522 5 0.25 10.0 3 5 909770 5899.33 28.9033 29.14 14.75 0.9994
|
|
26
|
+
PRJNA788522 5 0.25 3.3 3 5 182433 2945.67 12.32 28.17 7.36333 0.976367
|
|
27
|
+
PRJNA788522 5 1 10.0 3 5 501350 4129 24.2467 29.0267 41.29 1
|
|
28
|
+
PRJNA788522 5 1 3.3 3 5 167841 2888.33 11.5833 28.31 28.8833 1
|
|
29
|
+
PRJNA788522 5 WT 10.0 3 5 533736 4428 24.04 29.21 0 0
|
|
30
|
+
PRJNA788522 5 WT 3.3 3 5 146169 2868.67 10.1533 28.4567 0 0
|
|
31
|
+
PRJNA788522 5 cell_line 10.0 3 5 304544 4559.33 13.5433 27.53
|
|
32
|
+
PRJNA788522 5 cell_line 3.3 3 5 76465.3 2958.33 5.18 20.6033
|
|
33
|
+
PRJNA788522 5 cell_line 30.0 3 5 635122 6664.33 19.0733 29.2467
|
|
34
|
+
PRJNA788522 80 cell_line 10.0 3 5 1.49859e+06 26793 11.2 31.46
|
|
35
|
+
PRJNA788522 80 cell_line 3.3 3 5 582020 19310 6.02667 29.8167
|