Skip to content

lunartrace.projection

Cross-client projection models for LunarTrace derived products.

This module defines the pydantic models that serialise Python-computed results for other clients (TypeScript, the standalone validator, the web UI). Python is the canonical scientific authority: tools/export_contract.py exports the JSON Schema of these models, from which the TypeScript types and the standalone validator are generated. Clients render a projection and never recompute it.

Three projection families are defined here, each carrying a schema identifier:

  • lunartrace.projection.slope_quality/v0 (SlopeQualityProjection): a slope-quality result, its standing and provenance pointers.
  • lunartrace.projection.slope_raster/v0 (SlopeRasterProjection): per-cell rasters as a JSON manifest plus binary sidecar files.
  • lunartrace.projection.display_sun/v0 (DisplaySunProjection): a display-only geometric Sun direction at an explicit UTC epoch.

Nothing here imports the science extra.

Authority and standing Each projection carries its own proof ceiling in standing (one of Standing: METHOD_EXECUTED, NUMERIC_REPRODUCED, SCIENCE_VERTICAL_QUALIFIED, BLOCKED). A client may display no stronger claim than that standing. NUMERIC_REPRODUCED means the numbers were reproduced; it is not a scientific admission. The ordering and the precise meaning of each value are defined by the governing research and governance files, not by this module.

Units, CRS and nulls Slope values and thresholds are in degrees; grid transforms are in metres of the native projected CRS named by crs_proj4. included_fraction is a valid-cell fraction and never a physical area. Missing is never zero: a value that is not defined is None (or NaN in binary rasters), never 0.

class

SlopeQuantiles

Inverted-CDF quantiles of a slope sample, in degrees.

Each field is the quantile named by its key (p05 is the 0.05 quantile, and so on). Because the quantiles are inverted-CDF quantiles, every value is an observed slope sample and no interpolation is applied.

NameTypeDescription
p05, p25, p50, p75, p95floatSlope quantiles in degrees, serialised under the aliases "0.05", "0.25", "0.5", "0.75" and "0.95".

class

SlopeQualityRow

Slope-quality summary for one ROI (or the union of ROIs) at one threshold.

NameTypeDescription
roistrROI index as a string, or "union".
taufloatInclusion threshold on slope RMS, in degrees; non-negative.
roi_cells, valid_slope_cells, valid_err_cells, joint_cells, included_cellsintNon-negative cell counts. joint_cells counts cells that are valid in both the slope and slope-RMS layers; included_cells counts the jointly valid cells that pass tau.
included_fractionfloat or Noneincluded_cells / joint_cells, in [0, 1]. This is a valid-cell fraction, not an area. None where it is not defined; it is never encoded as 0.
slope_quantilesSlopeQuantiles or NoneInverted-CDF slope quantiles in degrees, or None when not defined.
slope_meanfloat or NoneMean slope in degrees, or None when not defined.
slope_histogramlist of intExactly 45 counts over bin edges 0, 2, …, 90 degrees. The last bin is closed.

class

SlopeQualityInputs

SHA-256 digests (64 lowercase hex characters) of the three inputs to the result.

The digests pin the input bytes so that a client can confirm which inputs produced a result. They are recorded by the producer and are not recomputed here.

NameTypeDescription
slope_sha256strDigest of the slope input.
slope_rms_sha256strDigest of the slope RMS input.
roi_shp_sha256strDigest of the ROI shapefile.

class

GridSpec

Raster grid geometry.

NameTypeDescription
shapelist of int[rows, cols], both strictly positive.
transformlist of floatSix affine coefficients (a, b, c, d, e, f) in rasterio order, mapping cell corners to the native CRS: x = a*col + b*row + c and y = d*col + e*row + f. The units are those of the native CRS (metres for the Site01 fixture).

class

SlopeQualityResult

Canonical slope-quality result as emitted by the primary implementation.

Its method is fixed to lunartrace.slope_quality/v1. results is a list of SlopeQualityRow, each identified by its roi and tau.

NameTypeDescription
methodstrAlways lunartrace.slope_quality/v1.
implementationstrIdentifier of the implementation that emitted the result.
inputsSlopeQualityInputsDigests of the three inputs.
gridGridSpecGeometry of the raster grid.
resultslist of SlopeQualityRowOne row per ROI (or union) and threshold tau.

class

SlopeQualityProjection

What a client may load: the result, its standing and its provenance pointers.

NameTypeDescription
schema_idstrSerialised as schema; always lunartrace.projection.slope_quality/v0.
standingStandingThe proof ceiling for result.
source_witness_idstrIdentifier string of the source witness; this module does not interpret it.
result_sha256str64-character lowercase hex SHA-256 digest pinned to result.
citationstrCitation text for the result.
crs_proj4strNative CRS of the grid as a PROJ string. Clients reproject for display.
resultSlopeQualityResultThe slope-quality result itself.

class

ValueRasterLayer

Native per-cell slope or slope RMS samples, in degrees, as little-endian float32.

nodata is the string "NaN": nodata cells hold IEEE-754 NaN in the sidecar (JSON cannot carry NaN), so a missing sample is never stored as zero. Non-NaN samples are the native values as loaded by lunartrace.slope_quality/v1. Samples outside the method’s validity domain are kept as-is and are classified by the inclusion layers, not here.

NameTypeDescription
kind{slope, slope_rms}Which layer this is.
unit{deg}Unit of the samples: degrees.
dtype{float32}Sample type of the sidecar.
nodata{NaN}Marker for missing samples, which are stored as IEEE-754 NaN.
byte_order{little}Byte order of the sidecar samples.
layoutstrRaster layout of the sidecar.
uristrSidecar file name, relative to the JSON document.
sha256strDigest of the sidecar bytes.
byte_lengthintSize of the sidecar in bytes.

byte_length must equal rows * cols * 4 for the grid. A client verifies byte_length and sha256 against the sidecar bytes before use.

class

InclusionRasterLayer

Per-cell inclusion at one tau under the slope_quality/v1 rule, as uint8 codes.

1 = included (jointly valid and slope_rms <= tau); 0 = excluded (jointly valid and slope_rms > tau); 255 = nodata (slope or slope_rms invalid under v1, so no decision). The layer covers the whole grid and is not ROI-limited. unit is null: the codes are categorical, not a quantity. tau is in degrees and is compared with slope RMS. byte_length must equal rows * cols * 1 for the grid.

NameTypeDescription
kind{inclusion}Layer kind.
taufloatThreshold on slope RMS, in degrees; non-negative.
unitNoneAlways None: the codes are categorical.
dtype{uint8}Sample type of the sidecar.
nodata{255}Code for cells with no decision.
byte_order{little}Byte order of the sidecar samples.
layoutstrRaster layout of the sidecar.
uristrSidecar file name, relative to the JSON document.
sha256strDigest of the sidecar bytes.
byte_lengthintSize of the sidecar in bytes.

class

SlopeRasterProjection

Per-cell slope, slope RMS and per-tau inclusion rasters for one grid.

The JSON Schema enforces each layer’s shape per kind (tau required for inclusion and forbidden otherwise). The model validator additionally enforces, in Python, that every byte_length equals rowscolsitemsize and that layers are unique per (kind, tau); clients re-check byte_length and sha256 against the fetched bytes.

NameTypeDescription
schema_idstrSerialised as schema; always lunartrace.projection.slope_raster/v0.
standingStandingProof ceiling for the rasters.
source_witness_idstrIdentifier string of the source witness; not interpreted by this module.
citationstrCitation text.
crs_proj4strNative CRS of the grid as a PROJ string.
gridGridSpecRaster grid geometry.
layerslist of RasterLayerValue and inclusion layers; at least one.
  • pydantic.ValidationError — If a layer shape is wrong, if two layers share the same (kind, tau), or if a byte_length does not match the grid and dtype. ValidationError is a ValueError subclass.

Each sidecar is raw little-endian samples in row-major order, row 0 at the affine origin. No raster data is embedded in the JSON document.

class

DisplayKernel

Pinned NAIF source bytes used by the display generator.

NameTypeDescription
namestrFile name of the kernel; letters, digits, ., _ and - only.
urlstrSource URL; must begin with https://naif.jpl.nasa.gov/pub/naif/.
sha256str64-character lowercase hex SHA-256 digest of the kernel bytes.
bytesintExact size of the kernel in bytes; strictly positive.

class

DisplaySite

Spherical, east-positive geographic display location, not observation provenance.

NameTypeDescription
namestrHuman-readable label for the location.
latitude_degfloatGeographic latitude in degrees, in [-90, 90]. Finite values only.
longitude_degfloatEast-positive longitude in degrees, in [-180, 180]. Finite values only.

The location is a spherical model of the Moon’s surface used for display. It carries no observation provenance.

class

DisplaySunProjection

Geometric display light at an explicit UTC epoch; never an illumination observation.

NameTypeDescription
schema_idstrSerialised as schema; always lunartrace.projection.display_sun/v0.
display_onlyboolAlways True.
epoch_utcstrISO-8601 UTC instant ending in Z, with optional fractional seconds.
framestrAlways MOON_ME_DE421 (the DE421 Moon mean-Earth frame).
aberration_correctionstrAlways NONE.
directionlist of floatUnit XYZ vector from the Moon centre toward the Sun, in frame. Dimensionless.
siteDisplaySiteSpherical display location.
elevation_degfloatSolar elevation at site in degrees, in [-90, 90].
azimuth_degfloatSolar azimuth at site in degrees, in [0, 360); north is 0 and east is 90.
kernelslist of DisplayKernelPinned NAIF kernels used to generate the result, in load order.
generatorstrGenerator identifier string.
  • ValueError — If the squared norm of direction differs from 1 by more than an absolute tolerance of 1e-12. Pydantic validation failures are also ValueError subclasses.

Elevation uses the site-to-Sun vector, including the parallax of the site. Azimuth is measured on the spherical local horizon with no terrain shadowing.