Virridy Home | Lume — Water Quality Sensing Water for Carbon

In-Stream Intra-Sensor Comparison — BC-13

5 sensors · co-located in-stream at BC-13, installed 2026-07-19 noon MT · agreement of the TLF slope (full-sweep bias-response, replacing mon2), ToF turbidity, and temperature reading the same water

Overview

Five Lume units are installed together in the same reach at BC-13, all submerged in the same water since noon on 2026-07-19. Because every unit sees an identical sample, this is a field precision / reproducibility test: any spread between the traces is sensor-to-sensor measurement disagreement, not a difference in what they are measuring. It is the in-stream analog of the bench serial-dilution's sensor-to-sensor spread, but under real water, real turbidity, and a real diel temperature swing rather than a controlled bath.

Three channels are compared: the tryptophan-like fluorescence mon2_val (TLF, the primary E. coli–bearing signal), the Time-of-Flight backscatter signal_per_spad_kcps (the turbidity proxy), and SiPM temperature. TLF uses a best-SNR-per-unit combo (the default below): each unit runs at the LED×bias that minimizes its concentration noise floor (blank noise ÷ sensitivity) in the dilution while staying unsaturated in-stream — not simply the highest unsaturated LED, which isn't always best. Here that puts 50062 at LED 32/bias 3468, 500212 at LED 128/3079, 500128 at LED 512/2942, 50090 at LED 32/3039 (halves its noise floor, 2.5→1.1 µg/L), and 50084 at LED 512/2934 (a lower bias that avoids the saturation which had forced it to 128). Each combo's own gain keeps the calibrated µg/L-equivalent comparable across units. Absolute TLF still carries each unit's own baseline/gain offset (what the per-sensor field calibration removes); the tracking agreement — do they rise and fall together as the water changes — is the more meaningful readout, and is reported separately below.

Load Window

Live data pulled per sensor from the sensor API, every reading shown at full resolution (no aggregation). A sweep-health QC gate drops any acquisition cycle where the SiPM did not respond to the bias ramp (a dead cycle pinned at the pedestal, e.g. 50090 at 12:11); rejected cycles are listed below. The window is locked to the deployment period: noon 2026-07-19 (install) to noon 2026-07-23.

Idle.

Inter-Sensor Agreement — Summary

Revisited 2026-08-21 with the canonical TLF amplitude and the current correction stack. Five units in the same water, so every difference between them is instrument. Agreement is measured on deviation from the fleet, never on each sensor’s own trend: real changes in the water are common-mode and must not count as disagreement, and a per-sensor detrend would quietly delete the shared signal along with the drift. devi(t) = ln(Si) − ln(basei) − mediani, binned to the 15-minute reporting cadence; figures are the median across-sensor SD of that deviation, in %. Reproduce with scripts/instream_agreement.mjs.

Fluorescence featureraw+ quench @20 °C+ quench + driftworst unit
mon2 at the calibrated combo7.407.205.8050062 11.9%
S-TLF slope6.156.462.3550084 4.8%
Two-way log-model amplitude ◀ best6.316.312.2050084 2.6%

Drift is the whole story; the estimator decides the tail. Removing differential drift takes the amplitude from 6.31 % to 2.20 % — a 2.9× tightening and the single largest effect here. Amplitude and slope finish within 0.15 % of each other on fleet spread but differ on the worst unit: 2.6 % for the amplitude against 4.8 % for the slope. Absolute mon2 never gets close, bottoming out at 5.80 %. Per-sensor after correction: 50090 1.16 %, 50062 1.82 %, 500212 2.35 %, 500128 2.51 %, 50084 2.64 %.

The per-sensor quench does not help here, and that is not a contradiction. These five share water, so they share temperature. A quench applied to a common T is a common-mode shift, which the fleet-median subtraction cancels exactly — a uniform ρ leaves the number unchanged (6.31 → 6.31, measured). Only the differences between per-sensor coefficients act, and here they made agreement worse (6.31 → 6.85). Per-sensor quench earns its place across sites at different temperatures; within one stream it can only add noise unless the coefficients are right.

This test found a real defect in the quench table. 50084 and 50090 were fitted on a “clean-water cooldown” of 2026-07-21/22 — a window this very archive shows them spending in Boulder Creek. Neither carries a site_installations row, so the in-creek guard never fired: install windows are authoritative but incomplete, and a unit with no window was being treated as never deployed. Their coefficients were fitted on river water, which the twin forbids at §12.3b. 50062’s value is stale for a different reason — it dates from April, before that unit was repaired and redeployed on 06-25. The generator now carries an explicit known-deployed list.

Raw TLF (mon2_val) — at each unit's operating combo

The raw single-combo mon2 per unit (auto-ranged LED, bias ≈ 3000; hover shows the LED). Under auto-range the units run at different LEDs and several rail near the ~3299 ceiling, so the absolute levels are not directly comparable here — which is exactly why the TLF slope below replaces it as the signal. Shown for reference.

TLF slope — Full-Sweep Bias-Response (replaces mon2)

The signal is the TLF slope (sTLF) — the full-sweep bias-response derivative at bias 3000, ×10³ (see the definition box on the dilution page), replacing absolute mon2. It is computed once per reading from the whole LED×bias sweep, so it needs no per-unit operating combo and cannot rail — the auto-range / saturation problems that made raw mon2 non-comparable across these units simply don’t arise. (Absolute levels still differ by each unit’s gain; the calibration below maps them to µg/L-equivalent.)

Calibrated TLF slope — “TLF slope temp + turbidity corrected” → µg/L-equivalent

Each unit's TLF slope is first temperature-corrected to 20 °C“TLF slope temp corrected” — then mapped into µg/L-equivalent tryptophan with a per-sensor gain (and optional curvature) fit from the 2026-07-16 serial dilution in slope units. Reading the same water, the five traces should collapse onto one line. Which they do depends on where the offset comes from:

The lab offset is temperature-specific, not transferable as-is. The baselines are not drifting and the sensors are not changing: the units run about 1 °C colder in-stream than the 22 °C lab bath (UV-LED temperature ~21.4 °C in the lab vs ~20.3 °C here), and each unit's TLF is strongly and idiosyncratically temperature-dependent near 20–22 °C. At the same SiPM bias (2998), that ~1 °C drop pushes 50084's raw mon2 from 996 to 1879 while 50062's barely moves, opposite-sign responses between units. So a raw lab offset can't align them, and the water also warms ~2 °C over the afternoon, which quenches TLF and would drag the signal tens of µg/L below zero. The recommended In-situ, temperature-corrected mode fixes both: it normalizes each unit's TLF slope to 20 °C with the exponential quench model s₀ = sTLF·eρ(20−T) (Watras et al. 2011, as used in Bedell et al. 2022 for this sensor), using that unit's own coefficient ρ (ln(TLF slope) vs SiPM temp) — this is the “TLF slope temp corrected” signal. Here ρ is fit empirically from this deployment because the dilution's 22–34 °C steps under-correct the steeper 20–23 °C field response; it then zeroes to the median of the cleanest period (the cleanest ~15% of time, selected from the smoothed signal). That baseline is de-biased: the old 5th-percentile floor sat ~1.6σ below the true zero, so noisy units read systematically high (a +6 µg/L bias on 50090); the median of the clean period is unbiased regardless of noise, and readings are not clamped to ≥0 (a true-zero reading must wobble slightly negative). 50090 (weak responder) and 50084 (most temperature-sensitive) carry wider honest noise, but are no longer biased.
Then one turbidity rule, applied identically to every unit. Turbidity attenuates the fluorescence multiplicatively, so it scales the bias-response slope. On a high-gain unit the tryptophan response is only a ~7% perturbation on a gain-dominated slope (50090 baseline slope 56 vs 500212's 12), so in turbid creek water that attenuation, not tryptophan, drives the swing: slope vs ToF backscatter r = −0.94 on 50090/50084 versus only −0.36 on the low-gain 500212. Crucially this is not an operating-point choice — tightening the saturation screen or restricting to the linear region leaves r at −0.9 and the dilution response unchanged, so it can't be measured away by running the unit lower; it is real optical attenuation. The fix is a single rule: regress each unit's slope on its own ToF backscatter and subtract the turbidity term (referenced to that unit's mean backscatter). The coefficient β falls out near zero where turbidity doesn't matter (500212 −0.04, essentially untouched) and removes it where it does — no per-sensor branching and no exclusions. This pulls 50090's field p95 from ~163 to ~53 µg/L, into agreement with the well-behaved units reading the same water.

Predicted E. coli — Calibrated TLF × Temperature, per Sensor

On 2026-07-20, five Colilert E. coli grabs were taken at BC-13. Because all five units sit in the same water, each grab is ground truth for every sensor (mWater logs them against primary 50062 with the rest in second_barcode; here each grab is paired to all 5). The table pairs each grab to every unit's calibrated TLF at that instant (the spread column is the co-located agreement); the chart below turns that calibrated TLF, plus temperature and their interaction, into a predicted E. coli trace per sensor.

Predicted vs actual log₁₀(E. coli), per sensor. A minimal two-term map, log₁₀(E. coli) = b₀ + b₁·TLF (TLF = the unit's calibrated µg/L-eq) — temperature and the TLF×temperature interaction were dropped to cut overfit. Each marker is one sensor at one grab: x = actual Colilert, y = model prediction, with a per-sensor regression line. The 249 CFU grab is dropped as a suspected error (excluded from the model, the points, and the fits). Horizontal bars are each grab's IDEXX Quanti-Tray/2000 published 95% CI — the remaining 34/41/58 CFU grabs all overlap, so Colilert itself resolves essentially one level here while the sensors spread across the y-axis. Dashed grey = 1:1; 126 CFU cross-hairs = exceedance quadrants.

Calibrated TLF — Deviation From the Fleet Median

Each unit's calibrated TLF minus the across-sensor median at that instant, in µg/L-equivalent (each other unit's nearest reading within 12 min), all points, no binning. This is the residual disagreement after calibration, using the mode selected above; if the calibration makes the five identical, every trace sits on 0. Bands mark ±2 and ±5 µg/L-eq.

ToF Turbidity (signal_per_spad_kcps → NTU)

Per-SPAD photon backscatter rate, the turbidity proxy (higher = more scattering particles). One reading per sweep cycle. Co-located units should report the same particle load. Raw signal_per_spad per unit first; the calibrated turbidity below uses the same model as the TLF-dilutions page: NTU = k × (SPS − SPS₀), with each unit's clean-water zero-point SPS₀ from the 2026-07-16 dilution 0 ppb step and pooled slope k = 1.59. In the same water the five NTU traces should agree. Two QC steps run first: a distance gate drops readings whose ToF range jumps off the cap target (bubble/particle mis-range), and a stability flag marks any unit whose reading-to-reading jitter is far above the fleet as a suspect (unstable detector) — shown dashed with a ⚠ and excluded from the agreement spread.

ToF Distance (distance_mm) — the second ToF feed

The ToF sensor returns two channels: the signal_per_spad_kcps backscatter above (turbidity) and the ranging distance to the cap interior it bounces off. In clean, stable operation the distance sits at each unit's fixed cap geometry (~70 mm); downward spikes are a bubble or particle mis-ranging onto something nearer than the cap (these are what the turbidity distance gate drops), and a slow drift would indicate the optical target changing (fouling on the cap). Shown pre-gate so the dropouts are visible.

SiPM Temperature

SiPM (detector) temperature per unit — the correct thermal covariate. (The diagnostics temperature field is the board temperature, which self-heats from the electronics and is not representative of the water, so it is not used; see the channel comparison below.) Submerged together, the units should read within a few tenths of a degree; a persistent gap flags a temperature-sensor offset. Temperature quenches TLF, so this is the covariate driving the quench correction.

Deviation From the Fleet Median

Each unit's temperature minus the across-sensor median at that instant (each other unit's nearest reading within 12 min), all points, no binning. Submerged in the same water, every trace should sit on 0; a persistent offset is a per-unit temperature-sensor bias (and it feeds straight into the TLF quench correction). Bands mark ±0.1 and ±0.25 °C.

Smoothed SiPM Temperature

Rolling-median smoothed temperature per unit, which separates each unit's real thermal trend from reading-to-reading noise. An excursion that survives smoothing is a real temperature difference; one that washes out was measurement noise. This is the cleaner temperature to drive the TLF quench correction (noisy T injects variability through eρ(20−T), amplified on high-gain units like 50090). Raw readings are shown faint behind each smoothed trace.

All Temperature Channels — water · SiPM · UV-LED

Each unit reports three temperatures: board (self-heated by the electronics, not the water), SiPM (detector), and UV-LED (excitation source). One panel per unit so you can see how the channels differ within and across units. The board runs ~1 °C above the two internal temps (its self-heating offset); SiPM and UV-LED track together. The quench correction uses the SiPM temperature — the board's self-heating makes it unrepresentative of the water.

Sensors Under Test

Five units co-located in-stream at BC-13 from noon on 2026-07-19. 50062 was previously a Boulder Creek field unit; the rest are acceptance-test units now joining the field.

BarcodePrior roleChannels