Reviewing AI detections on satellite imagery, quickly and on the record.
A workbench designed for imagery analysts who check what a detection model found, decide, and keep a trace of every decision — on networks isolated from the internet, on the hardware already in place.
Work in progress · real Copernicus Sentinel-2 imagery, three areas, three dates each.

A model proposes. A person decides, and the decision has to hold.
Detection models flag far more candidate changes in satellite imagery than a team can check by eye: flooded parcels, damaged structures, debris, vessels. Each detection is a proposal until an analyst rules on it.
Keep pace with the model
A model runs over every new satellite pass. The review has to move at its pace: the image, the candidate and the decision one key apart.
Decide on the evidence
The same ground before and after, under one camera, with the same colour stretch on every date, so a change reads at a glance and compares like with like.
Leave a record
For every detection: who validated what, when, and on which image. Teams act on these decisions, so each one has to hold up later.
Where it has to run.
The teams this workbench is designed for work under four constraints. Every technical choice answers one of them.
Isolated networks
Workstations run on networks cut off from the internet. The app carries everything it needs — code, fonts, icons, imagery — and talks to its own server only.
Sovereignty and security
Imagery and decisions stay on infrastructure the organisation runs. Every dependency is code to audit, so each one is a recorded decision with its reason.
Limited hardware
Analysts work for hours on laptops and workstations, often with integrated graphics. First load, memory and frame rate have budgets, checked before each release.
Interoperability
Imagery, annotations and decisions move to and from the tools already in place: standard tiles, standard metadata, standard geometry.
Built and running.
Screens of the running app, on real Sentinel-2 scenes of Valencia, Rotterdam and Toulouse.



Weighed, chosen, and why.
Each key decision, with the alternatives, the choice and the constraint it answers. Each has a written record in the repository, with the measurements behind it.
- 01
Drawing the imagery
- Weighed
- OpenLayers · MapLibre GL · a dedicated WebGL2 viewer
- Chosen
- A dedicated WebGL2 viewer for the imagery, with OpenLayers as the path once general GIS editing is needed.
- Because
- Limited hardware and security: raw four-band tiles drawn on the graphics card, two dates in one camera, in 4.9 kB gzipped against 89 kB for OpenLayers and 295 kB for MapLibre, measured for the same job. Snapping, a second projection or a second maintainer hand the map to OpenLayers.
- 02
Tiles and metadata
- Weighed
- A tree of tile files · MBTiles · Cloud-Optimised GeoTIFF · PMTiles with STAC
- Chosen
- One PMTiles archive per acquisition, read by HTTP range requests, described by a STAC catalog with a link back to the source scene.
- Because
- Isolated networks and interoperability: one static file to copy and serve from any web server, and the metadata standard the imagery source already speaks. All 18 archives pass the reference pmtiles verify; the catalog passes stac-validator, 13 of 13.
- 03
Which grid
- Weighed
- Everything reprojected to Web Mercator · the satellite's own grid
- Chosen
- The satellite's own 10 m UTM grid for analysis, plus a true-colour Web Mercator copy for GIS tools.
- Because
- Reliable decisions: pixels stay as the satellite measured them, so an annotation stored in image pixels means the same ground on every date of an area.
- 04
Colour and band maths
- Weighed
- Pre-rendered 8-bit images per date · raw bands and the graphics card
- Chosen
- True colour, false colour and vegetation index computed on the graphics card from the same raw bands, with one stretch for every date.
- Because
- Reliable decisions: a before/after compares like with like. The heavier tiles are offset by byte-plane encoding (55 % of raw size) and a 128 MB cap on graphics memory.
- 05
Storing decisions
- Weighed
- Prisma · an embedded store · PostgreSQL with plain SQL
- Chosen
- NestJS on PostgreSQL through plain SQL and numbered migrations: each annotation a row with an owner and a time.
- Because
- Traceability and security: a database the organisation runs, and a small stack to audit and install offline — Prisma's client alone is about 74 MB.
- 06
Proving the offline guarantee
- Weighed
- A one-off audit · a content-security policy alone · a gate on every release
- Chosen
- Before anything ships, the whole stack runs locally and the tests fail on any request outside the site, any policy violation, any outbound connection from the servers.
- Because
- Isolated networks: a single library calling home would break the guarantee. Planted violations make the gate fail, so the check is proven to catch them.
From the satellite to the analyst's screen.
Four parts, each running where it belongs: the pipeline upstream, the rest on the organisation's own server.
Pipeline
Searches Earth Search's STAC API, reads only the area's window from the Sentinel-2 COGs, and writes each acquisition as PMTiles described by STAC. Runs upstream, once; its monthly download is capped.
Transfer
The files are copied to the server and every checksum is verified there before the catalogue is updated.
Server
nginx serves the archives straight from disk and fronts the web app (Next.js) and the API (NestJS, PostgreSQL: the scene catalogue, the annotations).
Operations
Every release passes the gate; deploys switch atomically and roll back on failure — rehearsed on purpose. A nightly backup goes offsite.
Numbers from the live site.
Every figure is measured on the project itself, most of them on every release by the gate that ships it.
- First tile
- 0.8 sOn screen, live demo, cold cache: the scene is rendered with the page.
- Frame time
- 16.7 ms p95Dragging the divider and panning two dates: one 60 Hz frame, on every release.
- Script memory
- 7.8 MB peakAfter two minutes of panning. Tile data lives in graphics memory, capped at 128 MB.
- Demo JavaScript
- 189 kB gzippedEvery route has a budget; a release over budget is refused.
- Outside requests
- 0 Any request beyond the site fails the release, on every page and flow.
- Release gate
- ≈ 70 sLint, unit and database tests, build, size and memory budgets, offline checks, end-to-end tests.
tessera.1991computer.com · Chrome · Apple M1 · measured 1–2 November 2026
Built, in progress, next.
Built in the open, ticket by ticket, each decision written down as it is taken.
Built
- Deep zoomReal Sentinel-2 scenes, three areas, three dates each, from the whole scene to the 10 m pixel.
- Before and afterOne camera, a divider by pointer or keyboard, date pairs, a hold-to-flicker.
- Shareable viewsA link restores the place, the dates, the camera and the divider.
- Annotation storageValidated rectangles and polygons in a private sandbox per visitor.
- Standard dataPMTiles archives, a STAC catalog, a Web Mercator copy for GIS tools.
- The release gateTests, size and memory budgets, offline checks; English and French.
In progress
- Drawing annotationsDraw, edit and save shapes on the live scene.
Next
- A review queueAccept, correct, reject, undo — with a log of who decided what.
- Detections to reviewSeeded synthetic detections, including planted errors.
- Offline bundleInstalled and run on a machine with no network.
See it run,
on the Valencia floods.
The same ground before and after 29 October 2024, under one camera: drag the divider, zoom down to the 10 m pixel.