Virridy Home | Lume — Water Quality Sensing Water for Carbon

TLF Dilution Calibration — Tryptophan

This page carries TWO separate dilution experiments. They used different fleets, different dates and different reference signals, and their numbers are not interchangeable.
  1. Bench serial dilution — 14 sensors, 2026-07-16. A tryptophan stock stepped down through a serial dilution with every sensor in the same bath. Read on mon2_val at the calibrated combo and on the pooled-quadratic sTLF. This is the first half of the page.
  2. Burn-in fleet ladder — 25 sensors, 2026-08-06. A known ladder (0, 0.1, 0.5, 5, 10, 50 ppb) dosed into the burn-in bath, read four ways and used to derive each unit’s offset and gain. Merged here from the retired burn-in page on 2026-08-21; it is the second half, below. It uses the current tlf_amp two-way log-model amplitude, which replaced the pooled quadratic on 2026-08-13 — so the TLF feature differs between the two halves.

14 sensors · tryptophan serial-dilution bench test, 2026-07-16 · TLF (mon2_val) vs known tryptophan concentration

1 · Bench serial dilution — 14 sensors, 2026-07-16

Read on mon2_val and the pooled-quadratic sTLF, the feature in use at the time.

Overview

A serial-dilution characterization of the Lume’s tryptophan-like fluorescence (TLF) channel (mon2_val, the SiPM signal). A tryptophan stock solution is stepped down through a serial dilution while all 14 sensors read the same bath, so each sensor sees an identical, known concentration at each step. The goal is a per-sensor TLF → concentration response and a check of linearity, sensor-to-sensor spread, and limit of detection.

TLF is the primary E. coli–bearing signal in the field model, so a clean concentration–response curve on a pure tryptophan standard is the fluorophore analog of the ToF → NTU turbidity calibration. This page reads the sensors live during the test; enter the dilution steps below to build the calibration curve as data comes in.

Live TLF Readings

Raw mon2_val per sensor over the test window, pulled from the sensor API (combo: 'best' — the calibrated combo per unit). These 14 units auto-range, so the operating combo (LED power) can differ between units; hover a point to see its combo. Absolute mon2 is only comparable within a sensor across steps, not between sensors at different LED powers.

Idle.

All 14 Overlaid — Same Bath, Corrected to a Common Scale

All 14 units read the same solution at the same times, so once each unit’s instrument response is removed they should trace one curve. Each trace here is the full-sweep TLF slope with three per-sensor corrections applied: (1) temperature, corrected to 21 °C SiPM via the unit’s own quench (from the 50 ppb temperature sweep); (2) a two-point gain + offset that maps each unit’s blank → 0 and its 50 ppb → 1; and the lifetime sample count shown per unit in the legend (burn-in state). The dilution ramp should rise 0 → 1 together, and the 6-hour 50 ppb temperature hold should sit flat at 1.

SiPM Temperature

SiPM (detector) temperature per unit over the test window. (The diagnostics temperature field is the board temperature, which self-heats ~1 °C from the electronics and is not representative of the water, so it is not used.) Temperature quenches TLF, so it is the covariate to watch when interpreting the dilution response — a drift here can move mon2 independently of tryptophan.

The TLF Feature — Two-Way Log-Model Amplitude

Switched to the canonical amplitude, 2026-08-21. This page ran on the bias-response slope from 2026-07-21. Everything on it — the overlay, the calibration, the low-level test, the per-sensor prediction — now reads the two-way log-model amplitude (scripts/lib/tlf_model.js), the estimator the rest of the programme uses. The slope definition below is kept because it is still what production serves at Boulder and Chicago, and because the comparison is the evidence for the switch.

Measured on this ladder, same rows, same model form (conc = a + b·f + g·f² + c·ΔT + d·ΔT² + e·f·ΔT + h·f·ΔT²), pooled over the 12 sensors every feature can fit:

Feature pooled R² RMSE (ppb) fails to fit
absolute mon20.90974.9850059, 50091
S-TLF slope (previous)0.89635.3450059
two-way log amplitude ◀ now used0.90875.0150059

Read this honestly: the amplitude does not beat absolute mon2 on accuracy. On the sensors where all three work they are level (0.9087 vs 0.9097). What the amplitude buys is robustness — it fits 13 of 14 sensors where mon2 fits 12, and it beats the slope on 11 of 13 with the gap concentrated exactly where it should be: 50091 0.4835 → 0.8131, 500193 0.8713 → 0.9260, 50062 0.8232 → 0.8700. On well-behaved units the two tie. That is the estimator behaving as designed: it does not make good sweeps better, it stops bad ones from being wrong.

50059 fits under no feature and is excluded throughout — it carries no dilution blank of its own (see the transfer section on /colilert), not a fault of the estimator.

The feature — definition
TLF Bias-Response Slope — short name “TLF slope” (sTLF), scaled ×10³

The slope of the SiPM gain response, read at the calibrated bias — a self-normalizing, railing-proof replacement for absolute mon2. Computed once per reading from the full LED×bias sweep, in five steps:

  1. Sweep — take every combo in the reading: LED power ∈ {32, 128, 512} across the full SiPM sipm_bias sweep (~2800–3900).
  2. Screen — keep only unsaturated combos, 200 < mon2 < 3250 — dropping the low-bias pedestal (mon2 ≈ 170, SiPM below breakdown) and the high-end saturation (~3299 ceiling).
  3. Normalize — for each kept combo compute y = (mon2 − 170) / LED. Because mon2 ≈ 170 + LED·gain(bias)·fluor, dividing by LED power collapses all three LEDs onto one gain curve.
  4. Fit — regress y = a + b·bias + c·bias² (a quadratic, all kept combos pooled).
  5. Evaluate — the feature is the response slope at the fixed reference bias 3000: sTLF = (b + 2c·3000) × 10³.
sTLF  =  10³ · d/d(bias) [ (mon2 − 170) / LED ]  | bias = 3000

Constants: pedestal 170 · screen 200–3250 · reference bias 3000 · scale ×10³ · ≥6 combos required (fitSlope in the page source). Evaluating at one fixed bias — rather than a raw slope over each LED’s own auto-ranged bias arc — is what keeps the three LEDs in agreement and stops high-gain units from inverting.

Every reading carries a full LED×bias sweep, not one point. Rather than pick a single combo (absolute mon2), we sweep all combos, screen out the low-end pedestal (SiPM below breakdown, no gain: mon2 ≈ 170) and the high-end saturation (~3299 ceiling), and fit the bias-response. Because mon2 ≈ pedestal + LED·gain(bias)·fluor, dividing by LED power collapses all three LEDs onto one gain curve; its slope vs bias tracks fluorescence but partially cancels the SiPM gain’s own temperature drift.

One subtlety the sensors forced: the gain curve is curved, and the auto-range sweeps each LED over a different bias arc (bright units saturate LED 512 early, leaving a high-bias tail that is LED 32 only). A single straight-line slope over those mismatched arcs is dominated by the widest LED and can invert on high-gain units (it drove 500128 to a spurious −38σ at low ppb). Fix: fit (mon2−pedestal)/LED vs bias as a quadratic and take the derivative at a fixed reference bias (3000) — evaluating at one bias makes the feature independent of how far each LED’s sweep reaches, so the LEDs agree and 500128 reads correctly.

Why this matters for normalization: absolute mon2 quenches from both the fluorophore and the detector gain (SiPM breakdown voltage rises with heat), so it drifts ~4 %/°C. The bias-response, measured across the sweep, drifts less (roughly 2.5–3 %/°C) and it never rails. Below: at the fixed 50 ppb temperature sweep, each sensor’s signal indexed to its own value at 20 °C; the exact per-sensor quench and spread are printed under the chart.

On raw discrimination the two features tie (both track concentration and field E. coli about equally); the slope’s advantage is as a normalized reference — lower temperature sensitivity, immunity to railing, and (with the fixed-reference-bias fit) correct behavior on high-gain units. The whole page runs on it: the Normalized-TLF, temperature-correction, calibration, low-level, and prediction sections all use the bias-response derivative (scaled ×10³) in place of absolute mon2. This robustness trades a little in-sample fit: the fixed-reference-bias feature calibrates to pooled R² ≈ 0.86 (vs ~0.98 for a raw single-combo fit that leaned on the high-bias tail), the price of making every unit — including saturated/auto-ranging ones like 500128 — behave consistently.

ToF Turbidity (signal_per_spad_kcps) — Control Channel & Zero-Point

The Time-of-Flight photon backscatter (signal_per_spad_kcps, the turbidity proxy) per unit over the test. Tryptophan is dissolved, not scattering, so a pure-tryptophan dilution adds no turbidity: ToF should stay flat at the clean-water baseline across every step while TLF rises. That makes this a control (TLF and ToF are independent channels) and, at the same time, the 0-concentration step gives each unit its clean-water ToF zero-point SPS₀ — the turbidity offset used by the ToF → NTU calibration (NTU = k × (SPS − SPS₀), pooled k ≈ 1.59 from the turbidity page).

Below, the same ToF converted to NTU per unit via each sensor’s own SPS₀ and the pooled slope. Since the bath is clean water throughout, every trace should sit near 0 NTU; departures are ToF noise / drift, and set the turbidity detection floor.

Calibration — Normalized TLF vs Concentration

Median normalized TLF slope (median bias-response slope over a step − the 0-conc blank) vs concentration, per sensor, with a per-sensor linear regression (R² in the legend). Four views: the isothermal ramp only (the two temperature-change periods excluded) at full range and zoomed to 0–1 ppb, and the temperature-corrected version using all data (each reading corrected to 21 °C SiPM via the sensor’s slope quench) at full range and 0–1 ppb. Full-range panels are log–log (so each decade weighs equally and R² isn’t dominated by the 50 ppb endpoint); non-responsive sensors whose low-end normalized slope goes negative can’t be log-fit and show R² = —.

Isothermal 21 °C SiPM · 0–50 ppb (log–log)
Isothermal 21 °C SiPM · 0–1 ppb (zoom, linear)
Temperature-corrected · 0–50 ppb (log–log)
Temperature-corrected · 0–1 ppb (zoom, linear)

Low-Level Response — Significance at 0.1 & 0.5 ppb

Is there a detectable response at the lowest dilutions? Each point is one sensor at one level, expressed as SNR = (median TLF slope at that level − blank median) / blank noise (SD). Each reading’s slope is first put on the canonical corrected signal — temperature-corrected to 21 °C SiPM and sample-cycle (burn-in) corrected, from each sensor’s joint quench + burn-in fit on the 50 ppb sweep — so neither the small temperature differences nor cumulative-cycle drift between the blank and the 0.1/0.5 ppb steps are mistaken for signal. The shaded band |SNR| < 2 is indistinguishable from blank; 2 and 3 σ lines are marked. The 0 ppb group is the blank itself (SNR ≈ 0 by definition — the null reference). 5 & 10 ppb are shown as positive controls. (SNR is an effect size vs. blank noise; within-step readings are autocorrelated so it is descriptive, not a formal p-value.)

Predicted Concentration — Per-Sensor Calibration

Per-sensor calibration: each sensor gets its own coefficients, fit on its own full-sweep TLF slope — conc = aₛ + bₛ·slope + gₛ·slope² + cₛ·ΔT + dₛ·ΔT² + eₛ·(slope·ΔT) + fₛ·(slope·ΔT²), ΔT = TSiPM − 21 °C. The isothermal dilution ramp (SiPM ≈ 21 °C) fixes each sensor’s baseline/gain/curvature (aₛ,bₛ,gₛ); the fixed-50 ppb temperature sweep (SiPM ≈ 21→33→27 °C) fixes its quadratic quench (cₛ,dₛ,eₛ,fₛ). The bias-response slope is fit from every unsaturated combo in the sweep, so there is no LED-hopping and no railing — the units that pinned mon2 at the ~3299 ceiling (LED 512) are handled automatically. Predicted vs known concentration below (origin 0,0; dashed line = perfect 1:1). Units with weak response (small slope rise) calibrate poorly — see the per-sensor RMSE.

50066 — Dilution ppb Scale vs Field E. coli

How this dilution’s response maps onto field E. coli for the one strong field-Colilert overlap sensor, 50066. The dilution fixes the ppb scale (7.2 counts/ppb at LED 512). For the field grabs, the raw mon2 is the best E. coli predictor (r = 0.74): this field set was all cold and in a narrow band (~13–17 °C), so temperature-correcting with the warm-sweep quench actually degrades the correlation (r 0.74 → 0.55) and drags the coldest / highest-E. coli grab into the pack — so no aggressive correction is applied here. Each grab is referenced to the field’s own clean-water floor (the cleanest observed grab = 0 ppb) so estimates stay non-negative, then scaled at 7.2 counts/ppb. Bottom axis = ppb above the field floor, top axis = mon2 counts; y = measured log₁₀ E. coli. Fit log₁₀(E. coli) = 1.66 + 0.021·ppb (r = 0.74): ~46 CFU at 0 ppb, ~200 at 30 ppb, ~1400 at 70 ppb. The response is real but noisy at low concentration. Indicative: n = 14, per-sensor; ppb scale is nominal (field vs dilution temperature/matrix differ).

TLF ppb at the WHO risk levels — literature vs our sensors

WHO risk level
(E. coli / 100 mL)
Literature ppb
Sorensen 2018
Our sensors ppb
CBT low + 50066 high
Low (~10)1.3~3–12 (CBT)
Medium (~100)2.4~16 (50066; CBT 10–99 ~12)
High (~1000)6.9~63 (50066)
Very high (>1000)27.1>63 (50066)

E. coli anchors = WHO risk-band boundaries. Literature ppb: Sorensen et al. 2018 TLF thresholds (thermotolerant coliforms / E. coli). Our sensors ppb: low end from the CBT drinking-water dataset (165 grabs <10 CFU + 51 at 10–99, 3 sensors; E. coli signal referenced to each sensor’s own 0-CFU DOM background); high end from the 50066 Boulder field fit log₁₀(E. coli) = 1.66 + 0.021·ppb. Both on the nominal LED-512 dilution ppb scale (7.2 counts/ppb). The sensor does resolve the low-CFU regime — the E. coli signal rises 0 → ~3 → ~12 ppb across 0 / 1–9 / 10–99 CFU — though 1–9 vs 0 is marginal (within the ±3–4 ppb DOM-noise floor). Across all levels our sensors read ~2–9× more ppb than the literature for the same E. coli (nominal scale, noisy per-sensor relationship). CBT is right-censored at 100 CFU so it anchors the low end; 50066 covers 32–1986 CFU — together they span the full range.

Dilution Steps

The dilution steps for this completed test. Each point on the calibration and prediction charts is a sensor’s median over a step’s time window; steps are shaded on the charts above. This test is final — the step definitions are locked.

Sensors Under Test

The 14 units running the acceptance / dilution firmware (v0.1.18, 20-min reporting): 8 acceptance-test units and 6 units pulled from the Boulder Creek field deployment. (50059 was offline 14:10–16:34 MT, so it has no 0-conc blank and is omitted from the normalized/calibration/prediction charts; it appears on the raw and temperature charts.)

GroupBarcodes
Acceptance test500193, 50091, 500212, 50090, 500129, 50084, 500128, 500194
Boulder Creek field50046, 50048, 50052, 50059, 50062, 50066

2 · Burn-in fleet ladder — 25 sensors, 2026-08-06

Merged in from the burn-in page 2026-08-21. The 25-unit burn-in fleet's own tryptophan ladder, read four ways: the single-combo mon2 the fleet used before the slope, the full-sweep amplitude, that amplitude temperature-corrected, and the ppb it predicts.

Stage 1Dilution calibration — offset and gain

Known tryptophan ladder dosed into the bath on 2026-08-06: 0, 0.1, 0.5, 5, 10 and 50 ppb. Every step is read as a plateau — the window is checked for drift before it is averaged. The 50 ppb window sits at 15:24–15:33 rather than the logged 15:19–15:29, because that dose was still mixing for its first five minutes (24.6 % rise inside the logged window, against ≤ 2.5 % for every other step). The ladder ran before the DI baseline began, which is why the clean-water stages do not read it.

All four panels are built the same way: the mean at each dosed level, per sensor, with hashes at the 95 % CI of that mean (Student t, since the 50 ppb step is only a handful of sweeps). Markers only — a segment drawn between two dose levels would assert a trajectory that was never measured. Each series carries an OLS regression across all six plotted levels, and the R² in the legend is that line’s.

mon2 at the typical operating point

The single-combo reading the fleet used before the slope: mon2 at LED 512, SiPM bias nearest 3000, one sample, never averaged across biases. Raw and uncorrected. This is the comparison baseline for the other three panels, and its weakness is the point: it depends on that one operating point being the right one for that unit. Where auto-range has moved a sensor elsewhere, the number stops meaning what it means on its neighbours. That is exactly the dependency the full-sweep slope removes.

Full-sweep TLF slope (uncorrected)

tlf_amp, the two-way log-model amplitude of a single sample: every dark-subtracted cell of the sweep (screen 200 < mon2, cells at the 3250 ADC ceiling enter as censored bounds, ≥6 cells) estimates the same c in log(mon2 − dark) = aLED + bov + c. (Replaced the pooled quadratic d/d(bias) slope, still shown on tlf-dilutions, on 2026-08-13.) Because it pools the whole sweep, no single operating point can carry the reading, and nothing time-series or temperature enters the feature itself. No temperature correction, no gain, no offset: this is the signal itself, on the same axes as the panel beside it.

Same chart, temperature-corrected sTLF

Identical construction to the panel beside it, with one change: every sample’s slope is first divided by its own clean-water quench factor, so the two differ only by that correction. The quench is fit on the clean-water window alone (0 ppb throughout, DI outdoors, SiPM ~22 → 37 °C) as a cubic in temperature on log sTLF. The ladder itself cannot be used to fit it, because its temperature rise is collinear with the dose. This is the slope’s own quench, fit on the slope, not the mon2 quench.

Predicted ppb from the corrected sTLF

An offset and a gain taken from the panel beside it — per sensor, s = offset + gain · ppb — then inverted to give predicted ppb = (s − offset) / gain. No other terms. Plotted against the level actually dosed, with the dashed line at y = x. Six points and two parameters leaves 4 degrees of freedom, so departure from the diagonal is residual, not zero by construction. The legend carries each sensor’s R² for that offset/gain fit.

Where this leaves us

Clean water, so the truth on the panel above is zero and every departure from it is instrument. Two numbers matter: how far the corrected signal sits from zero (bias, which a per-deployment blank removes) and how much it moves about (noise, which nothing removes). The noise is the one that sets what a real measurement can resolve.

The ppb figures are the sTLF scatter divided by each sensor’s own gain from the Stage 1 ladder, mapped to 21 °C. They are therefore only as good as that gain: it was measured on one ladder, on a different day, over six dose levels. The percentages below need no such transfer — they are the scatter against that sensor’s own blank, measured entirely inside this window.

Against the published requirement

How good does this have to be? The literature answers that through the TLF concentration that corresponds to a given E. coli level. Sorensen et al. (2018) pooled groundwater and surface drinking-water data from India, Malawi, South Africa and Zambia (n = 564) and placed the WHO risk bands at 1.3, 2.4, 6.9 and 27.1 ppb for ≥1, ≥10, ≥100 and ≥1000 organisms per 100 mL. Independent field studies put the detection threshold in the same place: 1.9 ppb from thermotolerant coliforms and 1.7 ppb from E. coli in Malawi (Ward et al., 2021), 2.2 ppb in Uganda (Sorensen et al., 2021), 1.0 ppb in Kenya (Nowicki et al., 2019).

The recreational thresholds sit inside the published High band, so they are read by interpolating between 6.9 ppb at 100 and 27.1 ppb at 1000 CFU/100 mL: the EPA geometric mean of 126 lands at ≈ 7.9 ppb and the statistical threshold value of 410 at ≈ 16.0 ppb. That interpolation is ours, and it carries a drinking-water relationship into recreational surface water, which Ward et al. explicitly caution against. Treat the recreational rows as indicative.

Which noise applies. A threshold crossing is called at a moment, so the relevant figure is the short-term scatter, taken here as the standard deviation of successive-sample differences (÷ √2), which removes any slow drift. That is a different and much smaller quantity than the multi-day figure in the card above, and it is the one comparable to a published detection limit. Both are reported.

For scale, Sorensen et al. (2018) report field fluorimeter detection limits of 1.99 ± 0.53 ppb for the Cyclops-7 and 1.74 ppb for a modified GGUN–FL30, both of which fail their own 1.3 ppb threshold, against 0.17 ± 0.06 ppb for the photomultiplier-based UviLux; field repeatability in Sorensen et al. (2021) was σ = 0.4 ppb. Bedell et al. (2022) reported 0.05 ppb in deionised water for an earlier generation of this sensor.

Resolution is not the binding constraint for long. Sorensen et al. (2018) found their threshold fails outright below 10 cfu/100 mL and call that the limit of detection of the method, not of the instrument. Ward et al. (2021) recorded an 84 % false-positive rate at a borehole where TLF stayed elevated with almost no coliforms present. Below roughly 0.1 ppb the sensor is resolving detail the TLF-to-E. coli relationship cannot use, and the remaining error is biology and the reference method, not optics.