""" Create a uint16 mask PNG from a GeoJSON file and write two down‑sampled previews (black‑and‑white and random‑colour) for easy visual inspection. """ from pathlib import Path import warnings, numpy as np, geopandas as gpd, rasterio, cv2 from shapely.geometry import Polygon, MultiPolygon from PIL import Image from tifffile import imread from utils.constants import * Image.MAX_IMAGE_PIXELS = None warnings.filterwarnings("ignore", category=Image.DecompressionBombWarning) def geojson_to_mask_png(image_path: Path, geojson_path: Path, mask_out: Path) -> None: """ Rasterize GeoJSON polygons into a uint16 label mask PNG. """ with rasterio.open(image_path) as src: transform = src.transform height, width = src.height, src.width crs = src.crs mask = np.zeros((height, width), dtype=np.uint16) gdf = gpd.read_file(geojson_path) if crs is not None and gdf.crs is not None and gdf.crs != crs: gdf = gdf.to_crs(crs) def world_to_px(tx, x, y): c, r = ~tx * (x, y) return int(round(c)), int(round(r)) for idx, geom in enumerate(gdf.geometry, start=1): polys = geom.geoms if isinstance(geom, MultiPolygon) else [geom] for poly in polys: pts = np.array( [world_to_px(transform, x, y) for x, y in poly.exterior.coords], dtype=np.int32 ) cv2.fillPoly(mask, [pts], color=idx) cv2.imwrite(str(mask_out), mask) def make_bw_preview(label_mask: np.ndarray, out_png: Path, downsample: int = 8): preview = (label_mask > 0).astype(np.uint8) * 255 if downsample > 1: preview = preview[::downsample, ::downsample] Image.fromarray(preview, mode="L").save(out_png) print(f"saved B/W preview → {out_png}") def make_colored_preview(label_mask: np.ndarray, out_png: Path, downsample: int = 8): rng = np.random.default_rng(42) lut = np.vstack( [np.zeros((1, 3), np.uint8), rng.integers(0, 256, size=(label_mask.max(), 3), dtype=np.uint8)] ) rgb = lut[label_mask] if downsample > 1: rgb = rgb[::downsample, ::downsample] Image.fromarray(rgb, mode="RGB").save(out_png) print(f"saved colour preview → {out_png}")