Skip to content

Add --trim-poly-tails to remove poly(A) tails and poly(T) heads (#37) - #86

Merged
wdecoster merged 1 commit into
masterfrom
poly-tail-trim
Oct 29, 2026
Merged

wdecoster merged 1 commit into
masterfrom
poly-tail-trim

Conversation

@wdecoster

Copy link
Copy Markdown
Owner

Closes #37. Adds removal of poly(A) tails and poly(T) heads from RNA and cDNA reads. It is off by default: default output is unchanged, and the v0.14.1 digest test still passes.

Usage

--trim-poly-tails [<MIN_LENGTH>]

--trim-poly-tails removes a poly(T) head from the start and a poly(A) tail from the end of each read; U counts as T. Both ends are trimmed because cDNA reads come in both orientations. Runs shorter than MIN_LENGTH (default 10) are left alone. --trim-poly-tails 15 or --trim-poly-tails=15 sets another minimum. chopper has no positional arguments, so the optional value can't be confused with anything else: --trim-poly-tails -i x.fq uses the default.

How tails are found

The scoring is the same as cutadapt's --poly-a (poly_a_trim_index in qualtrim.pyx):

  • Scan in from the read end: +1 for each A (or T at the start), −2 for anything else.
  • The tail ends where the score peaks, provided at most 20% of it are other bases.

This tolerates sequencing errors inside the tail without running on into the transcript.

The one difference from cutadapt is the default minimum: 10 instead of 3. In simulation, a minimum of 3 trimmed about 3% of reads that have no tail, because they end in "AAA" by chance; 10 trims about 0.1% and loses nothing on real tails.

Anchored only: the tail must reach the end of the read, so adapters and primers have to be removed first, for example by the basecaller or dorado trim. A few leftover bases past a tail are removed with it, as long as the whole stays within the 20%.

How it fits

  • It runs after --trim-approach and narrows only the outer ends of the kept segments: a polyT head off the first segment and a polyA tail off the last. Interior split points are not read ends and are left alone.
  • Because it only narrows the (start, end) ranges, --update-mods, uBAM output and segment naming work without changes. A test checks that a modification call stays on its base.
  • As with the trim approaches, filters see the untrimmed read and only --minlength is checked again afterwards.

Validation (simulated only)

ONT-like errors: half deletions, a quarter substitutions, a quarter insertions. 400 bp body, 10,000 reads per condition, run through the release binary:

error rate tails removed tail bases left / read body bases lost / read tailless reads trimmed
2% 100% 0.1 0.8 0.1%
5% 100% 0.3 0.8 0.1%
10% 100% 0.5–0.7 0.8 0%
15% ≥99.9% 0.9–1.5 0.7–0.8 0.1%

Body bases are lost when a body happens to start with T or end with A. Those bases can't be told apart from a tail, so no method avoids this.

Not yet validated on real reads: the data at hand isn't adapter-clipped.

Tests

make ci passes: 80 unit tests (8 new, including a seeded simulation at 2–15% errors) and 21 end-to-end tests (2 new).

🤖 Generated with Claude Code

Off by default. Tails are found as cutadapt's --poly-a does: scanning in
from the read end, +1 for each A (T at the start, U counting as T), -2
for anything else, cutting where the score peaks with at most 20% other
bases, so sequencing errors inside a tail are tolerated. Runs shorter
than the minimum length are left alone: --trim-poly-tails uses 10,
--trim-poly-tails 15 sets another. Tails must reach the read end, so
adapters have to be removed first.

Applied after --trim-approach to the outer ends of the read only, by
narrowing the kept ranges, so --update-mods and BAM output work as is.
Validated on simulated reads with ONT-like errors up to 15%.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@wdecoster wdecoster mentioned this pull request Oct 29, 2026
@wdecoster
wdecoster merged commit 84e0be3 into master Oct 29, 2026
1 check passed
@wdecoster
wdecoster deleted the poly-tail-trim branch October 29, 2026 20:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Removing PolyA/T tails

1 participant