Stain Drift

Color is data too

Same tissue, three labs, three shades of pink.

Stained tissue gets its color from two dyes: hematoxylin turns cell nuclei purple, eosin turns most everything else pink. No two labs apply them alike. Dye lots, staining times and scanner lamps all differ. A person adapts without noticing. A model trained at one site doesn't, and a stain that drifts at one scanner can quietly break it. This page pulls the two dyes apart, puts them back in a standard color, then watches three days of scans stream in for drift.

Everything here is synthetic. The tissue is procedural and the stains are applied with the same light-absorption physics a scanner sees, so the true dye amounts are known exactly. The estimation, normalization and monitoring code only ever sees the pixels. Nothing leaves your browser.

Pull the colors apart, then put them back

One spot of tissue, scanned at four sites. The top row is what each scanner saw. The bottom row is after Macenko normalization: estimate each patch's two dye colors from its own pixels, work out how much of each dye every pixel holds, and repaint with a reference pair. ΔE is the average color difference from Scanner A; about 2.3 is the smallest difference a person can see.

Make your own scanner

Every pixel is a mix of two dye colors

Each dot is a pixel in optical density, the log of how much light it absorbs, projected onto the plane the two reference dyes span. Pixels fan out between two edges: pure hematoxylin and pure eosin. The lines are each scanner's estimated dyes. A different lab is a different fan.

One dye at a time

The same patch split into its two dyes using that scanner's estimated colors (color deconvolution). Hematoxylin alone is the nuclei; eosin alone is everything else.

Hematoxylin
Eosin

        

Watch three days of scans stream in

Three scanners, five slides an hour each. Every scan becomes an event. The pipeline checks focus, estimates the dyes, normalizes, and hands a patch to a model trained at Site A that flags atypical nuclei by how darkly they stain. A monitor groups events into 4-hour windows per scanner, learns what normal looks like on day one, then runs a CUSUM (cumulative sum) test on every metric to catch both sudden jumps and slow creep. It has no idea what's coming. The dashed lines mark what actually happened, as each moment arrives.

Latest events

    The last event, as the topic carries it

    Nothing yet. Press Play.

    Alerts

      How this maps to a real build

      What each piece on the page would be in production, on Google Cloud with open-source equivalents alongside.

      What's simulated

      PieceOn this pageIn a real system
      Tissue and dyesProcedural tissue; dye amounts turned into color with the Beer-Lambert lawReal slides, with real chemistry: dyes aren't perfectly linear, and fading, thickness and folds all change color
      Stain estimationMacenko on one 80 × 80 patch, with white measured on bare glassThe same method on many tiles per slide, or a learned method; white from the slide's background
      The modelOne threshold on how dark the nuclei are, learned on Site AA deep network, which fails the same way for the same reason, just less visibly
      AccuracyAgainst every patch's true label, known because the tissue is generatedAgainst an audited, labelled sample, so it lags and is noisier
      The streamA loop in your browser, 1,080 events over 72 simulated hoursA managed streaming job, with late data, retries and exactly-once sinks

      The engine is separate from the page and has its own tests. Read it in stain-drift/, and the tissue generator in shared/.