# Radiometric Indices Operation
#
# Calculate spectral indices from multispectral satellite imagery.
# Essential for vegetation monitoring, water detection, and land cover analysis.

id: imagery_radiometric_indices
name: Radiometric Indices
description: >
  Calculate spectral indices from multispectral imagery. Supports vegetation
  indices (NDVI, SAVI, etc.), water indices (NDWI, MNDWI), soil indices,
  and built-up indices.
version: 1.0.0
category: imagery
type: raster-to-raster
default_implementation: otb

inputs:
  - name: image
    type: raster
    format: cog
    description: >
      Multispectral input image. Must have the bands required for the
      requested indices (e.g., red and NIR for NDVI).
    required: true

# Parametric: the bands needed are determined by `params.indices`. The
# resolver should look up each requested index in the spectral-indices
# registry and intersect the union of required bands against the input
# scene's `eo:bands`. Per-index band requirements (selection):
#   NDVI / RVI / SAVI / MSAVI / ARVI / GEMI → red, nir
#   NDWI                                    → green, nir
#   NDWI2 (Gao)                             → nir, swir1
#   MNDWI                                   → green, swir1
#   NDCI                                    → red, rededge1
#   NDTI                                    → green, red
#   NBR                                     → nir, swir2
#   Soil RI / CI / BI                       → red, green, blue
# Op-level `requires:` is left empty because the union depends on the
# specific indices the user requests at call time.
requires: {}

outputs:
  - name: indices
    type: raster
    format: cog
    dtype: float32
    # index: / concept: both omitted at schema-author time — fully
    # parametric. The emitted artifact is an *index* (formula), derived
    # per-invocation from `params.indices[]`. The resolver should set
    # `outputs[0].index:` to the requested index short-name. Example
    # mappings (params.indices → derived outputs[0].index):
    #   Vegetation:NDVI   → index: NDVI
    #   Water:NDWI        → index: NDWI
    #   WaterQuality:NDCI → index: NDCI
    #   WaterQuality:NDTI → index: NDTI
    #   Fire:NBR          → index: NBR
    # Some indices have a downstream phenomenon mapping (NDCI →
    # `chlorophyll-a-inland`, NDTI → `turbidity`, NBR → `burn-severity`
    # via dNBR), but those are *concept* annotations on a chained op
    # (e.g. analysis_dnbr.outputs.severity) — not on this op's raw index
    # output. When multiple indices are requested, the output is a stack
    # of bands, each carrying its own derived `index:` slug; downstream
    # consumers should look up per-band metadata via the spectral-indices
    # registry. See OPERATIONS.md #outputs-concept-vs-index.
    description: >
      Output image with one band per requested index.
      Values typically range from -1 to 1 for normalized indices.

params:
  indices:
    type: array
    items: string
    default: ["Vegetation:NDVI"]
    description: >
      List of indices to compute. Format: Category:IndexName

      Vegetation indices:
      - Vegetation:NDVI - Normalized Difference Vegetation Index
      - Vegetation:RVI - Ratio Vegetation Index
      - Vegetation:SAVI - Soil Adjusted Vegetation Index
      - Vegetation:MSAVI - Modified SAVI
      - Vegetation:GEMI - Global Environment Monitoring Index
      - Vegetation:ARVI - Atmospherically Resistant VI

      Water indices:
      - Water:NDWI - Normalized Difference Water Index
      - Water:NDWI2 - NDWI variant (Gao)
      - Water:MNDWI - Modified NDWI

      Water-quality indices:
      - WaterQuality:NDCI - Normalized Difference Chlorophyll Index
        (Mishra & Mishra 2012, doi:10.1080/2150704X.2011.641164).
        Formula: (RedEdge - Red)/(RedEdge + Red). For Sentinel-2 use
        B5 (red-edge) and B4 (red); for Sentinel-3 OLCI use Oa10 and Oa08.
        Proxy for chlorophyll-a concentration in turbid productive inland
        waters; requires a red-edge band so not available on Landsat OLI.
      - WaterQuality:NDTI - Normalized Difference Turbidity Index
        (Lacaux et al. 2007, doi:10.1016/j.rse.2006.07.012).
        Formula: (Red - Green)/(Red + Green). For Sentinel-2 use B4 (red)
        and B3 (green); equivalent bands on Landsat, MODIS, Planet.
        Relative turbidity / suspended-sediment proxy.

      Soil indices:
      - Soil:RI - Redness Index
      - Soil:CI - Color Index
      - Soil:BI - Brightness Index

      Burn / fire indices:
      - Fire:NBR - Normalized Burn Ratio
        (Key & Benson 2006, doi:10.3133/ofr0211).
        Formula: (NIR - SWIR2)/(NIR + SWIR2). For Sentinel-2 use B8 (NIR)
        and B12 (SWIR2); for Landsat 8/9 OLI use B5 and B7.
        Used to map burn severity (dNBR = NBR_pre - NBR_post).

  channels_red:
    type: integer
    description: Red band number (1-based). Required for most vegetation indices.

  channels_green:
    type: integer
    description: Green band number (1-based). Required for some water indices.

  channels_blue:
    type: integer
    description: Blue band number (1-based). Required for some soil indices.

  channels_red_edge:
    type: integer
    description: >
      Red-edge band number (1-based). Required for WaterQuality:NDCI
      (Sentinel-2 B5, Sentinel-3 OLCI Oa10). Not available on Landsat OLI.

  channels_nir:
    type: integer
    description: NIR band number (1-based). Required for NDVI and most vegetation indices.

  channels_mir:
    type: integer
    description: MIR/SWIR band number (1-based). Required for some water indices.

  channels_swir2:
    type: integer
    description: >
      SWIR2 band number (1-based). Required for Fire:NBR
      (Sentinel-2 B12, Landsat 8/9 OLI B7).

execution:
  realtime_max_km2: 500
  cost_per_km2: 0.001
  time_per_km2_sec: 0.5
  memory_profile: medium

# 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.otb.radiometric_indices
    dispatch: folia/compute.py _BUILTIN_OP_MAP

display_hints:
  map:
    renderer: maplibre
    palette: vegetation
    opacity: 0.8
  info:
    fields: [index_value]
    format: "{value}"
    section: Indices

cache_policy:
  ttl_days: 7
  invalidate_on: [source_update]

examples:
  - name: NDVI from Sentinel-2
    description: Calculate NDVI using bands 4 (red) and 8 (NIR)
    inputs:
      image: "sentinel2_10m.tif"
    params:
      indices: ["Vegetation:NDVI"]
      channels_red: 4
      channels_nir: 8

  - name: Multiple indices
    description: Calculate NDVI, NDWI, and SAVI together
    inputs:
      image: "landsat8.tif"
    params:
      indices: ["Vegetation:NDVI", "Water:NDWI", "Vegetation:SAVI"]
      channels_red: 4
      channels_green: 3
      channels_nir: 5

  - name: NDCI from Sentinel-2 (inland chlorophyll-a)
    description: >
      Calculate NDCI using B4 (red, 665 nm) and B5 (red-edge, 705 nm) for
      chlorophyll-a proxy in turbid productive inland waters. Per Mishra &
      Mishra 2012; pairs with the `chlorophyll-a-inland` concept.
    inputs:
      image: "sentinel2_20m.tif"
    params:
      indices: ["WaterQuality:NDCI"]
      channels_red: 4
      channels_red_edge: 5

  - name: NDTI from Sentinel-2 (relative turbidity)
    description: >
      Calculate NDTI using B3 (green) and B4 (red) as a relative
      turbidity / suspended-sediment proxy. Per Lacaux et al. 2007;
      pairs with the `turbidity` concept.
    inputs:
      image: "sentinel2_10m.tif"
    params:
      indices: ["WaterQuality:NDTI"]
      channels_red: 4
      channels_green: 3
