The composition table says which half of the barcode a position is in
checkout.umi_composition.tsv gains a segment column (cell / umi). The table has spanned the whole barcode since 2.5.0 keyed the counters on cell + UMI; without this it is one anonymous run of 26 positions. The halves are read differently and that is the point: a cell position is drawn from a whitelist so its skew is the whitelist's, a UMI position is a free synthesiser mix so its skew is a defect you are paying for.
Three figures were lying, and one run was reported as broken
set format in a panel script needs a single brace and a single percent. Panel scripts are substituted with str.replace, not str.format — only _PREAMBLE is an f-string — so "10^{{%%T}}" reached gnuplot verbatim and every y tick of the committed assets/consensus_error.svg read a literal 10%T. That axis spans 10^0 to 10^-250 and no tick said so. Three panels affected: consensus_error, quality_calibration, coverage.
An empty table is skipped, not failed. A purely positional pattern has no constant bases, so checkout.quality_calibration.tsv is written with a header and no rows — the calibration is measured against the pattern's own constant bases and 10x has none. migec plot handed that to gnuplot, got x range is invalid, and reported a failure. Empty and missing are now reported apart, because the advice differs: a missing table means run the stage, an empty one means the stage ran and had nothing to put in it.
coverage is now log2 on x, since checkout.coverage.tsv is written in MIGEC's own doubling bins and a base-10 axis puts the ticks somewhere other than the data. umi_pwm autoscales y instead of pinning [0:1]: a well-made barcode sits at 1/4 everywhere, so a fixed unit axis spent three quarters of the panel on emptiness.
Two README panels, on a real library
assets/coverage.svg and assets/umi_pwm.PBMC.svg, drawn off checkout's own tables on sc5p_v2_hs_PBMC_1k VDJ-T lane 1: 3,155,166 read pairs, 100% assigned, 311,421 molecules, effective length 25.85 of 26 nt. Experimental, not simulated, and SOURCES.md says so next to the simulated panels it sits beside.
The PWM is the case for the segment column: the 16 cell positions swing between 0.198 and 0.305 while the 10 UMI positions sit tight around 1/4. The MIG size panel shows 195,251 molecules seen once against 1.80 M reads piled onto 5,118 molecules in the 256 bin.
Also in this line, from 2.5.0's follow-up audit
- A barcode too long for the key is refused. 20 nt of cell and 16 of UMI each clear their own 32-base bound; their 36 nt sum does not fit the key, and two molecules sharing their first 32 bases merged silently — measured, 1 distinct barcode where there were 2. Refused at pattern-compile time, and across both mates for dual-end.
>> 64is undefined, not zero. x86 masks the count to 6 bits, so it became>> 0and the UMI landed back on top of the cell barcode. Confirmed under UBSan that the old expression is flagged and returns a different value.place_readsindexes each read once, not once per pair: 40,786 -> 150,152 reads/s, output MD5-identical.
Full detail in CHANGELOG.md.
🤖 Generated with Claude Code