diff --git a/algorithm_catalog/esa/delineate_anything/benchmark_scenarios/delineate_anything.json b/algorithm_catalog/esa/delineate_anything/benchmark_scenarios/delineate_anything.json new file mode 100644 index 000000000..96448fb88 --- /dev/null +++ b/algorithm_catalog/esa/delineate_anything/benchmark_scenarios/delineate_anything.json @@ -0,0 +1,33 @@ +[ + { + "id": "delineate_anything", + "type": "openeo", + "description": "Delineate Anything field boundary detection benchmark over a small agricultural area in Ukraine.", + "backend": "openeofed.dataspace.copernicus.eu", + "process_graph": { + "delineate1": { + "process_id": "delineate_anything", + "namespace": "https://raw.githubusercontent.com/Lavreniuk/Delineate-Anything/1d3241be444f46e8c9fe4e8b8de2b2366d17be3b/openeo_udp/process_graph/delineate_anything_udp.json", + "arguments": { + "spatial_extent": { + "type": "Polygon", + "coordinates": [ + [ + [31.0, 49.0], + [31.1, 49.0], + [31.1, 49.1], + [31.0, 49.1], + [31.0, 49.0] + ] + ] + }, + "temporal_extent": [ + "2024-06-01", + "2024-09-30" + ] + }, + "result": true + } + } + } +] diff --git a/algorithm_catalog/esa/delineate_anything/openeo_udp/delineate_anything_description.md b/algorithm_catalog/esa/delineate_anything/openeo_udp/delineate_anything_description.md new file mode 100644 index 000000000..58cdc8186 --- /dev/null +++ b/algorithm_catalog/esa/delineate_anything/openeo_udp/delineate_anything_description.md @@ -0,0 +1,59 @@ +# Description + +Accurate delineation of agricultural field boundaries from satellite imagery is vital for land +management and crop monitoring. This service implements the **Delineate Anything** model — a +resolution-agnostic YOLOv11 instance-segmentation network trained on the large-scale FBIS-22M +dataset (672,909 satellite image patches from 0.25 m to 10 m, containing 22,926,427 instance masks). + +The processing pipeline is encapsulated as an openEO User-Defined Process (UDP) with two +`apply_neighborhood` stages: + +1. **BAP composite + ONNX inference** (512×512 px chunks with 128 px overlap): + - Generates a Best-Available Pixel (BAP) RGB composite from Sentinel-2, reusing the existing + APEx [BAP Composite](https://algorithm-catalogue.apex.esa.int/apps/bap_composite) service. + - Scales pixel values to [0, 1] via `linear_scale_range(0, 3000, 0, 1)`. + - Runs YOLOv11 ONNX inference producing per-pixel detection confidence and mask probability. + +2. **Post-processing** (2000×2000 px chunks): + - Thresholds mask probabilities into a binary field mask. + - Labels connected components to produce individual field instances. + - Filters out small fields below a configurable minimum area. + +## Inputs + +- `spatial_extent`: GeoJSON geometry (Polygon) defining the area of interest. +- `temporal_extent`: ISO-8601 date range `[start, end]` for BAP input scenes. +- `processing_options` (optional): dictionary with keys `confidence_threshold`, `mask_threshold`, + `min_area_px`, `min_hole_area_px`. +- `max_cloud_cover` (optional): maximum cloud cover percentage for BAP input (default: 75). + +## Outputs + +A 3-band raster: + +- `mask_probability` (float32) — per-pixel field boundary probability from the model. +- `binary_mask` (uint8) — thresholded binary field mask. +- `instances` (int32) — integer instance labels (0 = background, each field gets a unique ID). + +# Examples + +The model generalises across diverse resolutions (0.25 m–10 m) and geographic regions, including +zero-shot predictions on unseen areas with different climates, terrains, and agricultural practices. + +# Known limitations + +- The ONNX model is hosted on CloudFerro S3 as a `udf-dependency-archive`. + +- Post-processing uses simple connected-component labelling. Fields spanning tile boundaries may + not be perfectly merged in all cases. +- The model is trained primarily on agricultural fields. Non-agricultural land parcels may produce + unreliable results. +- The AGPL-3.0 licence allows research use but restricts commercial use. + +# References + +- Paper: [Delineate Anything: Resolution-Agnostic Field Boundary Delineation](https://arxiv.org/abs/2504.02534) +- Source repository: https://github.com/Lavreniuk/Delineate-Anything +- Project page: https://lavreniuk.github.io/Delineate-Anything/ +- Dataset: https://huggingface.co/datasets/MykolaL/FBIS-22M +- UDP definition: https://raw.githubusercontent.com/Lavreniuk/Delineate-Anything/1d3241be444f46e8c9fe4e8b8de2b2366d17be3b/openeo_udp/process_graph/delineate_anything_udp.json diff --git a/algorithm_catalog/esa/delineate_anything/records/delineate_anything.json b/algorithm_catalog/esa/delineate_anything/records/delineate_anything.json new file mode 100644 index 000000000..9efe6dea8 --- /dev/null +++ b/algorithm_catalog/esa/delineate_anything/records/delineate_anything.json @@ -0,0 +1,118 @@ +{ + "id": "delineate_anything", + "type": "Feature", + "conformsTo": [ + "http://www.opengis.net/spec/ogcapi-records-1/1.0/req/record-core", + "https://apex.esa.int/core/openeo-udp" + ], + "geometry": null, + "properties": { + "created": "2026-05-26T00:00:00Z", + "updated": "2026-05-26T00:00:00Z", + "type": "service", + "title": "Delineate Anything", + "description": "Resolution-agnostic agricultural field boundary delineation from satellite imagery. The service generates a Best-Available Pixel (BAP) RGB composite from Sentinel-2, runs YOLOv11 instance-segmentation ONNX inference on 512\u00d7512 px tiles, and post-processes the raw masks into individual field instances. Returns three bands: mask_probability (float), binary_mask (0/1), and instances (integer field labels).", + "cost_estimate": 0.05, + "cost_unit": "platform credits per km\u00b2", + "keywords": [ + "Agriculture", + "Field Boundary Delineation", + "Instance Segmentation", + "Sentinel-2", + "YOLO" + ], + "language": { + "code": "en-US", + "name": "English (United States)" + }, + "languages": [ + { + "code": "en-US", + "name": "English (United States)" + } + ], + "contacts": [ + { + "name": "Mykola Lavreniuk", + "organization": "European Space Agency", + "links": [ + { + "href": "https://github.com/Lavreniuk/Delineate-Anything", + "title": "Delineate-Anything source repository", + "rel": "about", + "type": "text/html" + } + ], + "emails": [], + "contactInstructions": "Contact via GitHub", + "roles": [ + "principal investigator" + ] + } + ], + "themes": [ + { + "concepts": [ + { + "id": "Agriculture" + }, + { + "id": "Land Use/Land Cover Classification" + } + ], + "scheme": "https://gcmd.earthdata.nasa.gov/kms/concepts/concept_scheme/sciencekeywords" + } + ], + "formats": [ + { + "name": "GeoTIFF" + } + ], + "license": "AGPL-3.0" + }, + "linkTemplates": [], + "links": [ + { + "rel": "application", + "type": "application/vnd.openeo+json;type=process", + "title": "openEO Process Definition", + "href": "https://raw.githubusercontent.com/Lavreniuk/Delineate-Anything/1d3241be444f46e8c9fe4e8b8de2b2366d17be3b/openeo_udp/process_graph/delineate_anything_udp.json" + }, + { + "rel": "code", + "type": "text/html", + "title": "Git source repository", + "href": "https://github.com/Lavreniuk/Delineate-Anything" + }, + { + "rel": "service", + "type": "application/json", + "title": "CDSE openEO federation", + "href": "https://openeofed.dataspace.copernicus.eu" + }, + { + "rel": "platform", + "type": "application/json", + "title": "CDSE openEO federation", + "href": "../../../../platform_catalog/cdse_openeo_federation.json" + }, + { + "rel": "provider", + "type": "application/json", + "title": "European Space Agency", + "href": "../../record.json" + }, + { + "rel": "webapp", + "type": "text/html", + "title": "OpenEO Web Editor", + "href": "https://editor.openeo.org/?wizard=UDP&wizard~process=delineate_anything&wizard~processUrl=https://raw.githubusercontent.com/Lavreniuk/Delineate-Anything/1d3241be444f46e8c9fe4e8b8de2b2366d17be3b/openeo_udp/process_graph/delineate_anything_udp.json&server=https://openeofed.dataspace.copernicus.eu" + }, + { + "rel": "about", + "type": "text/html", + "title": "Delineate Anything project page", + "href": "https://lavreniuk.github.io/Delineate-Anything/" + } + ] +} diff --git a/algorithm_catalog/esa/record.json b/algorithm_catalog/esa/record.json new file mode 100644 index 000000000..44115d68b --- /dev/null +++ b/algorithm_catalog/esa/record.json @@ -0,0 +1,61 @@ +{ + "id": "esa", + "type": "Feature", + "conformsTo": [ + "http://www.opengis.net/spec/ogcapi-records-1/1.0/req/record-core" + ], + "properties": { + "created": "2026-05-26T00:00:00Z", + "updated": "2026-05-26T00:00:00Z", + "type": "algorithm_provider", + "title": "European Space Agency", + "description": "The European Space Agency (ESA) is Europe's gateway to space. ESA contributes Earth observation algorithms and services leveraging satellite imagery for agricultural and environmental monitoring.", + "keywords": [], + "language": { + "code": "en-US", + "name": "English (United States)" + }, + "languages": [ + { + "code": "en-US", + "name": "English (United States)" + } + ], + "contacts": [ + { + "name": "Mykola Lavreniuk", + "organization": "European Space Agency", + "emails": [], + "roles": [ + "principal investigator" + ] + } + ], + "themes": [], + "license": "other", + "acl": { + "admin": ["@esa.int"] + } + }, + "linkTemplates": [], + "links": [ + { + "rel": "website", + "type": "text/html", + "title": "ESA", + "href": "https://www.esa.int/" + }, + { + "rel": "logo-light", + "type": "image/png", + "title": "Logo", + "href": "https://www.esa.int/var/esa/storage/images/esa_multimedia/images/2008/09/esa_logo1/9777051-3-eng-GB/ESA_logo1_pillars.jpg" + }, + { + "rel": "logo-dark", + "type": "image/png", + "title": "Logo", + "href": "https://www.esa.int/var/esa/storage/images/esa_multimedia/images/2008/09/esa_logo1/9777051-3-eng-GB/ESA_logo1_pillars.jpg" + } + ] +} diff --git a/algorithm_catalog/ucl/solar_pv_detection/records/solar_pv_detection.json b/algorithm_catalog/ucl/solar_pv_detection/records/solar_pv_detection.json new file mode 100644 index 000000000..5410cf6c1 --- /dev/null +++ b/algorithm_catalog/ucl/solar_pv_detection/records/solar_pv_detection.json @@ -0,0 +1,120 @@ +{ + "id": "solar_pv_detection", + "type": "Feature", + "conformsTo": [ + "http://www.opengis.net/spec/ogcapi-records-1/1.0/req/record-core", + "https://apex.esa.int/core/openeo-udp" + ], + "geometry": null, + "properties": { + "created": "2026-05-21T00:00:00Z", + "updated": "2026-05-21T00:00:00Z", + "type": "service", + "title": "Solar PV detection", + "description": "Detection of solar photovoltaic (PV) installations from Sentinel-2 L1C imagery. The service performs a cloud-free SLIC-based temporal mosaic of Sentinel-2 L1C bands (using Sentinel-2 L2A SCL for cloud masking), normalizes the 13-band composite with training-aligned percentile statistics, and then runs ONNX U-Net inference (256x256 chunks) inside an openEO UDF. Returns a binary 'solar_pv' band and a 'solar_pv_probability' band.", + "cost_estimate": 0.1, + "cost_unit": "platform credits per km\u00b2", + "keywords": [ + "Energy Production/Use", + "Solar Energy Production/Use", + "Sentinel-2" + ], + "language": { + "code": "en-US", + "name": "English (United States)" + }, + "languages": [ + { + "code": "en-US", + "name": "English (United States)" + } + ], + "contacts": [ + { + "name": "Rui Song", + "organization": "University College London", + "links": [ + { + "href": "https://github.com/ray-climate/solar_openEO", + "title": "solar_openEO source repository", + "rel": "about", + "type": "text/html" + } + ], + "emails": [ + { + "value": "rui.song@ucl.ac.uk" + } + ], + "contactInstructions": "Contact via email", + "roles": [ + "principal investigator" + ] + } + ], + "themes": [ + { + "concepts": [ + { + "id": "Sentinel-2 MSI" + }, + { + "id": "SOLAR ENERGY PRODUCTION/USE" + } + ], + "scheme": "https://gcmd.earthdata.nasa.gov/kms/concepts/concept_scheme/sciencekeywords" + } + ], + "formats": [ + { + "name": "GeoTIFF" + } + ], + "license": "proprietary" + }, + "linkTemplates": [], + "links": [ + { + "rel": "application", + "type": "application/vnd.openeo+json;type=process", + "title": "openEO Process Definition", + "href": "https://raw.githubusercontent.com/ray-climate/solar_openEO/c4f1b0c7ba6ab9acf2b11cc999c05e6346b6bf21/openeo_udp/process_graph/solar_pv_detection_udp.json" + }, + { + "rel": "code", + "type": "text/html", + "title": "Git source repository", + "href": "https://github.com/ray-climate/solar_openEO" + }, + { + "rel": "service", + "type": "application/json", + "title": "CDSE openEO federation", + "href": "https://openeofed.dataspace.copernicus.eu" + }, + { + "rel": "platform", + "type": "application/json", + "title": "CDSE openEO federation", + "href": "../../../../platform_catalog/cdse_openeo_federation.json" + }, + { + "rel": "provider", + "type": "application/json", + "title": "University College London", + "href": "../../record.json" + }, + { + "rel": "webapp", + "type": "text/html", + "title": "OpenEO Web Editor", + "href": "https://editor.openeo.org/?wizard=UDP&wizard~process=solar_pv_detection&wizard~processUrl=https://raw.githubusercontent.com/ray-climate/solar_openEO/c4f1b0c7ba6ab9acf2b11cc999c05e6346b6bf21/openeo_udp/process_graph/solar_pv_detection_udp.json&server=https://openeofed.dataspace.copernicus.eu" + }, + { + "rel": "thumbnail", + "type": "image/png", + "title": "Thumbnail image", + "href": "https://raw.githubusercontent.com/ESA-APEx/apex_algorithms/refs/heads/main/algorithm_catalog/ucl/solar_pv_detection/records/thumbnail.png" + } + ] +} diff --git a/qa/scripts/check_baselines.py b/qa/scripts/check_baselines.py new file mode 100644 index 000000000..fc0ec1ef2 --- /dev/null +++ b/qa/scripts/check_baselines.py @@ -0,0 +1,124 @@ +#%% +""" +Check available Parquet data on S3 for the biopar (BAP) scenario. +Validates that recent CI runs are correctly uploading usage metrics. +""" + +import os +import time + +os.environ["AWS_ACCESS_KEY_ID"] = "aacc3a7890a24f19979cbfde5c1fafc9" +os.environ["AWS_SECRET_ACCESS_KEY"] = "ae52d7200e814f8397648b1d1c1ab838" +os.environ["AWS_ENDPOINT_URL"] = "https://s3.waw3-1.cloudferro.com" +os.environ["AWS_S3_ENDPOINT"] = "s3.waw3-1.cloudferro.com" +os.environ["AWS_VIRTUAL_HOSTING"] = "FALSE" +os.environ["AWS_DEFAULT_REGION"] = "default" + +import pyarrow.dataset as ds +import pyarrow as pa +import pandas as pd + +BUCKET = "apex-benchmarks" +KEY = "metrics/v1/metrics.parquet" +SCENARIO_ID = "peakvalley" + +# ---- Connect to S3 ---- +s3 = pa.fs.S3FileSystem( + access_key=os.environ["AWS_ACCESS_KEY_ID"], + secret_key=os.environ["AWS_SECRET_ACCESS_KEY"], + endpoint_override=os.environ["AWS_ENDPOINT_URL"], +) +s3_path = f"{BUCKET}/{KEY}" + +# ---- List all available partitions ---- +from pyarrow.fs import FileSelector + +print("Available partitions on S3:") +try: + file_info = s3.get_file_info(FileSelector(s3_path, recursive=False)) + partitions = sorted([f.base_name for f in file_info if f.is_file is False]) + for p in partitions: + print(f" {p}") +except Exception as e: + print(f" Error listing: {e}") + partitions = [] + +# ---- Load ALL partitions with unified schema (int->float to avoid truncation) ---- +print(f"\nLoading all partitions...") +t0 = time.time() +dfs = [] +for p in partitions: + try: + part_path = f"{s3_path}/{p}" + raw_ds = ds.dataset(part_path, filesystem=s3, format="parquet") + # Convert int columns to float64 to handle cross-partition schema conflicts + new_fields = [] + for field in raw_ds.schema: + if pa.types.is_integer(field.type): + new_fields.append(field.with_type(pa.float64())) + else: + new_fields.append(field) + unified = ds.dataset(part_path, filesystem=s3, format="parquet", schema=pa.schema(new_fields)) + dfs.append(unified.to_table().to_pandas()) + except Exception as e: + print(f" Skipping {p}: {e}") + +if not dfs: + raise RuntimeError("No data found") + +df = pd.concat(dfs, ignore_index=True) +print(f"Loaded {len(df)} total rows in {time.time() - t0:.1f}s") + +# ---- Find: last date we had full usage metrics, per metric ---- +pd.set_option("display.max_columns", None) +pd.set_option("display.width", 220) +pd.set_option("display.max_colwidth", 50) + +ALL_USAGE = [ + "usage:cpu:cpu-seconds", + "usage:memory:mb-seconds", + "usage:duration:seconds", + "usage:max_executor_memory:gb", + "usage:network_received:b", +] +avail_metrics = [c for c in ALL_USAGE if c in df.columns] + +df_sorted = df[df["job_id"].notna()].sort_values("test:start:datetime").copy() + +print(f"\n{'='*80}") +print(f" LAST DATE EACH METRIC WAS LOGGED") +print(f"{'='*80}") +print(f"\n{'Metric':<35} {'Last Seen':<24} {'Scenario':<30} {'Job ID'}") +print(f"{'─'*35} {'─'*24} {'─'*30} {'─'*50}") +for m in avail_metrics: + has_m = df_sorted[df_sorted[m].notna()] + if len(has_m): + last_row = has_m.iloc[-1] + print(f" {m:<33} {last_row['test:start:datetime']:<24} {last_row['scenario_id']:<30} {last_row['job_id']}") + else: + print(f" {m:<33} NEVER") + +# Last 5 runs that had cpu (the metric most often missing) +print(f"\n\n{'='*80}") +print(f" LAST 5 RUNS WITH usage:cpu:cpu-seconds") +print(f"{'='*80}") +if "usage:cpu:cpu-seconds" in df.columns: + cpu_runs = df_sorted[df_sorted["usage:cpu:cpu-seconds"].notna()].tail(5) + print(cpu_runs[["test:start:datetime", "scenario_id", "job_id", "usage:cpu:cpu-seconds"]].to_string(index=False)) + +# First run AFTER last complete that was missing cpu +print(f"\n\n{'='*80}") +print(f" FIRST RUNS MISSING usage:cpu:cpu-seconds (after the last time it was present)") +print(f"{'='*80}") +if "usage:cpu:cpu-seconds" in df.columns: + last_cpu_date = df_sorted[df_sorted["usage:cpu:cpu-seconds"].notna()]["test:start:datetime"].max() + after_missing = df_sorted[ + (df_sorted["test:start:datetime"] > last_cpu_date) & + (df_sorted["usage:cpu:cpu-seconds"].isna()) + ].head(10) + if len(after_missing): + print(f" (Last cpu seen: {last_cpu_date})") + print(after_missing[["test:start:datetime", "scenario_id", "job_id", "usage:duration:seconds"]].to_string(index=False)) + else: + print(" No runs missing cpu after the last time it was present — all recent runs have it!") +# %%