SlackWater turns the short video the OneAquaHealth app already asks for into a flow measurement, or into a refusal that tells the citizen exactly how to film again.

Track

Track 3: AI-Supported Assessment. Citizens judge a stream's flow by eye, and those answers can disagree. SlackWater supports that judgement without replacing it, with the three things Track 3 asks for:

  • Validation checks: seven gates run on every clip. The first one that fails is the answer.
  • Explainable: every reading comes with an evidence frame showing each point it followed, and the value of every gate. No black-box model: classical computer vision, so every number can be traced.
  • Human in the loop: the citizen's own answer stays theirs. SlackWater adds a measurement next to it, or a plain-words reason to film again.

It also spans Track 7: Digital Health Standards. Every reading exports as a FHIR R4 Observation in OneAquaHealth's own profile, coded #hydrology, and is anchored on the OriginTrail DKG so anyone can check it.

Try it in two minutes

  1. Open the live demo (under "Try it out"). The top of the page is the real flood, measured.
  2. Scroll to Every point it followed. Pick Box too small, then By speed: you'll see why it refused.
  3. Open 3D field, click any disc, then Open this video's analysis.
  4. Measure your own clip right on the site: on a phone, tap Film live with your camera; on a computer, drop in any stream clip. (To run it yourself: ./mvnw spring-boot:run with Java 25.)

The problem

OneAquaHealth's citizen app asks people to judge the flow by eye: Fast, Slow, Stagnant or Dry. Two people at the same stream can disagree. The app also asks for a short video, and nothing measures it yet.

Still water matters. It's where the mosquitoes that carry West Nile virus lay their eggs, so knowing which stretches are really still could help crews decide where to look first.

What SlackWater does

You film about 10 seconds of a stream, with the phone held still and some bank in view, and draw a box over the water. SlackWater follows foam, leaves and ripples on the surface, and watches the banks to check the phone didn't move. Then it answers:

  • MOVING, with a surface speed (in metres per second when there's a scale)
  • STILL
  • REFUSED, with the reason and the fix

It gives the answer in the app's own codes too (FAS / NOR / STA). We found no published speed threshold behind those words, so ours (0.5 m/s between Slow and Fast) is a stated assumption: one setting the OneAquaHealth team can change. Dry can't be seen from video, so it never answers Dry.

Results on real footage

Real flood footage of the Geul at Hommerich, Netherlands, at the peak of a high-flow event (Zenodo 15002591, CC BY 4.0):

Clip Verdict Surface speed Points moving together Coherence Steady pairs
Camera view MOVING 185 px/s (no scale) 96% 0.93 40 / 40
Top-down, 0.01 m/px MOVING 1.59 m/s, app answer FAS 99% 0.97 40 / 40
Top-down, box drawn too small REFUSED none 40 / 40

On a synthetic strip moving at exactly 60 px/s it measures 60.000 px/s. The flood clip has no independent reference speed, so 1.59 m/s is the engine's answer, not a checked one. We say that on the site too.

Scored on labelled clips: Trinidad found nine more clips on Wikimedia Commons and labelled them still or moving before the engine saw them. Result: 1 right, 1 wrong, 7 refused. Most were filmed handheld, so the banks moved and the engine refused instead of guessing. One moving stream was called still: a real miss, kept in the score. That's the trade we chose: a refusal costs a citizen one more clip; a confident wrong answer costs trust.

Refusing is a result

Our first real clip found a way the tool could fool itself. With the box drawn over only part of the river, version 0.1 counted the moving water outside the box as background, raised its noise floor to match, and called a flood still. We added a gate for it. Now that clip is refused, and the person filming is told: "Things outside the water box are moving, like more water or plants in the wind, so we can't tell real motion from noise. Draw the box over all of the water."

The seven gates, in order: the video reads; at least 1 s and 5 frame pairs; at least 12 fixed points on the banks; the camera held still; a quiet background; at least 15 points on the water; one direction.

Refusing is a result

Every point it followed, in 3D

Every reading keeps every point the engine tracked: 50,698 across the three flood readings. The landing page draws them one disc each, in the frame, over time, or piled by speed. In the speed pile the banks sit at zero and the water far to the right, with the cut-off between them. That gap is the measurement. The 3D field puts every video in one faceted view: click a disc for its video, speed and moment, then open that video's analysis.

A record anyone can check

  • Fingerprint: SHA-256 of the clip, the box and scale, and the engine version. The clip is deleted after measuring; its hash proves which clip it was. Anyone with the clip can rebuild the fingerprint in two lines (shown in the README).
  • OriginTrail DKG: each reading is a Knowledge Asset in RDF (W3C SOSA, observed property #hydrology). The flood reading is published to Verifiable Memory on Base Sepolia, UAL did:dkg:base:84532/0x5ea07ffddc58dd261102746e6651747e18429dbe/19, so the record doesn't depend on our server.
  • FHIR R4: an Observation in the OneAquaHealth profile observation-indicators-oah, with the fingerprint and the UAL as identifiers. A refusal exports with dataAbsentReason. Built to the guide; not yet run through the official HL7 validator.

One reading as a knowledge graph

One Health: who acts on a reading

  • Environment: flow is part of the guide's #hydrology indicator ("flow type, diversity of flow types, longitudinal connectivity, runoff"). Urban streams get flashier as cities grow (the "urban stream syndrome", Walsh et al. 2005); repeat readings at one site could show that over time.
  • Animals and people: stretches measured as really still are where mosquitoes can breed. A crew can check those first, instead of every site.
  • Who acts: the citizen gets a number or a clear reason to film again. A researcher or city team gets a measured flow with its evidence, in FHIR, and decides what to do. SlackWater never claims mosquitoes, larvae or disease.

Fits OneAquaHealth today

  • Uses what the app already collects: the short video and the flow question, answered in the app's own codes.
  • Same stack as the app's backend: Spring Boot, JPA/Hibernate and a SQL database (the app's deliverable D5.3 describes Spring Boot, Hibernate and PostgreSQL). Moving from H2 to PostgreSQL is a driver and a connection string.
  • Cheap to run: no GPU, no trained model, no special hardware. A 1080p clip uploads and measures in about 0.7 s on our 8-core desktop CPU.
  • Private by design: the video is deleted after measuring; only its fingerprint and the numbers are kept.
  • Standards out: FHIR in the guide's profile, so any of the five cities can take readings into the systems they already use.

Built for the person at the stream

  • Three steps on one screen: film, box the water, get the answer.
  • One big drop zone for your clip, and real sample clips if you just want to look.
  • Film live with your phone camera: a live preview draws the water's motion and checks the banks are steady before you record, then the engine measures the clip. It catches the most common reason for a refusal (the phone moving) while you're still filming.
  • Refusals say what to change: rest the phone on something, film for at least a second, draw the box over all the water, or toss a leaf in upstream and film it from the bank.
  • Works on a phone.

How we built it

  • Engine: Java 25 and JavaCV (OpenCV 4.14, FFmpeg). Good Features to Track, then pyramidal Lucas-Kanade optical flow, forward and back; a point that misses its start by more than 0.5 px is dropped. Frame pairs about 0.1 s apart, timed by the video's own clock. The same method family as KLT-IV (Perks, 2020).
  • Server: Spring Boot 4.1, JPA and H2, a REST API, the fingerprints, the FHIR export and the DKG ledger (Knowledge Assets read back with SPARQL to verify).
  • Page: React 19, TypeScript and Vite, with three.js for the 3D views. It also runs on its own as a demo with the saved readings.
  • Tests: 35, on synthetic clips with known speeds, the real flood footage, the API, the seed readings and the ledger. 34 pass; one waits for a labelled set.

Challenges

  • The first real clip fooled the engine (above). That's now a gate and a test.
  • Clean, calm water has nothing on it to follow. Measuring it anyway needs other hardware, like a thermal camera. So SlackWater refuses, and asks for a leaf or small stick dropped in upstream, filmed from the bank.
  • Explaining a refusal to someone who has never seen an evidence frame. We redrew it as a simple river with the box, and kept the real frames one click away.

What it never claims

  • River speed. It measures the surface, which flows faster than the average.
  • Metres per second without a scale in the frame.
  • Mosquitoes, larvae or disease. It says still or moving; people decide where to look.
  • That the box holds water. It measures motion in the box; a check that it's really water is on our list.

What's next

  • More labelled clips, filmed on a tripod or resting phone, to measure accuracy and not just refusals.
  • Send readings into OneAquaHealth's systems through the FHIR export, and run the HL7 validator on it.
  • Calibrate the Slow and Fast cut-off with the OneAquaHealth team.
  • A thermal camera for water with nothing visible on it.

Credits

Footage: the Geul at Hommerich, Zenodo 15002591, CC BY 4.0.

Built With

Share this project:

Updates

Submission history