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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
500d136d998fc95afc347ca7eaca284a3d2ffe1e4408b43bfe29ba15f804775c
|
|
| MD5 |
be1654f4a66ac6a85463cb94efc289a8
|
|
| BLAKE2b-256 |
505ba56c58d5f4ce69720b4ff4d4018b553635b37f7a44d265a8279138602e29
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ea64179af68f7c2f4f6084bc2d5e9c979ffa18d9b75f54cd3d977c784c6833c3
|
|
| MD5 |
16d362f38398b8f6546de3dabf784012
|
|
| BLAKE2b-256 |
3e66d5f82b1e376979e6de3b733c37e0efa844b27d6c163d3383db6d6c376690
|