lunartrace.workbench.roi
User-drawn ROI evaluation for the Workbench.
The browser sends a ring of Moon geographic coordinates (degrees, sphere R = 1737.4 km).
Python projects it to the grid CRS, rasterises it with the cell-centre-inside rule and
computes slope_quality/v1 rows over that mask with the qualified row function. The
mask rule differs from the v1 lattice closed rule, so a user ROI is METHOD_EXECUTED over a
user mask: never NUMERIC_REPRODUCED, never admitted.
MOON_GEOGRAPHIC
Section titled “MOON_GEOGRAPHIC”attribute
MOON_GEOGRAPHICPROJ definition of the Moon’s geographic CRS: lon/lat in degrees on a sphere of 1737.4 km.
MASK_RULE
Section titled “MASK_RULE”attribute
MASK_RULELabel of the user-mask rasterisation rule, reported with every user ROI.
MAX_RING_VERTICES
Section titled “MAX_RING_VERTICES”attribute
MAX_RING_VERTICESLargest accepted vertex count of a user ring (the closing vertex is implicit).
SAMPLE_LIMIT
Section titled “SAMPLE_LIMIT”attribute
SAMPLE_LIMITLargest number of per-cell sample rows returned; larger masks are strided deterministically.
LonLat
Section titled “LonLat”attribute
LonLatA geographic vertex (lon, lat) in decimal degrees.
RoiError
Section titled “RoiError”class
RoiErrorThe ROI cannot be evaluated; the request fails closed.
CellNotFound
Section titled “CellNotFound”class
CellNotFoundThe picked location has no cell in the requested grid/ROI.
class
SiteThe frozen Site01 study inputs, verified and loaded for evaluation.
Attributes
Section titled “Attributes”| Name | Type | Description |
|---|---|---|
manifest_id | str | Source witness identifier from the manifest, or "?" when the manifest has none. |
manifest_sha256 | str | SHA-256 digest of the manifest file. |
slope | lunartrace.science.slope_quality.Grid | Slope grid in degrees, with its affine transform and CRS. |
slope_rms | lunartrace.science.slope_quality.Grid | Slope root-mean-square error grid in degrees, on the same grid as slope. |
reference_rois | tuple of shapely.geometry.Polygon | Source ROI exteriors in the grid CRS. |
inputs | dict of str to lunartrace.science.study.FrozenInput | Resolved frozen inputs keyed by role (for example "slope" and "roi_shp"). |
Instances are produced by load_site; they are immutable.
Site.cell_size_m
Section titled “Site.cell_size_m”Grid cell size in metres, taken from the x scale of the affine transform.
Returns
Section titled “Returns”float— Cell width in metres of the projected grid.
Site.shape
Section titled “Site.shape”Grid dimensions of the slope raster.
Returns
Section titled “Returns”tuple of int—(rows, cols)of the slope grid.
load_site
Section titled “load_site”function
def load_site(manifest_path: Path, store: Path) -> SiteResolve and verify the frozen Site inputs from the CAS once per process.
Parameters
Section titled “Parameters”| Name | Type | Description |
|---|---|---|
manifest_path | pathlib.Path | Path to the Site source manifest. |
store | pathlib.Path | Root of the content-addressed source store. |
Returns
Section titled “Returns”Site— The verified Site: slope and slope RMS grids, source ROI polygons and provenance.
The result is cached by argument, so a process loads the Site once per
(manifest_path, store) pair. Failures are not cached and are raised again on the
next call. The source ROIs are staged from the shapefile in a temporary directory and
checked against the roi_shp digest.
check_ring
Section titled “check_ring”function
def check_ring(ring: Sequence[Sequence[float]]) -> tuple[LonLat, ...]Validate a user ring and return it as a tuple of (lon, lat) pairs.
Parameters
Section titled “Parameters”| Name | Type | Description |
|---|---|---|
ring | sequence of sequence of float | Vertices as [lon, lat] in decimal degrees. The ring is closed implicitly, so the last vertex need not repeat the first. |
Returns
Section titled “Returns”tuple of LonLat— The validated vertices, in input order.
Raises
Section titled “Raises”RoiError— If there are fewer than 3 or more thanMAX_RING_VERTICESvertices, a vertex is not a pair, a coordinate is not finite, a longitude lies outside [-180, 180] or a latitude lies outside [-90, 90].
Longitudes are not wrapped; a ring crossing the antimeridian must use one branch.
check_taus
Section titled “check_taus”function
def check_taus(taus: Sequence[float]) -> tuple[float, ...]Validate Workbench taus: slope_quality rules plus distinct %g labels.
Parameters
Section titled “Parameters”| Name | Type | Description |
|---|---|---|
taus | sequence of float | Slope-RMS cutoffs in degrees. |
Returns
Section titled “Returns”tuple of float— The thresholds in the order given, as returned bysq.check_taus.
Raises
Section titled “Raises”lunartrace.science.slope_quality.MethodInputError— Ifsq.check_tausrefuses the values.RoiError— If two taus share a%glabel (for example2and2.0000001, or a repeated value). Cell picks key their inclusion flags and sample columns by that label, so a collision would silently drop one tau’s answer.
ring_digest
Section titled “ring_digest”function
def ring_digest(ring: Sequence[LonLat]) -> strStable identity of a ring: SHA-256 of its vertices rounded to 1e-9 degree.
Parameters
Section titled “Parameters”| Name | Type | Description |
|---|---|---|
ring | sequence of LonLat | Ring vertices in decimal degrees. |
Returns
Section titled “Returns”str— Lowercase hexadecimal SHA-256 digest.
Each vertex is formatted as lon,lat with nine decimals and vertices are joined by
;. A user ROI whose digest equals a served source ring digest is labelled as that
source ROI. Coordinates are rounded before formatting and the sign of zero is dropped,
so -0.0 (which a browser echoes back as 0) and values in (-5e-10, 0) all
format as 0.000000000.
project_ring
Section titled “project_ring”function
def project_ring(site: Site, ring: Sequence[LonLat]) -> PolygonProject a geographic ring into the Site grid CRS as a valid polygon.
Parameters
Section titled “Parameters”| Name | Type | Description |
|---|---|---|
site | Site | The Site whose grid CRS is the target. |
ring | sequence of LonLat | Ring vertices in lon/lat decimal degrees on the Moon’s sphere. |
Returns
Section titled “Returns”shapely.geometry.Polygon— The projected polygon in the grid CRS (metres). A polygon that is not valid is repaired withshapely.make_valid.
Raises
Section titled “Raises”RoiError— If a vertex does not project to finite grid coordinates, if the repaired geometry is not a single polygon (self-intersection), or if the polygon has zero area.
user_mask
Section titled “user_mask”function
def user_mask(site: Site, poly: Polygon) -> sq.BoolGridRasterise a projected polygon onto the Site grid by the cell-centre-inside rule.
Parameters
Section titled “Parameters”| Name | Type | Description |
|---|---|---|
site | Site | The Site providing the grid shape and affine transform. |
poly | shapely.geometry.Polygon | Polygon in the grid CRS, as returned by project_ring. |
Returns
Section titled “Returns”lunartrace.science.slope_quality.BoolGrid— Boolean grid with the Site’s shape,Truewhere a cell centre lies insidepoly.
Raises
Section titled “Raises”RoiError— If the polygon contains no cell centre (outside the grid or too small).
Rasterisation uses all_touched=False: a cell belongs to the mask only when its centre
is inside the polygon. This differs from the v1 lattice closed rule (MASK_RULE).
reference_rings
Section titled “reference_rings”function
def reference_rings(site: Site) -> list[dict[str, object]]The source ROIs as lon/lat rings, for display and selection only.
Parameters
Section titled “Parameters”| Name | Type | Description |
|---|---|---|
site | Site | The Site whose reference ROIs are converted. |
Returns
Section titled “Returns”list of dict— One entry per source ROI, in source order, with keys"roi"(the index as a string) and"ring"(a list of[lon, lat]pairs in degrees, including the closing vertex as served).
These rings are not inputs to evaluation; evaluation of a source ring is labelled by
evaluate_ring only when its digest matches.
resolve_cell
Section titled “resolve_cell”function
def resolve_cell( site: Site, lon: float, lat: float, taus: Sequence[float] = sq.DEFAULT_TAUS, ring: Sequence[LonLat] | None = None,) -> dict[str, object]Resolve a geographic pick to its containing pixel; only Python snaps.
Parameters
Section titled “Parameters”| Name | Type | Description |
|---|---|---|
site | Site | The Site to resolve against. |
lon | float | Picked longitude in decimal degrees. |
lat | float | Picked latitude in decimal degrees, in [-90, 90]. |
taus | sequence of float | Slope-RMS cutoffs in degrees for the inclusion flags. Defaults to sq.DEFAULT_TAUS. Default sq.DEFAULT_TAUS. |
ring | sequence of LonLat | A user ring; when given, the pick must lie inside it. Default None. |
Returns
Section titled “Returns”dict— Keys:rowandcol(int pixel indices);lonandlat(float, degrees, the geographic centre of the pixel);slopeanderr(float degrees, orNonewhere the value is not finite, since missing is never zero); andincluded, a dict from the formatted tau ("%g") to a bool.
Raises
Section titled “Raises”RoiError— If the lon/lat are not finite or the latitude is outside [-90, 90], or if the taus are invalid.CellNotFound— If the location cannot be projected, lies outside the Site grid, or lies outside the given ROI.
Pixel footprints are half-open in affine grid coordinates: top and left edges are included, bottom and right edges are excluded. The pixel index is
where is the inverse affine image of the projected pick. ROI membership is checked at the picked point, not at the cell centre, so a boundary cell may legitimately be absent from the samples.
evaluate_ring
Section titled “evaluate_ring”function
def evaluate_ring( site: Site, ring: Sequence[LonLat], taus: Sequence[float], with_samples: bool = True,) -> dict[str, object]Evaluate slope_quality/v1 rows over a user ring’s mask.
Parameters
Section titled “Parameters”| Name | Type | Description |
|---|---|---|
site | Site | The Site to evaluate on. |
ring | sequence of LonLat | Ring vertices in lon/lat decimal degrees. |
taus | sequence of float | Slope-RMS cutoffs in degrees; validated by check_taus. |
with_samples | bool | If true, include the per-cell samples block. Default True. |
Returns
Section titled “Returns”dict— Keys:roi(id, kind,ring_sha256, ring,mask_rule, cell count; a source ring also carriessource_index);method;implementation;status(always"METHOD_EXECUTED");admission(always"NOT_ADMITTED");taus;rows(oneslope_quality/v1row per tau);provenance; and, when requested,samples.
Raises
Section titled “Raises”RoiError— If the ring does not project to a valid polygon or contains no cell centre.ValueError— Errors fromcheck_tausfor invalid taus propagate.
The ROI id is source:<index> when the ring digest matches a served source ring, and
user:<first 12 hex digits of the digest> otherwise. The result is METHOD_EXECUTED over
a user mask. It is never NUMERIC_REPRODUCED and never admitted.
provenance
Section titled “provenance”function
def provenance(site: Site) -> dict[str, object]Source provenance of a Site, for reporting alongside results.
Parameters
Section titled “Parameters”| Name | Type | Description |
|---|---|---|
site | Site | The Site to describe. |
Returns
Section titled “Returns”dict— Keys:source_witness(the manifest id);manifest_sha256;inputs(for theslopeandslope_rmsroles, the inputnameandsha256); andgrid(shapeas[rows, cols]andcell_size_min metres).