Search Umbra's catalog and return compact context cards.
Filters (all optional, combine freely): ``bbox`` as
``[min_lon, min_lat, max_lon, max_lat]`` in WGS84 degrees; ``intersects`` as
a GeoJSON polygon object (a ``Polygon`` / ``MultiPolygon`` geometry, or a
``Feature`` / ``FeatureCollection`` wrapping one) to keep only footprints
intersecting it -- a tighter spatial filter than the rectangular ``bbox``,
and mutually exclusive with ``bbox`` / ``place``; ``place`` as a
free-text name geocoded to a bbox; ``area`` as a substring of the Umbra
task (site) name (set ``fuzzy`` to match it loosely -- word-order- and
punctuation-independent and typo-tolerant, resolved deterministically with
no model call, so ``"beet piler"`` still reaches ``"Beet Piler - ND"`` and
``"utah centerfield"`` still reaches ``"Centerfield,
Utah"``; set ``semantic`` instead to treat ``area`` as a plain-language
*description* of a site whose name you don't know and resolve it to the
closest task names by *meaning* via the prebuilt embedding index, so
``"grain storage north dakota"`` reaches ``"Beet Piler - ND"`` -- a query
that shares no word with the label, which ``fuzzy`` cannot and should not
fake; ``semantic`` and ``fuzzy`` are mutually exclusive, and the resolved
task name is returned as ``resolved_area`` with the ranked candidates in
``semantic_matches`` so you can audit and retry with a different one, and
``min_score`` -- a cosine threshold in ``[0, 1]`` -- drops weak aliases, so
a description that matches nothing confidently returns an empty result
rather than an arbitrary top pick); ``start``/``end`` as acquisition-date
bounds -- an ISO
``YYYY-MM-DD`` date, a bare year/month (``2024``, ``2024-03``), or a
relative expression (``today``, ``yesterday``, ``3 months ago``,
``last month``), resolved deterministically with no model call;
``products`` to restrict to product types
(any of
Parametersendareabboxfuzzylimitlocalplacestartproductssemanticcanonicalmin_score+6 more
Rank the archive's most repeat-imaged sites — where change detection has
something to measure.
The discovery step *before* every analysis verb. ``change`` / ``timescan`` /
``stack`` / ``pick_change_interval`` / ``narrate_change`` all answer *what*
changed at a site, but each assumes the caller already knows *which* site to
point them at. Umbra files every pass of an area under one task (site), so a
site's coverage is just how many dated passes share its task; the sites with
the most are exactly the ones where a time series exists to analyse. This
tool ranks them, so ``find_repeat_sites → pick_change_interval →
narrate_change`` is a complete chain a model can drive with no site known in
advance — the deterministic answer to "where is there something to look at?"
(the discovery moat; no model is called). Reach for this
(or ``search_catalog(area=..., fuzzy=True)``) before ``semantic=True`` when
the site name is roughly known.
The filters scope the *pool* of acquisitions the ranking is computed over,
and are the same ones ``search_catalog`` takes: ``bbox``
(``[min_lon, min_lat, max_lon, max_lat]`` WGS84), ``intersects`` (a GeoJSON
polygon, mutually exclusive with ``bbox`` / ``place``), ``place`` (free-text,
geocoded to a bbox), ``area`` (a task-name substring, loosened by ``fuzzy``),
``start`` / ``end`` date bounds, ``products`` (GEC / SICD / SIDD / CPHD), and
the SAR property filters ``polarizations`` / ``min_incidence`` /
``max_incidence`` / ``max_resolution``. ``top`` caps how many sites are
returned, and ``min_passes`` is how many passes a site needs to qualify (2 is
the minimum a change composite can use; raise it to find only deeply-revisited
series), counted on the depth ``rank_by`` measures -- raw dated passes by
default, the usable (comparable) series' depth under ``rank_by="comparable"``.
``rank_by`` chooses the order: ``"passes"`` (the default) ranks by
Parametersendtopareabboxfuzzylimitlocalplacestartrank_byproductsintersects+13 more
Return the full context card for one item.
``url`` is the JSON URL of an open-data STAC sidecar, a GEC/SICD (or other
product) filename such as ``2025-05-17-17-43-46_UMBRA-08_GEC.tif``, or the
acquisition stem ``2025-05-17-17-43-46_UMBRA-08``. A filename or stem is
resolved through the local catalog index: the stem is matched against
asset hrefs, which is what distinguishes ``sar-data/tasks/``,
``sar-data/task-data/``, and ``open-data/`` — the name itself does not.
Zero matches is an error. More than one match lists the candidate STAC
URLs; pass one of those. The hosted server resolves a filename or stem
with the catalog index it already loaded. A local server does the same
after ``umbra index fetch``.
When the index contains a STAC URL, the indexed snapshot is returned —
the same document ``search_catalog`` already answered from — rather than
a live re-fetch that can disagree on asset filenames or product types.
When the server is configured with a token (``$UMBRA_CANOPY_TOKEN``) and
``url`` is a bare acquisition id (no ``://``, and not a filename or stem),
it is instead looked up in Umbra's authenticated commercial archive by a
keyed STAC search — the retrieval complement to ``search_catalog`` over
the paid catalog, since archive cards carry an ``id`` (not a fetchable
sidecar URL) as their hand-off pointer. A filename or stem still resolves
from the local open-data index when a token is set.
On the public host a URL is fetched only when it is https on Umbra's
public open-data bucket. Any other URL is refused before a request.
A bare id that is not in the local index is refused the same way.
The card (:meth:`UmbraItem.to_llm_context`) carries the id, ISO datetime,
place, bbox, resolution, polarization *with the change-detection caveat*,
the per-product-type explanations and asset URLs, and the mandatory
attribution line — everything a model needs to reaso
Parametersurlrequired
Resolve a free-text place name to a bounding box via Nominatim.
Returns ``{"bbox": [min_lon, min_lat, max_lon, max_lat], "display_name":
...}`` so the agent can inspect the box before searching, or pass it
straight to ``search_catalog``'s ``bbox``.
Parametersqueryrequired
Report on the local catalog index, if one has been built or fetched.
Lets a long-running agent decide whether searches will be instant (index
present) or pay a live S3 walk, and how stale the snapshot is. ``end`` is
the newest acquisition that is not ``datetime_suspect``.
``datetime_suspect_count`` is how many kept rows carry that flag.
``quarantined_future_count`` is how many source rows the last build
dropped for a datetime more than a day ahead (they are not in the index).
``unique_captures`` is how many rows are the canonical copy of their
collect; it equals ``items`` until republished copies have been linked.
Takes no parameters.
Return a scene's quicklook PNG as an image block.
Prefers a baked catalog preview (``preview="auto"``, the default) so a
public host can show the radar scene without proxying Umbra COGs.
**A baked preview is typically 512 px** (the published weekly sidecar).
``max_size`` applies only to a fresh ``preview="render"`` stream; when a
cached thumbnail is smaller than requested, the caption says
``max_size`` was ignored *and* includes a JSON
``{"image": {source, width, height, requested_max_size, substituted}}``
sidecar. For a higher-resolution picture (typically 1024 px), run a
local ``uvx --from 'umbra-py[mcp]' umbra-mcp`` server, or download the
GEC href from ``get_item``. ``preview="baked"`` refuses rather than
stream; ``preview="render"`` always streams (needs ``[viz]``, refused
on ``umbra serve --public``).
Parametersdburlrequiredassetpreviewmax_size
Composite 2-3 passes of one site into a change image.
Pass the STAC URLs of two or three acquisitions of the *same* site in time
order. Colors encode change: bright green = signal appeared after the first
date, magenta = signal vanished, grey = unchanged. Refuses to mix
polarizations (HH vs VV are not comparable). Returns the image block plus a
caption naming the color semantics and attribution -- and, when a pass carries
no polarization metadata so same-polarization could not be *verified*, a
structured caution text block naming that gap.
Parametersdburlsrequiredassetmax_size
Summarize a whole time series into one activity image.
Pass the STAC URLs of a site's passes; the timescan encodes per-pixel
temporal statistics so bright/colored areas are *where activity happened*
over the series. Returns the image block plus attribution.
Parametersdburlsrequiredassetmax_size
Measure how much a site changed across its passes, as numbers.
The quantitative counterpart to ``change_composite`` / ``timescan``: those
return a picture of *where* change is, this returns the *how much*. Pass the
STAC URLs of a site's passes (2 or more, any order); they are co-registered
onto one shared grid and reduced to a per-pass statistics series plus a net
baseline-to-latest record — mean/median/spread of each pass, the signed
decibel change against the pass before it, the fraction of ground that moved
past ``change_threshold_db``, and that fraction as an **area in km²**.
Change is always reported in decibels (a ratio of backscatter is a difference
on the log scale). The default ``crs="utm"`` puts the grid in the site's UTM
zone so every cell covers the same ground and the area figures mean
something; pass ``crs="EPSG:4326"`` for a lon/lat grid, and the areas come
back ``None`` rather than wrong. ``extent="intersection"`` (the default)
compares only ground every pass covers; ``"union"`` keeps all of it and
compares per cell where two passes overlap. ``bbox`` (lon/lat
``[min_lon, min_lat, max_lon, max_lat]``) narrows the measurement to a
sub-area, e.g. one facility inside the scene.
``blocks=N`` adds the *where* to the *how much*: the scene is cut into an
N x N grid (6 is a good default; 0, the default, skips it) and the extra
``spatial`` key reports each block's own net change, a compass label and
lon/lat centre to locate it by, and the consecutive pair of passes that
block moved most between — plus ``peak_block`` and an ASCII ``grid_text``
heat-map of the net change. Ask for it whenever the question is *where on
this site* something happened, or when a scene-wide mean looks small but the
picture from ``change_composite`` shows an obvious hot spot: a change
confined to one corner is averaged away scene-wide and stands out per block.
``block_series=True
Parameterscrsbboxurlsrequiredassetblocksextentmax_sizeblock_seriesspeckle_filterspeckle_windowchange_threshold_db
Check whether a site's passes are one measurement, before measuring them.
``stack_stats`` refuses a selection whose rasters were made by *different*
``umbra convert`` settings — a calibrated pass differenced against an
uncalibrated one puts the difference between two conversions on the time
axis and reports it as change on the ground. This is the question that
refusal answers, asked ahead of it: it reads each acquisition's ``UMBRA_*``
conversion record straight from its raster header (kilobytes per pass, no
pixels, no co-registration) and groups the selection by what its pixel
values *are*.
Returns ``agrees`` (whether ``stack_stats`` would accept the selection),
``groups`` largest-first — each with the conversion ``record`` its members
share, their ``item_ids`` and the ``hrefs`` to re-run on — plus ``shared``
(the record they all agree on) when they agree, ``refusal`` (verbatim the
error ``stack_stats`` would raise) when they do not, and ``unreadable`` for
any source that could not be opened at all, which is an incomplete answer
rather than a mixed one.
Reach for it in two situations. **After** a ``stack_stats`` call fails with
a provenance refusal: ``groups[0]["hrefs"]`` is the largest set of passes
that *do* agree, so re-running ``stack_stats`` on those is a measurement
rather than a guess — the refusal itself cannot name that subset. And
**before** measuring a long series you did not assemble yourself, since the
check costs a header read per pass and saves the whole cube when the answer
is no.
A series of Umbra's published products as they ship (the common case) agrees
trivially: every pass reads as ``"(unrecorded)"``, which is one value, so
``agrees`` is true and ``shared`` is empty. **No model is called.**
Parametersurlsrequiredasset
Scan a site's whole series and name the two passes worth narrating.
The deterministic first half of the "watch a site over time" story, and the
step that turns a wall of passes into an answer. Given the STAC URLs of a
site's passes (2 or more, any order), it co-registers them into a
``to_stack`` cube, reduces it with ``stack_stats``, and returns the *one*
consecutive pass-pair whose measured change stands **furthest clear of the
speckle detection floor** — the pair a reader should look at first. No model
is called: a number picks the frames (the determinism boundary applied to
frame selection), so the choice is reproducible and safe
to quote.
Why this exists as its own tool: ``narrate_change`` reads a *pair* of passes,
but a site has many, and "which two?" is a measurement question, not a model
one — a picture past three dates encodes nothing a model can separate, so the
pair has to be chosen before the model is called. This answers it. The
returned ``urls`` are the two chosen passes in time order, ready to hand
straight to ``narrate_change`` (or ``change_composite`` to see them): the
two tools chain into scan → narrate with no server and no model in the
selection.
Returns ``selected_interval`` (the interval's ``earlier_id`` / ``later_id``,
their datetimes, the ``changed_fraction`` and signed ``mean_delta_db``, the
cube's ``false_alarm_fraction`` speckle floor, the ``excess`` of the change
over that floor, and ``stands_clear`` — whether the change cleared the floor
by a usable margin), ``urls`` (the earlier then the later pass's STAC URL),
and the license/attribution. When ``stands_clear`` is false the series' own
largest change is still inside the speckle, so the pair is offered but the
reader is warned the difference may be interference rather than change.
``selected_interval`` is ``None`` (and ``urls`` empty) when the series has
fewer than two comparabl
Parameterscrsurlsrequiredassetextentmax_sizechange_threshold_db
Download one asset of an item to local disk, gated by a size check.
SAR products can be multiple GB, so this is a two-step handshake: call it
first with ``confirm=False`` (the default) to get the byte size without
downloading, then again with ``confirm=True`` to fetch it. On a
local or stdio server, ``confirm=True`` returns ``{asset, path, bytes}``.
``confirm=False`` returns a size-and-hint object and does not write a file.
On a public host (``umbra serve --public``, COG streaming off),
``confirm=True`` does not transfer the file. It returns a link plan:
``mode`` ``"link"``, ``downloaded`` false, ``asset``, ``filename`` (a
basename), the direct public ``href``, ``bytes``, and ``how_to_fetch``.
When the href is https on the public open-data bucket, ``how_to_fetch``
is ``curl -L -C - -o <filename> <href>`` and
``umbra download ITEM --asset ASSET --dest DIR``. ``bytes`` is the STAC
``file:size`` or ``size`` when that is a non-negative integer; otherwise
the host HEADs that href for ``Content-Length``, with no redirects and
no retries. A href that is not on that bucket is not requested:
``how_to_fetch.curl`` and ``how_to_fetch.umbra`` are null, ``bytes`` is
null, and ``note`` says the commands were omitted. A 404 or 410 from
an allowlisted HEAD raises. Any other HEAD failure, including a full
quick-request slot, leaves ``bytes`` null and still returns the plan.
The hosted server never transfers the file. A local
``uvx --from 'umbra-py[mcp]' umbra-mcp`` server downloads to your own
disk. ``confirm=False`` on this host returns the same size-and-hint
object and does not write a file. It HEADs for ``Content-Length`` only
when the href is https on the public open-data bucket, with no retries,
and it does not follow redirects. A 404 or 410 raises, and so does any
other non-redirect HEAD error. A redirect leaves ``bytes`` null. A href
outside the bucket is not requeste
Parametersurlrequiredassetconfirmdest_dir
Report only the acquisitions **new** since the last check of this site.
SAR's value for monitoring is its cadence, so the natural workflow is
*standing*: run the same search each time and act only on what changed. This
tool packages that idempotent delta — call it in one turn, come back a day
(or a session) later and call it again, and it returns just the passes
published in between, not the whole list again. The scheduler is you: ask the
agent to check the site, and it reports the delta.
Define the site with the same filters as ``search_catalog`` — ``place``
(geocoded to a bbox), ``area`` (task/site-name substring, loosened by
``fuzzy``), ``bbox``, ``products``, and ``start``/``end`` date bounds. The
watch is identified by a stable ``name`` derived from those filters (pass an
explicit ``name`` to run several independent watches over the same site);
``reset=True`` re-establishes the baseline, reporting everything as new.
State persists in the local catalog index's ``meta`` table (created on first
use), so a watch survives across sessions with no extra setup. ``local``
selects the search backend exactly as in ``search_catalog`` — leave it unset
to use the on-disk index when present and a live S3 walk otherwise; for true
monitoring a live walk (``local=false``) catches freshly published passes.
**No model is called** — this is pure set arithmetic over the deterministic
search — and the returned ``new_items`` are context cards ready to hand
straight to ``change_composite`` / ``timescan``, closing the standing-analyst
loop (new pass → composite → describe) without leaving the conversation.
Parametersendareabboxnamefuzzylocalplaceresetstartproducts
Find archived scenes that **look like** the acquisition at ``url``.
This is the one search that lives in the pixels, not the metadata: where
``search_catalog`` filters by date/bbox/task-name, this renders the query
item's quicklook, embeds it, and ranks the pre-embedded scene index by visual
(cosine) similarity — "find scenes that look like this flooded field / crowded
berth / runway". The query item is excluded from its own results.
Requires a scene-embedding index built ahead of time with ``umbra embed
build`` (a sidecar ``catalog.embed.db`` beside the catalog index) and a
user-supplied multimodal embedding key (``OPENAI_API_KEY``, optionally
``OPENAI_BASE_URL`` / ``UMBRA_SCENE_EMBED_MODEL``) — the only model call is
turning the query image into a vector; ranking is deterministic. ``asset``
picks which product's quicklook to embed (default ``GEC``; match how the index
was built); ``model`` must match the index's embedding model. Returns the
ranked matches as compact records (each with the acquisition's ``href``, so
hand it to ``get_item`` / ``quicklook`` / ``change_composite`` next) plus the
attribution line.
Parametersurlrequiredassetmodeltop_kmin_score
Find archived scenes matching a plain-language ``query`` over their imagery.
Text-to-scene search: embeds ``query`` ("ships at a berth", "a flooded field")
and ranks the stored *image* vectors by cosine similarity. It only works when
the index was built and this query is embedded with the **same** joint
CLIP-family model whose text and image encoders share a vector space (a
dimension mismatch is reported as an error; ``model`` selects it).
Requires the same prebuilt ``catalog.embed.db`` (``umbra embed build``) and
embedding key as ``find_similar``; the only model call is turning the text
query into a vector. Returns the ranked matches as compact records (each with
the acquisition's ``href`` for ``get_item`` / ``quicklook``) plus attribution.
Parametersmodelqueryrequiredtop_kmin_score
Return a SAR-literate reading of the scene at ``url``.
The picture and the metadata card are produced deterministically; a model
only interprets them. How that model is reached depends on whether this
process holds a vision key:
- **No key** (the public community host): this tool does **not** call a
vision model. It returns the PNG plus a reading kit (SAR rules, the
metadata card, the JSON schema). Produce the JSON, then call
:func:`stamp_description` — that is the validation boundary
(provenance-stamped ``{summary, observed_features, confidence,
caveats}``). Until you stamp it, your prose is not pipeline output.
A baked preview is typically 512 px; the kit names the size and
that a local server can stream a higher-resolution GeoTIFF.
- **Key configured** (local stdio): sends the picture to that vision
model, validates through ``parse_description``, and returns the
stamped object directly.
The packaged SAR rules are the value over just looking at ``quicklook``:
brightness is backscatter not sunlight, speckle is not structure, a dark
patch may be radar shadow not water, one frame is not change.
``preview`` defaults to ``"auto"``: a baked catalog preview if this server
has one, else a fresh render. ``"render"`` is refused on the public host.
Parametersdburlrequiredassetmodelpreviewmax_size
Return a grounded reading of *what changed* between passes.
Sibling of :func:`describe_scene`. The composite and the per-block dB grid
are produced deterministically; a model only narrates change those numbers
support. Pass the STAC URLs in time order; mixed polarizations are refused.
This tool **streams Umbra COGs** to build the composite, so a public
community host refuses it (use a local ``uvx --from 'umbra-py[mcp]'
umbra-mcp`` server, or describe each pass with :func:`describe_scene` /
:func:`quicklook` on baked previews). On a local server:
- **No vision key:** returns the composite as an MCP image block plus a
reading kit (SAR change primer, the dB grid, the JSON schema). **You**
(the client's model) narrate, grounded in the numbers.
- **Key configured:** sends picture + grid to that vision model, validates
through ``parse_narration``, and returns a provenance-stamped
``{summary, changes[], confidence, caveats[], change_stats}`` object.
Parametersdburlsrequiredassetmodelmax_size
Validate a client-produced scene reading and stamp provenance.
This is the interpretation boundary for the no-key kit path: you read the
picture ``describe_scene`` returned, produce
``{summary, observed_features, confidence, caveats}``, and pass it here.
The library checks the shape, stamps CC-BY and the AI-interpretation
note (the model never sets those), and — when ``image`` is the record
from the kit — appends a deterministic caveat if the picture was a
smaller baked preview. Until this returns, the reading is unvalidated
prose, not pipeline output.
Parametersassetimagemodelitem_idreadingrequired
Validate a client-produced change narration and stamp provenance.
Sibling of :func:`stamp_description` for the no-key ``narrate_change``
kit. Pass the JSON you produced plus the ``change_stats`` grid from the
kit so the stamped object carries the numbers the prose was grounded in.
Parametersassetmodelreadingrequireditem_idschange_stats