CLI reference¶
Every umbra command and flag below is generated directly from the installed
Click application, so it always matches umbra --help.
umbra¶
umbra-py: discover, download and work with Umbra open SAR data.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--version |
boolean | Show the version and exit. | False |
--help, -h |
boolean | Show this message and exit. | False |
umbra ask¶
Plan a catalog search from a plain-language question with a model.
A configured model reads your sentence plus the library's domain context and returns the search parameters it maps to; the library then re-validates every one of them deterministically (dates, product types, bounding box) and prints the exact 'umbra search' command it resolves to. The LLM plans, the library executes, and you audit the command before it runs — nothing the model says becomes a filter without passing the deterministic layer.
Pass --aoi to let it plan a polygon search too: the areas you name are listed in the prompt and the model may only pick one of them by name, so the shape searched is always your own file — it has no way to write coordinates.
By default it only prints the plan; pass --run to execute it. Requires the
ai extra (pip install 'umbra-py[ai]') and a model API key: set
ANTHROPIC_API_KEY, OPENROUTER_API_KEY (for OpenRouter), or OPENAI_API_KEY
(optionally with OPENAI_BASE_URL for another compatible endpoint). Example::
umbra ask "what did Umbra image at Centerfield, Utah last spring?"
umbra ask "scenes over the delta since March" --aoi delta.geojson
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--run, -r |
boolean | Execute the planned search instead of only printing it. The command is always shown first, so you see exactly what will run. | False |
--model |
text | Override the planning model (default: $UMBRA_ASK_MODEL, else the provider default). The provider is chosen by which API key is set — ANTHROPIC_API_KEY, OPENROUTER_API_KEY, or OPENAI_API_KEY (with optional OPENAI_BASE_URL). | None |
--limit |
integer | Cap results, overriding whatever limit the model chose (only affects --run). | None |
--aoi |
text | Offer the planner an area of interest you already have, as '[NAME=]PATH' — a .geojson file (or inline GeoJSON); repeat for several. The model may only select one by name; it can never write coordinates, so the polygon searched is always your file. Without a name, the file stem is used. A selected area becomes 'umbra search --intersects PATH'. | Sentinel.UNSET |
--json |
boolean | Emit the resolved plan as JSON (see docs/schemas/search-plan.schema.json). | False |
--local |
boolean | Run the planned search against a prebuilt local index instead of a live S3 walk (see 'umbra index fetch'). Only affects --run. | False |
--db |
text | Index database for --run --local (default: $UMBRA_INDEX_DB or ~/.cache/umbra-py/catalog.db). Implies --local. | None |
--help, -h |
boolean | Show this message and exit. | False |
umbra change¶
Render multi-temporal SAR change: a color composite or a time-lapse.
Two outputs, picked by the --out extension:
- An image (.png/.jpg) is a 2-3 date color composite: unchanged ground stays gray, backscatter that appeared shows green and backscatter that vanished shows magenta (two dates), or a red/green/blue trail (three).
- A .gif is an animated time-lapse over every matched acquisition, all co-registered so the site stays put and only the scene evolves.
Two ways to choose what to render:
- Pass STAC JSON URLs directly, in chronological order (2-3 for a composite, 2+ for a .gif).
- Or search: give --area (or --bbox / --place / --intersects) with --start/--end and the command gathers a site's acquisitions automatically (preferring a single polarization).
Add --narrate (composite output only) to have a vision model describe what
changed, grounded in a per-block decibel-change grid written alongside the
image as '
Only downsampled overviews are streamed via HTTP range requests -- no full
download. Requires the viz extra (pip install "umbra-py[viz]").
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--out |
text | Output file. An image extension (.png/.jpg) writes a 2-3 date color composite; '.gif' writes an animated time-lapse across all the acquisitions. | Sentinel.UNSET |
--area |
text | Search mode: name of an Umbra site (e.g. 'Centerfield') to gather automatically instead of passing URLs. Combine with --start/--end to bound the time range. | None |
--bbox |
text | Search mode: footprint filter 'min_lon,min_lat,max_lon,max_lat'. | Sentinel.UNSET |
--place |
text | Geocode a place name (e.g. 'California', 'Tokyo') to a bounding box and gather within it, via OpenStreetMap Nominatim. Mutually exclusive with --bbox; the match is rectangular, so it can include nearby areas outside the named place. | None |
--intersects |
text | Keep only items whose footprint intersects this GeoJSON polygon -- a path to a .geojson file or an inline GeoJSON string (Polygon / MultiPolygon, or a Feature / FeatureCollection wrapping one). A tighter spatial filter than the rectangular --bbox; the two are mutually exclusive. | None |
--start |
text | Search mode: earliest acquisition date. YYYY-MM-DD, a year/month (2024, 2024-03), or relative ('3 months ago', 'last month'). | Sentinel.UNSET |
--end |
text | Search mode: latest acquisition date (same formats as --start). | Sentinel.UNSET |
--frames |
integer range (between 2 and 3) |
Composite (image) output only: how many dates to composite (2 or 3), spread evenly across the matched time range. A .gif time-lapse uses every matched acquisition. | 2 |
--max-search |
integer | Search mode: cap how many acquisitions the search pulls. | 50 |
--asset |
choice (GEC | CSI | SIDD | SICD | CPHD) |
Which product to compare. GEC (the detected GeoTIFF) is the sensible default; CSI also works. The complex SICD/CPHD products aren't amplitude rasters. | GEC |
--max-size |
integer | Max pixel dimension of the shared grid. Default 2048 for a composite, 1024 for a .gif (a time-lapse stacks many frames, so smaller keeps the file sane). Larger is sharper but fetches more bytes (~quadratic). | None |
--db |
boolean | Use a decibel (log-amplitude) stretch -- the radiometrically-correct SAR look. Reveals texture and structure the default linear stretch crushes toward black. | False |
--colormap |
text | Time-lapse (.gif) only: matplotlib colormap for pseudo-colored frames (e.g. viridis, magma). Default is grayscale. | None |
--fps |
float | Time-lapse (.gif) only: playback speed in frames per second. | 2.0 |
--percentile |
text | Low,high percentile cut for each frame's contrast stretch. | 2,98 |
--narrate |
boolean | Composite (image) output only: after rendering, have a vision model narrate WHAT changed, grounded in a per-block decibel-change grid. Writes a machine-readable ' |
False |
--model |
text | --narrate only: override the vision model (default: $UMBRA_NARRATE_MODEL, else the provider default). The provider is chosen by which API key is set. | None |
--local |
boolean | Gather items from a prebuilt local index (see 'umbra index fetch' / 'umbra index build') instead of walking S3 live -- near-instant, the fast path for repeat renders. Only uses acquisitions already indexed. | False |
--index-db |
text | Path to the local index database to read (default: $UMBRA_INDEX_DB or ~/.cache/umbra-py/catalog.db). Implies --local. Named --index-db because --db already means the decibel stretch on render commands. | None |
--token |
text | Canopy API token. When given, gather items from Umbra's authenticated COMMERCIAL archive (a real STAC API) instead of the open bucket — the same flags, over the paid catalog. Falls back to $UMBRA_CANOPY_TOKEN. Mutually exclusive with --local / --index-db. | None |
--fuzzy |
boolean | Match --area loosely: word-order- and punctuation-independent and typo-tolerant (so 'utah centerfield' or 'centrfield' still reach 'Centerfield, Utah'). Deterministic, no model call; a strict superset of the substring match. | False |
--json |
boolean | Emit a machine-readable {output, items_used, parameters} manifest on stdout instead of the human 'Wrote ...' line. Progress and warnings stay on stderr, so stdout is the JSON object alone. | False |
--pol |
text | Keep only items exposing this polarization (e.g. VV, HH; repeatable, case-insensitive, matches if the item has ANY of them). The filter that keeps a change comparison like-with-like -- HH and VV image different physics. Items with no polarization metadata are excluded. | Sentinel.UNSET |
--min-incidence |
float | Keep only items with view incidence angle >= this many degrees. Items missing an incidence angle are excluded. | None |
--max-incidence |
float | Keep only items with view incidence angle <= this many degrees. Items missing an incidence angle are excluded. | None |
--max-resolution |
float | Keep only items at least this fine: both range and azimuth resolution <= this many metres. Items missing a resolution are excluded. | None |
--help, -h |
boolean | Show this message and exit. | False |
umbra chips¶
Cut SAR scenes into fixed-size, georeferenced ML training tiles.
Walks the chosen acquisitions and writes each one's geocoded GeoTIFF as a grid of full chip-size tiles (GeoTIFF or .npy), plus a manifest carrying per-chip geo + acquisition metadata (bbox, CRS, transform, datetime, polarization, incidence angle, resolution, license) -- the data-loading layer for SAR foundation-model and change-detection research.
Two ways to choose what to chip:
- Pass STAC JSON URLs directly.
- Or search: give --area (or --bbox / --place / --intersects) with --start/--end and the command gathers a site's acquisitions automatically.
For the amplitude products (GEC, CSI) only the bytes for each tile are
streamed via HTTP range requests -- no full download, and memory stays
bounded to one chip. Requires the load extra
(pip install "umbra-py[load]").
--speckle-filter applies to any asset. It averages down the one uncertainty a SAR pixel carries that is not the sensor's fault and is larger than any that is: on a single look, power scatters about the surface's true backscatter as widely as its own mean, so an unfiltered tile teaches a model the interference pattern as much as the ground. On GEC/CSI the tiles themselves are averaged (each read with a halo, so overlapping tiles agree); with --asset SICD the scene is, before geocoding. What it costs is resolution, which is why it is opt-in and in every manifest record.
--asset SICD chips the complex archive instead: each scene is downloaded
whole and geocoded before its tiles are cut, so --dem, --rtc,
--calibrate and --subtract-noise apply too and the chips can
carry a physical backscatter coefficient with the sensor's own noise floor
taken off. That
path needs the convert extra
(pip install "umbra-py[convert]") and real bytes per scene, so give
--work-dir to keep the geocoded scenes and make a re-run cheap.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--out |
text | Output directory for the chips and manifest (created if needed). | Sentinel.UNSET |
--asset |
choice (GEC | CSI | SICD) |
Which product to chip. GEC (the geocoded GeoTIFF) is the sensible default and streams tile by tile; CSI also works. SICD is the complex slant-plane product: it has no map grid, so each scene is downloaded whole and geocoded first (the [convert] extra) before the same tiles are cut -- which is how a training set reaches the full-resolution archive, and where --calibrate / --rtc / --dem apply. CPHD is phase history, not an image. | GEC |
--dem |
text | SICD only: terrain-orthorectify each scene against a digital elevation model instead of the flat-earth projection. A path to any raster rasterio can open, or 'auto' to fetch the covering Copernicus GLO-30 tiles. | None |
--geoid |
text | SICD only: geoid-undulation grid converting sampled DEM heights to height-above-ellipsoid, or 'auto' to fetch one. Requires --dem. | None |
--rtc |
boolean | SICD only: radiometrically terrain-flatten each scene before chipping, so slopes facing toward or away from the radar don't teach a model their brightness. Requires --dem. | False |
--rtc-model |
choice (cosine | area | gamma | facet) |
SICD only: terrain-flattening model for --rtc (see 'umbra convert --help' for what each one corrects). | cosine |
--rtc-ref-angle |
float | SICD only: reference incidence angle the --rtc flattening normalises to. Omit to use each scene's own incidence angle. | None |
--calibrate |
choice (sigma0 | beta0 | gamma0 | rcs) |
SICD only: radiometrically calibrate the pixels using the SICD's own Radiometric scale factors, so chips carry a physical backscatter coefficient rather than relative brightness -- the difference between a model that transfers across scenes and one that doesn't. Composes with --rtc. Fails clearly when the product carries no such scale factor. | None |
--subtract-noise |
boolean | SICD only: subtract the receiver's own thermal-noise floor (the SICD's Radiometric.NoiseLevel polynomial) from pixel power before anything scales it, so a chip over water or shadow teaches a model the ground rather than the sensor's sensitivity limit. Where the floor comes from is --noise-model. | False |
--noise-model |
choice (measured | estimated | estimated-range) |
SICD only: where --subtract-noise gets the floor. 'measured' reads the product's own Radiometric.NoiseLevel and fails clearly without an ABSOLUTE level -- which is most of Umbra's open archive; 'estimated' infers one constant floor per scene from its own darkest pixels and needs no metadata; 'estimated-range' infers one per range line and fits it against range, so chips cut from opposite edges of a swath are not offset by the floor the constant model left behind. Each chip's manifest entry records which ran, so a training set never mixes two floors without saying so. | measured |
--speckle-filter |
choice (boxcar | lee) |
Speckle-filter every scene, so a tile teaches a model the surface rather than the interference pattern coherent illumination made on it (a single look's power scatters as widely as its own mean). 'boxcar' averages the window unconditionally; 'lee' averages only where the window is no more variable than speckle alone explains, keeping edges. It runs wherever it is most correct for the asset: on GEC/CSI the tiles themselves are averaged; with --asset SICD the scene is, in the radar's own image space before it is geocoded. What it spends is resolution -- a window that averages N pixels resolves ground N pixels across -- so every chip's manifest entry records the filter, its window, and the equivalent looks either side of it. | None |
--speckle-window |
integer | Edge of the odd, centred window --speckle-filter averages over. Wider removes more speckle and more detail; it costs no more to compute. | 5 |
--convert-resolution |
float | SICD only: geocoded pixel size in degrees. Omit to keep the finer of the two per-axis ground sample distances (throw no resolution away). | None |
--resampling |
choice (nearest | bilinear | cubic | average | lanczos) |
SICD only: warp kernel used to geocode each scene. | bilinear |
--work-dir |
directory | SICD only: keep the downloaded product and the geocoded COG here instead of a temporary directory. A re-run then reuses a scene already geocoded with the same settings rather than fetching and warping it again. | None |
--chip-size |
integer | Tile edge in pixels. Only full tiles are written; a partial edge strip is dropped, so every chip has this exact shape. | 512 |
--stride |
integer | Step between tile origins in pixels (default: --chip-size, non-overlapping). A smaller stride overlaps tiles for dense inference or augmentation. | None |
--format |
choice (geotiff | npy) |
Chip file format: georeferenced GeoTIFF, or a bare float32 .npy array (geo metadata then lives only in the manifest). | geotiff |
--db |
boolean | Write the decibel (log-amplitude) scale instead of linear amplitude. | False |
--min-valid |
float | Drop a tile whose fraction of valid (finite, positive) pixels is below this. 0.0 keeps every full tile; e.g. 0.5 drops the mostly-nodata corners of a rotated footprint. | 0.0 |
--clip-bbox |
text | Chip only a lon/lat window 'min_lon,min_lat,max_lon,max_lat' of each acquisition, numbering rows/columns from its corner. With --asset SICD it is also the conversion's clip, so each scene is geocoded over the area of interest rather than whole -- the expensive step then costs what the site costs, not what the scene does. (Distinct from --bbox, which filters which acquisitions the search returns.) | None |
--manifest |
text | Manifest filename inside --out. A .jsonl writes one chip record per line (the ML default); a .geojson writes a FeatureCollection of chip footprints for QGIS / geopandas; a .parquet writes a stac-geoparquet table DuckDB / geopandas can query at scale (needs the [export] extra). | manifest.jsonl |
--skip-unsupported |
boolean | Carry on past an acquisition whose own metadata cannot support the measurement asked of it (no Radiometric block for --calibrate, no stated noise floor for --noise-model measured, no stated collection geometry for --rtc) instead of ending the run on it, and report which ones were left out. Without it the first such product costs the whole batch; with it the dataset says where its holes are. | False |
--preflight |
boolean | With --asset SICD, read each product's metadata over the wire first (two HTTP range requests, tens of kilobytes) and drop the acquisitions that cannot support the request before downloading any of them -- so discovering that a pass carries no Radiometric block (or, under --rtc, no collection geometry) costs its header rather than the whole multi-gigabyte product. The dropped passes are reported exactly as --skip-unsupported reports them. Worth passing both: this asks only what the metadata answers. | False |
--preflight-workers |
integer | How many product headers --preflight reads in parallel (1 to read them one at a time). | 8 |
--area |
text | Search an Umbra task/site by name (e.g. 'Centerfield'). | None |
--bbox |
text | Footprint filter: 'min_lon,min_lat,max_lon,max_lat'. | Sentinel.UNSET |
--place |
text | Geocode a place name (e.g. 'California', 'Tokyo') to a bounding box and gather within it, via OpenStreetMap Nominatim. Mutually exclusive with --bbox; the match is rectangular, so it can include nearby areas outside the named place. | None |
--intersects |
text | Keep only items whose footprint intersects this GeoJSON polygon -- a path to a .geojson file or an inline GeoJSON string (Polygon / MultiPolygon, or a Feature / FeatureCollection wrapping one). A tighter spatial filter than the rectangular --bbox; the two are mutually exclusive. | None |
--start |
text | Earliest acquisition date (YYYY-MM-DD or a relative expression). | Sentinel.UNSET |
--end |
text | Latest acquisition date (same formats as --start). | Sentinel.UNSET |
--max-search |
integer | Max acquisitions to gather when searching (ignored with item URLs). | 20 |
--json |
boolean | Emit the dataset summary as JSON (see docs/schemas/chip-dataset.schema.json; the manifest's own records are docs/schemas/chip-record.schema.json). | False |
--fuzzy |
boolean | Match --area loosely: word-order- and punctuation-independent and typo-tolerant (so 'utah centerfield' or 'centrfield' still reach 'Centerfield, Utah'). Deterministic, no model call; a strict superset of the substring match. | False |
--pol |
text | Keep only items exposing this polarization (e.g. VV, HH; repeatable, case-insensitive, matches if the item has ANY of them). The filter that keeps a change comparison like-with-like -- HH and VV image different physics. Items with no polarization metadata are excluded. | Sentinel.UNSET |
--min-incidence |
float | Keep only items with view incidence angle >= this many degrees. Items missing an incidence angle are excluded. | None |
--max-incidence |
float | Keep only items with view incidence angle <= this many degrees. Items missing an incidence angle are excluded. | None |
--max-resolution |
float | Keep only items at least this fine: both range and azimuth resolution <= this many metres. Items missing a resolution are excluded. | None |
--local |
boolean | Gather items from a prebuilt local index (see 'umbra index fetch' / 'umbra index build') instead of walking S3 live -- near-instant, the fast path for repeat renders. Only uses acquisitions already indexed. | False |
--index-db |
text | Path to the local index database to read (default: $UMBRA_INDEX_DB or ~/.cache/umbra-py/catalog.db). Implies --local. Named --index-db because --db already means the decibel stretch on render commands. | None |
--token |
text | Canopy API token. When given, gather items from Umbra's authenticated COMMERCIAL archive (a real STAC API) instead of the open bucket — the same flags, over the paid catalog. Falls back to $UMBRA_CANOPY_TOKEN. Mutually exclusive with --local / --index-db. | None |
--help, -h |
boolean | Show this message and exit. | False |
umbra context¶
Print the library's LLM context document as JSON.
The product-type table, search-parameter semantics, and license rules an
agent needs to drive umbra-py — see :func:umbra_py.llm_context. Pipe it
into a model's context at the start of a session.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--help, -h |
boolean | Show this message and exit. | False |
umbra convert¶
Convert a downloaded SICD (complex) product to a map-ready GeoTIFF.
By default this geocodes the scene: it detects amplitude and warps it onto
a north-up EPSG:4326 cloud-optimized GeoTIFF using SICD's own image-
projection model, so the result opens straight onto a map, in QGIS, or as a
georeferenced array via umbra_py.to_xarray -- no hand-rolled geocoding.
The geocoding is flat-earth (pixels on the scene's height plane): exact over flat terrain, adequate for map placement elsewhere. Pass --dem PATH to terrain-orthorectify against a digital elevation model instead, so relief is placed in its true ground position. Add --rtc (with --dem) to also radiometrically terrain-flatten the output, removing the geometric brightness swings that slopes cause. Pass --slant-plane for a quick, ungeoreferenced amplitude image instead.
A scene is tens of square kilometres at 16-25 cm, and all of the above is proportional to it. Pass --clip-bbox to make it proportional to the area you care about instead: only the image window covering that ground is read and warped, and the output is cropped to it.
Geocoding and flattening both leave the pixel values relative -- an image comparable with itself and nothing else. Add --calibrate to make them physical: the SICD's own radiometric scale factors turn detected power into a backscatter coefficient (sigma0 / beta0 / gamma0) or an absolute radar cross-section, so the decibels mean the same thing across scenes and dates. It only works where the product supplies those scale factors.
A measured pixel is the ground's echo plus the receiver's own thermal noise, and over a dark surface the second term is most of it -- so a calibrated value there can be precise, physical and still be a report of the sensor rather than the scene. Add --subtract-noise to take that floor off first, where noise actually adds. By default the floor is the product's own stated one; --noise-model estimated infers it from the scene's own darkest pixels instead, which is what works on Umbra's open products, since they generally carry no noise metadata to read, and --noise-model estimated-range infers one per range line and fits it against range, so an inferred floor follows the swath rather than leaving a gradient the constant one could not. Whichever floor ran, the conversion then reports what the subtraction did to this scene: how much of the image it drove to the sensor's sensitivity limit, and -- for an inferred floor, which assumes the scene contained dark ground to read -- how far the scene's median sat above it, plus the swing a fitted profile found. A narrow margin says that assumption did not hold here. Where a product states its own floor there is a truth to check those inferences against: --noise-check converts nothing and scores them against it instead, reporting how far each estimate reads low and how well it follows the real floor across the swath once that offset is granted.
What is left after all of that is speckle, which is not an error at all: a coherently illuminated rough surface interferes with itself, so one look's power scatters about the surface's true backscatter as widely as its own mean. It is the dominant uncertainty in every number above, and averaging is the only correction. --speckle-filter does it -- 'boxcar' unconditionally, 'lee' only where the window is more uniform than an edge would be -- and reports the equivalent looks the scene reached, which on imagery sampled finer than it resolves is well under the pixels averaged. It is opt-in because what it spends is resolution, which is the reason to use this archive.
Every raster written here records how it was made -- the calibration, the terrain model and its reference angle, the DEM/geoid, the projection and the scale -- in the file's own metadata, so a converted scene can say what its pixel values mean. Read it back with --provenance (or gdalinfo).
SICD/CPHD are the complex products; the GEC asset is already a geocoded
COG and needs no conversion. Requires the convert extra
(pip install "umbra-py[convert]").
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--provenance |
boolean | Don't convert: read the conversion provenance umbra-py recorded in SRC (an already-converted raster) and print it as JSON -- which calibration, terrain-flattening model, DEM and scale produced those pixel values. Takes no DST. | False |
--noise-check |
boolean | Don't convert: score the inferred noise floors (--noise-model estimated / estimated-range) against the floor SRC's own metadata states, and print the comparison as JSON -- how far each estimate reads low, and how well it follows the real floor across the swath once that offset is granted. Needs a product declaring an ABSOLUTE noise level. Takes no DST. | False |
--slant-plane |
boolean | Skip geocoding: write the raw slant-plane amplitude with no geolocation (inspection only). Default is a north-up EPSG:4326 COG. | False |
--linear |
boolean | Write linear magnitude instead of the decibel (log-amplitude) scale. | False |
--gcp-grid |
integer | Edge of the square lattice of ground control points sampled across the image to model the sensor geometry (geocoded output only). | 15 |
--resolution |
float | Output pixel size in degrees (geocoded output only). Omit to pick the finer of the two per-axis ground sample distances. | None |
--resampling |
choice (nearest | bilinear | cubic | average | lanczos) |
Warp kernel for geocoding. | bilinear |
--projection |
choice (HAE | PLANE | DEM) |
SICD image-projection type. HAE is the flat-earth default (exact over flat terrain, adequate for map placement elsewhere). | HAE |
--dem |
text | Terrain-orthorectify against a digital elevation model instead of the flat-earth projection. Pass a path to any raster rasterio can open (e.g. a Copernicus/SRTM COG), or 'auto' to fetch the covering Copernicus GLO-30 tiles for the scene automatically. Supersedes --projection. | None |
--geoid |
text | Geoid-undulation grid giving ellipsoid-minus-geoid separation in metres. Pass a path to any raster rasterio can open (e.g. an EGM96/EGM2008 GeoTIFF), or 'auto' to fetch a global EGM geoid grid for the scene automatically. Global DEMs quote height above the geoid but SICD projects against the ellipsoid, so this converts sampled DEM heights to HAE for survey-grade placement over relief. Requires --dem. | None |
--rtc |
boolean | Radiometrically terrain-flatten the geocoded output: scale each pixel by the cosine correction cos(reference)/cos(local_incidence) from the DEM slope and scene look geometry, so slopes facing toward or away from the radar no longer look artificially bright or dark. Requires --dem. A geometric normalisation of detected amplitude, not a calibrated product. | False |
--rtc-ref-angle |
float | Reference incidence angle (degrees) the --rtc flattening normalises to. Omit to use the scene incidence angle, which leaves flat terrain unchanged. | None |
--rtc-model |
choice (cosine | area | gamma | facet) |
Terrain-flattening model for --rtc. 'cosine' scales by cos(reference)/cos(local_incidence) (the 3-D local incidence angle); 'area' scales by sin(local_range_incidence)/sin(reference), the projected-area / foreshortening correction in the range plane, which targets range foreshortening and layover; 'gamma' scales by cos(reference)*nz/cos(local_incidence), the per-pixel facet-area (gamma-nought) normalisation that adds the true tilted-facet-area term the other two omit; 'facet' integrates the illuminated area in the radar's own (slant range, azimuth) geometry and normalises each pixel by the total accumulated in its cell -- the only model that measures LAYOVER, where terrain folds several facets into one cell and their returns sum. On their own all four normalise detected amplitude; pair one with --calibrate to get a physical product. | cosine |
--calibrate |
choice (sigma0 | beta0 | gamma0 | rcs) |
Radiometrically calibrate the output using the SICD's own Radiometric scale factors, so pixel values are a physical quantity instead of relative brightness: 'sigma0'/'beta0'/'gamma0' are the backscatter coefficients referenced to unit ground, slant-plane and perpendicular-to-look area; 'rcs' is the absolute radar cross-section in m2. In the default decibel scale the output is that coefficient in dB. Composes with --rtc (--rtc-model facet --calibrate gamma0 is terrain-flattened gamma-nought). Fails clearly when the product carries no such scale factor -- Umbra's open products usually don't. | None |
--subtract-noise |
boolean | Subtract the receiver's own thermal-noise floor (the SICD's Radiometric.NoiseLevel polynomial) from pixel power before anything scales it, so low-backscatter surfaces -- calm water, radar shadow, dry sand -- report the ground instead of the sensor's sensitivity limit. Applied first, because noise adds where calibration and --rtc multiply. Where the floor comes from is --noise-model. | False |
--noise-model |
choice (measured | estimated | estimated-range) |
Where --subtract-noise gets the floor. 'measured' reads the product's own Radiometric.NoiseLevel polynomial, so the floor follows the across-swath variation the sensor states -- but it needs an ABSOLUTE noise level and fails clearly without one, which is most of Umbra's open archive. 'estimated' infers one constant floor from the scene's own darkest pixels (a SAR image's water, shadow and smooth ground return essentially nothing, so the low tail of its power distribution is the receiver), needs no metadata, and is recorded as an inference. 'estimated-range' takes that same read per range line and fits it against range, so an inferred floor follows the swath instead of leaving a gradient behind, and reports the swing it found in UMBRA_NOISE_FLOOR_SPREAD_DB. All three record themselves apart in UMBRA_NOISE_SUBTRACTION, and 'umbra stack' refuses to difference a series that mixes any two of them. | measured |
--speckle-filter |
choice (boxcar | lee) |
Speckle-filter the detected power before geocoding. Speckle is not sensor noise and no floor subtraction removes it: coherent illumination of a rough surface interferes with itself, so a single-look pixel's power scatters about the surface's true backscatter with a standard deviation equal to its mean -- which is why a pixel-by-pixel difference between two passes is mostly speckle. Averaging is the only correction. 'boxcar' averages the window unconditionally (the multilook: most variance removed, blind to edges); 'lee' averages only where the window is no more variable than speckle alone explains, so edges and points survive. Not a default, because what it spends is resolution -- a window that averages N pixels reports ground N pixels across. The raster records the filter, the window and the equivalent looks it reached, and 'umbra stack' refuses to difference a series that mixes two. | None |
--speckle-window |
integer | Edge of the odd, centred window --speckle-filter averages over. Wider removes more speckle and more detail; it costs no more to compute. | 5 |
--clip-bbox |
text | Convert only a lon/lat window 'min_lon,min_lat,max_lon,max_lat' of the scene. Only the image rows and columns covering that ground are read from the product and warped, and the output is cropped to the window, so a small area of interest costs a small conversion instead of a whole-scene one (the 'clipped' line reports how much of the scene was read). The download is whole-product either way -- a slant-plane NITF has no map grid to range-read. | None |
--help, -h |
boolean | Show this message and exit. | False |
umbra demo¶
Build a self-serve interactive catalog explorer as one HTML page.
Unlike the one-shot artifacts the other visual commands emit, this is an application: a single self-contained page over the whole gathered slice of the catalog with client-side filters (search box, date range, product-type and polarization chips), clustered markers that scale past a plain map's polygon ceiling, and a click-to-quicklook SAR overlay streamed on demand. Reads a prebuilt index with --local for a near-instant, offline build. Needs no extra: the page is pure HTML, and Leaflet + the on-click COG decode run browser-side from pinned CDNs.
Pass --server-url pointing at a running 'umbra serve' to add an "Analyze this view" panel that renders change/timescan/swipe products over the currently-filtered acquisitions on demand (the server does the raster work and caches results), and a Quantify button that measures them instead: how many decibels the site moved first-to-last, how much ground crossed the change threshold in km2, and which block moved most, when. Without --server-url the page stays fully static.
Pass --pmtiles PATH-OR-URL to explore the WHOLE archive instead of a gathered slice: the page draws every acquisition in a '.pmtiles' catalog (from 'umbra tiles') as a MapLibre vector layer read by range request, so the same sidebar filters cover the entire catalog from a page that stays a few KB. Nothing is searched or embedded in that mode.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--bbox |
text | Footprint filter: 'min_lon,min_lat,max_lon,max_lat'. | Sentinel.UNSET |
--place |
text | Geocode a place name (e.g. 'California', 'Tokyo') to a bounding box and gather items within it, via OpenStreetMap Nominatim. Mutually exclusive with --bbox. | None |
--intersects |
text | Keep only items whose footprint intersects this GeoJSON polygon -- a path to a .geojson file or an inline GeoJSON string (Polygon / MultiPolygon, or a Feature / FeatureCollection wrapping one). A tighter spatial filter than the rectangular --bbox; the two are mutually exclusive. | None |
--start |
text | Earliest acquisition date. Accepts YYYY-MM-DD, a year or month (2024, 2024-03), or a relative expression ('today', 'yesterday', '3 months ago', 'last month'). | Sentinel.UNSET |
--end |
text | Latest acquisition date (same formats as --start; a bare year, month or period like 'last month' snaps to that span's last day). | Sentinel.UNSET |
--area |
text | Case-insensitive name of an Umbra task/site to gather (e.g. 'Centerfield'). Faster than a broad scan -- it lists just that area's directory. | None |
--product |
choice (GEC | CSI | SIDD | SICD | CPHD) |
Keep items exposing this asset (repeatable). The explorer also lets you toggle product types client-side once the page is open. | Sentinel.UNSET |
--limit |
integer | Max acquisitions to load. | 500 |
--max-per-task |
integer | Cap items per Umbra task directory. '--max-per-task 1' gives one marker per distinct site -- a fast whole-archive overview. | None |
--out |
text | Output HTML file (e.g. demo.html). | Sentinel.UNSET |
--asset |
choice (GEC | CSI | SIDD | SICD | CPHD) |
Product the on-click 'Get SAR image' button streams. GEC (the detected GeoTIFF) is the sensible default; CSI also works. | GEC |
--no-lazy-imagery |
boolean | Build a metadata-only explorer without the on-click SAR overlay button (no geotiff.js CDN dependency at click time). | True |
--percentile |
text | Low,high percentile cut for the on-click SAR overlay's contrast stretch. | 2,98 |
--server-url |
text | Base URL of a running 'umbra serve' instance (e.g. http://localhost:8000). When set, the explorer gains an 'Analyze this view' panel whose buttons render change/timescan/swipe products over the currently-filtered acquisitions on demand, plus a Quantify button that measures the same view in numbers. Omit for a fully static page. | None |
--pmtiles |
text | URL or page-relative path of a whole-catalog .pmtiles archive (from 'umbra tiles' / 'umbra tiles --fetch'). The explorer then draws EVERY acquisition in that archive from vector tiles read on demand instead of an embedded search slice -- the whole-archive explorer, in a page that stays a few KB. Footprint outlines come from the archive's footprint polygons where it carries them, and the on-click 'Get SAR image' overlay works for any acquisition the archive references a COG for. The search options don't apply in this mode -- filter in the page instead. | None |
--local |
boolean | Gather items from a prebuilt local index (see 'umbra index fetch' / 'umbra index build') instead of walking S3 live -- near-instant, the fast path for repeat renders. Only uses acquisitions already indexed. | False |
--index-db |
text | Path to the local index database to read (default: $UMBRA_INDEX_DB or ~/.cache/umbra-py/catalog.db). Implies --local. Named --index-db because --db already means the decibel stretch on render commands. | None |
--fuzzy |
boolean | Match --area loosely: word-order- and punctuation-independent and typo-tolerant (so 'utah centerfield' or 'centrfield' still reach 'Centerfield, Utah'). Deterministic, no model call; a strict superset of the substring match. | False |
--help, -h |
boolean | Show this message and exit. | False |
umbra describe¶
Describe a SAR scene in plain language with a vision model.
Renders the item's quicklook, sends that picture plus the library's metadata context card to a configured vision model, and returns a structured reading: a summary, observed features, the model's confidence, and SAR-specific caveats. The model only interprets the imagery — every description is stamped as an AI interpretation and carries the mandatory CC-BY attribution, and nothing the model says becomes a filter, a URL, or a coordinate.
With --preview baked (or auto) the picture comes from the quicklook already cached in the local index instead of a fresh S3 overview stream, so a description costs no range read and needs no viz extra at all. It is a smaller picture than --max-size asks for, so the reading says which it read and carries a caveat about the detail it could not have seen.
Requires the ai extra for the model call and viz for the render
(pip install 'umbra-py[ai,viz]') plus a vision model API key: set
ANTHROPIC_API_KEY, OPENROUTER_API_KEY (for OpenRouter), or OPENAI_API_KEY
(optionally with OPENAI_BASE_URL for another compatible endpoint). Example::
umbra describe https://.../<item>/<id>.json
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--asset |
choice (GEC | CSI | SIDD | SICD | CPHD) |
Which product to read. GEC (the geocoded GeoTIFF) is the sensible default; CSI also works. The complex SICD/CPHD products aren't amplitude rasters. | GEC |
--model |
text | Override the vision model (default: $UMBRA_DESCRIBE_MODEL, else the provider default). The provider is chosen by which API key is set — ANTHROPIC_API_KEY, OPENROUTER_API_KEY, or OPENAI_API_KEY (with optional OPENAI_BASE_URL). | None |
--max-size |
integer | Max pixel dimension of the quicklook sent to the model. Larger is sharper but fetches more bytes and costs more tokens. | 1024 |
--db / --no-db |
boolean | Use a decibel (log-amplitude) stretch — the radiometrically-correct SAR look the model reads best. --no-db uses a linear stretch. | True |
--preview |
choice (render | baked | auto) |
Where the picture the model reads comes from. 'render' streams a fresh quicklook from S3; 'baked' reads the preview already cached in the local index ('umbra index bake-thumbnails' / 'fetch-thumbnails') — no range read, no viz extra — and fails if there is none; 'auto' prefers the cached one and renders when it is missing. A cached preview is smaller than --max-size, which the description records and caveats. | render |
--index-db |
text | Path to the local index database holding the baked previews (default: $UMBRA_INDEX_DB or ~/.cache/umbra-py/catalog.db). Only read when --preview is 'baked' or 'auto'. Named --index-db because --db means the decibel stretch. | None |
--json |
boolean | Emit the structured description as JSON (see docs/schemas/scene-description.schema.json). | False |
--help, -h |
boolean | Show this message and exit. | False |
umbra download¶
Download asset(s) of an item given its STAC JSON URL.
--json emits a machine-readable [{asset, path, bytes, sha256}, ...]
array (docs/schemas/download.schema.json) so an agent can verify each
file it just fetched without re-hashing it.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--asset |
choice (GEC | CSI | SIDD | SICD | CPHD) |
Asset(s) to download (repeatable). Defaults to all present. | Sentinel.UNSET |
--dest |
text | Output directory. | . |
--overwrite |
boolean | Re-download if the file exists. | False |
--json |
boolean | Emit one {asset, path, bytes, sha256} record per downloaded asset as a JSON array on stdout (see docs/schemas/download.schema.json), instead of the human progress lines. Progress stays on stderr. | False |
--help, -h |
boolean | Show this message and exit. | False |
umbra embed¶
Visual similarity search over the archive (embedding-based, C5).
Every other search matches metadata -- a date, a bbox, a task name. This
matches appearance: it embeds each acquisition's rendered quicklook into a
vector once ('umbra embed build'), then ranks scenes by cosine similarity, so
'umbra embed similar
Requires the ai extra for the model call and viz to render the
quicklooks (pip install 'umbra-py[ai,viz]') plus a multimodal embedding API
key: set OPENAI_API_KEY (optionally OPENAI_BASE_URL for a CLIP-family
/embeddings endpoint, UMBRA_SCENE_EMBED_MODEL to pick the model). The ranking
is deterministic; only turning an image or query into a vector calls a model.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--help, -h |
boolean | Show this message and exit. | False |
umbra embed build¶
Render and embed acquisition quicklooks into a scene-similarity index.
Two ways to choose what to embed:
- Pass STAC JSON URLs directly.
- Or search: give --area (or --bbox / --place) with --start/--end and the command gathers the acquisitions automatically.
Each item's quicklook is rendered once (only downsampled overviews stream over
HTTP -- no full download) and embedded; an item already in the index is skipped
so a rebuild only embeds what is new. Requires the ai + viz extras and
an embedding API key.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--area |
text | Search mode: name of an Umbra site to embed. | None |
--bbox |
text | Search mode: footprint filter 'min_lon,min_lat,max_lon,max_lat'. | Sentinel.UNSET |
--place |
text | Search mode: geocode a place name to a bounding box (mutually exclusive with --bbox). | None |
--intersects |
text | Keep only items whose footprint intersects this GeoJSON polygon -- a path to a .geojson file or an inline GeoJSON string (Polygon / MultiPolygon, or a Feature / FeatureCollection wrapping one). A tighter spatial filter than the rectangular --bbox; the two are mutually exclusive. | None |
--start |
text | Search mode: earliest acquisition date (YYYY-MM-DD or relative). | Sentinel.UNSET |
--end |
text | Search mode: latest acquisition date (same formats as --start). | Sentinel.UNSET |
--limit |
integer | Search mode: cap how many acquisitions to embed. | 200 |
--asset |
choice (GEC | CSI | SIDD | SICD | CPHD) |
Which product's quicklook to embed. GEC (the geocoded GeoTIFF) is the sensible default; the complex SICD/CPHD products aren't amplitude rasters. | GEC |
--model |
text | Embedding model label (default: $UMBRA_SCENE_EMBED_MODEL, else clip). The provider is an OpenAI-compatible multimodal /embeddings endpoint chosen by OPENAI_API_KEY / OPENAI_BASE_URL. | None |
--embed-db |
text | Where to write the scene index (default: the catalog index's sibling, e.g. catalog.embed.db). | None |
--local |
boolean | Gather items from a prebuilt local index (see 'umbra index fetch' / 'umbra index build') instead of walking S3 live -- near-instant, the fast path for repeat renders. Only uses acquisitions already indexed. | False |
--index-db |
text | Path to the local index database to read (default: $UMBRA_INDEX_DB or ~/.cache/umbra-py/catalog.db). Implies --local. Named --index-db because --db already means the decibel stretch on render commands. | None |
--fuzzy |
boolean | Match --area loosely: word-order- and punctuation-independent and typo-tolerant (so 'utah centerfield' or 'centrfield' still reach 'Centerfield, Utah'). Deterministic, no model call; a strict superset of the substring match. | False |
--help, -h |
boolean | Show this message and exit. | False |
umbra embed fetch¶
Download the published scene-embedding index for instant similarity search.
Building the index embeds every quicklook in the archive -- the one expensive, model-backed step. This instead fetches the prebuilt 'catalog.embed.db' from the project's rolling catalog-index GitHub release, so 'umbra embed similar' / 'umbra embed search' work with no rebuild (only the query still needs an embedding key). Re-run any time to refresh. Note the published vectors are model-specific: query with the model the index reports ('umbra embed info').
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--embed-db |
text | Where to write the scene index (default: the catalog index's sibling, e.g. catalog.embed.db). Overwritten if it already exists. | None |
--url |
text | Override the release asset URL (advanced -- e.g. to pull from a fork). | None |
--help, -h |
boolean | Show this message and exit. | False |
umbra embed info¶
Show what a scene index holds: scene-vector count, model and dimension.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--embed-db |
text | Scene index to inspect (default: the sibling). | None |
--help, -h |
boolean | Show this message and exit. | False |
umbra embed search¶
Find archived scenes matching a plain-language QUERY ("ships at a berth").
Embeds the text query and ranks the stored image vectors by cosine similarity. This needs a joint CLIP-family model whose text and image encoders share a space -- build the index and run this query with the same model.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--embed-db |
text | Scene index to query (default: the sibling). | None |
--top-k |
integer | How many matches to show. | 10 |
--min-score |
float | Drop matches below this cosine score (0=unrelated, 1=identical). | 0.0 |
--model |
text | Joint (CLIP-family) model to embed the text query -- must share a vector space with the model the index was built with (default: $UMBRA_SCENE_EMBED_MODEL, else clip). | None |
--json |
boolean | Emit the ranked matches as JSON (see docs/schemas/scene-matches.schema.json). | False |
--help, -h |
boolean | Show this message and exit. | False |
umbra embed similar¶
Find archived scenes that look like the acquisition at ITEM_URL.
Renders and embeds the query item's quicklook, then ranks the stored scene vectors by cosine similarity (the query item is excluded from its own results). "Find scenes that look like this flooded field" -- a search over pixels, not metadata. Build the index first with 'umbra embed build'.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--embed-db |
text | Scene index to query (default: the sibling). | None |
--top-k |
integer | How many matches to show. | 10 |
--min-score |
float | Drop matches below this cosine score (0=unrelated, 1=identical). | 0.0 |
--asset |
choice (GEC | CSI | SIDD | SICD | CPHD) |
Which product's quicklook of the query item to embed (match how the index was built). | GEC |
--model |
text | Embedding model for the query -- must match the model the index was built with (default: $UMBRA_SCENE_EMBED_MODEL, else clip). | None |
--json |
boolean | Emit the ranked matches as JSON (see docs/schemas/scene-matches.schema.json). | False |
--help, -h |
boolean | Show this message and exit. | False |
umbra gallery¶
Render search results as a browseable HTML SAR thumbnail gallery.
Searches the catalog, streams a small SAR quicklook for each match (only
downsampled overviews via HTTP range requests -- no full downloads), and
writes a single self-contained HTML contact sheet: a grid of thumbnails,
each tile linking to its STAC item with a footprint sketch. The missing
"browse the catalog visually" primitive. Requires the viz extra
(pip install "umbra-py[viz]").
With --local (or --index-db), any thumbnail already baked into the index by 'umbra index bake-thumbnails' is embedded straight from local bytes -- no S3 stream, and no viz extra needed when every tile is baked -- so a baked index renders the gallery instantly and offline.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--bbox |
text | Footprint filter: 'min_lon,min_lat,max_lon,max_lat'. | Sentinel.UNSET |
--place |
text | Geocode a place name (e.g. 'California', 'Tokyo') to a bounding box and gather tiles within it, via OpenStreetMap Nominatim. Mutually exclusive with --bbox. | None |
--intersects |
text | Keep only items whose footprint intersects this GeoJSON polygon -- a path to a .geojson file or an inline GeoJSON string (Polygon / MultiPolygon, or a Feature / FeatureCollection wrapping one). A tighter spatial filter than the rectangular --bbox; the two are mutually exclusive. | None |
--start |
text | Earliest acquisition date. Accepts YYYY-MM-DD, a year or month (2024, 2024-03), or a relative expression ('today', 'yesterday', '3 months ago', 'last month'). | Sentinel.UNSET |
--end |
text | Latest acquisition date (same formats as --start; a bare year, month or period like 'last month' snaps to that span's last day). | Sentinel.UNSET |
--area |
text | Case-insensitive name of an Umbra task/site to gather (e.g. 'Centerfield'). Faster than a broad scan -- it lists just that area's directory. | None |
--product |
choice (GEC | CSI | SIDD | SICD | CPHD) |
Keep items exposing this asset (repeatable). Defaults to --asset so every tile is renderable. | Sentinel.UNSET |
--limit |
integer | Max tiles. | 24 |
--max-per-task |
integer | Cap items per Umbra task directory. '--max-per-task 1' gives one tile per distinct site rather than every revisit -- a quick overview of where the archive has imagery. | None |
--out |
text | Output HTML file (e.g. gallery.html). | Sentinel.UNSET |
--asset |
choice (GEC | CSI | SIDD | SICD | CPHD) |
Which product to render in each thumbnail. GEC (the detected GeoTIFF) is the sensible default; CSI also works. The complex SICD/CPHD products aren't amplitude rasters. | GEC |
--max-size |
integer | Max pixel dimension of each thumbnail. Larger is sharper but fetches more bytes per tile (~quadratic). | 512 |
--db |
boolean | Use a decibel (log-amplitude) stretch -- the radiometrically-correct SAR look that reveals texture the default linear stretch crushes toward black. | False |
--colormap |
text | Matplotlib colormap for pseudo-colored thumbnails (e.g. viridis, magma). Default is grayscale. | None |
--percentile |
text | Low,high percentile cut for each thumbnail's contrast stretch. | 2,98 |
--workers |
integer | How many thumbnails to stream in parallel. | 8 |
--local |
boolean | Gather items from a prebuilt local index (see 'umbra index fetch' / 'umbra index build') instead of walking S3 live -- near-instant, the fast path for repeat renders. Only uses acquisitions already indexed. | False |
--index-db |
text | Path to the local index database to read (default: $UMBRA_INDEX_DB or ~/.cache/umbra-py/catalog.db). Implies --local. Named --index-db because --db already means the decibel stretch on render commands. | None |
--token |
text | Canopy API token. When given, gather items from Umbra's authenticated COMMERCIAL archive (a real STAC API) instead of the open bucket — the same flags, over the paid catalog. Falls back to $UMBRA_CANOPY_TOKEN. Mutually exclusive with --local / --index-db. | None |
--fuzzy |
boolean | Match --area loosely: word-order- and punctuation-independent and typo-tolerant (so 'utah centerfield' or 'centrfield' still reach 'Centerfield, Utah'). Deterministic, no model call; a strict superset of the substring match. | False |
--json |
boolean | Emit a machine-readable {output, items_used, parameters} manifest on stdout instead of the human 'Wrote ...' line. Progress and warnings stay on stderr, so stdout is the JSON object alone. | False |
--pol |
text | Keep only items exposing this polarization (e.g. VV, HH; repeatable, case-insensitive, matches if the item has ANY of them). The filter that keeps a change comparison like-with-like -- HH and VV image different physics. Items with no polarization metadata are excluded. | Sentinel.UNSET |
--min-incidence |
float | Keep only items with view incidence angle >= this many degrees. Items missing an incidence angle are excluded. | None |
--max-incidence |
float | Keep only items with view incidence angle <= this many degrees. Items missing an incidence angle are excluded. | None |
--max-resolution |
float | Keep only items at least this fine: both range and azimuth resolution <= this many metres. Items missing a resolution are excluded. | None |
--help, -h |
boolean | Show this message and exit. | False |
umbra index¶
Build and inspect a local SQLite catalog index for fast offline search.
Umbra has no STAC API, so a live search re-walks S3 every time. Index the archive once into a local database, then run 'umbra search --local' for near-instant repeat searches over the same data.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--help, -h |
boolean | Show this message and exit. | False |
umbra index bake¶
Reverse-geocode indexed acquisitions and cache their place labels.
Turns each acquisition's footprint into a human place name ("Reykjavik, Iceland") once and stores it in the index, so 'umbra demo', maps and galleries built with --local show real place labels instantly instead of re-geocoding at render time (OpenStreetMap Nominatim caps traffic at ~1 request/sec, so labelling thousands of items live is impractical).
Umbra files every pass over a site under one task, so --by-site resolves a site once and labels all of its passes -- the mode to use for a whole catalog, where most acquisitions are repeat passes over ground already geocoded.
Idempotent: only items without a label yet are geocoded, so re-running labels just what was added since. Bootstrap the index first with 'umbra index fetch' or 'umbra index build'.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--db |
text | Index to label (default: $UMBRA_INDEX_DB or ~/.cache/umbra-py/catalog.db). Must already exist -- create one with 'index fetch'/'build'. | None |
--limit |
integer | Cap how many geocode lookups to make this run (default: no cap). Reverse geocoding is throttled to ~1/sec, so use this to bake a large catalog in bounded batches -- re-run to continue where it left off. | None |
--by-site |
boolean | Geocode once per site instead of once per acquisition: passes sharing a task and a ~11 km cell take one label resolved from their mean centroid. Cuts the throttled lookups by the average passes-per-site, which is what makes labelling a whole catalog practical. | False |
--zoom |
integer | Nominatim address granularity: 3 = country, 8 = county, 10 = city, 14 = suburb, 18 = building. | 10 |
--help, -h |
boolean | Show this message and exit. | False |
umbra index bake-thumbnails¶
Render a small SAR quicklook per acquisition and cache it in the index.
Bakes a downsampled PNG preview for every indexed acquisition once, so 'umbra serve's GET /artifacts/thumbnail/{id}.png -- and any demo/gallery reading it -- shows a scene instantly from local bytes instead of re-streaming its cloud-optimized GeoTIFF from S3 at render time.
Idempotent: only acquisitions without a baked thumbnail yet are rendered, so
re-running bakes just what was added since. A scene that can't be rendered is
skipped and retried next run. Needs the viz extra
(pip install "umbra-py[viz]"); bootstrap the index first with 'umbra
index fetch' or 'umbra index build'.
Baking is the one derived artifact worth moving rather than recomputing (it costs an overview stream per scene), so 'umbra index fetch-thumbnails' pulls the published bake instead and 'umbra index export-thumbnails' writes yours out to share.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--db |
text | Index to bake into (default: $UMBRA_INDEX_DB or ~/.cache/umbra-py/catalog.db). Must already exist -- create one with 'index fetch'/'build'. | None |
--limit |
integer | Cap how many acquisitions to render this run (default: no cap). Each thumbnail streams a scene's overview from S3, so use this to bake a large catalog in bounded batches -- re-run to continue where it left off. | None |
--size |
integer | Longest edge of the baked PNG, in pixels. | 256 |
--asset |
text | Which asset to render the preview from (the geocoded GEC by default). | GEC |
--newest-first |
boolean | Bake the most recently acquired scenes first instead of in catalog order, so a --limit run spends its budget on the freshest passes -- the ones a demo or a monitoring view opens on. | False |
--help, -h |
boolean | Show this message and exit. | False |
umbra index build¶
Walk Umbra's archive and persist matching acquisitions into the index.
With no scope flags this indexes the whole bucket, which lists every task and takes a while; pass --area/--bbox/--place/--intersects/--start/--end to index just the slice you care about.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--db |
text | Output index database (default: $UMBRA_INDEX_DB or ~/.cache/umbra-py/catalog.db). Created if missing; existing rows are refreshed and new ones added (incremental). | None |
--bbox |
text | Scope the build to a footprint: 'min_lon,min_lat,max_lon,max_lat'. | Sentinel.UNSET |
--place |
text | Scope the build to a geocoded place name (mutually exclusive with --bbox). | None |
--intersects |
text | Keep only items whose footprint intersects this GeoJSON polygon -- a path to a .geojson file or an inline GeoJSON string (Polygon / MultiPolygon, or a Feature / FeatureCollection wrapping one). A tighter spatial filter than the rectangular --bbox; the two are mutually exclusive. | None |
--start |
text | Scope to acquisitions on/after this date. YYYY-MM-DD, a year/month, or relative ('3 months ago', 'last month'). | Sentinel.UNSET |
--end |
text | Scope to acquisitions on/before this date (same formats as --start). | Sentinel.UNSET |
--area |
text | Scope to one Umbra task/site by name (e.g. 'Centerfield'). Much faster than a full walk -- it lists just that task. | None |
--limit |
integer | Cap how many acquisitions to index this run (default: no cap -- index everything in scope). | None |
--fuzzy |
boolean | Match --area loosely: word-order- and punctuation-independent and typo-tolerant (so 'utah centerfield' or 'centrfield' still reach 'Centerfield, Utah'). Deterministic, no model call; a strict superset of the substring match. | False |
--help, -h |
boolean | Show this message and exit. | False |
umbra index export¶
Export a local index to stac-geoparquet for serverless catalog search.
Writes every indexed acquisition as one row of a stac-geoparquet file —
the whole catalog searchable in seconds with DuckDB, geopandas or pyarrow,
no server and no crawl. Each row carries the full STAC item plus a 'self'
link back to its sidecar JSON, so results lead straight to the data files.
Build the index first with 'umbra index build'. Requires the export extra
(pip install "umbra-py[export]").
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--db |
text | Index database to export (default: $UMBRA_INDEX_DB or ~/.cache/umbra-py/catalog.db). | None |
--out |
text | Output stac-geoparquet file (e.g. umbra-open-data.parquet). | Sentinel.UNSET |
--help, -h |
boolean | Show this message and exit. | False |
umbra index export-thumbnails¶
Write the index's baked thumbnails to a shareable sidecar database.
A baked quicklook costs a cloud-optimized GeoTIFF overview streamed per scene, so it is the one derived artifact worth moving rather than recomputing. This writes the PNGs already baked ('umbra index bake-thumbnails') to a standalone catalog.thumbs.db that any other index can merge with 'umbra index fetch-thumbnails --from'.
The sidecar is a separate file rather than a column of the published catalog.db on purpose: the pixels dwarf the metadata, so every 'umbra index fetch' would otherwise pay for previews most callers never open.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--db |
text | Index to export from (default: $UMBRA_INDEX_DB or ~/.cache/umbra-py/catalog.db). | None |
--out |
text | Sidecar file to write (default: catalog.thumbs.db beside the index). | None |
--help, -h |
boolean | Show this message and exit. | False |
umbra index fetch¶
Download the published prebuilt catalog index for instant local search.
Umbra has no STAC API, so the first 'umbra index build' crawls the whole bucket (minutes). This instead fetches the weekly-rebuilt snapshot from the project's rolling catalog-index GitHub release, so 'umbra search --local' works immediately -- no crawl. Re-run any time to refresh.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--db |
text | Where to write the index (default: $UMBRA_INDEX_DB or ~/.cache/umbra-py/catalog.db). Overwritten if it already exists. | None |
--url |
text | Override the release asset URL (advanced -- e.g. to pull from a fork). | None |
--help, -h |
boolean | Show this message and exit. | False |
umbra index fetch-thumbnails¶
Download the published SAR thumbnails and merge them into the index.
Every preview otherwise streams a scene's cloud-optimized GeoTIFF overview from S3 at render time. The weekly workflow bakes them once and publishes catalog.thumbs.db on the rolling catalog-index release; this fetches that sidecar and fills the index's thumbnail column, so 'umbra serve's GET /artifacts/thumbnail/{id}.png, the 'umbra demo' preview and a --local gallery all read local bytes with no range read at all.
Bootstrap the index first with 'umbra index fetch' or 'umbra index build'. Acquisitions the sidecar doesn't cover are left alone, so re-run after an 'umbra index update' to pick up newly published previews.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--db |
text | Index to merge the thumbnails into (default: $UMBRA_INDEX_DB or ~/.cache/umbra-py/catalog.db). Must already exist. | None |
--from |
text | Merge a local sidecar file instead of downloading the published one. | None |
--url |
text | Override the release asset URL (advanced -- e.g. to pull from a fork). | None |
--overwrite |
boolean | Replace thumbnails already baked locally (default: keep them). | False |
--help, -h |
boolean | Show this message and exit. | False |
umbra index info¶
Show what a local index holds: item count, date span and task count.
--json emits the stats as a machine-readable object
(docs/schemas/index-info.schema.json): path, size_bytes,
items, start, end, tasks, labeled, thumbnailed and
built_at.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--db |
text | Index database to inspect (default: $UMBRA_INDEX_DB or ~/.cache/umbra-py/catalog.db). | None |
--json |
boolean | Emit the index stats as a JSON object on stdout (see docs/schemas/index-info.schema.json) instead of the human summary. | False |
--help, -h |
boolean | Show this message and exit. | False |
umbra index update¶
Cheaply refresh an existing index by re-walking only recent acquisitions.
'umbra index build' fetches a sidecar for every acquisition in scope; on a snapshot only days old that re-reads mostly-unchanged data. 'update' instead derives a start date from the newest acquisition already indexed (minus --overlap-days) and walks only from there, so a weekly refresh reads just the new passes. Bootstrap the index first with 'umbra index fetch' or 'umbra index build'.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--db |
text | Index to refresh (default: $UMBRA_INDEX_DB or ~/.cache/umbra-py/catalog.db). Must already exist -- create one with 'index fetch'/'build'. | None |
--overlap-days |
integer | Re-scan this many days before the newest indexed acquisition to catch near-real-time publish lag. Widen it (or run 'index build') if back-dated late arrivals matter. | 1 |
--since |
text | Force the acquisition-date lower bound (YYYY-MM-DD, a year/month, or relative like '2 weeks ago') instead of deriving it from the index. | None |
--bbox |
text | Scope the refresh to a footprint 'min_lon,min_lat,max_lon,max_lat' (match the scope the index was built with). | Sentinel.UNSET |
--place |
text | Scope the refresh to a geocoded place name (mutually exclusive with --bbox). | None |
--intersects |
text | Keep only items whose footprint intersects this GeoJSON polygon -- a path to a .geojson file or an inline GeoJSON string (Polygon / MultiPolygon, or a Feature / FeatureCollection wrapping one). A tighter spatial filter than the rectangular --bbox; the two are mutually exclusive. | None |
--area |
text | Scope the refresh to one Umbra task/site by name (e.g. 'Centerfield'). | None |
--limit |
integer | Cap how many acquisitions to add this run (default: no cap). | None |
--fuzzy |
boolean | Match --area loosely: word-order- and punctuation-independent and typo-tolerant (so 'utah centerfield' or 'centrfield' still reach 'Centerfield, Utah'). Deterministic, no model call; a strict superset of the substring match. | False |
--help, -h |
boolean | Show this message and exit. | False |
umbra info¶
Show a summary of a STAC item.
Without --token (the default), ITEM is the JSON URL of an open-data
sidecar, read directly. With --token (or $UMBRA_CANOPY_TOKEN), ITEM
is instead an acquisition id, looked up in Umbra's Canopy commercial archive
by a keyed STAC search — the retrieval complement to umbra search
--token, over the paid catalog.
--json emits the explanation-rich context card
(:meth:umbra_py.UmbraItem.to_llm_context) — a compact object an agent
can consume directly, with per-product explanations and the license line.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--json |
boolean | Emit the item's LLM context card as JSON instead of a readable summary (see docs/schemas/item-context.schema.json). | False |
--token |
text | Canopy API token. When given, ITEM is treated as an acquisition id and looked up in Umbra's authenticated COMMERCIAL archive by a keyed STAC search (the retrieval complement to 'umbra search --token'), instead of being read as an open-data sidecar URL. Falls back to $UMBRA_CANOPY_TOKEN. | None |
--help, -h |
boolean | Show this message and exit. | False |
umbra llms-txt¶
Print the project's llms.txt context bundle to stdout.
The llms.txt convention <https://llmstxt.org/>_ document — a Markdown guide
a language model pulls in to learn how to drive umbra-py (the counterpart
to the machine-readable umbra context JSON). --full emits the
self-contained llms-full.txt. The committed repo-root llms.txt /
llms-full.txt are regenerated from this command::
umbra llms-txt > llms.txt
umbra llms-txt --full > llms-full.txt
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--full |
boolean | Emit the expanded llms-full.txt bundle (domain knowledge, the full CLI reference, the AI-native interfaces and a per-module map) instead of the concise llms.txt index. | False |
--help, -h |
boolean | Show this message and exit. | False |
umbra load¶
Load a clipped/decimated SAR scene from a STAC item URL to a GeoTIFF.
Streams only the requested window/resolution of the item's cloud-optimized
GeoTIFF via HTTP range requests and writes an analysis-ready, single-band
float32 GeoTIFF in the source CRS -- no full download. For an in-memory
array instead, use umbra_py.to_xarray. Requires the load extra
(pip install "umbra-py[load]").
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--out |
text | Output GeoTIFF path (e.g. scene.tif). | Sentinel.UNSET |
--asset |
choice (GEC | CSI | SIDD | SICD | CPHD) |
Which product to load. GEC (the geocoded GeoTIFF) is the sensible default; CSI also works. The complex SICD/CPHD products aren't amplitude rasters. | GEC |
--bbox |
text | Clip to a lon/lat window: 'min_lon,min_lat,max_lon,max_lat'. | Sentinel.UNSET |
--max-size |
integer | Cap the longest output side in pixels (decimates via COG overviews). Omit to write full resolution -- pair that with --bbox for a large scene. | None |
--db |
boolean | Write the decibel (log-amplitude) scale instead of linear amplitude. | False |
--help, -h |
boolean | Show this message and exit. | False |
umbra map¶
Render search results as an interactive map or GeoJSON file.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--bbox |
text | Footprint filter: 'min_lon,min_lat,max_lon,max_lat'. | Sentinel.UNSET |
--place |
text | Geocode a place name (e.g. 'California', 'Tokyo') to a bounding box and plot items within it, via OpenStreetMap Nominatim. Mutually exclusive with --bbox. (Distinct from --geocode, which labels each plotted footprint with its place name.) | None |
--intersects |
text | Keep only items whose footprint intersects this GeoJSON polygon -- a path to a .geojson file or an inline GeoJSON string (Polygon / MultiPolygon, or a Feature / FeatureCollection wrapping one). A tighter spatial filter than the rectangular --bbox; the two are mutually exclusive. | None |
--start |
text | Earliest acquisition date. Accepts YYYY-MM-DD, a year or month (2024, 2024-03), or a relative expression ('today', 'yesterday', '3 months ago', 'last month'). | Sentinel.UNSET |
--end |
text | Latest acquisition date (same formats as --start; a bare year, month or period like 'last month' snaps to that span's last day). | Sentinel.UNSET |
--area |
text | Case-insensitive name of an Umbra task/site to gather (e.g. 'Centerfield'). Faster than a broad scan -- it lists just that area's directory. | None |
--product |
choice (GEC | CSI | SIDD | SICD | CPHD) |
Keep items exposing this asset (repeatable). | Sentinel.UNSET |
--limit |
integer | Max results to plot. | 100 |
--out |
text | Output file. '.html' writes an interactive Folium map (requires the viz extra); '.geojson' / '.json' writes a GeoJSON FeatureCollection. | Sentinel.UNSET |
--imagery |
boolean | Overlay each item's GEC SAR image on the map (HTML output only; needs the viz extra including rasterio). | False |
--imagery-max-size |
integer | Max pixel dimension of each SAR overlay. Default is 1024 -- bump to 2048 or 4096 for sharper imagery at the cost of larger HTML output (quadratic in size). SAR data is inherently grainy (speckle); higher values reveal more detail but also more speckle noise. | None |
--max-per-task |
integer | Cap items per Umbra task directory. Each task is repeated imaging of the same area, so '--max-per-task 1' returns one item per distinct site rather than every revisit. | None |
--geocode / --no-geocode |
boolean | Reverse-geocode each footprint's centroid via OpenStreetMap Nominatim and include the resulting place name in the popup. Adds one HTTP request per item (throttled to ~1/sec to honor Nominatim's usage policy); pass --no-geocode to skip the network calls or when running offline. | True |
--timeline |
boolean | Render an animated timeline map instead of the static footprint map. Footprints appear at their acquisition timestamps and the page ships a play button + scrubber, so you can watch Umbra's coverage accumulate over the requested window. HTML output only; --imagery is not yet supported on this view. | False |
--timeline-period |
text | ISO 8601 step for the timeline slider (e.g. PT1H, P1D, P7D). Pick a period matching the cadence of your search: PT1H for one day of acquisitions, P1D for a month, P7D for a year. Ignored without --timeline. | P1D |
--lazy-imagery |
boolean | Add a 'Get SAR image' button to each popup. On click, the browser streams that item's GEC cloud-optimized GeoTIFF directly from the Umbra bucket via HTTP range requests (using georaster-layer-for-leaflet + geotiff.js from a CDN) and overlays it on the map. Unlike --imagery, the HTML stays ~30 KB regardless of how many items it carries -- you only pay the fetch cost for items you click. Works with --timeline. HTML output only; mutually exclusive with --imagery. | False |
--local |
boolean | Gather items from a prebuilt local index (see 'umbra index fetch' / 'umbra index build') instead of walking S3 live -- near-instant, the fast path for repeat renders. Only uses acquisitions already indexed. | False |
--index-db |
text | Path to the local index database to read (default: $UMBRA_INDEX_DB or ~/.cache/umbra-py/catalog.db). Implies --local. Named --index-db because --db already means the decibel stretch on render commands. | None |
--token |
text | Canopy API token. When given, gather items from Umbra's authenticated COMMERCIAL archive (a real STAC API) instead of the open bucket — the same flags, over the paid catalog. Falls back to $UMBRA_CANOPY_TOKEN. Mutually exclusive with --local / --index-db. | None |
--json |
boolean | Emit a machine-readable {output, items_used, parameters} manifest on stdout instead of the human 'Wrote ...' line. Progress and warnings stay on stderr, so stdout is the JSON object alone. | False |
--pol |
text | Keep only items exposing this polarization (e.g. VV, HH; repeatable, case-insensitive, matches if the item has ANY of them). The filter that keeps a change comparison like-with-like -- HH and VV image different physics. Items with no polarization metadata are excluded. | Sentinel.UNSET |
--min-incidence |
float | Keep only items with view incidence angle >= this many degrees. Items missing an incidence angle are excluded. | None |
--max-incidence |
float | Keep only items with view incidence angle <= this many degrees. Items missing an incidence angle are excluded. | None |
--max-resolution |
float | Keep only items at least this fine: both range and azimuth resolution <= this many metres. Items missing a resolution are excluded. | None |
--fuzzy |
boolean | Match --area loosely: word-order- and punctuation-independent and typo-tolerant (so 'utah centerfield' or 'centrfield' still reach 'Centerfield, Utah'). Deterministic, no model call; a strict superset of the substring match. | False |
--help, -h |
boolean | Show this message and exit. | False |
umbra mcp¶
Run the umbra Model Context Protocol server (stdio transport).
Exposes search / geocode / quicklook / change / timescan as MCP tools so an
MCP client (Claude Desktop / Code and others) can drive the archive in
natural language. Requires the mcp extra (pip install 'umbra-py[mcp]');
also runnable as umbra-mcp, or with nothing installed as
uvx --from 'umbra-py[mcp]' umbra-mcp.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--help, -h |
boolean | Show this message and exit. | False |
umbra preflight¶
Ask which complex acquisitions can support a measurement, before downloading any.
Radiometric calibration and a measured noise floor both read polynomials out
of the SICD's own Radiometric metadata, which Umbra's open products generally
do not carry -- so umbra convert --calibrate and umbra chips --calibrate
refuse on them, by design. Terrain flattening (--rtc) reads the collection
geometry out of the same file's SCPCOA block. Finding out which passes can
answer used to cost one whole-product download each: a SICD's metadata lives
inside the NITF.
This reads it over the wire instead. A NITF states its own layout, so the SICD XML is located and fetched with two HTTP range requests -- tens of kilobytes of a multi-gigabyte product -- and the verdict is the conversion's own support check applied to it. Over a site's twenty passes that is the difference between a few hundred kilobytes and tens of gigabytes.
Two ways to choose what to ask about: - Pass STAC JSON URLs directly. - Or search: give --area (or --bbox / --place / --intersects) with --start/--end.
Needs no extra: the parse is stdlib, so "can this archive answer my question?" is answerable from a core install.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--calibrate |
choice (sigma0 | beta0 | gamma0 | rcs) |
Ask whether each product could be radiometrically calibrated this way (the same choice --calibrate takes on convert/chips). | None |
--subtract-noise |
boolean | Ask whether each product's noise floor could be subtracted. Only --noise-model measured depends on the metadata; the inferred models read the scene's own pixels and so need nothing from a preflight. | False |
--noise-model |
choice (measured | estimated | estimated-range) |
Which floor --subtract-noise would use (see convert --noise-model). | measured |
--rtc |
boolean | Ask whether each product states the collection geometry radiometric terrain flattening needs (SCPCOA). Most do; the ones that do not refuse a --rtc run only after the download, the DEM fetch and the warp. | False |
--workers |
integer | How many product headers to read in parallel (1 to read them one at a time). The verdicts and their order are the same at any width. | 8 |
--area |
text | Search an Umbra task/site by name (e.g. 'Centerfield'). | None |
--bbox |
text | Footprint filter: 'min_lon,min_lat,max_lon,max_lat'. | Sentinel.UNSET |
--place |
text | Geocode a place name (e.g. 'California', 'Tokyo') to a bounding box and gather within it, via OpenStreetMap Nominatim. Mutually exclusive with --bbox; the match is rectangular, so it can include nearby areas outside the named place. | None |
--intersects |
text | Keep only items whose footprint intersects this GeoJSON polygon -- a path to a .geojson file or an inline GeoJSON string (Polygon / MultiPolygon, or a Feature / FeatureCollection wrapping one). A tighter spatial filter than the rectangular --bbox; the two are mutually exclusive. | None |
--start |
text | Earliest acquisition date (YYYY-MM-DD or a relative expression). | Sentinel.UNSET |
--end |
text | Latest acquisition date (same formats as --start). | Sentinel.UNSET |
--max-search |
integer | Max acquisitions to gather when searching (ignored with item URLs). | 20 |
--json |
boolean | Emit the report as JSON (see docs/schemas/preflight.schema.json). | False |
--fuzzy |
boolean | Match --area loosely: word-order- and punctuation-independent and typo-tolerant (so 'utah centerfield' or 'centrfield' still reach 'Centerfield, Utah'). Deterministic, no model call; a strict superset of the substring match. | False |
--pol |
text | Keep only items exposing this polarization (e.g. VV, HH; repeatable, case-insensitive, matches if the item has ANY of them). The filter that keeps a change comparison like-with-like -- HH and VV image different physics. Items with no polarization metadata are excluded. | Sentinel.UNSET |
--min-incidence |
float | Keep only items with view incidence angle >= this many degrees. Items missing an incidence angle are excluded. | None |
--max-incidence |
float | Keep only items with view incidence angle <= this many degrees. Items missing an incidence angle are excluded. | None |
--max-resolution |
float | Keep only items at least this fine: both range and azimuth resolution <= this many metres. Items missing a resolution are excluded. | None |
--local |
boolean | Gather items from a prebuilt local index (see 'umbra index fetch' / 'umbra index build') instead of walking S3 live -- near-instant, the fast path for repeat renders. Only uses acquisitions already indexed. | False |
--index-db |
text | Path to the local index database to read (default: $UMBRA_INDEX_DB or ~/.cache/umbra-py/catalog.db). Implies --local. Named --index-db because --db already means the decibel stretch on render commands. | None |
--token |
text | Canopy API token. When given, gather items from Umbra's authenticated COMMERCIAL archive (a real STAC API) instead of the open bucket — the same flags, over the paid catalog. Falls back to $UMBRA_CANOPY_TOKEN. Mutually exclusive with --local / --index-db. | None |
--help, -h |
boolean | Show this message and exit. | False |
umbra quicklook¶
Render a standalone SAR quicklook image from a STAC item URL.
Streams a downsampled preview of the item's cloud-optimized GeoTIFF via
HTTP range requests and writes it as an image -- no full download, no
map. Requires the viz extra (pip install "umbra-py[viz]").
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--out |
text | Output image file (extension picks the format, e.g. scene.png). | Sentinel.UNSET |
--asset |
choice (GEC | CSI | SIDD | SICD | CPHD) |
Which product to render. GEC (the detected GeoTIFF) is the sensible default; CSI also works. The complex SICD/CPHD products aren't amplitude rasters. | GEC |
--max-size |
integer | Max pixel dimension of the quicklook. Larger is sharper but reveals more SAR speckle and fetches more bytes (roughly quadratic). | 2048 |
--db |
boolean | Use a decibel (log-amplitude) stretch -- the radiometrically-correct SAR look. Reveals terrain texture and structure that the default linear stretch crushes toward black. | False |
--colormap |
text | Matplotlib colormap for a pseudo-colored quicklook (e.g. viridis, magma, inferno). Default is grayscale. | None |
--percentile |
text | Low,high percentile cut for the contrast stretch. | 2,98 |
--help, -h |
boolean | Show this message and exit. | False |
umbra search¶
Search the catalog by area, date and product type.
Searches Umbra's open data by default. Pass --token (or set $UMBRA_CANOPY_TOKEN) to search Umbra's commercial Canopy archive instead -- same query, same output, over the paid catalog.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--bbox |
text | Footprint filter: 'min_lon,min_lat,max_lon,max_lat'. | Sentinel.UNSET |
--place |
text | Geocode a place name (e.g. 'California', 'Tokyo') to a bounding box and search within it, via OpenStreetMap Nominatim. Mutually exclusive with --bbox; the match is rectangular, so it can include nearby areas outside the named place. | None |
--intersects |
text | Keep only items whose footprint intersects this GeoJSON polygon -- a path to a .geojson file or an inline GeoJSON string (Polygon / MultiPolygon, or a Feature / FeatureCollection wrapping one). A tighter spatial filter than the rectangular --bbox; the two are mutually exclusive. | None |
--start |
text | Earliest acquisition date. Accepts YYYY-MM-DD, a year or month (2024, 2024-03), or a relative expression ('today', 'yesterday', '3 months ago', 'last month'). | Sentinel.UNSET |
--end |
text | Latest acquisition date (same formats as --start; a bare year, month or period like 'last month' snaps to that span's last day). | Sentinel.UNSET |
--product |
choice (GEC | CSI | SIDD | SICD | CPHD) |
Keep items exposing this asset (repeatable). | Sentinel.UNSET |
--area |
text | Case-insensitive name of an Umbra task/site to search (e.g. 'Centerfield'). Umbra files every pass of a site under one named directory, so this returns just that area's acquisitions -- and skips listing the rest, so it's much faster. The easy way to gather the co-located passes that 'umbra change' needs. | None |
--fuzzy |
boolean | Match --area loosely: word-order- and punctuation-independent, and tolerant of a small typo (so 'utah centerfield' or 'centrfield' still reach 'Centerfield, Utah'). Deterministic, no model call; a strict superset of the default substring match, so it never drops a result. | False |
--pol |
text | Keep only items exposing this polarization (e.g. VV, HH; repeatable, case-insensitive, matches if the item has ANY of them). The filter that keeps a change comparison like-with-like -- HH and VV image different physics. Items with no polarization metadata are excluded. | Sentinel.UNSET |
--min-incidence |
float | Keep only items with view incidence angle >= this many degrees. Items missing an incidence angle are excluded. | None |
--max-incidence |
float | Keep only items with view incidence angle <= this many degrees. Items missing an incidence angle are excluded. | None |
--max-resolution |
float | Keep only items at least this fine: both range and azimuth resolution <= this many metres. Items missing a resolution are excluded. | None |
--limit |
integer | Max results. | 20 |
--max-per-task |
integer | Cap items per Umbra task directory. Each task is repeated imaging of the same area, so '--max-per-task 1' returns one item per distinct site rather than every revisit. | None |
--json |
boolean | Emit full STAC item JSON. | False |
--local |
boolean | Search a local SQLite index built with 'umbra index build' instead of walking S3 live -- near-instant for repeat searches. Only returns acquisitions already present in the index. | False |
--db |
text | Path to the local index database (default: $UMBRA_INDEX_DB or ~/.cache/umbra-py/catalog.db). Implies --local. | None |
--live |
boolean | With --local, read through to the live bucket: answer from the index AND walk only acquisitions newer than the index's freshest pass, merging the two -- so a repeat search stays near-instant but also catches anything published since the index was built (which it caches for next time). | False |
--token |
text | Canopy API token. When given, search Umbra's authenticated COMMERCIAL archive (a real STAC API) instead of the open bucket -- the same filters, the same results, over the paid catalog. Falls back to the $UMBRA_CANOPY_TOKEN environment variable. Mutually exclusive with --local. | None |
--help, -h |
boolean | Show this message and exit. | False |
umbra semantic¶
Semantic (embedding-based) task-name search -- the model-backed layer of natural-language search.
The deterministic matchers ('umbra search --area' and '--fuzzy') match by the words in a task label. Some queries share no word with the label they mean -- "grain storage north dakota" means "Beet Piler - ND" -- and only a model that has read about the world can bridge that. This embeds the task names once ('umbra semantic build') so 'umbra semantic search' can rank them by meaning.
Requires the ai extra (pip install 'umbra-py[ai]') and an embedding
API key: set OPENAI_API_KEY (optionally OPENAI_BASE_URL for a compatible
endpoint, UMBRA_EMBED_MODEL to pick the model). Embeddings only rank task
names; the resolved search still runs deterministically.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--help, -h |
boolean | Show this message and exit. | False |
umbra semantic build¶
Embed the index's task names into a semantic search index.
Reads the distinct task/site names from the catalog index and stores an embedding vector for each (idempotent -- a rebuild only embeds names not seen before). One embedding call per batch of names; nothing else touches a model.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--db |
text | Catalog index to read task names from (default: $UMBRA_INDEX_DB or ~/.cache/umbra-py/catalog.db). Build or fetch it first with 'umbra index build' / 'umbra index fetch'. | None |
--semantic-db |
text | Where to write the embedding index (default: the catalog index's sibling, e.g. catalog.semantic.db). | None |
--model |
text | Embedding model (default: $UMBRA_EMBED_MODEL, else text-embedding-3-small). The provider is an OpenAI-compatible /embeddings endpoint chosen by OPENAI_API_KEY / OPENAI_BASE_URL. | None |
--help, -h |
boolean | Show this message and exit. | False |
umbra semantic info¶
Show what a semantic index holds: task-vector count, model and dimension.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--semantic-db |
text | Embedding index to inspect (default: the catalog index's sibling). | None |
--db |
text | Catalog index whose sibling semantic index to inspect (default path). | None |
--help, -h |
boolean | Show this message and exit. | False |
umbra semantic search¶
Rank Umbra task/site names by how well they match a plain-language QUERY.
Embeds the query and scores it against the stored task embeddings, printing the closest names -- the semantic answer to a site you can describe but can't name. Prints the exact 'umbra search --area ...' command for the best match; pass --run to execute it (you audit the command first, as with 'umbra ask').
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--semantic-db |
text | Embedding index to query (default: the catalog index's sibling, e.g. catalog.semantic.db). Build it first with 'umbra semantic build'. | None |
--top-k |
integer | How many ranked matches to show. | 5 |
--min-score |
float | Drop matches below this cosine score (0=unrelated, 1=identical). | 0.0 |
--model |
text | Embedding model for the query -- must match the model the index was built with (default: $UMBRA_EMBED_MODEL, else text-embedding-3-small). | None |
--json |
boolean | Emit the ranked matches as JSON (see docs/schemas/task-matches.schema.json). | False |
--run, -r |
boolean | Run 'umbra search --area |
False |
--limit |
integer | Cap results when --run executes the search. | None |
--local |
boolean | With --run, search a prebuilt local index instead of walking S3 live. | False |
--db |
text | Catalog index for --run --local (default: $UMBRA_INDEX_DB or ~/.cache/umbra-py/catalog.db). Implies --local. | None |
--help, -h |
boolean | Show this message and exit. | False |
umbra serve¶
Run a read-only STAC API over the catalog index (HTTP server).
Umbra publishes a static STAC catalog and no search API, so the standard
STAC tooling (pystac-client, the QGIS STAC plugin, stac-browser, leafmap)
has nothing to query. This serves /search, /collections and
/collections/{id}/items -- plus an OpenAPI doc at /docs -- over the
local index, turning umbra-py into the STAC API bridge for the open archive.
It also renders artifacts on demand over any site: a quicklook
(GET /artifacts/quicklook/{id}.png), a change composite
(POST /artifacts/change) or a timescan (POST /artifacts/timescan),
each cached to disk by its inputs -- and answers the same change question in
numbers at POST /artifacts/stats, the umbra stack --stats
reduction (per-pass decibel statistics, changed area in km², and with
"blocks": N which part of the site moved) over HTTP. That last one is
the only endpoint whose cost grows with the number of acquisitions, so
--stack-lazy (plus --stack-chunk-size) gives it the same memory
ceiling-lift umbra stack --lazy has -- an instance-wide setting, since
it needs the dask extra here on the server. On a chunked instance a
request may also send "windowed": true to be measured in those windows
(umbra stack --stats-windowed), which is a request field rather than a
policy because it estimates the percentiles it no longer holds a pass for.
Any instance honours "speckle_filter": "boxcar" | "lee" (umbra stack
--speckle-filter), which averages speckle down on the shared grid before
anything is measured -- so a chunked instance takes both, and answers the
largest cube it can build with the interference averaged out of it.
With --narrate (and a model API key in the environment) it also mounts
POST /artifacts/narrate: a vision-language reading of what changed
between two passes, grounded in the deterministic dB grid and the speckle
detection floor. A series longer than a composite is scanned first and the
pair whose change stands clear of the floor is the one narrated. It is the
one endpoint that spends money per call, so it is opt-in, cached like every
artifact (a repeat request costs no model call), and guarded: capped per day
(--narrate-daily-limit), per client (--narrate-client-limit, so no
single caller drains the day's budget) and bounded to a curated area
(--narrate-allow-bbox, refusing scenes outside it with 403) -- the
hardening a public instance wants. The key is held server-side and never a
request field. Requires the serve extra (pip install
'umbra-py[serve]'), plus ai + viz for --narrate.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--host |
text | Interface to bind. | 127.0.0.1 |
--port |
integer | Port to listen on. | 8000 |
--db |
text | Catalog index to serve (default: the shared index path). Fetch one first with 'umbra index fetch'. | None |
--live |
boolean | Serve from a live S3 walk per request instead of a local index (correct but slow; for a quick try without building an index). | False |
--artifacts / --no-artifacts |
boolean | Mount the on-demand artifact endpoints (/artifacts/quicklook, /change, /timescan, /swipe, /stats). Use --no-artifacts for a public instance that wants to bound COG-streaming egress. | True |
--stack-lazy |
boolean | Build POST /artifacts/stats' datacube lazily (one dask task per pass) so a long series is measured a slice at a time instead of held whole. Needs the 'dask' extra on the server; the numbers are identical either way. | False |
--stack-chunk-size |
integer | With --stack-lazy, also cut each pass into N-square windows read independently, so one scene need not fit in memory either. Costs one range read per window instead of one per pass, and is what lets a stats request ask for "windowed": true (measured window by window, estimated percentiles). | None |
--stack-scheduler |
choice (synchronous | threads) |
With --stack-lazy, which dask scheduler evaluates the chunks: 'synchronous' on the request's own worker, or 'threads' for dask's thread pool (faster per request, multiplies under concurrent ones). | synchronous |
--narrate |
boolean | Enable POST /artifacts/narrate: a vision-language reading of what changed between two passes (a longer series is scanned for the pair worth reading). Needs a model API key (ANTHROPIC_API_KEY, OPENROUTER_API_KEY, or OPENAI_API_KEY) held server-side and the 'ai' + 'viz' extras. Off by default -- it is the one endpoint that spends money per call. | False |
--narrate-model |
text | With --narrate, override the vision model (default: $UMBRA_NARRATE_MODEL, then the provider default). The model is the instance's, not a request field. | None |
--narrate-daily-limit |
integer | With --narrate, cap the number of live model calls per UTC day (cached narrations never count). Unlimited if unset. A 429 is returned once the day's cap is reached. | None |
--narrate-client-limit |
integer | With --narrate, cap live model calls per client per UTC day (keyed by bearer token, else peer address), so one caller cannot burst through the whole day's budget. Unlimited if unset. The hardening a public instance wants on top of --narrate-daily-limit. | None |
--narrate-allow-bbox |
text | With --narrate, bound the endpoint to a curated area ('min_lon,min_lat,max_lon,max_lat'): a scene whose footprint centroid falls outside is refused with 403, so an open endpoint cannot be pointed at arbitrary scenes to run up model spend. Unbounded if unset. | None |
--cache-dir |
text | Directory for cached render artifacts (default: alongside the index). | None |
--help, -h |
boolean | Show this message and exit. | False |
umbra showcase¶
Assemble a static, hostable showcase site into a directory.
This composes the pieces the other visual commands already produce into one self-contained folder you drop on any static host (GitHub Pages, a bucket):
index.html a landing page linking the pieces below + install/docs/source map.html a MapLibre viewer over the whole-catalog PMTiles basemap explore.html the interactive 'umbra demo' catalog explorer featured/ precomputed artifacts (with --featured / --featured-area)
Give the basemap with --pmtiles PATH, or --fetch-pmtiles to pull the published 'catalog.pmtiles' (the same artifact 'umbra tiles --fetch' fetches). The explorer is built from a gathered slice of the catalog (--local answers from a prebuilt index in milliseconds; --max-per-task 1, the default, gives a one-pin-per-site overview); pass --no-explore for a map-only showcase.
--unified collapses the two map pages into one: the explorer reads the .pmtiles archive itself, so a visitor gets every acquisition in the catalog and the live filters on a single page, and map.html is not written. That needs a basemap and ignores the explorer's search options -- nothing is gathered, because the archive is the data source.
--featured N precomputes an artifact for the N most repeat-imaged sites in the catalog (or name them yourself with repeated --featured-area) and puts them on the landing page, so a first-time visitor sees what SAR change looks like with no render round-trip. --featured-view picks which artifact: a 'change' composite (the default), a whole-series 'timescan' composite, or an interactive before/after 'swipe' page the gallery links to. That step alone needs the 'viz' extra and streams each scene's overview; without it every page is self-contained HTML, so this runs in a core install and is the front end the '.github/workflows/docs.yml' Pages deploy publishes beside the docs.
--narrate adds a precomputed vision-language reading under each featured 'change' tile: at build time it narrates the same two passes the composite shows and bakes the result into the page (a summary + a JSON sidecar with the dB grid it cites), so a visitor reads 'what changed here' with no live model call and no key ever near the browser. It needs the 'ai' extra and a model key (ANTHROPIC_API_KEY, OPENROUTER_API_KEY, or OPENAI_API_KEY); with no key the readings are skipped and the gallery builds unchanged.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--bbox |
text | Footprint filter: 'min_lon,min_lat,max_lon,max_lat'. | Sentinel.UNSET |
--place |
text | Geocode a place name to a bounding box and gather the explorer's items within it, via OpenStreetMap Nominatim. Mutually exclusive with --bbox. | None |
--intersects |
text | Keep only items whose footprint intersects this GeoJSON polygon -- a path to a .geojson file or an inline GeoJSON string (Polygon / MultiPolygon, or a Feature / FeatureCollection wrapping one). A tighter spatial filter than the rectangular --bbox; the two are mutually exclusive. | None |
--start |
text | Earliest acquisition date for the explorer (see 'umbra demo'). | Sentinel.UNSET |
--end |
text | Latest acquisition date for the explorer (see 'umbra demo'). | Sentinel.UNSET |
--area |
text | Case-insensitive Umbra task/site name to gather for the explorer. | None |
--product |
choice (GEC | CSI | SIDD | SICD | CPHD) |
Keep explorer items exposing this asset (repeatable). | Sentinel.UNSET |
--limit |
integer | Max acquisitions to load. | 2000 |
--max-per-task |
integer | Cap items per Umbra task directory. The default '1' gives one marker per distinct site -- a fast whole-archive overview fit for a landing page. | 1 |
--out |
text | Output directory for the showcase site (index.html + map/explore pages). | Sentinel.UNSET |
--pmtiles |
text | Local whole-catalog .pmtiles basemap to include (copied in beside a MapLibre viewer). Mutually exclusive with --fetch-pmtiles. | None |
--fetch-pmtiles |
boolean | Download the published whole-catalog 'catalog.pmtiles' basemap into the showcase (the same artifact 'umbra tiles --fetch' pulls) instead of supplying one with --pmtiles. | False |
--pmtiles-url |
text | Override the --fetch-pmtiles asset URL. | None |
--unified |
boolean | Build ONE page instead of two: the explorer reads the .pmtiles archive directly, so it covers every acquisition (with the filters) and the separate map.html is dropped. Needs a basemap (--pmtiles / --fetch-pmtiles); the explorer's search options don't apply. | False |
--no-explore |
boolean | Skip building the interactive 'umbra demo' explorer page (a map-only showcase). By default an explorer is built from the gathered slice. | True |
--asset |
choice (GEC | CSI | SIDD | SICD | CPHD) |
Product the explorer's on-click 'Get SAR image' button streams. | GEC |
--no-lazy-imagery |
boolean | Build the explorer metadata-only (no on-click SAR overlay button). | True |
--featured |
integer | Precompute an artifact for this many repeat-imaged sites and show them as a gallery on the landing page. Needs the 'viz' extra and streams each scene's overview; 0 (the default) skips the gallery. | 0 |
--featured-view |
choice (change | timescan | swipe) |
What to precompute per featured site: 'change' (a 2/3-date composite), 'timescan' (the whole series collapsed to temporal statistics, needs 3+ passes) or 'swipe' (an interactive before/after page linked from the gallery). | change |
--featured-area |
text | Curate a featured site by name instead of auto-selecting (repeatable, matched like --area). Implies --featured for the sites named. | Sentinel.UNSET |
--featured-frames |
choice (2 | 3) |
Passes per featured composite: 2 (green=new, magenta=gone) or 3 (temporal RGB). Applies to --featured-view change only. | 2 |
--featured-limit |
integer | Size of the candidate pool the auto-selected featured sites are chosen from (tasks are scanned in name order). | 1500 |
--narrate |
boolean | Precompute a vision-language reading of each featured 'change' site and bake it into the page (a summary under the tile + a JSON sidecar), so a visitor gets a plain-language 'what changed here' with no live model call and no key near the browser. Reads the same passes the composite shows. Needs the 'ai' + 'viz' extras and a model API key (ANTHROPIC_API_KEY, OPENROUTER_API_KEY, or OPENAI_API_KEY); without a key the narrations are skipped and the gallery still builds. Applies to --featured-view change. | False |
--narrate-model |
text | With --narrate, override the vision model (default: $UMBRA_NARRATE_MODEL, then the provider default). E.g. an OpenRouter id like 'anthropic/claude-3.5-sonnet'. | None |
--title |
text | Override the landing-page title. | None |
--tagline |
text | Override the landing-page one-line pitch. | None |
--updated |
text | Freshness stamp shown on the landing page (e.g. the index build date). | None |
--local |
boolean | Gather items from a prebuilt local index (see 'umbra index fetch' / 'umbra index build') instead of walking S3 live -- near-instant, the fast path for repeat renders. Only uses acquisitions already indexed. | False |
--index-db |
text | Path to the local index database to read (default: $UMBRA_INDEX_DB or ~/.cache/umbra-py/catalog.db). Implies --local. Named --index-db because --db already means the decibel stretch on render commands. | None |
--fuzzy |
boolean | Match --area loosely: word-order- and punctuation-independent and typo-tolerant (so 'utah centerfield' or 'centrfield' still reach 'Centerfield, Utah'). Deterministic, no model call; a strict superset of the substring match. | False |
--help, -h |
boolean | Show this message and exit. | False |
umbra sites¶
Rank the archive's most repeat-imaged sites -- where change detection has something to measure.
Umbra files every pass of an area under one task, so a site's coverage is how many dated passes share it; the best-covered are exactly the ones worth feeding to 'umbra change' / 'timescan' / 'stack'. This is the discovery step that answers which site before those verbs answer what changed there.
Each site reports its pass count, date span, revisit cadence, footprint and products; the pass line adds a 'usable' figure when fewer passes are differenceable together than exist (the largest same-polarization dated subset, since the analysis verbs refuse a mixed-polarization series), and its own span when that subset covers a narrower window than the whole range. The revisit line notes the usable series' own longest gap when it differs from the all-passes one, and the pol line names which polarization that usable series is when the site spans more than one. --json adds those as 'comparable_passes' / 'comparable_span_days' and the full usable-series cadence ('comparable_min_revisit_days' / 'comparable_median_revisit_days' / 'comparable_max_revisit_days'), 'comparable_polarizations' (the usable series' shared signature, where 'polarizations' lists every one present), all the pass URLs in 'hrefs' (oldest-first), and in 'comparable_hrefs' just that usable subset -- the selection to pipe straight into 'umbra change' / 'stack' without tripping the refusal.
--rank-by chooses the order. Two depth orders: 'passes' (the default) ranks by raw pass count; 'comparable' ranks by that usable-series depth instead, so a deeply-imaged single-polarization site is not outranked by a broader one whose passes a change verb cannot difference together (and --min-passes then counts that same usable depth, so '--rank-by comparable --min-passes 3' returns only sites whose differenceable series is at least three passes deep). Three temporal orders: 'recency' ranks by each site's newest pass (the still-active site to monitor or task, which a depth order buries under a deeper but dormant series), 'span' by each site's observation baseline (the site watched long enough for slow change to show), and 'cadence' by each site's typical revisit gap (tightest first -- the most-frequently-imaged site; the median gap, not the worst one, so a single outage does not bury an otherwise reliably-imaged series) -- ordering by the same figures --active-*, --min-span / --max-span and --median-revisit filter on, so the discovery answer ranks on every axis it filters on, not only on depth. A recently-active, long-baseline or tightly-revisited site outside the raw top-N is promoted rather than truncated first.
--active-since keeps only sites still imaged on or after a date (a recency filter on each site's newest pass), so a deep series that stopped long ago is dropped and an actively-revisited one is kept -- the discovery answer for "which repeat-imaged sites are still live monitoring targets?" It is orthogonal to --rank-by / --min-passes and, unlike --start (which truncates every series to a window), selects whole sites and keeps each survivor's full history. --active-before is the complement (sites last imaged on or before a date, i.e. dormant series); set both to select sites whose newest pass falls within a window.
--first-since / --first-before are the onset twins of the --active- pair: they gate each site's earliest pass rather than its newest, so --first-since keeps newly-appeared series (first imaged on or after a date) and --first-before keeps long-established ones (first imaged on or before it) -- set both to bound the onset to a window. Orthogonal to the --active- recency filters, since when a site started and whether it is still going are independent.
--max-revisit keeps only sites revisited at least that often -- a cadence filter on each site's worst-case gap (in days), so a series with any stretch longer than that between consecutive passes is dropped: the answer for "which sites are imaged often enough to monitor?" It counts the cadence --rank-by measures (the usable series' worst gap under --rank-by comparable) and is orthogonal to the --active-* recency filters. --median-revisit is its typical-cadence twin -- keep only sites whose median gap is at most that many days, so a site usually imaged often is kept even if a single stretch runs long: "usually imaged frequently" rather than "never blind for longer than N days". Set both to combine the two.
--min-span keeps only sites imaged over at least that long -- a baseline filter on each site's observation span (in days, first pass to last), so a series confined to a short window is dropped: the answer for slow change (subsidence, construction, deforestation) that needs a long window to show. It is a different axis from --max-revisit (cadence is the worst gap; span is the total baseline), counts the span --rank-by measures (the usable series' span under --rank-by comparable), and is orthogonal to the --active-* and --max-revisit filters. --max-span is its upper twin (sites imaged over at most that long, a short-lived series); set both to bound each site's baseline to a window, as --active-since / --active-before do the newest pass. Runs against the open bucket, a --local index, or the Canopy archive (--token) -- the same backends as 'umbra search'. With --local the whole index is ranked directly (a GROUP BY task), so a site's depth is measured across every indexed pass rather than the first --limit acquisitions.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--bbox |
text | Footprint filter: 'min_lon,min_lat,max_lon,max_lat'. | Sentinel.UNSET |
--place |
text | Geocode a place name (e.g. 'California', 'Tokyo') to a bounding box and rank sites within it, via OpenStreetMap Nominatim. Mutually exclusive with --bbox; the match is rectangular, so it can include nearby areas outside the named place. | None |
--intersects |
text | Keep only items whose footprint intersects this GeoJSON polygon -- a path to a .geojson file or an inline GeoJSON string (Polygon / MultiPolygon, or a Feature / FeatureCollection wrapping one). A tighter spatial filter than the rectangular --bbox; the two are mutually exclusive. | None |
--start |
text | Earliest acquisition date. Accepts YYYY-MM-DD, a year or month (2024, 2024-03), or a relative expression ('today', '3 months ago'). | Sentinel.UNSET |
--end |
text | Latest acquisition date (same formats as --start; a bare year, month or period like 'last month' snaps to that span's last day). | Sentinel.UNSET |
--area |
text | Case-insensitive name of an Umbra task/site to gather (e.g. 'Centerfield'). Faster than a broad scan -- it lists just that area's directory. | None |
--fuzzy |
boolean | Match --area loosely: word-order- and punctuation-independent and typo-tolerant (so 'utah centerfield' or 'centrfield' still reach 'Centerfield, Utah'). Deterministic, no model call; a strict superset of the substring match. | False |
--product |
choice (GEC | CSI | SIDD | SICD | CPHD) |
Only count passes exposing this asset (repeatable). | Sentinel.UNSET |
--pol |
text | Keep only items exposing this polarization (e.g. VV, HH; repeatable, case-insensitive, matches if the item has ANY of them). The filter that keeps a change comparison like-with-like -- HH and VV image different physics. Items with no polarization metadata are excluded. | Sentinel.UNSET |
--min-incidence |
float | Keep only items with view incidence angle >= this many degrees. Items missing an incidence angle are excluded. | None |
--max-incidence |
float | Keep only items with view incidence angle <= this many degrees. Items missing an incidence angle are excluded. | None |
--max-resolution |
float | Keep only items at least this fine: both range and azimuth resolution <= this many metres. Items missing a resolution are excluded. | None |
--limit |
integer | Size of the acquisition pool to rank sites from on the live / --token path: bigger finds more sites and deeper series but scans more of the archive; --area narrows it. Ignored with --local, which ranks the whole index directly (a GROUP BY task, so a site's depth is never capped). | 500 |
--top |
integer | Number of best-covered sites to report. | 20 |
--min-passes |
integer | Passes a site needs to qualify (2 is the minimum a change composite can use; raise it to find only deeply-revisited series). Counts the depth --rank-by measures: raw passes by default, the usable (comparable) series' depth under --rank-by comparable. | 2 |
--rank-by |
choice (passes | comparable | recency | span | cadence) |
What to order sites by. Depth: 'passes' (raw pass count) or 'comparable' (the usable series' depth -- the largest same-polarization dated subset a change verb can difference -- so a deep single-polarization site is not outranked by a broader mixed one). Temporal: 'recency' (newest pass first -- the still-active site to monitor or task), 'span' (longest baseline first -- the site watched long enough for slow change to show) or 'cadence' (tightest typical revisit first -- the most-frequently-imaged site; the median gap, not the worst one), ordering by the same figures --active-*, --min-span / --max-span and --median-revisit filter on. --min-passes still qualifies on depth. | passes |
--active-since |
text | Keep only sites still imaged ON OR AFTER this date -- a recency filter on each site's newest pass, so a deep series that stopped long ago is dropped and an actively-revisited one is kept. Accepts an ISO date, a bare year/month, or a relative expression ('6 months ago'). Unlike --start (which truncates every series to a window), this selects whole sites by recency and keeps each survivor's full history. | None |
--active-before |
text | Keep only sites last imaged ON OR BEFORE this date -- the complement of --active-since, selecting dormant series that stopped imaging. Set both to find sites whose newest pass falls WITHIN a window. Same grammar as --active-since, but a bare year/month covers the whole period (--active-before 2024 is 'last imaged on or before 2024-12-31'), symmetric with --end. | None |
--first-since |
text | Keep only sites FIRST imaged ON OR AFTER this date -- an onset filter on each site's earliest pass, selecting newly-appeared series (ones that entered the archive recently), where --active-since gates the newest pass (still live). Same grammar as --active-since. Orthogonal to the --active-* recency filters: a site can be new and still active, or new and already dormant. | None |
--first-before |
text | Keep only sites FIRST imaged ON OR BEFORE this date -- the complement of --first-since (and the onset twin of --active-before), selecting long-established series watched since before then. Set both to bound the onset to a window (--first-since A --first-before B keeps sites first imaged between A and B). A bare year/month covers the whole period (--first-before 2024 is 'first imaged on or before 2024-12-31'), symmetric with --active-before / --end. | None |
--max-revisit |
float | Keep only sites revisited AT LEAST this often -- a cadence filter on each site's worst-case revisit gap (in days), so a series with any stretch longer than this between consecutive passes is dropped and a reliably-imaged one is kept: the discovery answer for 'which sites are imaged often enough to monitor?' Counts the cadence --rank-by measures: the whole dated series by default, the usable (comparable) series' worst gap under --rank-by comparable. Orthogonal to --active-since / --active-before. | None |
--median-revisit |
float | Keep only sites TYPICALLY revisited at least this often -- a cadence filter on each site's MEDIAN gap (in days), so a site usually imaged often is kept even if a single stretch runs long, where --max-revisit drops it the moment any gap exceeds the bound. The answer for 'usually imaged frequently' rather than 'never blind for longer than N days'; set both to combine the two. Counts the cadence --rank-by measures (the usable series' typical gap under --rank-by comparable). Orthogonal to --active-since / --active-before. | None |
--min-span |
float | Keep only sites imaged over AT LEAST this long -- a baseline filter on each site's observation span (in days, first pass to last), so a series confined to a short window is dropped and a long-baseline one kept: the discovery answer for slow change (subsidence, construction, deforestation) that needs a long window to show. A different axis from --max-revisit (cadence is the worst gap; span is the total baseline). Counts the span --rank-by measures: the whole dated series by default, the usable (comparable) series' span under --rank-by comparable. Orthogonal to --active-since / --active-before / --max-revisit. | None |
--max-span |
float | Keep only sites imaged over AT MOST this long -- the upper twin of --min-span, selecting a short-lived series (a burst of imaging, now over) rather than a long-baseline one. Set with --min-span to bound the baseline to a window (--min-span A --max-span B keeps sites whose span is between A and B days), as --active-since / --active-before bound the newest pass. Counts the span --rank-by measures (the usable series' span under --rank-by comparable). | None |
--json |
boolean | Emit one SiteCoverage JSON object per line. | False |
--local |
boolean | Gather items from a prebuilt local index (see 'umbra index fetch' / 'umbra index build') instead of walking S3 live -- near-instant, the fast path for repeat renders. Only uses acquisitions already indexed. | False |
--index-db |
text | Path to the local index database to read (default: $UMBRA_INDEX_DB or ~/.cache/umbra-py/catalog.db). Implies --local. Named --index-db because --db already means the decibel stretch on render commands. | None |
--token |
text | Canopy API token. When given, gather items from Umbra's authenticated COMMERCIAL archive (a real STAC API) instead of the open bucket — the same flags, over the paid catalog. Falls back to $UMBRA_CANOPY_TOKEN. Mutually exclusive with --local / --index-db. | None |
--help, -h |
boolean | Show this message and exit. | False |
umbra stack¶
Co-register a site's acquisitions into one analysis-ready datacube.
The time-series half of umbra load, and the step between search and
analysis: several passes over one site are warped onto one shared grid
and written as a multi-band float32 GeoTIFF -- one band per acquisition,
oldest first, each described by its timestamp. Because the bands are
pixel-aligned, band arithmetic is an honest per-ground-cell comparison, so
the file drops straight into QGIS, GDAL, rioxarray or a notebook.
The grid is lon/lat (EPSG:4326) by default, whose cells stretch with latitude; pass --crs utm (or any CRS) when cells have to be equal-area, e.g. to turn a count of changed cells into an area.
Where umbra change and umbra timescan render this comparison as a
picture, this writes the numbers. In Python, umbra_py.to_stack
returns the same cube as an xarray DataArray with a real time
dimension (cube.mean("time"), cube.diff("time"), ...).
--stats reduces the cube to a JSON summary instead of making you open it: each pass's distribution, the signed decibel change against the pass before it, and the net first-to-last change -- including how much ground moved, in km2, when --crs makes the cells equal-area. Give it without --out to measure a site without writing the file.
--blocks N adds the spatial half of that answer: the scene is cut into an N x N grid and each block reports its own net change, a compass label and lon/lat centre to find it by, and the consecutive pair of passes it moved most between -- so a change confined to one corner, which a scene-wide mean hides, reads as "the northeast brightened, between these two passes". --block-series keeps each block's whole sequence rather than just that peak, which is what distinguishes a steady drift from a single step.
--speckle-filter averages down the one uncertainty every number above otherwise carries. A single-look SAR pixel's power scatters about its surface's true backscatter as widely as its own mean, so an unfiltered cell-to-cell difference is mostly interference; averaging is the only correction, and this is the only place it reaches the published GEC rasters ('umbra convert --speckle-filter' filters complex products before geocoding, so it never sees them). Each pass is filtered on the shared grid, and the cube records the filter and its window -- so --stats states both halves of the trade (less noisy cells, coarser resolution) and a later stack refuses to difference this cube against an unfiltered one.
--lazy lifts the ceiling on how much series fits: passes are read on demand (one dask chunk each) and written or measured a slice at a time, so peak memory follows --max-size instead of the number of acquisitions. Reach for it when a long series would otherwise have to be stacked coarse; the cube and the statistics are the same either way. --chunk-size N lifts what is left of that ceiling: each pass is cut into N-square windows read and written independently, so --max-size stops being bounded by how much of one scene fits in memory (at one read per window rather than per pass). --stats-windowed gives the measurement the same lift: the reduction walks those windows instead of whole passes, so a cube sharper than a slice you can hold is measurable and not only writable. The trade is stated in the output -- every count, mean and change number stays exact, while each pass's median/p5/p95 become histogram estimates, since a percentile is the one statistic that needs the whole pass at once.
--provenance answers, before anything is streamed, the question a stack otherwise answers by failing: a series whose passes were converted with different settings is not a measurement, because a cell-to-cell difference between them is partly the difference between the two conversions. It reads each source's UMBRA_* record straight from the raster header, groups the selection by what its pixel values are, and -- when they disagree -- names the largest agreeing subset and the URLs to re-run on, which is the advice the refusal could only give in the abstract.
--pick-interval answers the question a long series poses before you can render it: which two of these passes is the change worth looking at between? A picture past three dates encodes nothing to separate, so the pair has to be chosen first -- and by a number, not a model. It reduces the cube and returns the one consecutive interval whose measured change stands furthest clear of the speckle detection floor, with the two URLs ready to hand to 'umbra change --narrate'. That is the scan half of scan -> narrate, deterministic end to end.
Two ways to choose what to stack:
- Pass 2+ STAC JSON URLs directly (order doesn't matter).
- Or search: give --area (or --bbox / --place) with --start/--end and the command gathers a site's acquisitions automatically.
Stack one polarization: mixing VV and VH puts a polarization difference on
the time axis where you'll read it as change (--pol filters the search).
Only downsampled overviews are streamed via HTTP range requests -- no full
download. Requires the load extra (pip install "umbra-py[load]").
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--out |
text | Output multi-band GeoTIFF path (one band per acquisition, oldest first). Required unless --stats asks for the statistics alone. | None |
--stats |
boolean | Also print the cube's time-series statistics as JSON: per-pass distribution, the decibel change between consecutive passes, and the net first-to-last change (with the changed area in km2 under --crs utm). Pass it without --out to measure without writing a file. See docs/schemas/stack-stats.schema.json. | False |
--blocks |
integer | Break the statistics down over a N x N grid of the scene: each block reports its own net change and the pair of passes it moved most between. Implies --stats; 6 is a good starting grid. | 0 |
--block-series |
boolean | With --blocks: report each block's whole pass-to-pass sequence, not only the interval it moved most in -- so a block that drifted every pass reads differently from one that jumped once and held. | False |
--stats-windowed |
boolean | Implies --stats: measure the cube one window at a time (the windows --chunk-size cut it into) instead of one whole pass at a time, so a cube too big to hold a slice of can still be measured. Every count, mean and change number stays exact; each pass's median/p5/p95 become histogram estimates, and the output says so. | False |
--change-threshold-db |
float | With --stats: how many decibels a cell must move between two passes to count as changed (3 dB is a doubling of backscatter power). | 3.0 |
--provenance |
boolean | Don't stack: read each acquisition's conversion record from its raster header and group the selection by what its pixel values are, so a series that cannot be measured says so before anything is warped or streamed. Names the largest agreeing subset (with URLs to re-run on) when the selection is mixed. See docs/schemas/stack-provenance.schema.json. | False |
--pick-interval |
boolean | Don't write a cube: scan the whole series and print the one consecutive pass-pair whose measured change stands furthest clear of the speckle detection floor -- the pair worth looking at first, and the two URLs to hand to 'umbra change --narrate'. A number picks the frames, not a model. Prints the interval's ids, datetimes, changed fraction and signed dB delta, the cube's false-alarm floor, and whether the change stands clear of it. Uses --change-threshold-db, --asset, --max-size, --extent and --crs. | False |
--area |
text | Search mode: name of an Umbra site (e.g. 'Centerfield') to gather automatically instead of passing URLs. Combine with --start/--end to bound the time range. | None |
--bbox |
text | Search mode: footprint filter 'min_lon,min_lat,max_lon,max_lat'. | Sentinel.UNSET |
--place |
text | Search mode: geocode a place name (e.g. 'California', 'Tokyo') to a bounding box and stack within it, via OpenStreetMap Nominatim. Mutually exclusive with --bbox; the match is rectangular, so it can include nearby areas outside the named place. | None |
--intersects |
text | Keep only items whose footprint intersects this GeoJSON polygon -- a path to a .geojson file or an inline GeoJSON string (Polygon / MultiPolygon, or a Feature / FeatureCollection wrapping one). A tighter spatial filter than the rectangular --bbox; the two are mutually exclusive. | None |
--start |
text | Search mode: earliest acquisition date. YYYY-MM-DD, a year/month (2024, 2024-03), or relative ('3 months ago', 'last month'). | Sentinel.UNSET |
--end |
text | Search mode: latest acquisition date (same formats as --start). | Sentinel.UNSET |
--max-search |
integer | Search mode: cap how many acquisitions the search pulls into the stack. | 50 |
--asset |
choice (GEC | CSI | SIDD | SICD | CPHD) |
Which product to stack. GEC (the geocoded GeoTIFF) is the sensible default; CSI also works. The complex SICD/CPHD products aren't amplitude rasters. | GEC |
--clip-bbox |
text | Clip the cube to a lon/lat window 'min_lon,min_lat,max_lon,max_lat' inside whatever --extent selected. (Distinct from --bbox, which filters which acquisitions the search returns.) | None |
--max-size |
integer | Longest side of the shared grid in pixels. Larger is sharper but fetches more bytes (~quadratic), and the grid is shared by every band. | 1024 |
--extent |
choice (intersection | union) |
intersection: only ground every acquisition covers, so no cell has a gap. union: all ground any acquisition covers, NaN outside each scene. | intersection |
--crs |
text | CRS of the shared output grid. Default is lon/lat (EPSG:4326), whose cells are not equal-area; 'utm' picks the UTM zone the site falls in so every cell covers the same ground (what area measurements need). Any CRS string also works (e.g. EPSG:32633). --clip-bbox stays lon/lat either way. | None |
--db |
boolean | Stack the decibel (log-amplitude) scale -- the radiometrically meaningful scale for differencing, where a backscatter ratio becomes a subtraction. | False |
--speckle-filter |
choice (boxcar | lee) |
Average speckle down in every pass, on the shared grid, before the cube is assembled. Speckle is the interference pattern coherent illumination makes on a rough surface, so a single look's power scatters about the surface's true backscatter as widely as its own mean -- which makes it the dominant uncertainty in every number --stats reports, and a cell-by-cell difference between two passes mostly interference rather than change. 'boxcar' averages the window unconditionally (the multilook); 'lee' averages only where the window is no more variable than speckle alone explains, so edges and points survive. Not a default: what it spends is resolution. The cube records the filter and its window, so the statistics state the trade and a later stack refuses to difference it against an unfiltered cube. (Filters the published GEC rasters too, which 'umbra convert --speckle-filter' cannot reach.) | None |
--speckle-window |
integer | Edge of the odd, centred window --speckle-filter averages over, in cells of the shared grid (so --max-size decides what it covers on the ground). Wider removes more speckle and more detail; it costs no more to compute. | 5 |
--lazy |
boolean | Read each pass on demand (one dask chunk per acquisition) instead of holding the whole cube in memory, and write/measure it a slice at a time. Same output; peak memory is set by --max-size rather than by how many acquisitions the series has, so a long series can be stacked sharp. Needs the dask extra: pip install "umbra-py[dask]". | False |
--chunk-size |
integer | With --lazy, cut each pass into CHUNK_SIZE-square windows read (and written) independently, so a single pass no longer has to fit in memory either. Costs one read per window instead of one per pass, so keep it a decent fraction of --max-size (e.g. 1024). Same output -- including under --speckle-filter, where each window is read with a half-window halo so the filter never sees a truncated window at a chunk edge. | None |
--local |
boolean | Gather items from a prebuilt local index (see 'umbra index fetch' / 'umbra index build') instead of walking S3 live -- near-instant, the fast path for repeat renders. Only uses acquisitions already indexed. | False |
--index-db |
text | Path to the local index database to read (default: $UMBRA_INDEX_DB or ~/.cache/umbra-py/catalog.db). Implies --local. Named --index-db because --db already means the decibel stretch on render commands. | None |
--token |
text | Canopy API token. When given, gather items from Umbra's authenticated COMMERCIAL archive (a real STAC API) instead of the open bucket — the same flags, over the paid catalog. Falls back to $UMBRA_CANOPY_TOKEN. Mutually exclusive with --local / --index-db. | None |
--fuzzy |
boolean | Match --area loosely: word-order- and punctuation-independent and typo-tolerant (so 'utah centerfield' or 'centrfield' still reach 'Centerfield, Utah'). Deterministic, no model call; a strict superset of the substring match. | False |
--json |
boolean | Emit a machine-readable {output, items_used, parameters} manifest on stdout instead of the human 'Wrote ...' line. Progress and warnings stay on stderr, so stdout is the JSON object alone. | False |
--pol |
text | Keep only items exposing this polarization (e.g. VV, HH; repeatable, case-insensitive, matches if the item has ANY of them). The filter that keeps a change comparison like-with-like -- HH and VV image different physics. Items with no polarization metadata are excluded. | Sentinel.UNSET |
--min-incidence |
float | Keep only items with view incidence angle >= this many degrees. Items missing an incidence angle are excluded. | None |
--max-incidence |
float | Keep only items with view incidence angle <= this many degrees. Items missing an incidence angle are excluded. | None |
--max-resolution |
float | Keep only items at least this fine: both range and azimuth resolution <= this many metres. Items missing a resolution are excluded. | None |
--help, -h |
boolean | Show this message and exit. | False |
umbra swipe¶
Render an interactive before/after swipe map of two SAR passes.
Drag the divider to wipe one acquisition over the other across the same ground: SAR backscatter is stable between passes, so anything that changed -- a ship that docked, a field that flooded, a building that rose -- snaps in and out as you sweep the seam. The output is a single self-contained HTML file.
Two ways to choose what to compare:
- Pass exactly two STAC JSON URLs, in chronological order (before after).
- Or search: give --area (or --bbox / --place / --intersects) with --start/--end and the command gathers a site's acquisitions and compares the earliest with the latest (preferring a single polarization).
Only downsampled overviews are streamed via HTTP range requests -- no full
download. Requires the viz extra (pip install "umbra-py[viz]").
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--out |
text | Output HTML file for the interactive swipe map. | Sentinel.UNSET |
--area |
text | Search mode: name of an Umbra site (e.g. 'Centerfield') to gather automatically instead of passing two URLs. Combine with --start/--end to bound the time range; the earliest and latest passes are compared. | None |
--bbox |
text | Search mode: footprint filter 'min_lon,min_lat,max_lon,max_lat'. | Sentinel.UNSET |
--place |
text | Geocode a place name (e.g. 'California', 'Tokyo') to a bounding box and gather within it, via OpenStreetMap Nominatim. Mutually exclusive with --bbox; the match is rectangular, so it can include nearby areas outside the named place. | None |
--intersects |
text | Keep only items whose footprint intersects this GeoJSON polygon -- a path to a .geojson file or an inline GeoJSON string (Polygon / MultiPolygon, or a Feature / FeatureCollection wrapping one). A tighter spatial filter than the rectangular --bbox; the two are mutually exclusive. | None |
--start |
text | Search mode: earliest acquisition date. YYYY-MM-DD, a year/month (2024, 2024-03), or relative ('3 months ago', 'last month'). | Sentinel.UNSET |
--end |
text | Search mode: latest acquisition date (same formats as --start). | Sentinel.UNSET |
--max-search |
integer | Search mode: cap how many acquisitions the search pulls. | 50 |
--asset |
choice (GEC | CSI | SIDD | SICD | CPHD) |
Which product to compare. GEC (the detected GeoTIFF) is the sensible default; CSI also works. The complex SICD/CPHD products aren't amplitude rasters. | GEC |
--max-size |
integer | Max pixel dimension of each overlay. Larger is sharper but fetches more bytes (~quadratic). | 1024 |
--db |
boolean | Use a decibel (log-amplitude) stretch -- the radiometrically-correct SAR look. Reveals texture and structure the default linear stretch crushes toward black. | False |
--percentile |
text | Low,high percentile cut for each overlay's contrast stretch. | 2,98 |
--local |
boolean | Gather items from a prebuilt local index (see 'umbra index fetch' / 'umbra index build') instead of walking S3 live -- near-instant, the fast path for repeat renders. Only uses acquisitions already indexed. | False |
--index-db |
text | Path to the local index database to read (default: $UMBRA_INDEX_DB or ~/.cache/umbra-py/catalog.db). Implies --local. Named --index-db because --db already means the decibel stretch on render commands. | None |
--token |
text | Canopy API token. When given, gather items from Umbra's authenticated COMMERCIAL archive (a real STAC API) instead of the open bucket — the same flags, over the paid catalog. Falls back to $UMBRA_CANOPY_TOKEN. Mutually exclusive with --local / --index-db. | None |
--fuzzy |
boolean | Match --area loosely: word-order- and punctuation-independent and typo-tolerant (so 'utah centerfield' or 'centrfield' still reach 'Centerfield, Utah'). Deterministic, no model call; a strict superset of the substring match. | False |
--json |
boolean | Emit a machine-readable {output, items_used, parameters} manifest on stdout instead of the human 'Wrote ...' line. Progress and warnings stay on stderr, so stdout is the JSON object alone. | False |
--pol |
text | Keep only items exposing this polarization (e.g. VV, HH; repeatable, case-insensitive, matches if the item has ANY of them). The filter that keeps a change comparison like-with-like -- HH and VV image different physics. Items with no polarization metadata are excluded. | Sentinel.UNSET |
--min-incidence |
float | Keep only items with view incidence angle >= this many degrees. Items missing an incidence angle are excluded. | None |
--max-incidence |
float | Keep only items with view incidence angle <= this many degrees. Items missing an incidence angle are excluded. | None |
--max-resolution |
float | Keep only items at least this fine: both range and azimuth resolution <= this many metres. Items missing a resolution are excluded. | None |
--help, -h |
boolean | Show this message and exit. | False |
umbra tiles¶
Tile the whole catalog into a single-file PMTiles vector archive.
Where 'umbra map' and 'umbra demo' embed every footprint in the page (great up to a few thousand items), this pre-cuts the catalog into a vector tile pyramid so a map fetches only the tiles in view -- the fast, zoom-anywhere whole-archive answer. Each acquisition is tiled as a centroid at every zoom and (unless --no-footprints) as its clipped footprint polygon from --footprint-min-zoom down, so zooming in shows coverage shape. Each feature also references its --cog-asset cloud-optimized GeoTIFF, so a viewer over the archive ('umbra demo --pmtiles') can stream the actual radar picture on click. The output is one .pmtiles file: drop it on GitHub Pages or in a bucket, no tile server. With --viewer it also writes a MapLibre GL page that renders it.
Skip the tiling entirely with --fetch: the weekly index workflow publishes a ready-made whole-catalog 'catalog.pmtiles' on the catalog-index release, so a fresh install gets the same basemap with no crawl and no index -- the visual sibling of 'umbra index fetch'.
Needs no extra: the encoder is pure standard library, and the viewer's map runs browser-side from pinned CDNs. Use --local for a near-instant build from a prebuilt index.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--bbox |
text | Footprint filter: 'min_lon,min_lat,max_lon,max_lat'. | Sentinel.UNSET |
--place |
text | Geocode a place name to a bounding box and tile items within it, via OpenStreetMap Nominatim. Mutually exclusive with --bbox. | None |
--intersects |
text | Keep only items whose footprint intersects this GeoJSON polygon -- a path to a .geojson file or an inline GeoJSON string (Polygon / MultiPolygon, or a Feature / FeatureCollection wrapping one). A tighter spatial filter than the rectangular --bbox; the two are mutually exclusive. | None |
--start |
text | Earliest acquisition date (YYYY-MM-DD, a year/month, or a relative expression like '3 months ago'). | Sentinel.UNSET |
--end |
text | Latest acquisition date (same formats as --start; a bare year/month snaps to that span's last day). | Sentinel.UNSET |
--area |
text | Case-insensitive name of an Umbra task/site to tile (e.g. 'Centerfield'). Faster than a broad scan. | None |
--product |
choice (GEC | CSI | SIDD | SICD | CPHD) |
Keep items exposing this asset (repeatable). | Sentinel.UNSET |
--limit |
integer | Max acquisitions to tile (default: all). The whole catalog is the point of tiling, so leave unset with --local for the full archive. | None |
--max-per-task |
integer | Cap items per Umbra task directory ('--max-per-task 1' tiles one point per distinct site). | None |
--out |
text | Output PMTiles archive (e.g. catalog.pmtiles). Required unless --fetch is given, where it is the download destination (default: the cached basemap path beside the index). | None |
--fetch |
boolean | Skip tiling: download the prebuilt whole-catalog basemap published weekly on the catalog-index release (no crawl, no index needed). Writes to --out if given, else the default cache path. | False |
--url |
text | With --fetch, override the release asset URL (advanced -- e.g. to pull from a fork). | None |
--min-zoom |
integer | Lowest zoom level to generate (world view). | 0 |
--max-zoom |
integer | Highest zoom level to generate. 9 reaches city scale, where SAR sites read individually; raise it for denser sites at the cost of a larger file. | 9 |
--footprints / --no-footprints |
boolean | Also tile each acquisition's footprint polygon (clipped per tile) so a zoomed-in map shows coverage shape, not just a marker. --no-footprints writes a smaller centroids-only archive. | True |
--footprint-min-zoom |
integer | Lowest zoom carrying footprint polygons. Below it a footprint is sub-pixel, so tiling it only inflates the tiles a viewer loads first. | 6 |
--cog-asset |
choice (GEC | CSI | SIDD | SICD | CPHD) |
Product whose cloud-optimized GeoTIFF each tiled acquisition references, so a viewer ('umbra demo --pmtiles') can stream the picture on click. GEC is the detected, map-projected GeoTIFF; CSI also works. | GEC |
--no-cog |
boolean | Tile metadata only, with no image reference (a smaller archive whose viewers show no 'Get SAR image' button). | False |
--viewer |
text | Also write a self-contained MapLibre GL viewer HTML that renders the archive (points the page at the .pmtiles by its filename, so host them side by side). | None |
--local |
boolean | Gather items from a prebuilt local index (see 'umbra index fetch' / 'umbra index build') instead of walking S3 live -- near-instant, the fast path for repeat renders. Only uses acquisitions already indexed. | False |
--index-db |
text | Path to the local index database to read (default: $UMBRA_INDEX_DB or ~/.cache/umbra-py/catalog.db). Implies --local. Named --index-db because --db already means the decibel stretch on render commands. | None |
--fuzzy |
boolean | Match --area loosely: word-order- and punctuation-independent and typo-tolerant (so 'utah centerfield' or 'centrfield' still reach 'Centerfield, Utah'). Deterministic, no model call; a strict superset of the substring match. | False |
--help, -h |
boolean | Show this message and exit. | False |
umbra timescan¶
Collapse a whole SAR time series into one temporal-statistics image.
Where umbra change compares 2-3 dates, this summarises the entire
stack of a site's acquisitions per pixel and maps the statistics to color:
- red = average backscatter
- green = peak backscatter
- blue = temporal variability (standard deviation)
Stable terrain renders gray/yellow; anything that came and went across the series -- ships cycling through a berth, vehicles in a lot, a field flooding -- glows blue/cyan. The whole archive of a site becomes one glanceable "where did activity happen" picture.
Two ways to choose what to summarise:
- Pass 3+ STAC JSON URLs directly (order doesn't matter).
- Or search: give --area (or --bbox / --place) with --start/--end and the command gathers a site's acquisitions automatically (preferring a single polarization).
Only downsampled overviews are streamed via HTTP range requests -- no full
download. Requires the viz extra (pip install "umbra-py[viz]").
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--out |
text | Output image file (.png/.jpg) for the temporal-statistics composite. | Sentinel.UNSET |
--area |
text | Search mode: name of an Umbra site (e.g. 'Centerfield') to gather automatically instead of passing URLs. Combine with --start/--end to bound the time range. | None |
--bbox |
text | Search mode: footprint filter 'min_lon,min_lat,max_lon,max_lat'. | Sentinel.UNSET |
--place |
text | Search mode: geocode a place name (e.g. 'California', 'Tokyo') to a bounding box and summarise within it, via OpenStreetMap Nominatim. Mutually exclusive with --bbox; the match is rectangular, so it can include nearby areas outside the named place. | None |
--intersects |
text | Keep only items whose footprint intersects this GeoJSON polygon -- a path to a .geojson file or an inline GeoJSON string (Polygon / MultiPolygon, or a Feature / FeatureCollection wrapping one). A tighter spatial filter than the rectangular --bbox; the two are mutually exclusive. | None |
--start |
text | Search mode: earliest acquisition date. YYYY-MM-DD, a year/month (2024, 2024-03), or relative ('3 months ago', 'last month'). | Sentinel.UNSET |
--end |
text | Search mode: latest acquisition date (same formats as --start). | Sentinel.UNSET |
--max-search |
integer | Search mode: cap how many acquisitions the search pulls into the stack. | 50 |
--asset |
choice (GEC | CSI | SIDD | SICD | CPHD) |
Which product to summarise. GEC (the detected GeoTIFF) is the sensible default; CSI also works. The complex SICD/CPHD products aren't amplitude rasters. | GEC |
--max-size |
integer | Max pixel dimension of the shared grid. Larger is sharper but fetches more bytes (~quadratic). | 2048 |
--db |
boolean | Summarise in the decibel (log-amplitude) domain -- the radiometrically-correct SAR look, measuring variability in log space. | False |
--percentile |
text | Low,high percentile cut for each statistic's contrast stretch. | 2,98 |
--local |
boolean | Gather items from a prebuilt local index (see 'umbra index fetch' / 'umbra index build') instead of walking S3 live -- near-instant, the fast path for repeat renders. Only uses acquisitions already indexed. | False |
--index-db |
text | Path to the local index database to read (default: $UMBRA_INDEX_DB or ~/.cache/umbra-py/catalog.db). Implies --local. Named --index-db because --db already means the decibel stretch on render commands. | None |
--token |
text | Canopy API token. When given, gather items from Umbra's authenticated COMMERCIAL archive (a real STAC API) instead of the open bucket — the same flags, over the paid catalog. Falls back to $UMBRA_CANOPY_TOKEN. Mutually exclusive with --local / --index-db. | None |
--fuzzy |
boolean | Match --area loosely: word-order- and punctuation-independent and typo-tolerant (so 'utah centerfield' or 'centrfield' still reach 'Centerfield, Utah'). Deterministic, no model call; a strict superset of the substring match. | False |
--json |
boolean | Emit a machine-readable {output, items_used, parameters} manifest on stdout instead of the human 'Wrote ...' line. Progress and warnings stay on stderr, so stdout is the JSON object alone. | False |
--pol |
text | Keep only items exposing this polarization (e.g. VV, HH; repeatable, case-insensitive, matches if the item has ANY of them). The filter that keeps a change comparison like-with-like -- HH and VV image different physics. Items with no polarization metadata are excluded. | Sentinel.UNSET |
--min-incidence |
float | Keep only items with view incidence angle >= this many degrees. Items missing an incidence angle are excluded. | None |
--max-incidence |
float | Keep only items with view incidence angle <= this many degrees. Items missing an incidence angle are excluded. | None |
--max-resolution |
float | Keep only items at least this fine: both range and azimuth resolution <= this many metres. Items missing a resolution are excluded. | None |
--help, -h |
boolean | Show this message and exit. | False |
umbra view¶
Explore one SAR scene at full resolution in an interactive web viewer.
Starts a local tile server and opens a Leaflet map in the browser. Pan and
zoom to roam the acquisition's cloud-optimized GeoTIFF at native resolution
-- only the tiles in view are streamed via HTTP range requests and warped
onto the web map, so there's no full download. Where umbra quicklook
collapses the scene to one downsampled PNG, this keeps every pixel a zoom
away. Runs until you press Ctrl-C. Requires the viz extra
(pip install "umbra-py[viz]").
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--asset |
choice (GEC | CSI | SIDD | SICD | CPHD) |
Which product to view. GEC (the geocoded GeoTIFF) is the sensible default; CSI also works. The complex SICD/CPHD products aren't amplitude rasters. | GEC |
--host |
text | Host to bind the server to. | 127.0.0.1 |
--port |
integer | Port to bind (0 picks a free one). | 0 |
--db |
boolean | Use a decibel (log-amplitude) stretch -- the radiometrically-correct SAR look. Reveals terrain texture and structure that the default linear stretch crushes toward black. | False |
--colormap |
text | Matplotlib colormap for a pseudo-colored view (e.g. viridis, magma, inferno). Default is grayscale. | None |
--percentile |
text | Low,high percentile cut for the global contrast stretch. | 2,98 |
--no-browser |
boolean | Don't open the viewer in a browser. | False |
--help, -h |
boolean | Show this message and exit. | False |
umbra watch¶
Report only acquisitions new since the last run -- standing site monitoring.
SAR re-images a site pass after pass, so the natural way to monitor one is to run the same search on a schedule and act only on what's new. This command is that primitive: it searches, compares against what previous runs already reported (state kept in a local SQLite database), prints only the new acquisitions, and remembers them. It is idempotent -- an immediate re-run with no newly published data reports nothing -- so cron, a GitHub Action, or an agent loop can supply the schedule and this supplies the delta.
Pair it with 'umbra change --narrate' or 'umbra describe' for a standing analyst: new pass lands -> composite against the previous pass -> narration.
Usage:
Options:
| Name | Type | Description | Default |
|---|---|---|---|
--bbox |
text | Footprint filter: 'min_lon,min_lat,max_lon,max_lat'. | Sentinel.UNSET |
--place |
text | Geocode a place name to a bounding box to watch (mutually exclusive with --bbox). | None |
--intersects |
text | Keep only items whose footprint intersects this GeoJSON polygon -- a path to a .geojson file or an inline GeoJSON string (Polygon / MultiPolygon, or a Feature / FeatureCollection wrapping one). A tighter spatial filter than the rectangular --bbox; the two are mutually exclusive. | None |
--start |
text | Earliest acquisition date (YYYY-MM-DD, a year/month, or a relative expression like '3 months ago'). Same formats as 'umbra search'. | Sentinel.UNSET |
--end |
text | Latest acquisition date (same formats as --start). | Sentinel.UNSET |
--product |
choice (GEC | CSI | SIDD | SICD | CPHD) |
Watch only acquisitions exposing this asset (repeatable). | Sentinel.UNSET |
--area |
text | Name of an Umbra task/site to watch (e.g. 'Centerfield'). The usual way to monitor one site -- it lists just that task, so a scheduled check is fast. | None |
--fuzzy |
boolean | Match --area loosely: word-order- and punctuation-independent and typo-tolerant (so 'utah centerfield' or 'centrfield' still reach 'Centerfield, Utah'). Deterministic, no model call; a strict superset of the substring match. | False |
--limit |
integer | Cap acquisitions inspected per run (default: no cap -- watch everything in scope). | None |
--name |
text | Stable identifier for this watch's state. Defaults to a slug derived from the query, so repeat runs of the same search line up automatically; set it explicitly to run several distinct watches over overlapping areas. | None |
--state-db |
text | SQLite database that stores this watch's memory of already-reported acquisitions (default: $UMBRA_INDEX_DB or ~/.cache/umbra-py/catalog.db). Reuses the catalog index's metadata table; the acquisition rows are untouched. | None |
--local |
boolean | Search a prebuilt local index instead of walking S3 live -- e.g. to diff two index snapshots. The default (live) is usually what you want, since monitoring is about newly published acquisitions. | False |
--index-db |
text | Path to the local index to search when --local is set (default: the same catalog.db as --state-db). Implies --local. | None |
--reset |
boolean | Forget this watch's prior state and re-establish a baseline -- every acquisition found this run is reported as new. | False |
--exit-code |
boolean | Exit 10 when there are new acquisitions and 0 when there are none, so a scheduler's shell 'if' can branch without parsing output. | False |
--json |
boolean | Emit the delta as JSON (see docs/schemas/watch-delta.schema.json). | False |
--help, -h |
boolean | Show this message and exit. | False |