Detect cell barcodes from read counts; add --split_molecules - #420
Open
andrewprzh wants to merge 18 commits into
Open
Detect cell barcodes from read counts; add --split_molecules#420andrewprzh wants to merge 18 commits into
andrewprzh wants to merge 18 commits into
Conversation
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.
Why
With a stock 10x whitelist (737k for v2, ~3M for v3), per-read barcode matching collapses
into exact matching —
min_scoreequals the barcode length, so any read with a sequencingerror in the barcode is lost. Three mechanisms compound it: the score threshold,
kgrowingwith whitelist size until a mid-barcode error destroys every k-mer, and
hits_deltapruningthe true barcode before alignment.
A run has a few thousand cells, not millions. Counting extracted barcodes first turns the
whitelist into a filter over a few thousand candidates instead of a per-read search space.
What changed
Cell barcode detection.
--n_cellsnow decides what--barcode_whitelistmeans: unset itis the cell list (one pass, matched as today); set to a number or
autoit is a pool, so afirst pass extracts barcode windows and counts them, the counts pick the cell barcodes, and an
ordinary second pass matches reads against those.
--barcode_whitelist autodetects with nopool at all. Correction is always the existing SSW matcher — only the list it matches against
varies.
Measured on three real 10x datasets, against a 7.4M stock whitelist:
--n_cells(recall / precision). In every case the result is identical to supplying the true cell list
— the user no longer has to know which barcodes are the cells.
--n_cells autoequals an exactcount, and selection tolerates being off by ±25%.
--split_molecules {true,false,auto}replaces the split mode names, soIsoQuantModehasone member per chemistry.
auto(default) splits wherever the protocol supports it;trueaborts on a protocol that cannot split rather than silently not splitting. Defaulting to
autois justified by measurement: splitting improves recall ~1 point on non-concatenated data at
unchanged precision.
tenX_v3_split,tenX_v2_splitandstereoseq_nosplitstill work asdeprecated aliases.
visium_5primegains splitting via the 10x detector.Bug fixes, each found while working nearby:
score_diffnever rejected anything — the runner-up was untracked and same-offset tiesinvisible. See the baseline note below.
estimate_cell_numberreported a single cell on a flat count distribution (argmaxof anall-zero array).
non-whitelisted artifact pushed genuine cells below it.
index was pickled to every worker per 100k-read chunk (−32% peak RSS).
Mouse.10x.v2.737K.fullran undertenX_v3, reading a 12bp UMI from a 10bp-UMI chemistry.last_potential_posguard, unreachable h5 whitelist path, deadmin_scoresbranch,a broken lazy-logging call.
Baseline changes reviewers should know about
Repairing
score_diffmakes ambiguous calls get rejected instead of guessed. The effect scaleswith how many ties the whitelist creates — negligible for 10x, large for
custom_sc:The VisiumHD cases trade real recall for large precision gains. Accepted deliberately: a
misassigned barcode puts a read in the wrong cell and silently corrupts that cell's counts,
whereas a dropped read only costs depth. Five baselines refreshed from measurement. Attribution
was confirmed, not inferred — setting
SCORE_DIFF = 0reproduces the old numbers exactly. Thenative
visium_hdmode is unaffected (different function).Testing
1401 unit tests pass. All 13 dispatchable barcode workflows ran on this branch: 9 pass, and the
4 failures are exactly the
score_diffbaseline shifts above, now refreshed.The three
Barcode.Mouse.10x.CellSelectionconfigs were run locally through the CI runner(99.60/74.36, 99.75/87.33, 99.81/86.78 — all matching baselines) because a new workflow file
cannot be dispatched until it is on the default branch. That workflow's first real run will
be after this merge.
Notes
the same barcodes, the existing SSW matcher scored 87.35 vs 86.55 recall, because a local
alignment slides to find a shifted barcode and a fixed-window edit distance cannot. Only the
counting and selection remain.
barcode per read cost ~126 bytes/read, about 12 GB on a 100M-read run, written and re-read
for nothing.
--resumeon a run started before this change will fail to unpickle the removed modemembers. Normal for a version bump, but worth a changelog line.
See
.claude/CELL_BARCODE_SELECTION.mdfor the full design, benchmarks and gotchas.🤖 Generated with Claude Code