# catalog/operations/vectorization/perimeter_progression.yaml

id: perimeter_progression
name: Burned-area perimeter progression (raster -> vector)
description: >
  Convert a stack of per-scene burn-probability rasters into one perimeter
  polygon per acquisition. Thresholds burn probability, drops small
  connected components, polygonizes the binary mask, simplifies with a
  Douglas-Peucker tolerance, runs make_valid for topology cleanup, and
  stamps each polygon with {fire_id, acquisition_dt, burn_pct, area_m2}.

  NaN inputs are excluded (not thresholded as < threshold). The op preserves
  the input CRS -- if the CRS is geographic and simplify_meters > 0, a
  warning is emitted (no silent reprojection).
version: 1.0.0
category: vectorization
type: raster-to-vector
default_implementation: native

inputs:
  - name: burn_prob_stack
    type: raster
    format: [cog, tif]
    cardinality: many
    description: >
      Per-scene burn-probability rasters. Each scene is a single-band
      float raster in [0, 1] (NaN = masked-out).
    required: true

# Per-scene burn-probability rasters are the burned-area concept in its
# raster form. The perimeters output is the same concept rendered as
# vector polygons -- representation changes, concept does not.
requires:
  concepts: [burned-area]

outputs:
  - name: perimeters
    type: vector
    format: [geojson, geoparquet]
    concept: burned-area
    description: >
      FeatureCollection of perimeter polygons, one or more per scene.
      Properties: fire_id, acquisition_dt, burn_pct, area_m2.

params:
  threshold:
    type: number
    default: 0.5
    description: >
      Burn-probability threshold. Pixels with prob >= threshold enter the
      binary mask. Lower values yield larger, noisier perimeters; higher
      values yield tighter perimeters at the cost of recall.
  min_patch_pixels:
    type: integer
    default: 50
    description: >
      Minimum connected-component size in pixels. Smaller patches are
      treated as noise and removed before polygonization. Set to 1 to
      disable filtering.
  simplify_meters:
    type: number
    default: 10.0
    description: >
      Douglas-Peucker tolerance in CRS units (expected to be metres). Set
      to 0 to disable simplification. If the input CRS is geographic, a
      warning is emitted and the value is used as-is in CRS units --
      reproject upstream for correct behaviour.

granularity:
  min_resolution_m: 1
  max_resolution_m: 100
  recommended_resolution_m: 10
  output_resolution: inherit
  minimum_bands: 1
  requires_continuous: true
  suitable_aoi_km2:
    min: 0.1
    max: 100000
  realtime_max_km2: 1000

uncertainty:
  error_model: propagated
  factors:
    - name: threshold_sensitivity
      description: "Perimeter area is sensitive to the threshold choice."
      severity: high
      mitigations:
        - "Calibrate threshold against MTBS/NBAC validation polygons."
        - "Run a sweep over thresholds and report perimeter stability."
    - name: speckle
      description: "Single-pixel false positives inflate perimeter counts."
      severity: medium
      mitigations:
        - "Increase min_patch_pixels (50 is a reasonable default for 10 m S1)."
        - "Pre-smooth the probability raster before this op."
    - name: simplification_loss
      description: "Aggressive simplify_meters drops perimeter detail."
      severity: low
      mitigations:
        - "Lower simplify_meters or set to 0 for full-resolution output."
  limitations:
    - "Geographic CRS yields area in degrees^2 (caller must reproject)."
    - "v0 emits per-scene polygons -- cumulative union is a recipe step."

execution:
  realtime_max_km2: 1000
  cost_per_km2: 0.0005
  time_per_km2_sec: 0.1
  memory_profile: low
  profile:
    cpu: medium
    memory: low
    io: medium
  scaling:
    model: linear
    parallelizable: true
    min_chunk_km2: 1

cache_policy:
  ttl_days: 30
  invalidate_on: [source_update]

# backends: audited 2026-08-14 (defect 50). A key means a runtime that DISPATCHES this op —
# folia-engine `dispatch_op` (products/sdk/folia-engine/src/lib.rs), a `registerOp`/OP_TABLE
# entry in packages/compute, `_BUILTIN_OP_MAP` in folia/compute.py, or a backend manifest
# (folia/backends/*/backend.yaml).
backends:
  python:
    function: geo.perimeter.perimeter_progression
    dispatch: folia/compute.py _BUILTIN_OP_MAP

display_hints:
  map:
    renderer: maplibre
    type: polygon
    palette: sequential
    colors: ["#ffeda0", "#feb24c", "#f03b20"]
  table:
    columns: [fire_id, acquisition_dt, burn_pct, area_m2]

ui:
  icon: fire
  color: "#E65100"
