Skip to content

Repository files navigation

Table of Contents

  1. Configuration
    1. YAML config file
    2. Wrapper Snakefile
    3. Resource management
    4. Reference assemblies
    5. Experiments (differential methylation)
    6. Conda environments
    7. Analysis module setup
      1. External tool repositories → repos.* config keys
      2. Analysis reference files → haplotype.* and deconv.* config keys
  2. emseq.smk — Core Processing
    1. Wrapper variables
    2. Processing steps
    3. Outputs
  3. emseq_analysis.smk — Downstream Analysis
    1. Wrapper variables
    2. Processing steps
    3. Outputs
  4. Testing
    1. Test wrappers
    2. Test data limitations
  5. Continuous Integration
  6. Change Log

The EM-seq repository provides two modular Snakemake workflows for enzymatic methylation sequencing data. Both are designed to be included from a project-specific wrapper Snakefile that defines samples, references, and resource limits.

Configuration

Configuration is split between a YAML config file and a wrapper Snakefile. The YAML holds all declarative settings: sample lists, reference genome definitions, tool parameters, environment paths, and resource limits. The wrapper Snakefile reads the YAML and assigns every variable that the modules consume — environments, directories, sample lists, tool parameters, and project-level decisions like which references to align against and which spike-ins were included. The wrapper also defines the rule all output targets and any project-specific custom rules (e.g. input symlinks, sample aliases).

This separation serves two purposes. First, the YAML is portable across machines and projects while the wrapper encodes the specific run configuration. Second, when a project composes multiple pipeline modules (e.g. EM-seq + cfDNA CNA + fragmentation analysis), the wrapper is the single place where all variable names from all modules are visible. This makes namespace collisions immediately obvious rather than hidden inside separate module files.

YAML config file

Passed via --configfile. See config/test.yaml for a complete working example.

Core keys:

main-data-dir Root data directory; all subdirectories derived from this
library-ids List of sample IDs to process
keep-bed BED of regions to retain after filtering
exclude-bed BED of blacklist regions to exclude
emseq_ref_assemblies Nested map of reference genomes (see below)
meth-map Experiment definitions for differential methylation (see below)
envs Paths to conda environment YAMLs (emseq, methylkit, haplotype, deconv)
repos Paths to this repo and external tool repos
mosdepth-quant-levels Coverage quantization thresholds (default: 1,5,10,20)
emseq-mincov Minimum coverage for methylKit (default: 2)
fastp.extra Additional fastp arguments (default: none)

Analysis-specific keys (required only for emseq_analysis.smk; see Analysis module setup below for how to prepare these):

repos.mhaptools Path to cloned mHapTools repo (with compiled binary)
repos.wgbs_tools Path to cloned wgbstools repo (with built C extensions)
repos.uxm_deconv Path to cloned UXMdeconv repo
haplotype.cpg-ref Tabixed CpG reference (generated from reference FASTA)
haplotype.mhb-bed Methylation haplotype block BED (Guo 2017 liftover to hg38)
haplotype.metrics List of haplotype metrics to compute (e.g. [MHL, PDR, Entropy])
deconv.genome-name wgbstools genome identifier
deconv.atlas UXM deconvolution atlas TSV (from UXMdeconv repo)

Wrapper Snakefile

The wrapper reads the YAML config and assigns every variable that the included modules expect. This is deliberate: by centralizing all variable assignments in one file, you can see at a glance every name in scope across all modules and catch collisions before they become runtime bugs. The wrapper also makes project-level decisions that do not belong in the YAML — which reference genomes to align against and which spike-in controls were included — and defines the rule all output targets.

A minimal wrapper follows this structure:

import os
configfile: "config/my_project.yaml"

def resolve_config_paths(config_dict):
    for k, v in config_dict.items():
        if isinstance(v, str):
            config_dict[k] = os.path.expandvars(os.path.expanduser(v))
        elif isinstance(v, dict):
            resolve_config_paths(v)
        elif isinstance(v, list):
            config_dict[k] = [os.path.expandvars(os.path.expanduser(i))
                              if isinstance(i, str) else i for i in v]
resolve_config_paths(config)

# --- Environments ---
ENV_EMSEQ = config['envs']['emseq']
ENV_METHYLKIT = config['envs']['methylkit']

# --- Repositories ---
R_EMSEQ = config['repos']['emseq']

# --- Data directories ---
D_DATA = config['main-data-dir']
D_EMSEQ = f"{D_DATA}/emseq"
D_REF = f"{D_DATA}/ref"
D_LOGS = f"{D_DATA}/logs"
D_BENCHMARK = f"{D_DATA}/benchmark"
D_INPUTS = f"{D_DATA}/inputs"

# --- Tool parameters ---
MOSDEPTH_QUANT_LEVELS = config.get("mosdepth-quant-levels", "1,5,10,20")
EMSEQ_MINCOV = config.get("emseq-mincov", 2)
FASTP_EXTRA = config.get("fastp", {}).get("extra", "")
EMSEQ_REF_INPUTS = {k: v['input'] for k, v in config['emseq_ref_assemblies'].items()}

# --- Samples and references ---
emseq_library_ids = config["library-ids"]
emseq_ref_names = ["ncbi_decoy_hg38"]   # project decision
emseq_align_methods = ["bwa_meth"]       # project decision
spike_builds = ["puc19", "unmeth_lambda"]  # project decision
KEEP_BED = config["keep-bed"]
EXCL_BED = config["exclude-bed"]
meth_map = config["meth-map"]

rule all:
    input:
        expand(f"{D_EMSEQ}/dmr/tabix/{{lib}}.{{ref}}.bwa_meth.methyldackel.txt.bgz",
               lib=emseq_library_ids, ref=emseq_ref_names),
        # ... additional targets

include: "emseq.smk"
include: "emseq_analysis.smk"   # optional

The test wrappers (workflows/test.smk, workflows/test-analysis.smk) and config/example-config.yaml serve as templates for creating project-specific wrappers.

Resource management

Resource management uses two mechanisms: threads and concurrency.

Threads is a built-in Snakemake feature. Some rules use threads: workflow.cores to consume all available cores (e.g. alignment), while others use a hardcoded thread count for I/O-bound operations where more cores don't help (e.g. threads: 8 for samtools sort, dedup, BAM filtering). Rules that use workflow.cores are automatically capped at the --cores flag value, so the same workflow runs correctly on any machine.

Concurrency is a custom resource pattern using Snakemake's resources: directive. Each rule declares a concurrency cost, and a global budget is set at runtime via --resources concurrency=N. Snakemake schedules jobs as long as the total concurrency cost of running jobs stays within the budget. The system is calibrated so that a rule with concurrency = 100 runs one at a time when --resources concurrency=100. On a larger machine, setting --resources concurrency=300 allows three such jobs in parallel. Lighter-weight rules declare lower costs — for example, a rule with concurrency = 10 allows 10 simultaneous instances at --resources concurrency=100, or 30 at --resources concurrency=300. This ratio-based approach means the same rule definitions work across machines of different sizes; only the global budget changes.

Not every rule needs concurrency. Rules that are I/O-bound and individually lightweight (BAM filtering, samtools stats, M-bias, spike-in methylation calling) have no concurrency declaration — Snakemake schedules them freely based on available cores. Concurrency is reserved for rules that are either resource-intensive (alignment: concurrency = 100), memory-constrained (methylKit R jobs: concurrency = 50), or benefit from controlled parallelism (methylation calling, dedup: concurrency = 25).

To run the pipeline, pass both --cores and --resources concurrency=N:

# Small machine (8 cores)
snakemake -s workflows/test.smk --cores 8 --resources concurrency=100

# Large workstation (48 cores)
snakemake -s workflows/test.smk --cores 48 --resources concurrency=100

# Very large machine (96+ cores, run more jobs in parallel)
snakemake -s workflows/test.smk --cores 96 --resources concurrency=300

Reference assemblies

Reference genomes are specified as a nested map in emseq_ref_assemblies. Each entry includes a download URL, short name, and the expected input filename. The core module handles indexing automatically. This map should include both the primary alignment reference and the spike-in control references used for conversion rate estimation.

emseq_ref_assemblies:
  # Primary alignment reference
  ncbi_decoy_hg38:
    url: https://ftp.ncbi.nlm.nih.gov/...
    name: ncbi_decoy_hg38
    input: GCA_000001405.15_GRCh38_no_alt_plus_hs38d1_analysis_set.fna.gz
  # Spike-in controls (EM-seq kit includes these for conversion rate QC)
  unmeth_lambda:
    url: https://www.neb.com/...
    name: unmeth_lambda
    input: lambda.fa.gz
  puc19:
    url: https://www.neb.com/...
    name: puc19
    input: pUC19.fa.gz

Experiments (differential methylation)

The meth-map config key defines one or more differential methylation experiments. Each experiment specifies the samples, reference, aligner, treatment vector, and methylKit parameters.

meth-map:
  tumor_vs_normal:
    libs: ["sample_A", "sample_B", "sample_C", "sample_D"]
    emseq_ref_name: ["ncbi_decoy_hg38"]
    align_method: ["bwa_meth"]
    tx: [1, 1, 0, 0]
    mincov: 10
    mingroup: 1
    chunksize: "1e9"
    win_size: 10000

Conda environments

Four conda environment YAMLs are provided in config/. Wrappers reference them by relative path. When running with --use-conda, Snakemake creates isolated prefix-based environments from these files.

emseq-conda-env.yaml Core tools: samtools, mosdepth, R packages
methylkit-conda-env.yaml R/methylKit, annotatr, GenomicRanges
haplotype-conda-env.yaml mhaptk (Java), htslib, pytabix
deconv-conda-env.yaml wgbstools and UXM dependencies

Analysis module setup

The core pipeline (emseq.smk) has no external prerequisites — all tools are installed via conda and reference indexing is automated.

The analysis pipeline (emseq_analysis.smk) requires external tool repositories and reference files that must be prepared once before running. These are one-time setup steps that depend on the genome build. The results feed into the analysis-specific YAML config keys listed above.

External tool repositories → repos.* config keys

Clone the three external repos:

./tools/setup_repos.sh

Then build each tool:

# mHapTools — ships macOS binary; must be compiled from source on Linux
cd ~/repos/mHapTools
cd htslib && ./configure && make -j4 && cd ..
sed -i 's/libhts.dylib/libhts.so/' CMakeLists.txt
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j4
cp build/mhaptools .

# wgbs_tools — C extensions must be built inside the deconv conda env
cd ~/repos/wgbs_tools
/path/to/deconv-conda-prefix/bin/python setup.py

# UXM_deconv — pure Python, no build needed

Set the repos config keys to the resulting paths:

repos:
  mhaptools: ~/repos/mHapTools
  wgbs_tools: ~/repos/wgbs_tools
  uxm_deconv: ~/repos/UXM_deconv

Analysis reference files → haplotype.* and deconv.* config keys

Three reference files are needed. Detailed preparation steps are documented in emseq.org under ** Reference preparation.

CpG referencehaplotype.cpg-ref — a tabixed file of all CpG dinucleotide positions in the reference genome. Generate from the reference FASTA:

awk 'BEGIN{OFS="\t"} /^>/{chr=substr($1,2);pos=0;next}
  {seq=toupper($0); for(i=1;i<length(seq);i++)
    if(substr(seq,i,2)=="CG") print chr,pos+i-1,pos+i;
  pos+=length(seq)}' ref.fa | bgzip > hg38_CpG.gz
tabix -b 2 -e 3 hg38_CpG.gz

MHB referencehaplotype.mhb-bed — methylation haplotype blocks from Guo et al. 2017 Nature Genetics. The published coordinates are hg19; liftOver to hg38:

# 1. Download Supplementary Table 1 from Guo 2017 (Excel)
# 2. Extract chr/start/end to BED (see emseq.org Reference preparation)
# 3. liftOver hg19 → hg38
liftOver guo2017_mhb_hg19.bed hg19ToHg38.over.chain.gz guo2017_mhb_hg38.bed unmapped.bed
sort -k1,1 -k2,2n guo2017_mhb_hg38.bed > guo2017_mhb_hg38_sorted.bed

UXM atlasdeconv.atlas — available in the cloned UXMdeconv repo:

# Use the atlas matching your genome build:
~/repos/UXM_deconv/supplemental/Atlas.U25.l4.hg38.tsv

emseq.smk — Core Processing

Paired-end FASTQ files in, per-sample CpG methylation calls and QC metrics out.

Wrapper variables

The wrapper assigns all variables before include: "emseq.smk" (see the Wrapper Snakefile section for the full pattern):

ENV_EMSEQ, ENV_METHYLKIT Conda environment YAML paths
R_EMSEQ Repository root path
D_DATA, D_EMSEQ, D_REF, D_LOGS, D_BENCHMARK, D_INPUTS Directory layout (derived from main-data-dir)
emseq_library_ids List of sample IDs (from config)
emseq_ref_names Reference genome names to align against (project decision)
emseq_align_methods Alignment methods to run (e.g. ["bwa_meth"] or ["bwa_meth","biscuit"])
spike_builds Spike-in reference names included in this run (project decision)
EMSEQ_REF_INPUTS Dict mapping ref name → input FASTA filename
KEEP_BED, EXCL_BED Region filter BED file paths
meth_map Experiment map for differential methylation
MOSDEPTH_QUANT_LEVELS Coverage quantization thresholds (default: 1,5,10,20)
EMSEQ_MINCOV Minimum coverage for methylKit (default: 2)
FASTP_EXTRA Additional fastp arguments (default: none)

Processing steps

  • Trimming and QC: Adapter removal and quality trimming (fastp), read quality assessment before and after trimming (FastQC)
  • Alignment: Bisulfite-aware alignment to one or more reference genomes via BWA-meth and/or Biscuit, coordinate sorting
  • Duplicate handling: Duplicate marking with dupsifter, then BAM filtering that removes marked duplicates along with secondary, supplementary, and failed QC reads
  • Methylation calling: CpG methylation extraction in methylKit format (MethylDackel), per-sample methylKit object creation, tabix indexing
  • Coverage: Depth profiling with threshold summaries and quantization tracks (mosdepth), aggregated coverage plots
  • Spike-in controls: Alignment to pUC19 (methylated) and Lambda (unmethylated) spike-in references for conversion rate estimation
  • Bias detection: Position-dependent methylation bias assessment (MethylDackel mbias)
  • Reporting: Aggregated QC report (MultiQC)

Design decision — duplicate marking vs. removal: Deduplication is deliberately split into two rules. emseq_dedup uses dupsifter to mark duplicates (SAM flag 0x400) without removing them. emseq_filter_bam then removes marked duplicates as part of a combined filter (-F 3840: secondary + supplementary + failed QC + duplicates) while also requiring proper pairs (-f 2), MAPQ ≥ 30, and intersection with keep/exclude BED regions. Separating these steps ensures that the marked-but-unfiltered BAM is available for M-bias assessment, where the full read set is needed to accurately detect position-dependent biases before duplicate removal.

img

Outputs

emseq/fastqs/{lib}.trimmed_{R1,R2}.fastq.gz Adapter-trimmed paired-end reads
emseq/bams/{lib}.{ref}.{aligner}.coorsort.deduped.bam Coordinate-sorted BAM with duplicates marked (not removed)
emseq/bams/{lib}.{ref}.{aligner}.coorsort.filt.bam Final filtered BAM: duplicates removed, MAPQ ≥ 30, proper pairs, on-target
emseq/dmr/tabix/{lib}.{ref}.{aligner}.methyldackel.txt.bgz Per-CpG methylation calls, tabix-indexed
emseq/meth/{lib}.{ref}.{aligner}_methyldackel_CpG.methylKit Per-sample methylKit object
emseq/spike/{lib}.{spike}.{aligner}_methyldackel_CpG.methylKit Spike-in methylation calls for conversion rate estimation
emseq/qc/{lib}.{raw,trimmed}_{R1,R2}_fastqc.{html,zip} Per-sample FastQC reports (pre- and post-trimming)
emseq/qc/{lib}.{ref}.{aligner}_emseq_mbias.txt M-bias profile for position-dependent bias detection
emseq/qc/mosdepth_{lib}.{ref}.{aligner}.mosdepth.summary.txt Coverage depth summary with threshold and quantization tracks
emseq/qc/{lib}_emseq_fastp.{html,json} Fastp trimming report
emseq/qc/multiqc.html Aggregated QC report across all samples

emseq_analysis.smk — Downstream Analysis

Filtered BAMs and methylation calls in, differential methylation results, haplotype metrics, and tissue deconvolution out.

Wrapper variables

All core variables (above), plus these analysis-specific variables assigned in the wrapper:

ENV_HAPLOTYPE, ENV_DECONV Conda environment YAML paths for haplotype and deconvolution
R_MHAPTOOLS, R_WGBSTOOLS, R_UXM Paths to cloned external tool repositories
CPG_REF Tabixed CpG reference file (hg38)
MHB_BED Methylation haplotype block BED (e.g. Guo 2017 liftover)
HAP_METRICS Space-separated haplotype metric names
DECONV_GENOME wgbstools genome identifier
DECONV_ATLAS UXM deconvolution reference atlas TSV

External tool repositories must be cloned before running (see tools/setup_repos.sh).

Processing steps

  • Differential methylation: Per-sample methylKit unite across an experiment, differential methylation calling at both per-base and tiled (windowed) resolution, extraction of positional methylation values for significant sites (methylKit)
  • CpG annotation: Genomic feature annotation of differentially methylated regions — CpG islands, shores, shelves, inter-CGI regions, gene promoters, exons, introns (annotatr)
  • Methylation haplotypes: BAM-to-mhap format conversion (mHapTools), per-MHB haplotype metrics including MHL, PDR, and entropy (mhaptk)
  • Tissue deconvolution: Chromosome-prefix BAM reheading, pat/beta file generation (wgbstools), cell-type-of-origin deconvolution against a reference atlas (UXM)

img

Outputs

emseq/dmr/diff/methylBase_{experiment}.txt.bgz United methylation data across samples, tabix-indexed
emseq/dmr/diff/methylDiff_{experiment}.txt.bgz Differentially methylated CpGs (per-base), tabix-indexed
emseq/dmr/diff/methylBase_{experiment}.tiled.txt.bgz Tiled (windowed) united methylation data
emseq/dmr/diff/{experiment}_pos_meth.tsv Positional methylation values for significant sites
emseq/dmr/annotation/{experiment}_annotated.tsv DMRs annotated with genomic features (CpG islands, promoters, etc.)
emseq/haplotype/{lib}.{ref}.{aligner}.mhap.gz Methylation haplotype calls per read (mhap format), tabix-indexed
emseq/haplotype/{lib}.{ref}.{aligner}_mhl.txt Per-MHB haplotype metrics (MHL, PDR, entropy)
emseq/deconv/{lib}.{ref}.{aligner}.chr.pat.gz Per-read methylation patterns for deconvolution (wgbstools pat format)
emseq/deconv/{lib}.{ref}.{aligner}.chr.beta Per-CpG average methylation (wgbstools beta format)
emseq/deconv/uxm_results.csv Cell-type-of-origin deconvolution fractions across all samples

Testing

The repository includes in-repo test data and wrapper Snakefiles for both modules. Test data consists of real EM-seq reads subsetted to chr22 with matching spike-in references (pUC19, Lambda), blacklist regions, and analysis references (CpG sites, MHB blocks).

Test wrappers

  • workflows/test.smk — tests the core module (emseq.smk) end-to-end: FASTQ → trimming → alignment (BWA-meth + Biscuit) → dedup → filter → methylation calling → QC
  • workflows/test-analysis.smk — tests the analysis module (emseq_analysis.smk): DMR calling → annotation
  • config/test.yaml — shared test configuration defining samples, references, experiments, and conda environments

To run locally:

# Core pipeline
snakemake -s workflows/test.smk --configfile config/test.yaml --cores 4 --use-conda --resources concurrency=100

# Analysis pipeline (requires core outputs + external repos)
snakemake -s workflows/test-analysis.smk --configfile config/test.yaml --cores 4 --use-conda --resources concurrency=100

Test data limitations

The in-repo test data is subsetted to chr22 to keep the repository small. This is sufficient for validating the full core pipeline and the DMR/annotation portion of the analysis pipeline. Two analysis features are excluded from CI testing and included as commented-out targets in the test wrapper:

  • Methylation haplotypes (mHapTools/mhaptk) — mHapTools requires C++ compilation from source with a bundled htslib version; too fragile for CI runners.
  • Tissue deconvolution (wgbstools/UXM) — requires broader genomic coverage than a single chromosome provides.

Both have been validated on production-scale data.

Continuous Integration

test-data

Core processing (emseq.smk):

core-dry core-run

Downstream analysis (emseq_analysis.smk):

analysis-dry analysis-run

Change Log

  • Development since last tag
    • None
  • wf/emseq/v5.1.0
    • [2026-04-07 Tue] Parameterized align_method in modules:
      • New required wrapper variable emseq_align_methods — list of alignment methods to run. Replaces hardcoded ["biscuit","bwa_meth"] in emseq_multiqc and emseq_analysis_uxm_deconv rules.
      • Wrappers must now define emseq_align_methods before including modules. Updated test.smk, test-analysis.smk, README documentation, and module header comments.
      • Removed obsolete test-wrap.smk wrapper and old untangled development workflow from emseq.org.
  • wf/emseq/v5.0.0
    • [2026-03-31 Tue] Two-module architecture with comprehensive documentation:
      • Split README into per-module documentation (intro, wrapper variables, processing steps, DAG, outputs).
      • Added Configuration section: YAML/wrapper architecture, analysis module setup, resource management with basecamp concurrency model.
      • Section banners and rationale comments throughout both modules.
      • Updated Writeup section for two-module split.
      • Biscuit align: added samtools view -u -t ref.fai pipe fix.
      • Relocated orphaned R scripts to active Methylkit section; removed duplicates.
      • Untracked stale diff/ files.
    • [2026-03-31 Tue] CI overhaul:
      • Added analysis CI workflows (smk-analysis-dry, smk-analysis-run). All four CI workflows pass.
      • Analysis CI tests DMR + annotation; haplotypes and deconvolution excluded (validated on production data only).
      • Added --conda-frontend conda to all CI workflows (mamba prefix collision workaround).
      • Added test reference fixtures: chr22 CpG ref, MHB BED, UXM atlas subset.
    • [2026-03-31 Tue] Resource and architecture changes:
      • Reverted to direct thread/concurrency values per rule (basecamp pattern).
      • Removed project-specific AERO wrapper files.
    • [2026-03-25 Wed] Production validation fixes:
      • Added mkdir -p to emseq_align_bwameth_spike shell block for samtools sort tmp dir.
      • Added wildcard_constraints: experiment = "[^.]+" to emseq_analysis_methylkit_unite and emseq_analysis_methylkit_diff to prevent greedy matching of .tiled suffix.
      • Haplotype conda env: added mhaptk, pandas, matplotlib, scipy, seaborn, tqdm as pip deps.
      • Fixed mosdepth quantize string bug (single → global comma replacement).
    • [2026-03-16 Mon] Added emseqanalysis.smk module: DMR (migrated from emseq.smk), methylation haplotypes (mHapTools/mhaptk), tissue deconvolution (wgbstools/UXM), CpG annotation. New conda envs, test-analysis.smk wrapper, reference preparation docs.
    • Added CI workflows: core dry-run, core full run, analysis dry-run, analysis full run.
  • wf/emseq/v4.0.0
    • Minor testing updates
    • Fixed test.yaml file locations
    • Changed bam filtering step to remove duplicates marked by dupsifter. So rule emseqdedup MARKS duplicates while rule emseqfilterbam removes them as part of `samtools view -@ {threads} -u -f 2 -q 30 -F 3840 "{input.bam}`
    • Changed mosdepth to take filtered bam and quantify by keep.bed, ignoring filtered regions in depth calculation.
    • Conforms to v1.3 snakemake style guide
  • wf/emseq/v3.1.0
    • [2025-09-19 Fri] Added a first github workflow test
    • [2025-09-19 Fri] Robust annotation of methylkit outputs validated as rscript
    • [2025-09-19 Fri] Added mature github tests for building in-repo test data and smkdry
  • [2025-09-19 Fri] Updated EM-seq main pipeline to wf/emseq/v3.0.0.
    • Includes in-repo small test data for a complete run of emseq.smk
    • Includes test.smk wrapper and corresponding test.yaml for in-repo small test run
    • emseq.smk expanded to include differential methylation from nested list map
    • Many small fixes for consistent naming and run condition optimization
  • [2025-09-18 Thu] Updated EM-seq main pipeline to wf/emseq/v2.0.0. Mainly improved and simplified variable naming.

About

No description, website, or topics provided.

Resources

Stars

4 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages