CountMut
At each site in a modification assay you want the same small number: how many reads still show the reference base, how many show the conversion, and what that is as a rate. Existing tools made this hard — a flag for every QC idea, two BAM-walking strategies that disagreed on deep overlapping sites, and read-level filters priced once per aligned position instead of once per read.
CountMut collapses this into one design: a single base counter, QC and trimming
as expressions (the samtools grammar, in C), and two BAM-walks that provably
agree. What you see is an output format — the per-base table, an allele VCF,
or a column template you write yourself. On a deep rRNA transcriptome
(784 k reads / 90 Mb) a genome-wide mapq >= 20 adds ~0.5 s, and the per-base
filter was cut ~3×.
pip install -e .
Quick start
# every base, per strand -> composition table
countmut -i in.bam -r ref.fa -o depth.tsv
# your own columns (e.g. an A/T conversion ratio) -> custom output template
countmut -i in.bam -r ref.fa -o mine.tsv \
--output-format "{pos+1}\t{ref}\t{a}\t{t}\t{round(t/(a+t)+0*a, 4)}" \
--fmt-header "pos\tref\tA\tT\trate"
# alleles as VCF -> VCF
countmut -i in.bam -r ref.fa --vcf -o allele.vcf
There is no --mode, no --ref-base/--mut-base: one counter, and the output
is whatever you choose. Bare runs print the per-strand base composition
(ref depth a c g t n); --vcf gives an allele VCF; --output-format gives
your own columns (a conversion ratio is just {t}/({c}+{t})). Output is per
strand by default, and --strandless merges the two strands.
Filtering with one expression instead of ten flags
In RNA there is no genomic mutation to count: the "converted" base is a
modification read out through reverse transcription, so the conversion rate
reports modification level rather than a variant. Whatever your sample, the
QC and trimming live in one expression language — read-level rules are -e
expressions, site-level rules -p, in the samtools filter= grammar,
evaluated inside the C core. The old --min-mapq / --trim-* flags are gone —
write them as -e expressions.
# quality, and not on the error-prone read ends
countmut -i x -r ref -o out -e "mapq >= 20 and bq >= 20 and dist5 >= 2"
# one sample
countmut -i x -r ref -o out -e "tag('RG') == 'sampleA'"
# samtools-style: low mismatch, not a PCR duplicate, read 1 only
countmut -i x -r ref -o out -e "[NM] <= 3 and not (flag.dup ~= 0) and flag.read1 != 0"
# site-level: only well-covered sites with ≥2 G reads
countmut -i x -r ref -o out -p "depth >= 5 and g >= 2"
-e is also a group router. A bare boolean expression is a filter
(true → count, nil/false → drop), but an expression that returns an
integer 0..3 routes each kept base into that group; true routes to
group 0. Anything else drops the base (with a stderr warning). The split
shows up in --output-format templates as per-group cells {a.0} … {n.3}
(plain {a} stays the total over all groups):
# bisulfite A->G, 2-group router: group 1 = high-conversion bases, group 0 =
# everything else that passes the hard NS gate (low quality / read-end trim)
countmut -i x -r ref -o out \
-e "([NS] <= 1) and (([Yf] >= 1 and [Zf] <= 3 and bq >= 20 and qpos >= 2 and qlen - qpos > 2) and 1 or 0)" \
--output-format "{chrom}\t{pos+1}\t{strand}\t{motif}\t{a.0}\t{a.1}\t{g.0}\t{g.1}" \
--motif-pad 15 --fmt-header "chrom\tpos\tstrand\tmotif\tu0\tu1\tm0\tm1"
Most filters use roughly ten variables — mapq, bq (base quality), flags,
qpos (position in the read), dist5/dist3 (distance to the read ends),
base/ref, tag('XX'), and rname. A couple of things are worth knowing.
A missing tag is nothing: comparing tag('NM') errors and drops that read,
so guard with exists('NM') when tags are optional. Only the six per-base
values (qpos, bq, base, ref, dist5, dist3) cost anything to
evaluate; everything else runs once per read and is essentially free. A syntax
error stops the run (exit code 2), so a typo can never silently change your
numbers.
The full grammar is in
docs/filter_grammar.md, with an exhaustive
reference in docs/expression_reference.md.
Output format
--output-format takes a row template: literal text plus {expr}
placeholders evaluated per site over the site values (chrom, pos,
strand, motif, ref, depth, a c g t n, ins del ref_skip fail, and
per-group counts a.0 … n.3 whenever -e routes into groups). Placeholders
run real Lua, so you can compute cells — a conversion ratio is just
{t}/({c}+{t}) — and round(x, n) and int(x) are helpers for formatting:
countmut -i x -r ref -o out \
--output-format "{pos+1}\t{ref}\t{a}\t{t}\t{round(t/(a+t), 4)}"
{{ writes a literal {; a placeholder that yields nothing renders as an
empty cell. --fmt-header "…" supplies the header line (\t/\n expanded);
without it custom templates print no header. With no --output-format, the
default output is the per-strand ref depth a c g t n composition table
(--vcf instead produces an allele VCF).
Engines and options
Two BAM-walking strategies live in the C core and emit identical output, so
the engine choice only affects speed (--engine auto uses the pileup walk for
the per-position counting). The options are few: input/reference/output,
--region, --threads/-t, --engine, --strandless, --count-indels,
--vcf (+ --min-depth/--min-allele-support), -e/-p, --motif-pad,
and --output-format/--fmt-header.
Input formats
BAM (indexed, fast, threaded) and SAM (plain or gzipped — auto-transcoded
to a temp BAM + index, same output as the equivalent BAM) are both supported,
detected automatically. CRAM is not read by this self-contained core; convert
first: samtools view -b in.cram -o out.bam — for a CRAM with an embedded
reference, that conversion also works without a separate FASTA.
Performance
Measured on a bimodal benchmark (232 k reads, 23.2 M read-bases, deep rRNA-style
hotspots): composition counting reads 1.30 s @1 thread → 0.31 s @16 (read-walk)
and 1.96 → 0.70 s (pileup); both walks are byte-identical (the dynamic work
queue keeps deep hotspots from serializing). Read-constant filters run once
per read (≈ free); only true per-base filters
qpos/bq/base/ref/dist5/dist3 add cost (~0.3 s @8 threads for bq and dist5).
tests/make_bench_bam.py regenerates the fixture; scripts/plot_perf.py re-renders
this figure.
License
MIT
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 countmut-0.2.3.tar.gz.
File metadata
- Download URL: countmut-0.2.3.tar.gz
- Upload date:
- Size: 235.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bd82d16f9769520ba48ff9731390da86e46a04284a232bdfbf50aeca97d431cf
|
|
| MD5 |
b7b10840d0f7c809eeeb8d1a63427649
|
|
| BLAKE2b-256 |
98940e1ead10a9d682b9ccc1356b818b3138e841077aad9640fd70fc54c0b306
|
Provenance
The following attestation bundles were made for countmut-0.2.3.tar.gz:
Publisher:
publish.yml on y9c/countmut
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
countmut-0.2.3.tar.gz -
Subject digest:
bd82d16f9769520ba48ff9731390da86e46a04284a232bdfbf50aeca97d431cf - Sigstore transparency entry: 2752468470
- Sigstore integration time:
-
Permalink:
y9c/countmut@c5fb682acb68a7d23f983e8048ef58c8e1dc4bf9 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/y9c
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c5fb682acb68a7d23f983e8048ef58c8e1dc4bf9 -
Trigger Event:
push
-
Statement type:
File details
Details for the file countmut-0.2.3-py3-none-any.whl.
File metadata
- Download URL: countmut-0.2.3-py3-none-any.whl
- Upload date:
- Size: 135.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
071cfd07ca951e08e78b969cfb749809a4f9c25432a90f84bd182a11d925fab4
|
|
| MD5 |
1a4e0da0c20076f07f7e882d07ba3b47
|
|
| BLAKE2b-256 |
d6a8c7e1ae3acff5efb7e6e308af3039e8c4d5c67c4424a888de3f600d86778b
|
Provenance
The following attestation bundles were made for countmut-0.2.3-py3-none-any.whl:
Publisher:
publish.yml on y9c/countmut
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
countmut-0.2.3-py3-none-any.whl -
Subject digest:
071cfd07ca951e08e78b969cfb749809a4f9c25432a90f84bd182a11d925fab4 - Sigstore transparency entry: 2752468476
- Sigstore integration time:
-
Permalink:
y9c/countmut@c5fb682acb68a7d23f983e8048ef58c8e1dc4bf9 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/y9c
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c5fb682acb68a7d23f983e8048ef58c8e1dc4bf9 -
Trigger Event:
push
-
Statement type: