DisclosureOS documentation · v2 Beta
Satellite raster profile
Validate a multispectral raster, trace a cell to its source bytes, and handle quality flags and missing bands.
A multispectral raster stores several band values at each grid cell. The picture is one way to display those values. This profile keeps the original numbers, quality annotations and grid coordinates available for inspection.
You will reproduce a fictional scene, validate its two source arrays, inspect one cell and diagnose three failures. A passing check establishes source integrity and consistency with the profile. It does not verify physical reflectance, cloud labels or geographic accuracy.
Profile status
Published in npm 2.0.0-beta.4. The profile evaluators and readers are available through @disclosureos/schema/experimental/v2. The retained source revision below is only needed to regenerate this example.
Equipment and captures explains the shared equipment model. This optional profile connects a multispectral raster to an observation, acquisition context and source selection. It does not require scalar measurement bindings or claim history.
1. Open the published-package example
Requirements: Node.js 22, npm and a ZIP extractor. Create a local project and install the published API:
mkdir disclosureos-satellite-beta4
cd disclosureos-satellite-beta4
npm init -y
npm install --save-exact @disclosureos/schema@2.0.0-beta.4
curl --fail --location https://os.disclosure.org/examples/meridian.zip --output meridian.zip
unzip meridian.zip
curl --fail --location https://os.disclosure.org/examples/v2/satellite/check-satellite.mjs --output check-satellite.mjs
node check-satellite.mjs meridian/satellite-v1The download contains the full fictional example case. This guide uses only meridian/satellite-v1. Keep the terminal in this npm project for the checks below. The displayed profile and selected-value results are explained in the following sections.
What the profile describes
The optional SatelliteRasterProfile describes an uncompressed, north-up multispectral reflectance raster on a WGS84 UTM grid. It declares band order, scaling, missing-value codes, whole-pixel quality and a red/near-infrared index policy.
Four connected documents
| Document | What it provides |
|---|---|
observation.json | Sources and the two retained raster products. |
acquisition.json | Equipment, pinned configuration, reflectance channel and raw artifacts. |
source-selection.json | Array shapes, byte order, lengths, digests and source units. |
satellite-profile.json | References to those documents, band definitions, grid and quality rules. |
The profile selects acquisition:satellite-capture and channel reflectance, whose quantity is surface_reflectance with unit 1 (dimensionless). Both acquisitionProducts and products contain reflectance and quality roles. The first maps to acquisition products; the second maps to observation products and source layouts. Matching digests, lengths and media types bind the arrays to the capture.
Both products are raw in this fixture. That identifies the retained inputs to the example, not physically collected satellite telemetry. dataOrigin: synthetic identifies their actual origin. No atmospheric correction or processing activity is declared.
Two source arrays
| File | Layout | Bytes |
|---|---|---|
reflectance.u16 | Six band planes, each 72 × 96, unsigned 16-bit little-endian | 82,944 |
quality.u8 | One 72 × 96 plane of unsigned 8-bit quality codes | 6,912 |
Reflectance axes are [band, row, column]. Each band occupies a complete row-major plane; the values are not interleaved RGB pixels. Quality axes are [row, column]. Both arrays begin at byte offset zero.
This profile reads declared arrays, not GeoTIFF, Cloud Optimized GeoTIFF or a vendor archive. An adapter must supply supported arrays and preserve how they relate to the original artifact. A different file extension alone does not make an input compatible.
methods.json, model.mjs and README.md describe the authored scene. index.json is a convenience file index, not another standard contract.
2. Validate files and inspect a cell
To use the public API separately from fixture reproduction, download the check script. Save it as check-satellite.mjs in your npm project so it uses the installed Beta 4 package.
From your npm project, run:
node check-satellite.mjs \
meridian/satellite-v1The runner imports evaluateSatelliteRasterProfile and satelliteCellBounds from @disclosureos/schema/experimental/v2, loads the four documents and both local arrays, then calls:
const result = await evaluateSatelliteRasterProfile(
observation,
context,
selection,
profile,
{ assets },
);assets maps product:reflectance and product:quality to complete file bytes. It is not keyed by filename, band or cell index. The runner locates local files through the example index and does not fetch artifact URLs.
The first report is:
{
"success": true,
"checks": {
"structure": "passed",
"sources": "passed",
"semantics": "passed"
},
"scientific": "not_checked",
"physicalCalibration": "not_checked",
"qualityAccuracy": "not_checked",
"georeferencing": "not_checked",
"issues": []
}| Check | Meaning |
|---|---|
structure | All four inputs satisfy their prerequisite contracts. |
sources | Source selections and retained bytes pass their checks, including lengths and digests. |
semantics | Document and acquisition bindings, array layouts, decoded band ranges, quality rules and grid declarations satisfy the profile. |
Read the issue as well as the stage name: a document-binding error can fail semantics before source checking. All four physical and scientific checks remain not_checked even when success is true.
Only after success, select a cell and calculate its bounds:
const { cells } = result.data.products;
const cell = cells[4845];
const bounds = satelliteCellBounds(profile, cell.row, cell.column);The runner prints the complete decoded cell and its projected bounds:
{
"cell": {
"index": 4845,
"row": 50,
"column": 45,
"easting": 501365,
"northing": 4100645,
"qualityCode": 0,
"quality": "clear",
"dn": [
429,
954,
478,
4690,
2208,
1156
],
"reflectance": [
0.0429,
0.0954,
0.0478,
0.46900000000000003,
0.22080000000000002,
0.11560000000000001
],
"byteOffsets": [
9690,
23514,
37338,
51162,
64986,
78810
],
"qualityByteOffset": 4845,
"ndvi": 0.815015479876161,
"ndviReason": "valid"
},
"bounds": {
"west": 501350,
"east": 501380,
"north": 4100660,
"south": 4100630
}
}Cell 4845 is at row 50, column 45: 50 × 96 + 45 = 4845. Array indices and byte offsets start at zero. The six values in each band array follow the declared band order below. Small decimal tails in this output come from floating-point arithmetic; they do not imply extra measurement precision.
For this layout, band b at cell i starts at byte (b × 6912 + i) × 2. Its two-byte range is half-open: the red value uses [37338, 37340) and the near-infrared value uses [51162, 51164) in reflectance.u16. The quality code occupies [4845, 4846) in quality.u8. The returned offsets refer to source files, not display-image pixels.
Use an explicit little-endian read, such as DataView.getUint16(offset, true), when inspecting these bytes independently. A native typed-array view does not declare byte order.
The dependency-free readSatelliteRasterProducts reader checks array content and declarations, but does not verify hashes or acquisition links. Prefer the decoded products returned by the successful evaluator when presenting checked data.
Inspect this cell in the showcase.
Stored numbers and band values
A stored digital number (DN) becomes reflectance using the band's declared formula: DN × scale + offset. Check for the band's no-data sentinel before applying that formula. In this fixture, every band uses scale 0.0001, offset 0, no-data code 65535 and decoded valid range [0, 1].
| Band index | ID | Nominal center |
|---|---|---|
| 0 | blue | 482 nm |
| 1 | green | 561 nm |
| 2 | red | 655 nm |
| 3 | nir | 865 nm |
| 4 | swir1 | 1,610 nm |
| 5 | swir2 | 2,200 nm |
These are nominal wavelength centers, not spectral response functions or a claim that this is a named satellite sensor. The selected cell's red DN is 478, giving reflectance 0.0478; its near-infrared DN is 4690, giving approximately 0.469. A retained DN of zero is valid. The sentinel decodes to null, not zero or 6.5535.
The viewer maps red/green/blue to natural color and near-infrared/red/green to an infrared composite. Its 0–0.6 display stretch affects brightness only. Neither RGB color nor a stretched pixel replaces the six source values. No physical radiometric calibration is verified.
Quality and derived indices
Quality codes annotate the whole pixel. They do not substitute for per-band availability:
| Code | Label | Meaning in this fixture |
|---|---|---|
| 0 | clear | Authored clear annotation; individual bands can still be absent. |
| 1 | cloud | Authored cloud patch with retained band values. |
| 2 | shadow | Authored shadow patch with retained band values. |
| 3 | nodata | Every band must contain its declared no-data sentinel. |
These are four enumerated values, not a bitmask or Landsat QA encoding. They come from the fictional model, not a cloud classifier. qualityAccuracy remains unchecked.
The normalized difference vegetation index (NDVI) uses scaled near-infrared and red reflectance: (NIR − red) / (NIR + red). The USGS explanation of NDVI describes this ratio. This fixture has its own scaling and exclusion rules; do not substitute a sensor-specific scaling recipe or treat the result as a land-cover classification.
The reader evaluates each cell in this order:
- If either required band is absent, return
nullwith reasonnodata. - If the quality code is not clear, return
nullwith reasonquality_excluded. - If either reflectance is negative, return
nullwith reasonnegative_reflectance. - If their sum is zero, return
nullwith reasonzero_denominator. - Otherwise, calculate the ratio and return reason
valid.
Negative reflectance is excluded by this profile's index policy. The generated fixture has no negative values; a different permitted band declaration with an offset and negative valid range can reach that branch. Do not replace a missing or excluded index with zero.
The runner compares five cells:
{
"availability": [
{
"index": 582,
"quality": "clear",
"red": 0,
"nir": 0,
"ndvi": null,
"ndviReason": "zero_denominator"
},
{
"index": 583,
"quality": "clear",
"red": 0.23420000000000002,
"nir": null,
"ndvi": null,
"ndviReason": "nodata"
},
{
"index": 91,
"quality": "nodata",
"red": null,
"nir": null,
"ndvi": null,
"ndviReason": "nodata"
},
{
"index": 4966,
"quality": "cloud",
"red": 0.8380000000000001,
"nir": 0.7931,
"ndvi": null,
"ndviReason": "quality_excluded"
},
{
"index": 4187,
"quality": "shadow",
"red": 0.020300000000000002,
"nir": 0.0223,
"ndvi": null,
"ndviReason": "quality_excluded"
}
]
}Cell 582 contains valid zeros, while cell 583 has only the near-infrared band missing. Cell 91 is whole-pixel no-data. The cloud and shadow cells retain numeric spectra, but their annotations exclude them from this index. Turning off the viewer's quality overlay does not change that policy or restore the surface hidden beneath an authored cloud.
Grid coordinates
The example declares a WGS84 UTM zone 11 north grid (EPSG:32611) in metres. Its six-coefficient transform is [500000, 30, 0, 4102160, 0, -30]. There is no rotation. Columns increase eastward; rows increase southward.
The transform locates cell corners. As in the GDAL geotransform convention, a center lies half a cell along each grid axis:
easting = 500000 + (column + 0.5) × 30
northing = 4102160 - (row + 0.5) × 30satelliteCellBounds returns the projected edges; cell.easting and cell.northing give the center. For row 50, column 45, the bounds span 501350–501380 m east and 4100630–4100660 m north. The center is 501365 m east, 4100645 m north. These are not longitude and latitude.
The grid is authored and arbitrary. It does not depict the real place at those coordinates. Its 30 m spacing is neither a claim of physical sensor resolution nor positional accuracy. registration: authored_grid_not_surveyed states that limit explicitly.
This version supports north-up WGS84 UTM rasters, 2–16 bands and at most 262,144 cells. It does not support a rotated grid, arbitrary projection or automatic reprojection. Cell order is spatial, not temporal. No acquisition timestamp, orbit or platform ephemeris is supplied; document publication dates are not capture times.
3. Diagnose failures
Each flag changes only the runner's in-memory inputs. Every deliberate failure exits with status 1 before selected-cell output. Run one flag at a time.
Missing quality array
node check-satellite.mjs \
meridian/satellite-v1 \
--missing-qualityThe profile requires both arrays. It reports sources: failed and semantics: not_checked:
{
"code": "SATELLITE_RASTER.SOURCE_BYTES",
"pointer": "/products",
"message": "Every retained source array is required for verification."
}The runner also prints the source evaluator's report to standard error. Its external and content checks are not_checked, with no source issues, because bytes were withheld. The profile's stronger requirement produces the failure above. Supply the declared local quality bytes before retrying; do not assume missing quality means clear.
Changed source bytes
node check-satellite.mjs \
meridian/satellite-v1 \
--changed-qualityThis changes cell 4845 from clear to no-data but retains the original digest declarations. Source checking fails before raster content is read:
{
"code": "SATELLITE_RASTER.SOURCE.EXTERNAL.DIGEST",
"pointer": "product:quality",
"message": "Artifact digest differs."
}The separate source report marks external and content checks failed, with code EXTERNAL.DIGEST. Restore the expected artifact or investigate the change through your normal source-revision process. Do not accept an unexpected file simply by replacing its digest.
Contradictory quality annotation
node check-satellite.mjs \
meridian/satellite-v1 \
--inconsistent-qualityThis makes the same quality change and deliberately updates all three in-memory digest declarations: the observation product, acquisition artifact and source layout. Source integrity passes, but semantics fail:
{
"code": "SATELLITE_RASTER.CONTENT",
"pointer": "/products",
"message": "No-data quality must use every band's declared no-data code."
}The cell still contains six retained band values. Calling it whole-pixel no-data contradicts those values. Matching hashes establish which bytes were supplied; they do not make the annotations consistent. This is a controlled counterexample, not a repair procedure.
Rerun without a flag to recover the original passing result. No restoration step is needed because the scripts never change source files. Unsupported or extra arguments exit with usage status 2.
Sources and reproduction
The guide and portal use the same satellite-v1 packet and source revision.
- Canonical fixture, model and verifier
- Profile schema, evaluator and source reader
- Runner notes and full example-case download
The shared equipment field reference covers acquisition and measurement bindings, not this newer profile. Use the pinned definitions above for raster-specific fields.
Return to the Instruments implementation overview to choose another capture or profile. Use Connect measurements when you need to attach a supported scalar value to an observation; a displayed raster cell does not automatically create that measurement binding.