Spaces:
Paused
Paused
Download backend/evidence/area.py from Sahil-nith/SatQuery-api: direct link, hf CLI and curl.
- Browser
- Download file 5.91 kB
-
https://huggingface.co/spaces/Sahil-nith/SatQuery-api/resolve/main/backend/evidence/area.py
- Command line
-
hf download hf://spaces/Sahil-nith/SatQuery-api/backend/evidence/area.py
-
curl -L -o area.py https://huggingface.co/spaces/Sahil-nith/SatQuery-api/resolve/main/backend/evidence/area.py
5.91 kB
| """ | |
| Area estimation for a previously-grounded region — Phase 16, follow-up | |
| chain hop 2 ("How much area?" after "Where exactly?"). | |
| Closes part of the gap flagged in PHASE15.md §2 ("What this does NOT | |
| do... it does not chain area calculation... automatically") and | |
| LEFT.md §3 ("Multi-step follow-up conversations... the rest of the | |
| chain (area size, then radar cross-check) is not built yet"). | |
| HONESTY CONTRACT: | |
| This is a geometric calculation over the PREVIOUS turn's bounding box — | |
| not a new model call, not a segmentation of the actual object, and not a | |
| guaranteed-accurate measurement. It only produces a real area figure | |
| when the source image carries usable georeferencing (a CRS + bounds from | |
| GeoTIFF metadata, in projected/metric units): | |
| - PNG/JPEG benchmark images have no CRS at all -> area is reported as | |
| unavailable, never guessed. | |
| - GeoTIFFs in a geographic (degree-based) CRS, e.g. plain WGS84 | |
| (EPSG:4326) without a projection, would need proper reprojection to | |
| convert degrees to metres — this module deliberately does NOT do that | |
| (no pyproj dependency added for a hackathon follow-up feature) and | |
| instead reports area as unavailable rather than silently treating | |
| degrees as if they were metres, which would be badly wrong at most | |
| latitudes. | |
| - Only GeoTIFFs in a recognisably projected/metric CRS (UTM zones, Web | |
| Mercator, a handful of common projected EPSG codes) get a real number, | |
| computed as: pixel-box footprint -> ground distance (using this | |
| image's own bounds/width/height as its ground-sample distance) -> | |
| rectangular area in km². This is the box's rectangular footprint, not | |
| a precise outline of the detected object. | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass | |
| from typing import Optional | |
| class AreaResult: | |
| area_km2: Optional[float] | |
| area_note: str | |
| method: str | |
| # Pragmatic prototype check on the CRS string only — not a full units | |
| # lookup via a projections database. Good enough to catch the common | |
| # "GeoTIFF in a UTM/Mercator-family projected CRS" case that matters for | |
| # a hackathon demo; anything else is honestly reported as unavailable | |
| # rather than guessed. State this limitation wherever this is surfaced. | |
| _PROJECTED_CRS_HINTS = ("UTM", "3857", "MERCATOR", ":326", ":327", ":3035", ":5070", ":2163") | |
| _GEOGRAPHIC_CRS_HINTS = ("4326", "WGS 84", "WGS84", "LONGLAT", "LONG/LAT", "GEOGCS") | |
| def _looks_projected(crs: Optional[str]) -> Optional[bool]: | |
| """Returns True (projected/metric), False (geographic/degrees), or | |
| None (couldn't tell from the string — treated as unsupported).""" | |
| if not crs: | |
| return None | |
| c = crs.upper() | |
| if any(h in c for h in _GEOGRAPHIC_CRS_HINTS): | |
| return False | |
| if any(h in c for h in _PROJECTED_CRS_HINTS): | |
| return True | |
| return None | |
| def estimate_box_area_km2( | |
| box: tuple[float, float, float, float], | |
| image_width: int, | |
| image_height: int, | |
| crs: Optional[str], | |
| bounds: Optional[tuple], | |
| ) -> AreaResult: | |
| """box is (x_min, y_min, x_max, y_max) in PIXEL coordinates on the | |
| same image the box was originally computed from — image_width/ | |
| image_height/crs/bounds must all come from that same image's own | |
| upload metadata (api/upload.py's UploadResponse), never a different | |
| image, or the ground-sample distance will be silently wrong.""" | |
| x_min, y_min, x_max, y_max = box | |
| if not crs or not bounds or image_width <= 0 or image_height <= 0: | |
| return AreaResult( | |
| area_km2=None, | |
| area_note=( | |
| "Area unavailable — the source image has no georeferencing metadata " | |
| "(no CRS/bounds), so pixel dimensions cannot be converted to a ground " | |
| "distance. This is expected for PNG/JPEG benchmark images." | |
| ), | |
| method="unavailable_no_georeferencing", | |
| ) | |
| projected = _looks_projected(crs) | |
| if projected is not True: | |
| reason = ( | |
| f"source CRS ({crs}) appears to be a geographic (degree-based) coordinate " | |
| "system, not a projected metric one" | |
| if projected is False | |
| else f"source CRS ({crs}) could not be confidently identified as projected/metric" | |
| ) | |
| return AreaResult( | |
| area_km2=None, | |
| area_note=( | |
| f"Area unavailable — {reason}. Converting degrees to km\u00b2 without proper " | |
| "reprojection would silently misestimate area (the error varies with " | |
| "latitude), so this is intentionally left unreported rather than guessed. " | |
| "Reproject the source GeoTIFF to a projected CRS (e.g. the appropriate UTM " | |
| "zone) to get a real area figure here." | |
| ), | |
| method="unavailable_geographic_crs", | |
| ) | |
| minx, miny, maxx, maxy = bounds | |
| px_width_m = abs(maxx - minx) / image_width | |
| px_height_m = abs(maxy - miny) / image_height | |
| box_width_px = abs(x_max - x_min) | |
| box_height_px = abs(y_max - y_min) | |
| box_width_m = box_width_px * px_width_m | |
| box_height_m = box_height_px * px_height_m | |
| area_km2 = (box_width_m * box_height_m) / 1_000_000.0 | |
| return AreaResult( | |
| area_km2=round(area_km2, 4), | |
| area_note=( | |
| f"Approximate area from the previously grounded bounding box: " | |
| f"{box_width_m:.0f} m \u00d7 {box_height_m:.0f} m, using this image's own GeoTIFF " | |
| f"ground-sample distance ({px_width_m:.2f} m/px \u00d7 {px_height_m:.2f} m/px, derived " | |
| "from its bounds/width/height — not a hardcoded sensor resolution). This is the " | |
| "box's rectangular footprint, not a precise segmentation of the actual " | |
| "object/region — treat it as a rough order-of-magnitude estimate, not a " | |
| "validated measurement." | |
| ), | |
| method="bbox_pixel_to_ground_geotiff_gsd", | |
| ) | |