# catalog/operations/analysis/change_detection.yaml

id: raster_change_detection
name: Change Detection
description: >
  Detect change between two rasters by computing pixel-wise difference.
  Supports simple difference, normalized difference, and threshold classification.
  Optional patch filtering removes isolated changed pixels.
version: 1.0.0
category: analysis
type: raster-raster-to-raster
default_implementation: native

inputs:
  - name: baseline
    type: raster
    format: geotiff
    description: Baseline raster (e.g., earlier NDVI composite)
    required: true
  - name: current
    type: raster
    format: geotiff
    description: Current raster (e.g., later NDVI composite)
    required: true

# Structural: parametric difference / normalized-difference / threshold
# classification on any pair of co-registered rasters. requires: omitted.

outputs:
  - name: change
    type: raster
    format: cog
    dtype: float32
    description: >
      Change raster. Values depend on method: continuous difference,
      normalized difference (-1 to 1), or classified (-1/0/1).

params:
  method:
    type: string
    enum: [difference, normalized_difference, threshold_classification]
    default: difference
    description: >
      Detection method. "difference" computes current - baseline (continuous).
      "normalized_difference" computes (current - baseline) / (current + baseline).
      "threshold_classification" classifies change into loss (-1), stable (0), gain (1).
  threshold:
    type: number
    default: 0.0
    description: >
      Threshold for classification. For threshold_classification, pixels with
      diff < -abs(threshold) are loss, diff > abs(threshold) are gain.
      For difference with min_patch_pixels, defines what counts as "changed".
  min_patch_pixels:
    type: integer
    default: 0
    description: >
      Minimum connected patch size in pixels. Patches of changed pixels
      smaller than this value are removed. Set to 0 to disable filtering.
      Requires scipy for connected component analysis.

granularity:
  min_resolution_m: 1
  max_resolution_m: 1000
  recommended_resolution_m: 30
  output_resolution: inherit
  minimum_bands: 1
  requires_continuous: true
  suitable_aoi_km2:
    min: 0.1
    max: 500000
  realtime_max_km2: 500

uncertainty:
  error_model: propagated
  factors:
    - name: registration_error
      description: "Misalignment between baseline and current rasters produces false change"
      severity: high
      mitigations:
        - "Ensure both inputs are co-registered to sub-pixel accuracy"
        - "Use min_patch_pixels to filter out isolated false-change pixels"
    - name: threshold_sensitivity
      description: "Classification results are highly sensitive to threshold choice"
      severity: medium
      mitigations:
        - "Use normalized_difference method for more robust detection"
        - "Calibrate threshold on known-change areas"
    - name: phenological_variation
      description: "Seasonal differences can be confused with real change"
      severity: high
      mitigations:
        - "Compare same-season imagery (anniversary dates)"
        - "Use multi-temporal composites rather than single dates"
  limitations:
    - "Binary change detection cannot distinguish change types"
    - "Assumes inputs are radiometrically comparable"

execution:
  realtime_max_km2: 500
  cost_per_km2: 0.001
  time_per_km2_sec: 0.3
  memory_profile: medium
  profile:
    cpu: medium
    memory: medium
    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.raster.change_detection
    dispatch: folia/compute.py _BUILTIN_OP_MAP

display_hints:
  map:
    renderer: maplibre
    type: raster
    palette: diverging
    colors: ["#d73027", "#f7f7f7", "#1a9850"]
    labels: [loss, stable, gain]
  chart:
    renderer: observable-plot
    chartType: histogram

ui:
  icon: git-compare
  color: "#E65100"
