Skip to content

CLI reference

Every umbra command and flag below is generated directly from the installed Click application, so it always matches umbra --help.

umbra --help

umbra

umbra-py: discover, download and work with Umbra open SAR data.

Usage:

umbra [OPTIONS] COMMAND [ARGS]...

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:

umbra ask [OPTIONS] QUESTION

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 '.narration.json' (needs the ai extra and a model API key).

Only downsampled overviews are streamed via HTTP range requests -- no full download. Requires the viz extra (pip install "umbra-py[viz]").

Usage:

umbra change [OPTIONS] [ITEM_URLS]...

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 '.narration.json' sidecar and prints the reading. Needs the 'ai' extra and a model API key (ANTHROPIC_API_KEY, OPENROUTER_API_KEY, or OPENAI_API_KEY). 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:

umbra chips [OPTIONS] [ITEM_URLS]...

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:

umbra context [OPTIONS]

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:

umbra convert [OPTIONS] SRC [DST]

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:

umbra demo [OPTIONS]

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:

umbra describe [OPTIONS] ITEM_URL

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:

umbra download [OPTIONS] ITEM_URL

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 ' finds acquisitions that look like a given one and 'umbra embed search "a flooded field"' finds them from a text description (with a joint CLIP-family model).

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:

umbra embed [OPTIONS] COMMAND [ARGS]...

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:

umbra embed build [OPTIONS] [ITEM_URLS]...

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:

umbra embed fetch [OPTIONS]

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:

umbra embed info [OPTIONS]

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

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:

umbra embed search [OPTIONS] QUERY

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:

umbra embed similar [OPTIONS] ITEM_URL

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

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:

umbra gallery [OPTIONS]

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:

umbra index [OPTIONS] COMMAND [ARGS]...

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:

umbra index bake [OPTIONS]

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:

umbra index bake-thumbnails [OPTIONS]

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:

umbra index build [OPTIONS]

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:

umbra index export [OPTIONS]

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:

umbra index export-thumbnails [OPTIONS]

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:

umbra index fetch [OPTIONS]

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:

umbra index fetch-thumbnails [OPTIONS]

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:

umbra index info [OPTIONS]

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:

umbra index update [OPTIONS]

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:

umbra info [OPTIONS] ITEM

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:

umbra llms-txt [OPTIONS]

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:

umbra load [OPTIONS] ITEM_URL

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:

umbra map [OPTIONS]

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:

umbra mcp [OPTIONS]

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:

umbra preflight [OPTIONS] [ITEM_URLS]...

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:

umbra quicklook [OPTIONS] ITEM_URL

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

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:

umbra search [OPTIONS]

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:

umbra semantic [OPTIONS] COMMAND [ARGS]...

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:

umbra semantic build [OPTIONS]

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:

umbra semantic info [OPTIONS]

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

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:

umbra semantic search [OPTIONS] QUERY

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 ' for the top result. The command is always shown first, so you see exactly what will run. 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:

umbra serve [OPTIONS]

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:

umbra showcase [OPTIONS]

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:

umbra sites [OPTIONS]

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:

umbra stack [OPTIONS] [ITEM_URLS]...

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:

umbra swipe [OPTIONS] [ITEM_URLS]...

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:

umbra tiles [OPTIONS]

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:

umbra timescan [OPTIONS] [ITEM_URLS]...

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:

umbra view [OPTIONS] ITEM_URL

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:

umbra watch [OPTIONS]

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