Skip to content

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.

attribute

MOON_GEOGRAPHIC

PROJ definition of the Moon’s geographic CRS: lon/lat in degrees on a sphere of 1737.4 km.

attribute

MASK_RULE

Label of the user-mask rasterisation rule, reported with every user ROI.

attribute

MAX_RING_VERTICES

Largest accepted vertex count of a user ring (the closing vertex is implicit).

attribute

SAMPLE_LIMIT

Largest number of per-cell sample rows returned; larger masks are strided deterministically.

attribute

LonLat

A geographic vertex (lon, lat) in decimal degrees.

class

RoiError

The ROI cannot be evaluated; the request fails closed.

class

CellNotFound

The picked location has no cell in the requested grid/ROI.

class

Site

The frozen Site01 study inputs, verified and loaded for evaluation.

NameTypeDescription
manifest_idstrSource witness identifier from the manifest, or "?" when the manifest has none.
manifest_sha256strSHA-256 digest of the manifest file.
slopelunartrace.science.slope_quality.GridSlope grid in degrees, with its affine transform and CRS.
slope_rmslunartrace.science.slope_quality.GridSlope root-mean-square error grid in degrees, on the same grid as slope.
reference_roistuple of shapely.geometry.PolygonSource ROI exteriors in the grid CRS.
inputsdict of str to lunartrace.science.study.FrozenInputResolved frozen inputs keyed by role (for example "slope" and "roi_shp").

Instances are produced by load_site; they are immutable.

Grid cell size in metres, taken from the x scale of the affine transform.

  • float — Cell width in metres of the projected grid.

Grid dimensions of the slope raster.

  • tuple of int — (rows, cols) of the slope grid.

function

def load_site(manifest_path: Path, store: Path) -> Site

Resolve and verify the frozen Site inputs from the CAS once per process.

NameTypeDescription
manifest_pathpathlib.PathPath to the Site source manifest.
storepathlib.PathRoot of the content-addressed source store.
  • 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.

function

def check_ring(ring: Sequence[Sequence[float]]) -> tuple[LonLat, ...]

Validate a user ring and return it as a tuple of (lon, lat) pairs.

NameTypeDescription
ringsequence of sequence of floatVertices as [lon, lat] in decimal degrees. The ring is closed implicitly, so the last vertex need not repeat the first.
  • tuple of LonLat — The validated vertices, in input order.
  • RoiError — If there are fewer than 3 or more than MAX_RING_VERTICES vertices, 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.

function

def check_taus(taus: Sequence[float]) -> tuple[float, ...]

Validate Workbench taus: slope_quality rules plus distinct %g labels.

NameTypeDescription
taussequence of floatSlope-RMS cutoffs in degrees.
  • tuple of float — The thresholds in the order given, as returned by sq.check_taus.
  • lunartrace.science.slope_quality.MethodInputError — If sq.check_taus refuses the values.
  • RoiError — If two taus share a %g label (for example 2 and 2.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.

function

def ring_digest(ring: Sequence[LonLat]) -> str

Stable identity of a ring: SHA-256 of its vertices rounded to 1e-9 degree.

NameTypeDescription
ringsequence of LonLatRing vertices in decimal degrees.
  • 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.

function

def project_ring(site: Site, ring: Sequence[LonLat]) -> Polygon

Project a geographic ring into the Site grid CRS as a valid polygon.

NameTypeDescription
siteSiteThe Site whose grid CRS is the target.
ringsequence of LonLatRing vertices in lon/lat decimal degrees on the Moon’s sphere.
  • shapely.geometry.Polygon — The projected polygon in the grid CRS (metres). A polygon that is not valid is repaired with shapely.make_valid.
  • 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.

function

def user_mask(site: Site, poly: Polygon) -> sq.BoolGrid

Rasterise a projected polygon onto the Site grid by the cell-centre-inside rule.

NameTypeDescription
siteSiteThe Site providing the grid shape and affine transform.
polyshapely.geometry.PolygonPolygon in the grid CRS, as returned by project_ring.
  • lunartrace.science.slope_quality.BoolGrid — Boolean grid with the Site’s shape, True where a cell centre lies inside poly.
  • 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).

function

def reference_rings(site: Site) -> list[dict[str, object]]

The source ROIs as lon/lat rings, for display and selection only.

NameTypeDescription
siteSiteThe Site whose reference ROIs are converted.
  • 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.

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.

NameTypeDescription
siteSiteThe Site to resolve against.
lonfloatPicked longitude in decimal degrees.
latfloatPicked latitude in decimal degrees, in [-90, 90].
taussequence of floatSlope-RMS cutoffs in degrees for the inclusion flags. Defaults to sq.DEFAULT_TAUS. Default sq.DEFAULT_TAUS.
ringsequence of LonLatA user ring; when given, the pick must lie inside it. Default None.
  • dict — Keys: row and col (int pixel indices); lon and lat (float, degrees, the geographic centre of the pixel); slope and err (float degrees, or None where the value is not finite, since missing is never zero); and included, a dict from the formatted tau ("%g") to a bool.
  • 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

row=⌊prow⌋,col=⌊pcol⌋\mathrm{row} = \lfloor p_{\mathrm{row}} \rfloor,\qquad \mathrm{col} = \lfloor p_{\mathrm{col}} \rfloor

where (pcol,prow)(p_{\mathrm{col}}, p_{\mathrm{row}}) 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.

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.

NameTypeDescription
siteSiteThe Site to evaluate on.
ringsequence of LonLatRing vertices in lon/lat decimal degrees.
taussequence of floatSlope-RMS cutoffs in degrees; validated by check_taus.
with_samplesboolIf true, include the per-cell samples block. Default True.
  • dict — Keys: roi (id, kind, ring_sha256, ring, mask_rule, cell count; a source ring also carries source_index); method; implementation; status (always "METHOD_EXECUTED"); admission (always "NOT_ADMITTED"); taus; rows (one slope_quality/v1 row per tau); provenance; and, when requested, samples.
  • RoiError — If the ring does not project to a valid polygon or contains no cell centre.
  • ValueError — Errors from check_taus for 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.

function

def provenance(site: Site) -> dict[str, object]

Source provenance of a Site, for reporting alongside results.

NameTypeDescription
siteSiteThe Site to describe.
  • dict — Keys: source_witness (the manifest id); manifest_sha256; inputs (for the slope and slope_rms roles, the input name and sha256); and grid (shape as [rows, cols] and cell_size_m in metres).