Skip to main content

DAISY-MRD: Distributed Analysis of Integrated Sites for Yielding MRD — WGS-based measurable residual disease monitoring in pediatric AML

Project description

DAISY-MRD

Distributed Analysis of Integrated Sites for Yielding MRD

A Python package for WGS-based measurable residual disease (MRD) monitoring in pediatric AML using leukemia-specific passenger variants (LSPVs).


Background

DAISY-MRD identifies Leukemia specific passenger variants (LSPVs) - somatic mutations with no biological effect that are shared by the entire leukemic clone, and calculate an MRD score based in the patient specific LSPVs.

The method is described in:

Shiloh R*, Dayan D*, et al. Passenger Variants as Pan-Clonal Markers of Pediatric AML: Implications for Molecular MRD. Hemasphere, 2026.


How it works

Diagnosis WGS (VCF)                 Remission BAM/CRAM
       │                                      │
  ┌────▼────────────────────┐    ┌────────────▼──────────────┐
  │    Step 1: LSPV ID      │    │    Step 2: MRD Scoring    │
  │                         │    │                           │
  │  • PASS filter          │    │  • samtools mpileup       │
  │  • gnomAD annotation    │    │    over LSPV positions    │
  │  • PoN filter           │    │  • Count reads            │
  │  • GMM clonality fit    │────▶  • Noise filtering        │
  │  • Remove coding vars   │    │  • DAISY-MRD score        │
  └─────────────────────────┘    └───────────────────────────┘
           │                                  │
     LSPVs CSV                     score = Σ(alt) / Σ(depth)
     GMM plot                      noise distribution plot
     Pie chart

DAISY-MRD score = total ALT-supporting reads across all LSPV positions ÷ total read depth across all LSPV positions. With ~300 LSPVs per patient at 100× WGS depth, this aggregates ~30,000 reads, giving a sensitivity of ~3 × 10⁻⁴.


Installation

From GitHub (recommended)

git clone https://github.com/maruvka-lab/daisy-mrd.git
cd daisy-mrd
pip install -e .

From PyPI (coming soon)

pip install daisy-mrd

Requirements

Requirement Version
Python ≥ 3.9
numpy ≥ 1.24
pandas ≥ 2.0
scipy ≥ 1.10
scikit-learn ≥ 1.3
matplotlib ≥ 3.7
seaborn ≥ 0.12
requests ≥ 2.28
samtools ≥ 1.17 (external binary, required for Step 2)

Optional dependencies:

Package Purpose Install
twobitreader C>TG / CG>A trinucleotide context filter in Step 2 pip install twobitreader
Docker + ensemblorg/ensembl-vep VEP annotation in Step 1 (skip if VCF already annotated) docker pull ensemblorg/ensembl-vep

Quick Start

Step 1 — Identify LSPVs from a diagnosis VCF

from daisy_mrd import run_lspv_pipeline

result = run_lspv_pipeline(
    vcf_path="patient_001_diagnosis.vcf",
    output_dir="results/patient_001/",
    patient_id="001",
)

print(result.summary)
#    patient  total_variants  clonal  subclonal  n_lspvs
# 0      001            1204     892        312      734

result.lspvs.head()       # DataFrame of LSPVs
result.fig_gmm.show()     # Gaussian Mixture Model plot
result.fig_pie.show()     # Clonal vs sub-clonal pie chart

Step 2 — Compute DAISY-MRD score from a remission BAM/CRAM

from daisy_mrd import run_mrd_single

mrd = run_mrd_single(
    lspv_csv="results/patient_001/001_lspvs.csv",
    remission_bam="patient_001_remission.cram",
    reference="GRCh38.fa",
    output_dir="results/patient_001/mrd/",
    patient_id="001",
)

print(f"DAISY-MRD score: {mrd.score.score:.2e}")
# DAISY-MRD score: 3.41e-04

Running a cohort

from daisy_mrd import run_mrd_cohort

patients = [
    {
        "patient_id": "001",
        "lspv_csv": "results/001/001_lspvs.csv",
        "remission_bam": "data/001_remission.cram",
    },
    {
        "patient_id": "002",
        "lspv_csv": "results/002/002_lspvs.csv",
        "remission_bam": "data/002_remission.cram",
    },
]

scores_df = run_mrd_cohort(
    patients=patients,
    reference="GRCh38.fa",
    output_dir="results/cohort/",
)

scores_df.to_csv("daisy_mrd_scores.csv", index=False)

Full API Reference

run_lspv_pipeline()

result = run_lspv_pipeline(
    # Required
    vcf_path        = "diagnosis.vcf",   # Diagnosis VCF (plain or .vcf.gz)
    output_dir      = "results/",        # Created if absent

    # Label
    patient_id      = "001",             # Used in filenames and plot titles

    # VEP annotation (optional — skip if VCF already annotated)
    run_vep_annotation     = False,      # True = annotate via Docker
    vep_assembly           = "GRCh38",   # or "GRCh37"
    vep_use_cache          = False,      # True = offline (needs local cache)
    vep_cache_dir          = "~/.vep",

    # gnomAD germline filtering
    gnomad_path     = None,              # Path to local chrom_pos.txt index, or None
    gnomad_use_api  = True,              # Fall back to gnomAD GraphQL API

    # Panel of Normals
    pon_path        = None,              # None = built-in PoN; or path to custom CSV

    # GMM clonality
    gmm_max_components           = 5,
    clonality_pvalue_threshold   = 0.05,

    # Input already PASS-filtered?
    vcf_is_filtered = False,
)

Returns LspvResult:

Attribute Type Description
lspvs pd.DataFrame Final LSPV table
all_variants pd.DataFrame All variants with clonality labels
summary pd.DataFrame Variant counts at each step
fig_gmm Figure GMM plot
fig_pie Figure Clonal/subclonal pie chart
clonal_peak_mean float Mean VAF of the clonal peak
output_dir Path Directory of saved outputs

Output files:

results/
├── 001_pass.vcf                  # PASS-filtered VCF
├── 001_gnomad.vcf                # gnomAD-annotated VCF
├── 001_lspvs.csv                 # ← LSPVs (input to Step 2)
├── 001_summary.csv               # Variant counts
├── 001_gmm.pdf                   # GMM plot
└── 001_clonality_pie.pdf         # Pie chart

run_mrd_single()

mrd = run_mrd_single(
    # Required
    lspv_csv        = "results/001/001_lspvs.csv",
    remission_bam   = "001_remission.cram",
    reference       = "GRCh38.fa",
    output_dir      = "results/001/mrd/",

    # Label
    patient_id      = "001",

    # samtools
    samtools_path         = "samtools",  # Full path if not on $PATH
    extra_mpileup_flags   = None,        # e.g. ["--min-BQ", "20"]

    # Skip mpileup if pileup file already exists
    pileup_file     = None,              # Path to pre-computed .pileup

    # Panel of Normals
    pon_path        = None,              # None = built-in PoN

    # Context filter (needs twobitreader + a .2bit genome file)
    twobit_path     = None,              # e.g. "/data/hg38.2bit"

    # Filter thresholds (all optional — defaults match the paper)
    max_depth                    = 200,
    germline_pvalue_threshold    = 0.01,
    pon_pvalue_threshold         = 0.05,
    max_vaf                      = 0.05,
    use_noise_pvalue_filter      = True,
)

Returns MrdResult:

Attribute Type Description
score MrdScore .score, .total_alt_reads, .total_read_depth, .n_lspv_positions
filter_layers dict[str, DataFrame] All 7 intermediate DataFrames, keyed by layer name
filter_summary pd.DataFrame LSPV count at each filter layer
fig_noise Figure or None Noise distribution plot (populated by cohort run)
output_dir Path Directory of saved outputs

Output files:

results/001/mrd/
├── 001_remission.pileup
├── 001_pileup.csv
├── 001_read_counts.csv
├── 001_filter_summary.csv
├── 001_daisy_mrd_score.csv
└── filter_layers/
    ├── no_filters/
    ├── filter_ct/
    ├── no_xy/
    ├── no_xy_no_200/
    ├── no_xy_no_germline/
    ├── no_xy_no_germline_no_pon/
    └── no_xy_no_germline_no_pon_no_hVAF/   ← used for final score

run_mrd_cohort()

scores_df = run_mrd_cohort(
    patients        = [...],             # list of dicts (see above)
    reference       = "GRCh38.fa",
    output_dir      = "results/cohort/",
    # All run_mrd_single() options are accepted as cohort-level defaults
    # and can be overridden per-patient inside each patient dict
    plot_noise      = True,              # Save noise distribution PDFs
)

Returns a pd.DataFrame with columns patient_id, daisy_mrd_score, total_alt_reads, total_read_depth, n_lspv_positions.


Panel of Normals

A built-in PoN (built on the Ultima Genomics sequencing platform) is included. To use your own:

run_lspv_pipeline(..., pon_path="/path/to/my_pon.csv")
run_mrd_single(...,    pon_path="/path/to/my_pon.csv")

Required PoN CSV columns:

Column Description
CHROM Chromosome (e.g. chr1)
POS 1-based position
P_N Background noise rate (float in [0, 1])

Using individual modules

Every function is importable on its own:

# Step 1
from daisy_mrd.lspv.filter   import filter_vcf_pass, apply_hard_filters
from daisy_mrd.lspv.annotate import annotate_vcf, run_vep
from daisy_mrd.lspv.reads    import extract_info, get_reads, get_vaf
from daisy_mrd.lspv.pon      import load_pon, filter_pon
from daisy_mrd.lspv.gmm      import fit_gmm, get_clonal_peak_mean, label_clonality, plot_gmm
from daisy_mrd.lspv.identify import extract_lspvs, plot_clonality_pie
from daisy_mrd.utils         import read_vcf

# Step 2
from daisy_mrd.mrd.pileup    import run_mpileup, pileup_to_df, merge_lspv_alts
from daisy_mrd.mrd.readcount import apply_read_counts
from daisy_mrd.mrd.filters   import (
    add_flanking_nucleotides, filter_noisy_context,
    filter_sex_chromosomes, filter_high_depth,
    filter_germline, filter_pon_remission, filter_high_vaf,
    apply_all_filters,
)
from daisy_mrd.mrd.score     import compute_mrd_score, plot_noise_distributions

Project structure

daisy-mrd/
├── daisy_mrd/
│   ├── __init__.py              # Package entry-point; exposes main functions
│   ├── utils.py                 # VCF reader, path helpers, PoN resolver
│   ├── data/
│   │   └── pon_default.csv      # Built-in Panel of Normals
│   ├── lspv/                    # Step 1: LSPV identification
│   │   ├── annotate.py          # VEP (optional) + gnomAD annotation
│   │   ├── filter.py            # PASS, germline, rs, indel hard filters
│   │   ├── gmm.py               # GMM fitting + clonality classification
│   │   ├── identify.py          # LSPV extraction + pie chart
│   │   ├── pipeline.py          # run_lspv_pipeline() orchestrator
│   │   ├── pon.py               # Panel of Normals (diagnosis-side)
│   │   └── reads.py             # DP / AD / VAF extraction from FORMAT
│   └── mrd/                     # Step 2: DAISY-MRD scoring
│       ├── filters.py           # 6 independent noise filter functions
│       ├── pipeline.py          # run_mrd_single() / run_mrd_cohort()
│       ├── pileup.py            # samtools mpileup + pileup parser
│       ├── readcount.py         # ALT read counting from pileup strings
│       └── score.py             # Score calculation + noise plots
├── tests/
│   ├── test_filter.py
│   ├── test_gmm.py
│   └── test_mrd.py
├── LICENSE
├── CONTRIBUTING.md
└── pyproject.toml

Running tests

pip install -e ".[dev]"
pytest

Citation

If you use DAISY-MRD in your research, please cite:


License

© 2026 Dana Dayan, Yosef Maruvka — Maruvka Lab, Technion

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

daisy_mrd-0.1.1.tar.gz (3.2 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

daisy_mrd-0.1.1-py3-none-any.whl (3.2 MB view details)

Uploaded Python 3

File details

Details for the file daisy_mrd-0.1.1.tar.gz.

File metadata

  • Download URL: daisy_mrd-0.1.1.tar.gz
  • Upload date:
  • Size: 3.2 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for daisy_mrd-0.1.1.tar.gz
Algorithm Hash digest
SHA256 500d136d998fc95afc347ca7eaca284a3d2ffe1e4408b43bfe29ba15f804775c
MD5 be1654f4a66ac6a85463cb94efc289a8
BLAKE2b-256 505ba56c58d5f4ce69720b4ff4d4018b553635b37f7a44d265a8279138602e29

See more details on using hashes here.

File details

Details for the file daisy_mrd-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: daisy_mrd-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 3.2 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for daisy_mrd-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 ea64179af68f7c2f4f6084bc2d5e9c979ffa18d9b75f54cd3d977c784c6833c3
MD5 16d362f38398b8f6546de3dabf784012
BLAKE2b-256 3e66d5f82b1e376979e6de3b733c37e0efa844b27d6c163d3383db6d6c376690

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page