Skip to content

DNAscent

DNAscent detects the thymidine analogues BrdU and EdU in R10.4.1 reads from the raw signal and calls replication origins, terminations and fork direction from them. It is optional (dnascent.enabled: true) and adds four rules per sample after the final BAM:

pod5 links ──► dnascent_index ─┐
bam/final ─────────────────────┴► dnascent_detect (GPU) ──► dnascent_sort ──► dnascent_per_read
                                        └──────────────────► dnascent_forksense
Output Content
dnascent/<s>/index.dnascent read id → POD5 map
dnascent/<s>/<s>.detect.bam detect output: input records with MM:Z:N+b? (BrdU) / N+e? (EdU) and ML = round(p·255) at thymidines
dnascent/<s>/<s>.detect.sorted.bam(.bai) coordinate-sorted copy for IGV / pysam
dnascent/<s>/<s>.per_read.tsv.gz per read: n calls, mean and fraction-above-threshold for BrdU and EdU (per_read_probs_full: true adds the vectors)
dnascent/<s>/forksense/<s>.forkSense + *_DNAscent_forkSense.bed origins, terminations, left/right forks, analogue tracks, stress signatures

How it runs

DNAscent is a C++ binary linked against TensorFlow 2.4 and CUDA 11, not on conda, so it runs from the maintainers' Singularity image:

pixi run install-dnascent     # pulls library://mboemo/dnascent/dnascent:4.2.1 (5 GB, pinned by digest)

into resources/tools/dnascent/4.2.1/DNAscent.sif. Alternatives:

dnascent:
  sif: /cluster/singularity_images/DNAscent.sif    # Bodhi's own image (4.1.1; what `module load DNAscent` points at)
  command: /path/to/DNAscent/bin/DNAscent          # a native build; takes precedence over sif
  singularity_setup: "module load singularity"     # Alpine
  singularity_bind: [/pl/active/mylab]             # /beevol, /scratch/alpine, /projects, /pl are bound automatically

dnascent.gpu: true adds --GPU 0 and --nv; the cluster profiles put dnascent_detect on the GPU partition. On CPU (gpu: false) detect took about a day at 8 threads for one barcode of a P2 flowcell, so also move the rule to a CPU partition with more cores in the profile.

Requirements DNAscent imposes: reads must be aligned (the pipeline's final BAM), the reference must be the plain FASTA the BAM was aligned to (the pipeline hands it the staged copy), and reads shorter than min_length (1000) or below min_mapq (20) are skipped. 5–10% of reads typically fail event alignment and carry no calls; they appear in the per-read table with n_calls = 0.

Mapping from the manual workflow

The lab's manual fork-stalling scripts (run_*_DNAscent.sh, one per barcode) do, per barcode: samtools merge of MinKNOW's bam_pass/<barcode>/*.bamdorado aligner against CHM13 → samtools sortDNAscent index -f pod5_pass/<barcode>DNAscent detect -t 8 → a per-read probability extractor → DNAscent forkSense --order EdU,BrdU --markAnalogues --markOrigins --markTerminations --markForks --makeSignatures.

The same run is expressed as config/examples/fork-stalling-pilot.yml: a basecalled: true run with four barcodes, dnascent.enabled: true, forkSense order EdU,BrdU. dnascent_per_read.py replaces the extractor (it uses pysam's modBAM parser and scales by 255, which is what DNAscent writes).

Re-basecalling before DNAscent

DNAscent only needs an alignment; it recomputes its own signal alignment from POD5. Setting basecalled: false re-basecalls with the configured stack (e.g. sup@v5.2.0 + 6mA) and DNAscent runs on that BAM instead. Either way the 6mA (or 5mC) calls from dorado stay on the records and modkit summarises them alongside.