akashch1512's picture
Add object detection and visualization features
b710eca
Raw History Blame Contribute Delete
2.48 kB
"""Class-map and object previews: the two PNGs a person reads to trust `objects.json`.
* `class_preview` — the seg head's class map blended over the optical image.
* `objects_overlay` — detected tree crowns, building footprints and water
outlines drawn on the optical image. This is the check for the object step:
a crown circle that does not sit on a tree is visible at a glance.
Pure numpy + Pillow, like `figures.colorize` / `figures.hillshade`. The palette
matches the frontend's "Object classes" layer so the two read the same.
"""
from __future__ import annotations
import numpy as np
from PIL import Image, ImageDraw
CLASS_COLORS: dict[str, tuple[int, int, int]] = {
"other": (154, 160, 166),
"ground": (200, 181, 138),
"low_veg": (156, 204, 101),
"building": (224, 90, 71),
"water": (58, 143, 217),
"road": (95, 99, 104),
"tree": (46, 125, 50),
}
_UNKNOWN = (154, 160, 166)
def colorize_classes(seg: np.ndarray, names: tuple[str, ...]) -> np.ndarray:
"""(H, W) class ids -> (H, W, 3) uint8, by class *name* so ids can move."""
lut = np.array([CLASS_COLORS.get(n, _UNKNOWN) for n in names] + [_UNKNOWN] * 256,
np.uint8)[:256]
return lut[np.clip(seg, 0, 255).astype(np.uint8)]
def class_preview(rgb: np.ndarray, seg: np.ndarray, names: tuple[str, ...],
alpha: float = 0.6) -> Image.Image:
color = colorize_classes(seg, names).astype(np.float32)
out = alpha * color + (1.0 - alpha) * rgb.astype(np.float32)
return Image.fromarray(np.clip(out, 0, 255).astype(np.uint8))
def objects_overlay(rgb: np.ndarray, objects: dict) -> Image.Image:
"""Crowns as circles of their radius, footprints and water as outlines."""
gsd = float(objects["grid"]["gsd_m"])
base = Image.fromarray((rgb.astype(np.float32) * 0.7).astype(np.uint8))
draw = ImageDraw.Draw(base)
line = max(1, round(max(rgb.shape[:2]) / 800))
for w in objects["water"]:
draw.polygon([tuple(pt) for pt in w["poly"]], outline=CLASS_COLORS["water"], width=line)
for b in objects["buildings"]:
draw.polygon([tuple(pt) for pt in b["poly"]], outline=(255, 120, 90), width=line)
for t in objects["trees"]:
r = t["r"] / gsd
x, y = t["x"], t["y"]
draw.ellipse((x - r, y - r, x + r, y + r), outline=(120, 230, 120), width=line)
d = max(1.0, line)
draw.ellipse((x - d, y - d, x + d, y + d), fill=(255, 255, 255))
return base