Repository navigation
SEQ and Unmapped.out.mate keep the read's letters (lowercase, IUPAC) - #376
Open
BenjaminDEMAILLE wants to merge 1 commit into
Open
BenjaminDEMAILLE wants to merge 1 commit into
BenjaminDEMAILLE wants to merge 1 commit into
Conversation
Lowercase bases and IUPAC codes are written as in the FASTQ, reverse complemented with case on the reverse strand (STAR's Read0 and revComplementNucleotides). Paired Unmapped.out.mate records have the whole mate, not the clipped one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Nov 3, 2026
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #354 (lowercase and IUPAC letters in SEQ and
Unmapped.out.mate*).The other three items of the same audit (NH under
--outSAMmultNmax, flag 0x200,--outFilterMismatchNoverReadLmax) are #375. This one is separate because it touches every place SEQ is written. The two PRs are independent; both add a test at the end oftests/alignment_features.rsand an entry under the same CHANGELOG heading, so the second to merge has a trivial conflict there.Problem
rustar-aligner encodes the read to
0..4(ACGTN) when it reads the FASTQ and builds SEQ from that encoding. So:acgtACGT→ACGTACGT);R→N), in SAM and in BAM;Unmapped.out.mate*had the same uppercase text.While there: for pairs,
Unmapped.out.mate*was written from the clipped mates, so--clip3pNbases 10 10gave 90-base records where STAR writes all 100. Single-end already wrote the whole read.What STAR does (2.7.11b source)
ReadAlign_outputTranscriptSAM.cpp:194-204,232: SEQ isRead0[Mate], the read line as loaded, or on the reverse strandrevComplementNucleotides(Read0[Mate], ...). Unmapped records writeRead0[imate]as it is (:43).SequenceFuns.cpp:16-54:revComplementNucleotidescomplements A C G T N R Y K M S W B D V H in both cases, keeps the case, and leaves any other byte unchanged.ReadAlign_alignBAM.cpp:489-514,SequenceFuns.cpp:99-129: BAM packs the same text withnuclToNumBAM, which is case-blind and keeps the 16 IUPAC codes (anything else is N). noodles' BAM encoder does the same mapping, so putting the text in the record gives STAR's BAM.SequenceFuns.cpp:131-146:convertNucleotidesToNumbersmakes the0..4copy used for mapping, lowercase as uppercase. rustar'sencode_basealready does this; mapping does not change.ReadAlign_outputAlignments.cpp:267,270:Unmapped.out.mate*getsRead0[im]andQual0[im], the whole mate.Change
src/io/fastq.rs:EncodedRead.raw_letterskeeps the read line, only when it has a byte outsideACGTN, so plain reads cost nothing.EncodedRead::letters()gives the text either way.revcomp_lettersis STAR's table.src/io/sam.rs:restore_read_lettersputs those letters in SEQ after the records are built: the mate is chosen by theLAST_SEGMENTflag, the text is reverse complemented for reverse-strand mapped records, and a leading hard clip is skipped. It replaces SEQ only where the record's bases are that text's bases in ACGTN form, so records from anything else are untouched.src/lib.rs: called next to the per-file read-group pass on the genomic and transcriptome records, in the single-end, paired-end and both STARsolo paths.UnmappedFastqWriter::write_recordnow takes the text; the four call sites passletters()and, for pairs, the whole mate's quality.Measured against STAR 2.7.11b
Synthetic reads on a synthetic genome, macOS, each tool with its own index. SEQ columns of
Aligned.out.sam,samtools viewofAligned.out.bam, andUnmapped.out.mate1:Racgcag...TCTTRAGAAC...ACGCAG...TCTTNAGAAC...Randn...GTGnGCTGACTGTYCGG...TGTgtttcg......GTGNGCTGACTGTNCGG...TGTGTTTCG...Unmapped.out.mate1)nnNR...CTGTYCGG......CTGTNCGG...--clip3pNbases 10 10 --clip5pNbases 3 3,Unmapped.out.mate1lengthOver the two synthetic sets (8 single-end reads, 5 pairs, plus the test's 3 reads and 2 pairs) every SEQ in SAM and BAM and every sequence line of
Unmapped.out.mate*is identical to STAR's after the change. TheUnmapped.out.mate*name lines still differ (0:Y:and the mate-mapped field); that is #364.Tests
tests/alignment_features.rs::test_seq_keeps_read_letters_like_star: single-end forward, reverse and unmapped reads and a mapped and an unmapped pair, with lowercase and IUPAC letters and--clip3pNbases, checking FLAG and SEQ of every record andUnmapped.out.mate1. I ran STAR on the same input; its output is what the test expects. It fails without the fix (ACGCAG...N...instead ofacgcag...R...).src/io/fastq.rs::revcomp_letters_as_star: the wholerevComplementNucleotidestable, both cases, and two bytes outside it.src/io/sam.rs::restore_read_letters_after_hard_clip_and_not_on_other_bases.cargo test --release,cargo clippy --all-targets -- -D warningsandcargo fmt --checkare clean on macOS.Not checked
--chimOutType WithinBAM HardClip) against STAR: covered by the unit test only.Aligned.toTranscriptome.out.bamand the STARsolo paths: the code runs there, not compared with STAR.--readFilesType SAM: not run.Unmapped.out.mate*under--outQSconversionAdd(STAR writesQual0; not compared).raw_lettersstaysNone).🤖 Generated with Claude Code