I wanted a tile to go in and polygons to come out, the way a GIS service already expects.

oriented-det ships that as a container: deploy/example/. You bake a checkpoint, docker build, and POST a JPEG. GeoJSON comes back. A large scene is tiled inside the image.

I tried it with Oriented R-CNN on the bus-lot demo, at score 0.55. 100 boxes came back — 95 large vehicles and 5 small ones. It is the same picture the command line has been drawing since June, now as polygons a map can store.

The Dockerfile and the request are below.

A JPEG goes in. The container returns polygons.

A JPEG goes in. The container returns polygons.

GeoJSON from POST /api/v1/process — Oriented R-CNN DOTA 1× Hub on demo/demo.jpg (score ≥ 0.55, NMS 0.1)

GeoJSON from POST /api/v1/process — Oriented R-CNN DOTA 1× Hub on demo/demo.jpg (score ≥ 0.55, NMS 0.1)


What the container is Link to heading

deploy/example/Dockerfile starts from nvidia/cuda:12.1.0-runtime-ubuntu22.04, installs torch/vision cu121, then oriented-det and a small Sanic app. Non-root user 10001. Healthcheck hits /api/v1/health. Port 8080.

VerbPathRole
GET/api/v1/healthliveness (OK)
GET/api/v1/describeclass list + metadata (description.json)
GET/api/v1/openapiTile Geo Process YAML
POST/api/v1/process{resolution, tiles[]} → FeatureCollection
GET/swagger/live OpenAPI UI

resolution is meters per pixel. It does not change the detector; it scales length / width on each feature into meters. tiles is a base64 JPEG or PNG array; the example runs the first tile.

One request at a time. A second POST while inference is running returns 429.

Swagger UI on the published Oriented R-CNN DOTA 1× image

Swagger UI on the published Oriented R-CNN DOTA 1× image


Bake a checkpoint Link to heading

Weights and config.json are not in git. Copy a Hub sidecar (resolved config) and its .pth, then generate description.json so /describe and Swagger get a title:

odet pretrained download oriented_rcnn_dota_le90_1x

mkdir -p deploy/example/app/weights
cp pretrained/oriented_rcnn_*_dota_le90_1x*.json deploy/example/app/config.json
cp pretrained/oriented_rcnn_*_dota_le90_1x*.pth  deploy/example/app/weights/model.pth

python deploy/scripts/generate_description.py \
  --config deploy/example/app/config.json \
  --out deploy/example/app/description.json \
  --deploy-version 0.3.1

After your own train, copy runs/<family>/<ts>/config.json and checkpoints/checkpoint_best.pth instead. Same two filenames inside deploy/example/app/.

Build from the repo root (build context is the library, not the app/ folder):

docker build -f deploy/example/Dockerfile -t odet-example:latest .
docker run --rm -p 8080:8080 --gpus all odet-example:latest

--gpus all is the NVIDIA path this image is for. The Python falls back to CPU if CUDA is missing; the base image is still a CUDA runtime, so a Mac or CPU-only host is a smoke test, not the production shape.

curl -fsS http://127.0.0.1:8080/api/v1/health   # OK
open http://127.0.0.1:8080/swagger/

POST a tile Link to heading

import base64, json, urllib.request
from pathlib import Path

tile = base64.b64encode(Path("demo/demo.jpg").read_bytes()).decode()
req = urllib.request.Request(
    "http://127.0.0.1:8080/api/v1/process",
    data=json.dumps({"resolution": 0.5, "tiles": [tile]}).encode(),
    headers={"Content-Type": "application/json"},
)
fc = json.loads(urllib.request.urlopen(req).read())
print(len(fc["features"]), fc["features"][0]["properties"])

On this Hub checkpoint that printed 100 and:

{
  "category": "large-vehicle",
  "confidence": 0.9995,
  "length": 48.22,
  "width": 11.24
}

length / width are meters at resolution=0.5. Geometry is an image-pixel polygon, closed ring, Polygon in a FeatureCollection.

Input — demo/demo.jpg, 1024×1024 (single forward, no sliding window)

Input — demo/demo.jpg, 1024×1024 (single forward, no sliding window)

The same publish on the Pleiades Neo export tile (export/demo/planes_pleiades_neo.jpg, resolution=0.3) returned 6 boxes: 4 plane, 2 helicopter. That is the tile the ONNX post reuses.

Same engine, planes tile — 4 plane + 2 helicopter at score ≥ 0.55

Same engine, planes tile — 4 plane + 2 helicopter at score ≥ 0.55


What production.* does in the container Link to heading

Deploy does not use the eval-val floor of 0.05. InferenceEngine reads config.production.* after apply_inference_config_to_model:

KnobThis Hub slugEffect
score_threshold0.55post-decode keep
final_nms_iou_threshold0.1merge NMS
stick_to_model_canvastrue1024×1024; larger images tile
overlap_pixels200sliding-window overlap
ignore_margin_pixels0full-image centroid edge filter (0 = off)

demo.jpg is already 1024, so this run was one forward. demo/large.jpg (1299×1904) would slide — six windows at the default 200 px overlap, same tile as the sliding-window note. That path stays in this container. It is out of the ONNX graph in v0.3.

DOTA deploy floors are eval-val global F1 − 0.05 (Oriented R-CNN 0.55, Faster R-CNN 0.6, FCOS 0.2, RetinaNet OBB 0.25). Do not copy 0.55 onto FCOS.



Written on October 5, 2026 by Jeff Faudi. Link to heading