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
- agents
- amazon-bedrock
- amazon-dynamodb
- amazon-eventbridge
- amazon-web-services
- aws-lambda
- computer-vision
- opencv
- python
- usgs
Log in or sign up for Devpost to join the conversation.