What inspired it

USGS runs thousands of river cameras pointed at staff gauges and publishes the images but no water level. The official level comes from a single stage sensor that can drift, stick or spike for days before anyone notices. A camera that already watches the gauge is a second instrument that already exists; it just needs to be read. Waterline reads it, and uses the disagreement between the two instruments to flag whichever one is lying.

How it works

Each frame is registered to a reference image with SIFT (OpenCV 5), the plate is rectified to a strip, a quality gate checks blur, glare, darkness and tick visibility, and a changepoint detector finds the waterline row. An affine fit per site converts the row to feet.

A deterministic agent decides what to do with each read: lift contrast, mask glare, register against a wet or older reference, fetch a neighbouring frame, then accept, mark the water below the plate, abstain, or send the frame to a person. Every decision is written as an auditable trace. A z-scored CUSUM detector plus stuck, step and spike rules watches the camera-vs-sensor residual and drafts a review note for a human.

On AWS, EventBridge invokes a Lambda container running the same pipeline and agent; readings, traces and flags persist to DynamoDB. Amazon Bedrock is an optional second-opinion chooser that falls back to the deterministic policy; the committed evaluation used the deterministic policy only.

Results

Test windows were picked from gage data alone, code frozen at tag freeze-final with a protocol hash, test run once.

site frames baseline MAE ft agent MAE ft sent to a person within 2 sigma
hancock 295 0.307 0.290 29 98.2%
in_overland 1356 0.032 0.032 2 95.4%

Injected faults (20 per type per window, seed 20261026): Hancock caught 95% of drifts, 58% of stuck sensors, 100% of steps and spikes. At the sparse Indiana site camera-dependent recall was weak (drift 13%, stuck 20%) and we report it as weak. Clean-run flags: 8 at Hancock, all with the camera reading low, so we label them false alarms or camera bias.

Agentic qualifier: what OpenCV reports changes the next tool call. 252 of 471 held-out traces used at least one tool. Replaying every trace from the frozen code reproduces status, stage and source frame with 0 mismatches.

What we learned

  • A single datum offset does not transfer across flow regimes: the camera bias was +0.40 ft in August low water and -0.64 ft during the July flood at Hancock. We fit an affine row-to-feet correction per site instead.
  • Two bad frames can agree with each other. An early agent let a night frame confirm a bad neighbour; acceptance now requires the source frame to pass the quality gate itself.
  • A cross-check is only as good as its second method. Our two-detector disagreement gate was systematically wrong at both sites and we removed it after documenting it.
  • Reference images age out: registration failure falls from 53% on 2023 frames to 23% on 2026 frames at the Indiana site.

Challenges

  • Underwater plates: solved with wet reference frames and per-reference plate quads.
  • Two USGS-listed images returned 404 from S3 during the final run; they are logged in results/FINAL_RUN_NOTES.md and excluded transparently.
  • Honest scoring: our first reported agent gain was inflated by the broken cross-check and a scoring bug. We retracted it, fixed the scoring, and the numbers above come from windows chosen afterwards.
  • The dashboard ships its own limits: a rule-picked failure gallery and every false flag are visible in the UI.

Everything is reproducible: the repo carries the protocol, every result file, a fresh-clone check script, and the deploy script for the AWS layer.

Built With

Share this project:

Updates

Submission history