Lume Bench Annotations

Lume TLF+ToF sensor: a controlled bench characterization (2026-04-18 → 2026-04-19) plus continuing desktop-dashboard recordings (2026-04-22 onward). Annotations mark each operator-driven condition change.

Loading data…
air (sensor exposed) water, no shake twist shake / submerged
Drag to zoom · Double-click to reset · Gaps > 1 h compressed

Test Setup

The chart combines two distinct recording sessions, both at one sample per minute (~0.017 Hz):

Three signals are plotted in stacked panels:

sipm_mon2_raw
SiPM monitor channel on the TLF (time-resolved fluorescence) detector — proportional to the fluorescence signal.
model_temp_c
Model-input temperature (°C) derived from the HDC2080.
model_tof_raw
Raw signal from the ToF (time-of-flight) distance sensor that looks into the sample cell.

Conditions

Findings (from the 2026-04-18 bench recording)

All four findings are drawn from the controlled bench session on 2026-04-18 → 2026-04-19. The 2026-04-22 desktop-dashboard data is visible in the chart as a second segment and operates in absolute value ranges different from the bench (TLF roughly 480–740 throughout submerged operation), so don’t expect bench thresholds to translate directly to it.

1. The ToF channel is a clean air-vs-water discriminator.

model_tof_raw is essentially bimodal in the bench recording: it sits near 25 whenever the sensor is in water and jumps to ~70 whenever the sensor is in air. Transitions between the two states are effectively step functions — a fixed threshold near 45 separates the two conditions with no ambiguity on bench data. The 2026-04-22 session stays in the ~26–29 water band throughout, consistent with this gate.

2. The TLF channel alone cannot separate air from water.

Bench air-phase sipm_mon2_raw values (roughly 2000–2600) overlap heavily with still-water bench values. The signal is temperature- and condition-sensitive, so any air/water gate built on TLF alone would need the ToF channel as a prior.

3. Still water traps micro-bubbles that the ToF cannot see but the TLF reports.

In every bench water-no-shake region the TLF reads noticeably higher than it does immediately after a twist-shake, even though model_tof_raw stays firmly in its “water” band (~25) throughout. The ToF sensor confirms that water is present against the window; the elevated TLF reading must therefore be driven by something the ToF cannot resolve — consistent with small air bubbles trapped on the optical window scattering additional light into the SiPM.

4. Twist-shaking reveals the true water-only TLF baseline.

In the bench recording, each twist-shake event produces an immediate drop in sipm_mon2_raw — drops of roughly 720, 1300, and 600 across the bench shake events — well below the still-water readings that precede or follow them. This is the bubble-free baseline for that session. The fact that the baseline itself is not perfectly repeatable (720 vs 1300 vs 600) suggests that either residual turbidity, the angle the sensor was re-seated in, or partial re-bubbling between annotations still modulates the reading. The 2026-04-22 session records its own much-lower baseline (~480–500 after its twist-shake), reinforcing that the bubble-free TLF level is sensor- and reseating-specific and is best treated as a per-session reference rather than a fixed value.

Implication for the classifier

A two-stage decision makes physical sense: (a) use model_tof_raw as a hard gate for air vs. water, and (b) within water, treat the TLF reading as an upper bound that can be inflated by trapped bubbles. A periodic agitation cycle or a bubble-scrub routine before measurement would make the TLF reading reflect the water itself rather than the air trapped against the window. Because the absolute TLF baseline drifts session-to-session, all bubble-detection logic should reference a recent post-disturbance baseline rather than a hard-coded threshold.

Toward a bubble-aware field algorithm

The field problem. In deployment we cannot inspect the optical window — we only see what the sensor reports. ToF tells us reliably whether the sensor is in water, but it can't tell us whether air bubbles are trapped against the window inside the water. A bubble-inflated TLF reading would be misread by the E. coli classifier as biological signal, producing false positives in microbiologically clean water.

What makes the 2026-04-18 bench dataset uniquely useful. The water in that recording is clean tap water with no measurable real turbidity and no real fluorophore content. So any TLF or turbidity signal observed while submerged (ToF in the water band) must be coming from bubbles trapped on the window — not from microbes, not from suspended sediment. The numbers below are computed only from the bench session (the 2026-04-22 session is a different sensor reseating with different absolute values and is excluded from these statistics):

TLF in water + bubbles
median 1944 (range 955–2629)
TLF in water, post twist-shake
median 1008 (range 465–1295)
Turbidity (turb_raw) with bubbles
median 27.1 (std 3.4)
Turbidity post twist-shake
median 24.5 (std 0.6)
5-min rolling std of TLF, with bubbles
1.8
5-min rolling std of TLF, post-shake
0.8
TLF vs turb_raw correlation, with bubbles
|r| = 0.98 — turbidity is an almost-perfect bubble co-indicator

Three signals separate bubbly water from bubble-free water in the bench data:

  1. Turbidity tracks bubbles tightly. Within the bubble-laden water-no-shake periods, the raw turbidity channel and TLF move together with |r|=0.98. Turbidity also shifts up by roughly 10% relative to the post-shake baseline (median 27.1 vs 24.5) and its variability inflates ~6× (std 3.4 vs 0.6). In clean water this entire signal is bubble-driven.
  2. TLF temporal variance jumps. Bubbles form and detach on second-to-minute timescales, producing a noisier TLF trace. The 5-min rolling standard deviation of TLF is roughly 2× higher when bubbles are present.
  3. TLF magnitude jumps. Bubble TLF roughly doubles the bubble-free baseline. The bubble-free baseline itself drifts between sensor reseatings (here 600–1300), so this is most useful as a relative signal against a recent post-disturbance reading rather than a fixed threshold.

Proposed bubble-aware classifier

Run the existing E. coli model exactly as today, then attach a bubble-confidence layer that gates the prediction:

1. Turbidity-deviation flag
Maintain a rolling baseline (e.g. 24-hour 5th-percentile) of turb_raw while the sensor is submerged. If the current reading deviates from baseline by more than ~1.5 σ and ToF still reports water, raise the bubble flag.
2. TLF-variance flag
Compute the rolling 5-min std of sipm_mon2_raw. If it exceeds the per-site post-shake std (call it σ0) by ~1.5×, raise the bubble flag.
3. Confidence downgrade
If either flag is raised, do not emit an alert from the E. coli prediction. Either suppress the reading entirely, or report it with explicit "low-confidence: bubble-suspect" metadata so the consuming dashboard can grey-out or annotate the point.
4. Periodic re-baseline
Whenever a known-disturbance event occurs (programmed agitation, a flow surge, a maintenance touch), record the immediate-post-event TLF as a fresh post-shake baseline. This re-anchors the variance and magnitude thresholds and corrects for sensor reseating drift.

Calibration caveat for real water sources

Real source water has real turbidity from suspended sediment, organic matter, and biota — so deploying these thresholds verbatim would mistake real turbidity for bubbles. The fix is to calibrate per site: on first deployment, capture (a) the natural turbidity baseline of the source under quiescent conditions, and (b) the post-agitation baseline. The delta between these two states is the bubble signal — the same quantity this bench dataset measures, just on top of a non-zero source-water turbidity floor instead of zero.

The existing E. coli model uses TLF + temperature + ToF and has no input that can distinguish bubble-driven TLF from microbial TLF. Adding the turbidity channel as a gate (rather than a feature, which would require retraining) is the lightest-weight path to deploying a bubble-aware classifier without changing the model itself.

Data & Reproducibility

All three panels share one timebase. Annotation rows in the source CSV contain only a timestamp and a note field; each annotation marks the start of a section that runs until the next annotation. The three plotted categories merge the raw labels: “Air”/“air” → air, “water no shake” → water no shake, “twist shake”/“submerged”/“air cleared” → twist shake. Time gaps longer than 1 hour are visually compressed in the chart with a // break marker.

Source: data.csv in this project. The interactive chart fetches the CSV directly on each page load via Plotly — refresh to pick up new data. A static fallback (plot.png) is also produced by python3 plot.py. New rows from the Lume desktop dashboard are appended via ./update.sh, which pulls lumelog.csv from SweetSenseInc/lume_desktop_dashboard (branch pc-sandbox), appends only timestamps strictly newer than the latest in data.csv, regenerates the static plot, and redeploys.