Skip to content

Make insert size thresholds robust and thread safe - #1877

Merged
jrobinso merged 4 commits into
mainfrom
insert-size-thresholds
Sep 22, 2026
Merged

jrobinso merged 4 commits into
mainfrom
insert-size-thresholds

Conversation

@jrobinso

Copy link
Copy Markdown
Contributor

Review of the insert size (TLEN) threshold estimate in PEStats, prompted by igvteam/igv.js@96e3776, which fixed the equivalent code in igv.js. The load-order bug fixed there is not present here — desktop has always pooled samples across loads and recomputed — but the review turned up two other problems.

Thread safety

track.load(frame) runs on a 5 thread pool, so in multi-locus view several tiles of the same track load at once, all writing the shared peStats map and the shared PEStats objects with no synchronization. Concurrent DoubleArrayList.add could lose a sample or leave an uninitialized 0.0 in the pool (enough zeros drag the 0.5th percentile to 0 and silently disable small-insert coloring), and toArray()'s trimToSize() could swap the backing array under a concurrent add, throwing ArrayIndexOutOfBoundsException or NumberIsTooLargeException out of loadTile — an error dialog, and a tile returned before finish().

Each tile now samples into its own thread-confined PEStats (PEStats.forLoad) and merges once when the load completes. merge, computeInsertSize and computeExpectedOrientation are synchronized; the published thresholds are volatile so the renderer reads them without a monitor. The shared map is a ConcurrentHashMap.

Robustness of the estimate

The thresholds are percentiles in the tails, so a region dense with proper-pair-flagged discordant reads could drag the maximum past every read worth flagging. Following igv.js:

  • outliers more than 10 robust standard deviations (median absolute deviation) from the median are trimmed before the percentiles are taken, unless that would discard a majority of the sample
  • the pool grows from 1000 to 20 000 samples, and no single load may contribute more than 1000, so one deep region can no longer own it

Also

  • loadTile recomputed the thresholds from the global preferences on every load, discarding percentiles set per-track through Set insert size options .... It now uses the track's RenderOptions, which fall back to the same preferences when unset.
  • A minimum percentile of 0 threw OutOfRangeException out of the load. It now means what it reads as: no read is colored as a small insert. The dialog accepts 0 accordingly.
  • minTLENPercentile / maxTLENPercentile were written to JSON sessions as numbers but read back with getString, so a session carrying them failed to load. igv.js's legacy minFragmentLength / maxFragmentLength are now accepted as aliases for minTLEN / maxTLEN.
  • InsertSizeSettingsDialog restored a Swing component dump instead of the previous value after a bad entry.

Notes

Insert size coloring is not a useful metric for RNA data, so no accommodation is made for libraries whose large TLENs are genuine (intron-spanning pairs); desktop and igv.js behave identically here.

Three tests in PEStatsTest cover the trimming, the per-load cap and the zero minimum; each was verified to fail without its fix. Full suite: 681 tests, 0 failures. User documentation for the estimator is drafted separately for igv.org.

🤖 Generated with Claude Code

jrobinso and others added 4 commits September 21, 2026 20:09
Mirrors igv.js 96e3776.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jrobinso
jrobinso merged commit e6e9759 into main Sep 22, 2026
2 checks passed
@jrobinso
jrobinso deleted the insert-size-thresholds branch September 22, 2026 03:49
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.

1 participant