Fragmentomics pipeline for cell-free DNA whole-genome sequencing analysis. Extracts fragment length features, DELFI ratios, end-motif profiles, and NMF signatures from paired-end WGS data.
frag.org is the source of truth: the workflows, scripts, configs, tests and tools in this repository are tangled from it. The tangled files are committed complete, so the pipeline is runnable as committed without Emacs; edit frag.org and re-tangle rather than editing a tangled file.
| Path | Holds |
|---|---|
frag.org |
literate source; tangles every file below |
workflows/frag.smk |
the module: rules only, reads no config; its preamble lists the variables a wrapper binds |
workflows/test.smk |
example wrapper and the CI target; binds every module variable |
Snakefile |
entry point that includes the example wrapper, so snakemake -n works from the root |
config/common.yaml |
configuration template for a project |
config/test.yaml, config/samples_test.tsv |
fixture configuration and sample sheet |
config/config.schema.yaml |
JSON schema of every config key; the wrapper checks the config against it and lists every problem |
envs/frag-conda-env.yaml |
the one pinned conda environment (Snakemake and every tool) |
scripts/ |
per-step R, Python and shell scripts called by the rules |
resources/data/delfi_bins_hg38.tsv |
DELFI 5 Mb bins on hg38, built by tools/make_delfi_bins.py |
tools/ |
fixture fetch, pinned-output check, DELFI bin builder, end-motif summary, org update |
tests/unit/ |
known-answer and contract tests (pytest) |
tests/expected_outputs.md5, tests/expected/ |
pinned fixture outputs |
resources/figures/frag.dag.png |
rule graph |
LICENSE, CITATION.cff |
MIT license; citation metadata |
workflows/frag.smk contains rules only and reads no config. Every path and parameter is a Python variable the including wrapper defines before its include: line; the list, one line per variable, is the comment at the top of workflows/frag.smk. workflows/test.smk is the complete example: copy it, keep its variable block, and replace rule all with the outputs your project needs.
A run takes one YAML config passed with --configfile. config/common.yaml is the template for a new project (every key, with placeholder paths); config/test.yaml configures the committed chr22 fixture. config/config.schema.yaml declares every key the wrappers read, and the wrapper validates the loaded config against it before building the DAG, printing every violation and every undeclared key together. The sample sheet (sample-tsv-path) is a TSV with columns r1_basename, r2_basename (relative to directories.inputs), library_id and cohort; config/samples.tsv is the template and config/samples_test.tsv the fixture sheet. repo-dir is the frag repository root the rules run scripts from, available-concurrency the concurrency budget, and frag_ref_assemblies holds exactly one reference. delfi_bins is the DELFI 5 Mb bin table (resources/data/delfi_bins_hg38.tsv for hg38) and blklist the blacklist removed from the read regions.
Reads are kept on chr1 to chr22 outside blacklisted bases, with MAPQ ≥ 30, and without duplicates (marked at alignment), secondary or supplementary alignments. End motifs are the 4 reference bases at the 5′ end of every paired, primary, mapped, non-duplicate read with MAPQ ≥ 30, strand-aware (see End motifs in frag.org). end_motif.max_ends caps the ends counted per library by read-name-hash subsampling, which keeps mates together and samples the whole genome; null (the default) counts every end. end_motif.seed sets the subsample seed.
The conda environment is envs/frag-conda-env.yaml (every package pinned, Snakemake included). Run Snakemake from inside it, or from any Snakemake with --use-conda.
- Fixture run:
tests/full/inputs/holds a chr22 reference subset and 60,000 read pairs from each of four PRJNA326698 cfDNA WGS libraries (the wrapper uses lib001 and lib002), rebuilt bytools/get_test_data.sh. From the repository root,snakemake --cores 4runs it inside an environment built fromenvs/frag-conda-env.yaml(or add--use-conda; the rootSnakefileincludesworkflows/test.smk); add--dry-runto plan only. - Pinned outputs:
tools/check_expected_outputs.sh tests/fullcompares 28 byte-stable fixture outputs to the md5 pins intests/expected_outputs.md5and 5 floating-point tables numerically (relative tolerance 1e-6) to copies intests/expected/. - Unit tests:
python -m pytest -q tests/unit(known-answer tests of the end-motif extractor, the motif matrix, the read regions and the DELFI bins, and a test of the module's wrapper contract). Tests that need samtools or bedtools skip when they are not on PATH;FRAG_REQUIRE_TOOLS=1makes that an error. - Continuous integration (GitHub Actions, on push to master and on pull requests): smk-dry (DAG dry run and unit tests), smk-run (full fixture run and pinned-output check), test-data (fixture rebuild).
frag/
├── frag.org literate source; tangles everything below
├── README.md exported from the README section
├── LICENSE, CITATION.cff MIT; citation metadata
├── Snakefile entry point: includes workflows/test.smk
├── config/ common.yaml (template), test.yaml, samples.tsv (template), samples_test.tsv, config.schema.yaml, cfdna-test.yaml (kh_01 validation)
├── envs/ the one conda environment YAML
├── workflows/ frag.smk (module), test.smk (example wrapper), cfdna-test.smk (kh_01 validation)
├── scripts/ per-step scripts called by the rules
├── tools/ fixture fetch, pinned-output check, DELFI bin builder, end-motif summary, org update
├── resources/
│ ├── data/ DELFI bins, blacklists, Mathios 2021 bin table
│ └── figures/ rule graph
└── tests/
├── unit/ pytest tests of scripts/ and of the module contract
├── expected/ expected copies of the floating-point fixture outputs
└── full/inputs/ committed fixtures; the rest of tests/full/ is the run's output, git-ignored
- Development since last tag
- [2026-09-24 Thu] Read regions, duplicates and bins (round 2 of v2). Every motif, motif-diversity, F-profile, fragment-length, ratio and arm z-score output changes. The filtered BAM keeps reads on chr1 to chr22 outside blacklisted bases (new rule
frag_read_regions,bedtools subtractof the blacklist, restoring the 2022 logic) instead of reads inside whole blacklist-free GC-filtered 5 Mb windows, which kept 1,573 Mb on kh_01 and no chrX; it also drops unmapped, secondary, duplicate and supplementary reads (samtools view -q 30 -F 3332). The alignment step marks duplicates (samtools fixmate -m, sort,samtools markdup). Fragment counts use DELFI 5 Mb bins rebuilt on hg38 by the Mathios 2021 rule (GC ≥ 0.3, Umap k100 mappability ≥ 0.9; 487 bins, 2.435 Gb;resources/data/delfi_bins_hg38.tsv, built bytools/make_delfi_bins.py); config keydelfi_binsreplacesgc5mb. On kh_01 (Validation) 11.9 % of primary reads are duplicates, the read regions keep 2.69 Gb of the 2.88 Gb of autosomes, 290,002,628 ends are counted (CC-end share 0.159, r = 0.9996 against round 1), and all 487 bins hold fragments. Verified: fixture run completes; kh_01 rerun exits 0. - [2026-09-24 Thu] Module contract and structure. Wrappers bind
R_FRAGfrom the new keyrepo-dirand rules runR_FRAG/scripts/..., so the module works when included from another directory; every rule declares a concurrency cost and the new keyavailable-concurrencysets the budget; the schema allows one reference and requires at least one healthy library, which must be among the selected libraries. The rootSnakefilesets the fixture as its default target. Fixture pins hold across BLAS kernels (5 floating-point tables compared numerically) and fail when the pin file is truncated. New unit tests cover the read regions, the DELFI bins, the module contract and the subsampling, MAPQ, soft-clip and soft-mask cases of the end-motif extractor;FRAG_REQUIRE_TOOLS=1(set in CI) makes a missing tool an error. CI runs the fixture inside the env it builds, on node24 action versions.frag.orgfollows the biopipe notebook skeleton (README, Repository setup with test-data tool, GitHub tests, LICENSE and CITATION blocks, processing Overview, Reference preparation, Reference, one Ideas). Snakemake is pinned to 9.21.0. Verified: unit tests pass; fixture run completes; pins ok. - [2026-09-24 Thu] End motifs are counted strand-aware from the reference at the 5′ end of every paired, primary, mapped, non-duplicate read with MAPQ ≥ 30 (
samtools view -q 30 -f 1 -F 3340, both mates). The previous extraction keyed the end on read number (read 1's leftmost 4 bases, read 2's rightmost 4 bases reverse-complemented), so for every pair whose read 1 maps to the minus strand it returned the read's 3′ end, interior to the fragment whenever the fragment is longer than the read (about 40 % of motifs in Axel Hidalgo's measurement on one EM-seq library); its subsample was truncated in coordinate order, and it used MAPQ 60 and kept duplicates.scripts/frag_end_motifs.shandscripts/frag_motif_matrix.pyare adapted from the extractor by Axel Hidalgo, commit 7a5767ab1 (2026-09-18); rulefrag_sample_motifsis nowfrag_end_motifs, per-library outputs aremotifs/{library_id}.{ref_name}.motif_counts.tsv(256 motifs plusOTHER), and a motif x library count matrix is written besideall_motifs.tsv. Configend_motif.max_ends(null counts every end) andend_motif.seedreplacen_motifandn_reads. This changes every motif, motif-diversity and F-profile output, and changes the module interface, so v2.0.0 is the intended next tag. On the chr22 fixture lib001 counts 3,068 ends (CC-end share 0.121) and lib002 2,979 (0.105). On the full-genome healthy library kh_01 (Validation) every one of 192,998,342 qualifying reads is counted, CCCA, CCTG and CCAG lead the table (the old extraction had AAAA second), the CC-end share is 0.157 against 0.112 for the old extraction, and a 10 Mmax_endssubsample reproduces the all-ends fractions (r = 0.99998) with every chromosome sampled in proportion, where the old extraction sampled only chr1 to chr11. Verified: 10 known-answer unit tests pass; fixture run completes; kh_01 runs exit 0. - [2026-09-24 Thu] Repository brought to the current biopipe standard. The conda env moves to
envs/frag-conda-env.yamlwith every package pinned (snakemake 9.14.5, samtools 1.24, bedtools 2.31.1, plus jsonschema and pytest).workflows/frag.smkreads no config: the wrappers bind every value as a variable, listed in the module preamble.config/config.schema.yamlandcheck_config()validate the config at startup.config/common.yamlandconfig/samples.tsvare templates; the fixture sheet isconfig/samples_test.tsv; a one-line rootSnakefileincludes the test wrapper; fixture logs and benchmarks go undertests/full/. Reference data files move toresources/data/, the DAG toresources/figures/frag.dag.png; trackeddocs/files are untracked;LICENSE(MIT) andCITATION.cffare added;config/int_test.yaml(read by nothing) and the copied reference implementation folder are removed. The full-genome kh_01 wrapper and config get org blocks under Validation, which also records the kh_01 end-motif comparison. 32 deterministic fixture outputs are pinned intests/expected_outputs.md5and checked bytools/check_expected_outputs.sh. CI triggers on push to master and pull requests, and runs the unit tests and the pin check. This change log replacesCHANGELOG.md. Verified: unit tests pass; fixture run completes; every pin ok on a clean run.
- [2026-09-24 Thu] Read regions, duplicates and bins (round 2 of v2). Every motif, motif-diversity, F-profile, fragment-length, ratio and arm z-score output changes. The filtered BAM keeps reads on chr1 to chr22 outside blacklisted bases (new rule
- [2026-06-11 Thu] v1.0.0. First tagged release: a biopipe-standard-conformant cfDNA fragmentomics pipeline.
- Restructured to the biopipe module pattern; migrated the fragment-length (GC normalization chain) and end-motif pipelines from the archived
cfdnarepo intoworkflows/frag.smk. - Migrated feature extraction from
nf1_fragmentomics: fragment-length histograms, DELFI arm z-scores, ratio row-normalization, and QC plots (all R). - Migrated from
candetect/delfi: NMF fragment-length features, F-profiles (NMF + NNLS on motifs), motif-diversity score (Shannon entropy). - Added GitHub Actions CI (test-data, smk-dry, smk-run) and the
frag_check_idsidentifier-compatibility gate. - Brought the repo structure in line with the biopipe standard: CLAUDE.md / AGENTS.md / CHANGELOG.md, test sample sheet at
config/samples.tsv, DAG figure atdocs/frag.dag.png; removed legacytest/, the org LaTeX-preview cache, thefrag.mdstub, the orphanedcount_scale.R, and the tangle preamble headers. - Added the
frag_checks.{R,py}data-integrity library and conformed all 13 R analysis scripts plus the 2 Python scripts to the code style guide, guarding read inputs. - Seeded
sample_frags_by_gc(set.seed(42)) for reproducibility; madefigure_schema.Roptional for CI with relative test-config paths. - Full-genome validation: 6-sample run (3 healthy, 3 cancer) on ncbi_hg38; all steps pass.
- Added the test data set (WGS cfDNA, PRJNA326698, chr22).
- Restructured to the biopipe module pattern; migrated the fragment-length (GC normalization chain) and end-motif pipelines from the archived
- The tangled files under
workflows/,scripts/,config/,envs/,tools/,tests/and.github/are complete and runnable as committed: clone, create the environment fromenvs/frag-conda-env.yaml, and runsnakemake -nfrom the repository root to plan the fixture run. Emacs is not needed to run or to read the pipeline. frag.orgis the source of truth. Code changes are made there and tangled out; a change made directly to a tangled file is overwritten by the next tangle, so a contributor without Emacs sends the change as a patch naming the file, and the maintainer applies it in the org.tools/shell/org_update.shtangles, exports README.md and runs the git workflow; it depends on the maintainer's Emacs setup.python -m pytest -q tests/unitruns the unit tests;FRAG_REQUIRE_TOOLS=1makes a missing samtools or bedtools an error instead of a skip.tools/check_expected_outputs.sh tests/fullcompares a fixture run with the pinned outputs;--writeregenerates them after an intended output change.
