Registering analysis results#
After computing a comparison or validation result for a matchup, you can attach the output file to the matchup’s STAC item as a versioned asset. This keeps the catalogue as the single source of truth: anyone querying the central catalogue can see what analysis has been run on each matchup and retrieve the result directly from the item metadata.
Two asset keys are written each time:
{prefix}:YYYY-MM-DD— a dated snapshot that is never overwritten. Each run adds a new dated key alongside any previous ones.{prefix}:latest— always updated to point at the most recently registered file. Useful when you only want the current result without having to know its date.
The default prefix is comparison; you can supply any prefix to distinguish
different kinds of analysis (e.g. validation, uncertainty).
—
From the command line#
eomatch-add-asset \
--catalogue /data/my_catalogue \
--collection-id LANDSAT_C2L1-Landsat-9-vs-S2_MSI_L1C-S2A \
--item-id LC09_L1GT_089087_20220607_20220616_02_T2--S3A_EFR_20220607 \
--file /data/results/comparison_20260512.nc
This writes two assets to the item JSON on disk:
comparison:2026-05-12(today’s date by default)comparison:latest
Override the date, prefix, or MIME type if needed:
eomatch-add-asset \
--catalogue /data/my_catalogue \
--collection-id LANDSAT_C2L1-Landsat-9-vs-S2_MSI_L1C-S2A \
--item-id LC09_L1GT_... \
--file /data/results/validation_run2.nc \
--key-prefix validation \
--date 2026-05-01 \
--media-type application/x-netcdf \
--title "Validation run 2"
Add --push to also upsert the updated item directly into the running
pgSTAC database (requires the ingest extra and a reachable database):
eomatch-add-asset \
--catalogue /data/my_catalogue \
--collection-id LANDSAT_C2L1-Landsat-9-vs-S2_MSI_L1C-S2A \
--item-id LC09_L1GT_... \
--file /data/results/comparison_20260512.nc \
--push \
--db-host your-server
Without --push, run eomatch-ingest afterwards to synchronise the
whole catalogue (or just this item’s collection) with pgSTAC.
—
From Python#
import datetime as dt
from eomatch.add_asset import register_analysis
register_analysis(
catalogue_path="/data/my_catalogue",
collection_id="LANDSAT_C2L1-Landsat-9-vs-S2_MSI_L1C-S2A",
item_id="LC09_L1GT_089087_20220607_20220616_02_T2--S3A_EFR_20220607",
file_path="/data/results/comparison_20260512.nc",
)
Retrieve the item afterwards to confirm both keys were registered:
from eomatch.mu_stac import MatchupCatalogue
cat = MatchupCatalogue.open("/data/my_catalogue")
col = cat.catalog.get_child("LANDSAT_C2L1-Landsat-9-vs-S2_MSI_L1C-S2A")
item = next(col.get_items("LC09_L1GT_..."))
print(item.assets.keys())
# dict_keys(['comparison:2026-05-12', 'comparison:latest'])
print(item.assets["comparison:latest"].href)
# /data/results/comparison_20260512.nc
Multiple runs accumulate dated snapshots while latest rolls forward:
register_analysis(..., file_path="comparison_v1.nc", date=dt.date(2026, 5, 1))
register_analysis(..., file_path="comparison_v2.nc", date=dt.date(2026, 5, 12))
# item.assets now contains:
# comparison:2026-05-01 → comparison_v1.nc
# comparison:2026-05-12 → comparison_v2.nc
# comparison:latest → comparison_v2.nc
Push to pgSTAC in the same call by passing push=True:
register_analysis(
catalogue_path="/data/my_catalogue",
collection_id="LANDSAT_C2L1-Landsat-9-vs-S2_MSI_L1C-S2A",
item_id="LC09_L1GT_...",
file_path="/data/results/comparison_20260512.nc",
push=True,
db_host="your-server",
db_name="eomatch",
)
Connection parameters fall back to the ingest section of your eomatch
config if not supplied explicitly:
# ~/.config/eomatch/user_config.yaml
ingest:
db_host: your-server
db_name: eomatch
db_user: postgres
Pass the password via the PGPASSWORD environment variable rather than
storing it in the config file:
export PGPASSWORD=your-postgres-password
—
Media type detection#
The MIME type is inferred from the file extension when media_type is not
given:
Extension |
MIME type |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
other |
|
—
How assets appear in the central catalogue#
Assets with file:// hrefs (NFS paths) are visible on the internal API
(/api/) but are stripped from responses served by the external API
(/external/). This means:
Internal users (VPN access) see all assets, including analysis NetCDFs on NFS.
External collaborators using the STAC Browser or
eomatch-querysee the matchup metadata but not the file paths.
To expose an analysis result to external users, serve it over HTTP and
register it with an http:// href rather than a local path.
—
Generating assets in bulk#
generate_missing_assets() wraps the common
“generate one asset per item that doesn’t already have it” loop: it skips
items that already have asset_key (unless overwrite=True), calls your
generator function to produce the file, and registers it via whichever
add_*_asset method you pass in — so it works for both matchups and
events, and is not tied to the dated-snapshot convention above:
import matplotlib.pyplot as plt
from eomatch.add_asset import generate_missing_assets
def make_thumbnail(matchup, path):
fig = build_preview(matchup, output_path=path)
plt.close(fig)
events = cat.get_events(missing_assets=["thumbnail"])
matchups = [mu for event in events for mu in (event.matchup_set or [])]
n = generate_missing_assets(
items=matchups,
asset_key="thumbnail",
output_path=lambda mu: f"/data/thumbnails/{mu.stac_id}.png",
generate=make_thumbnail,
add_asset=cat.add_matchup_asset,
media_type="image/png",
roles=["thumbnail"],
)
print(f"Generated {n} thumbnail(s)")
A generation failure for one item (any exception raised by generate) is logged and that item is skipped, rather than aborting the whole run.
—
Removing assets in bulk#
remove_assets() is the counterpart to
generate_missing_assets(): it removes asset_key
from every item you pass in that has it, via whichever remove_*_asset
method you give it. Select the set of matchups or events first (by collection
pair, time range, has_assets filter, etc.), then pass them in:
from eomatch.add_asset import remove_assets
events = cat.get_events(has_assets=["thumbnail"])
matchups = [mu for event in events for mu in (event.matchup_set or [])]
n = remove_assets(
items=matchups,
asset_key="thumbnail",
remove_asset=cat.remove_matchup_asset,
delete_file=True,
)
print(f"Removed {n} thumbnail(s)")
Items without the asset are skipped. With delete_file=True (the default)
the local file or directory the asset points to is also deleted; remote URLs
are never deleted. A failure for one item is logged and that item is skipped,
unless raise_error=True.
—
Full reference#
- eomatch.add_asset.register_analysis(catalogue_path: str, collection_id: str, item_id: str, file_path: str, key_prefix: str = 'comparison', date: date | None = None, media_type: str | None = None, title: str | None = None, push: bool = False, config=None, db_host: str | None = None, db_port: int | None = None, db_name: str | None = None, db_user: str | None = None, db_password: str | None = None, assets_base_url: str | None = None) None[source]
Register an analysis output file as versioned STAC assets on a matchup item.
Adds two assets to the item in the local catalogue:
{key_prefix}:{YYYY-MM-DD}— dated snapshot, never overwritten by future runs.{key_prefix}:latest— always updated to point at the most recent file.
The local catalogue JSON is written immediately. Pass
push=Trueto also upsert the updated item into the running pgSTAC database.Example:
from eomatch.add_asset import register_analysis register_analysis( catalogue_path="/data/catalogue", collection_id="LANDSAT_C2L1-Landsat-8-vs-S2_MSI_L1C-S2A", item_id="L8-S2A-20220613T001234", file_path="/data/results/comparison_20260512.nc", push=True, db_host="localhost", db_name="eomatch", )
- Parameters:
catalogue_path – path to the root catalogue directory or
catalog.json.collection_id – STAC collection ID containing the target item.
item_id – STAC item ID to attach the asset to.
file_path – path to the analysis output file.
key_prefix – prefix for the asset keys; defaults to
"comparison".date – date for the dated snapshot key; defaults to today.
media_type – MIME type for the asset; auto-detected from extension if
None.title – human-readable title for the asset; defaults to the filename.
push – if
True, upsert the updated item into pgSTAC after saving locally.config – path to a eomatch YAML config file, or a dict of overrides.
db_host – pgSTAC database host (used only when push is
True).db_port – pgSTAC database port.
db_name – pgSTAC database name.
db_user – pgSTAC database user.
db_password – pgSTAC database password.
assets_base_url – HTTP base URL at which catalogue assets are served; used to rewrite
file://hrefs when pushing to pgSTAC.
- eomatch.add_asset.generate_missing_assets(items: Iterable[Any], asset_key: str, output_path: Callable[[Any], str], generate: Callable[[Any, str], Any], add_asset: Callable[[...], bool], asset_href: Callable[[Any, str], str] | None = None, media_type: str | None = None, title: str | None = None, roles: List[str] | None = None, overwrite: bool = False, return_values: bool = False, raise_error: bool = False) Any[source]
Generate and register asset_key for every item in items that doesn’t already have it.
For each
MatchupEventorMatchupin items missing asset_key on its.assets(or every item, if overwrite isTrue):output_path(item)gives the local file path to generate the asset at.generate(item, path)is called to produce that file; its return value is ignored, so callers whose generator function returns something (e.g. amatplotlibfigure to close) can just do that inline in the callable passed here.asset_href(item, path), if given, computes thehrefto register from path — e.g. relative to the item’s own directory, for a portable link. Defaults to path itself when not given.add_asset(item, asset_key, href, media_type=media_type, title=title, roles=roles)registers it — pass a bound method such asadd_matchup_asset()oradd_event_asset().
A failure raised by any of the above for one item is logged and that item is skipped, rather than aborting the whole run.
Example — generating a matchup thumbnail with a user-provided plotting function:
from eomatch.add_asset import generate_missing_assets def make_thumbnail(matchup, path): fig = build_preview(matchup, output_path=path) plt.close(fig) n = generate_missing_assets( items=[mu for event in cat.get_events() for mu in (event.matchup_set or [])], asset_key="thumbnail", output_path=lambda mu: f"/data/thumbnails/{mu.stac_id}.png", generate=make_thumbnail, add_asset=cat.add_matchup_asset, media_type="image/png", roles=["thumbnail"], )
- Parameters:
items –
MatchupEventorMatchupinstances to process.asset_key – asset key to check for and register, e.g.
"thumbnail".output_path – given an item, returns the local file path to generate the asset at.
generate – given
(item, output_path), writes the asset file. Return value ignored.add_asset – bound method used to register the generated file as an asset, e.g.
add_matchup_asset()oradd_event_asset().asset_href – given
(item, output_path), returns the href to register — use this when the file must be written to one location but linked to from the STAC item with a different (typically relative) path. Defaults to registering output_path unchanged.media_type – MIME type passed through to add_asset; auto-detected if
None.title – title passed through to add_asset; defaults to the filename.
roles – STAC asset roles passed through to add_asset, e.g.
["thumbnail"].overwrite – if
True, regenerate and re-register even when asset_key is already present.return_values – if
True, return the values returned by generate for each successfully generated item rather than the count.raise_error – if
True, raise errors instead of catching them in a try statement
- Returns:
number of assets successfully generated and registered, or a list of generate return values when return_values is
True.
- eomatch.add_asset.remove_assets(items: Iterable[Any], asset_key: str, remove_asset: Callable[[...], bool], delete_file: bool = True, raise_error: bool = False) int[source]
Remove asset_key from every item in items that has it.
The counterpart to
generate_missing_assets(): items is whatever subset ofMatchupEventorMatchupobjects you have selected (by collection pair, time range, property filter, etc.), and the asset is removed from each one that carries it.A failure raised for one item is logged and that item is skipped, rather than aborting the whole run.
Example — removing a stale thumbnail from a filtered set of matchups:
from eomatch.add_asset import remove_assets n = remove_assets( items=[mu for event in cat.get_events() for mu in (event.matchup_set or [])], asset_key="thumbnail", remove_asset=cat.remove_matchup_asset, )
- Parameters:
items –
MatchupEventorMatchupinstances to process.asset_key – asset key to remove, e.g.
"thumbnail".remove_asset – bound method used to remove the asset, e.g.
remove_matchup_asset()orremove_event_asset().delete_file – if
True(default), also delete the local file or directory the asset points to. Remote URLs are never deleted.raise_error – if
True, raise errors instead of logging them and continuing.
- Returns:
number of items the asset was successfully removed from.