2026 — v1.0 in progress

blotquant

QC-first western blot densitometry — a bad number is a refusal, not a warning.

github.com/sofiapetrusenko/blotquant

band detection F1
0.851

band detection F1

mean error, clean bands
7.05%

mean error, clean bands

tests in CI
690

tests in CI

real crops measured
0 of 19

real crops measured

Problem

Western blot quantification is among the most common measurements in biology and among the least reproducible. Most tools give you a number; almost none tell you whether to trust it.

Western blot quantification is one of the most common measurements in molecular biology and one of the least reproducible: hand-drawn regions, silent saturation, undocumented normalization. Most tools give you a number; almost none tell you whether you should trust it.

Approach

Lanes and bands are detected, background subtracted, signal normalized — and quality control is a first-class output rather than a footnote. Every result carries its provenance.

Analysis

Explore an analysis — hover a lane, click for densitometry, open the provenance record.

Verification

Development is gated on a synthetic gold set with per-band ground truth. The held-out split has never been scored, and nothing has been measured on a real blot.

What QC is for

Mean absolute error over three subsets of the same run — everything matched, then progressively only the bands whose ground truth is clean.

  • all matched bands17.39% / 279 bands
  • excluding bands whose truth carries saturated17.06% / 256 bands
  • bands with no truth QC flag at all7.05% / 211 bands

7.05% is the accuracy claim; 17.39% is what you get if you quantify everything and flag nothing. From the intensity-recovery table at c77fae3f9.

Parameter sweeps

The measurements behind the shipped configuration. Select a parameter; the shipped value is marked.

profile_smoothing_px

Lane F1 climbs to 0.9967 at the shipped window of 5 and then plateaus — every wider window measures the same. Band F1 does not: it peaks earlier, at 3 (0.8571), and degrades from there down to 0.7823 at the widest setting. The shipped window is therefore a stated trade-off and not an optimum for both: it buys the lane plateau for 0.0065 of band F1.

band.min_prominence_fraction vs band.min_prominence_sigma

Two criteria gate the same decision — whether a peak is a band — and the record shows they are redundant but not equally weighted. Dropping the prominence fraction to 0.001, the nearest legal stand-in for switching it off, costs 0.1522 of band F1 (0.6984 against 0.8506). Switching the noise criterion off outright at 0.0 costs 0.0039. One of the two is doing nearly all of the work, and the record is what says which.

profile_smoothing_pxshipped: 5

profile_smoothing_px ships at 5 (lane F1 0.9967, band F1 0.8506). Moving average over both 1D profiles.

Generated by evals/sweep.py and re-measured by --check in CI. Read from evals/dev_sweeps.json at c77fae3f9.

External validation, designed and not yet run

The Fiji/ImageJ comparison is pre-registered with its thresholds fixed in advance. Its first run measured nothing, and that refusal is the recorded result.

The first real-data run

Nineteen CC-BY crops, and what the pipeline did with them.

The run measured nothing: 0 of 19 crops produced a result document and 19 were refused. That is the recorded result, not a run still pending.

19 crops · refused

Every crop is a 3-channel PNG, and the loader quantifies single-channel images only.

  • byte-identical channels10 · 0 DN
  • divergence at or below the pre-named bound2 · 2 DN
  • divergence above the bound6 · 3 DN to 43 DN
  • real colour content1 · 255 DN

The fix is a documented loader decision about channel handling — the ruling puts 12 of 19 crops within reach — and it has to be specified before any measurement is taken, not after. It is not a change to the pre-registration, which stands as frozen. Read from c77fae3f9.

How it's built

Each phase runs as an implementer/reviewer agent loop to zero required findings, with a mechanical checker that fails CI when two documents state the same figure differently.

Status

CLI, QC, normalization, provenance and the HTTP API are merged. Next is getting a real blot through the loader, then the agreement run.

Stack

  • Python
  • OpenCV / scikit-image
  • FastAPI
  • NumPy / SciPy
  • pytest

Links