Skip to content

feat(models): add unet_roads road surface segmentation model - #80

Open
RafaelCardoso990 wants to merge 1 commit into
hotosm:developfrom
RafaelCardoso990:feat/unet-roads
Open

RafaelCardoso990 wants to merge 1 commit into
hotosm:developfrom
RafaelCardoso990:feat/unet-roads

Conversation

@RafaelCardoso990

@RafaelCardoso990 RafaelCardoso990 commented Nov 5, 2026 •

Copy link
Copy Markdown

What this adds

models/unet_roads/: a fine-tunable base model that segments roads in VHR RGB imagery and returns the road surface as polygons. Open call category: Transportation infrastructures. It supports both inference and fine-tuning.

Only models/unet_roads/ is touched.

How it works

  • Network: U-Net with a ResNet34 encoder (segmentation-models-pytorch), one road probability channel per 256 x 256 chip.
  • Inference: the probability maps of all chips in a request are pasted into one mosaic, thresholded and vectorised, so a road is one polygon across chip edges. Polygons are simplified and filtered by area.
  • Training: polygon labels are burned as drawn; line or point labels are buffered to road_width_m. BCE + Dice loss, flips and rotations, plus zoom and colour jitter. Spatial block split.
  • Weights: https://huggingface.co/Rafael990/unet-roads (checkpoint and ONNX, CC-BY-SA-4.0).

Training data and results

Base stage on SpaceNet 3 roads (Khartoum, Paris). Fine-tuning stage on OpenAerialMap drone imagery with OpenStreetMap highways (Maputo, Freetown, Dar es Salaam).

Held-out set IoU Relaxed F1 (3 px)
SpaceNet 3 Khartoum 0.53 0.44
SpaceNet 3 Paris 0.56 0.20

The OSM reference on the drone sets is incomplete, so I also reviewed the predicted masks by eye, chip by chip. Chip-level accuracy among chips with a road: 80% on a hard sample of 60 Khartoum validation chips, 78% on 90 Maputo chips (mostly in the fine-tuning set) and 69% on 120 Banepa chips, which the model never saw in training. Details and limitations are in the model card.

What I ran locally

Same commands as the workflows, on this branch, with no changes outside models/unet_roads/:

  • style: ruff check, ruff format --diff, ty check, pytest for fair (526 passed, 1 skipped)
  • validate-stac: validate_stac_items.py (including the asset URL checks) and validate_model.py
  • build-model-images: test image, 11 step tests inside it, models/test_integration.py, runtime and inference images, and both inference smoke tests (published ONNX and fine-tuned ONNX) on the Banepa bbox
  • test-model: I ran the flow on a local kind cluster (just up, just example unet_roads, scripts/test_serve.py). Registration, fine-tuning in the pod, promotion, batch prediction and the serving test all passed, with one deviation: the image is not in ghcr.io/hotosm/fair-models, so for that run I pointed mlm:training and mlm:inference at the digest of the image loaded into kind. The STAC item in this PR uses the normal :latest tags.

What I expect to fail in CI, and why

As in #68 and #77, a PR from a fork cannot push to ghcr.io/hotosm/fair-models:

  • Build Model Images stops at "Push runtime image".
  • Test Single Model stops at registration with Image digest resolution failed: ghcr.io/hotosm/fair-models/unet_roads:latest ... 403 Forbidden.

Could a maintainer run these from a branch on your side?

Notes for reviewers

  • fair-py-ops version: the Dockerfile pins 0.3.17, like the other models. With 0.3.25 the integration test fails for this model at promotion (Evaluation metrics do not match the base model's fair:metrics_spec): models/conftest.py mocks the metric logging for every test, and promotion now validates those metrics. With the mock disabled the test passes on 0.3.25.
  • Polygons, not centrelines: the contributing guide lists LineString for roads, but examples/run.py trains every model on the buildings-banepa-* polygon sample, which a line model fails the compatibility check against. I kept this PR to polygons so it runs with the existing sample data. A centreline variant of the same network is ready (mask skeletonised and traced into LineStrings), and I can open a follow-up PR if you tell me how you would like line models tested.
  • Road width: the training labels are centrelines buffered to 6 m, so the polygons show where a road runs, not its measured width.

Checklist

  • stac-item.json declares fair:metrics_spec, fair:split_spec and fair:hyperparameters_spec
  • keywords has a feature tag, a task value and exactly one geometry type
  • license is a supported SPDX identifier (CC-BY-SA-4.0)
  • just validate passes for the model and STAC item
  • README follows the model card sections

Add a fine-tunable U-Net (ResNet34 encoder) that segments roads in very
high resolution RGB imagery and returns the road surface as polygons.

- pipeline: training, evaluation, ONNX export and inference; chips of a
  request are mosaicked before vectorising, so a road is one polygon
  across chip edges
- training labels: polygons burned as drawn, lines buffered to a road width
- colour and zoom augmentation for satellite-to-drone transfer
- STAC-MLM item, model card, Dockerfile and tests
- weights: https://huggingface.co/Rafael990/unet-roads (CC-BY-SA-4.0)
@RafaelCardoso990

Copy link
Copy Markdown
Author

Hi @kshitijrajsharma, this PR adds a road surface segmentation model for the Open Call for EO GeoAI Models. The application form and the email to fair@hotosm.org were sent on 5 November.

The workflows are waiting for maintainer approval. Could you approve them when convenient? As with #68 and #77, "Build Model Images" and "Test Single Model" will also need the model image in ghcr, which a PR from a fork cannot push. Everything else passes locally, as described in the PR.

Thank you!

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant