Graph

Principle

Processing graph defines a sequence of steps which will be executed.

A step contains an execution command with some parameters and configurations, logical condition could be added in order to validate step status.

Every step could be defined with one of engine among Python, Snap, cmd and Docker.

Graph should have input or/and output files (depends on kind of processing).

Processing will be manage for all tiles/scenes defined by workflow.

JSON Graph

Structure

type ProcessingGraphJSON struct {
    Config   map[string]string `json:"config"`
    Envs     []string          `json:"envs,omitempty"`
    Steps    []ProcessingStep  `json:"processing_steps"`
    InFiles  [3][]InFile       `json:"in_files"`
    OutFiles [][]OutFile       `json:"out_files"`
}

Config

List of configuration values useful for the correct execution of processing steps.

Steps

List of processing execution script (Python, Snap of inside docker container).

Engine

Define the kind of engine that will be used in order to manage processing.

Available engines:

  • Snap (for Sentinel1-2 constellation)
  • Python (for custom processing simple script)
  • Docker (for custom processing with specific binaries)
  • Cmd (for basic command available in processor OS)
Command

Name of processing binary to use.

MyBinary Example:

{
  "engine": "cmd",
  "command": "./cmd/MyBinary"
}

Docker Example:

{
  "engine": "docker",
  "command": "containerRegistry/myImage:myTag"
}
Args

List of command arguments useful for the correct execution of processing steps.

  • Structure:
type ArgIn struct {
    Input     int               `json:"tile_index"` // Index of input [0, 1, 2]
    Layer     service.Layer     `json:"layer"`
    Extension service.Extension `json:"extension"`
}

type ArgOut struct { 
    service.Layer `json:"layer"`
    Extension     service.Extension `json:"extension"`
}

type ArgFixed string  // fixed arg
type ArgConfig string // arg from config
type ArgTile string   // arg from tile info
  • Available layers
const (
    Product Layer = "product"

    LayerPreprocessed  Layer = "preprocessed"
    LayerCoregistrated Layer = "coregistred"
    LayerCoregExtract  Layer = "coregextract"
    LayerCoherence     Layer = "coherence"

    LayerBackscatterVV Layer = "sigma0_VV"
    LayerBackscatterVH Layer = "sigma0_VH"
    LayerCoherenceVV   Layer = "coh_VV"
    LayerCoherenceVH   Layer = "coh_VH"
    LayerPanchromatic  Layer = "P"
    LayerMultiSpectral Layer = "MS"
)
  • Available extensions
const (
    ExtensionSAFE      Extension = "SAFE"
    ExtensionZIP       Extension = "zip"
    ExtensionDIMAP     Extension = "dim"
    ExtensionDIMAPData Extension = "data"
    ExtensionGTiff     Extension = "tif"
)
  • Example
{
  "engine":"docker",
  "command":"containerRegistry/myImage:myTag",
  "args":{
    "workdir":{
      "type":"config",
      "value":"workdir"
    },
    "image":{
      "type":"in",
      "layer":"product",
      "extension":"SAFE"
    },
    "srtm-uri":{
      "type":"config",
      "value":"srtm-uri"
    },
    "roadmap":{
      "type":"config",
      "value":"roadmap-robot"
    },
    "options":{
      "type":"config",
      "value":"roadmap-options"
    },
    "roadmap-params":{
      "type":"config",
      "value":"roadmap-params"
    },
    "libs":{
      "type":"config",
      "value":"libs"
    },
    "constellation":{
      "type":"fixed",
      "value":"sentinel2"
    },
    "out-pattern":{
      "type":"out",
      "layer":"*",
      "extension":"tif"
    }
  }
}
Condition

Condition to consider step as success.

  • Structure
type TileCondition struct {
    Name string
    Pass func([]common.Tile) bool
}
  • Available tileConditions
// condPass is a tileCondition always true
var pass = TileCondition{"pass", func(tiles []common.Tile) bool { return true }}

// condDiffTile returns true if tile1 != tile2
var condDiffT0T1 = TileCondition{"different_T0_T1", func(tiles []common.Tile) bool { return tiles[0].Scene.SourceID != tiles[1].Scene.SourceID }}
var condDiffT0T2 = TileCondition{"different_T0_T2", func(tiles []common.Tile) bool { return tiles[0].Scene.SourceID != tiles[2].Scene.SourceID }}
var condDiffT1T2 = TileCondition{"different_T1_T2", func(tiles []common.Tile) bool { return tiles[1].Scene.SourceID != tiles[2].Scene.SourceID }}

// condEqualTile returns true if tile1 == tile2
var condEqualT0T1 = TileCondition{"equal_T0_T1", func(tiles []common.Tile) bool { return tiles[0].Scene.SourceID == tiles[1].Scene.SourceID }}
var condEqualT0T2 = TileCondition{"equal_T0_T2", func(tiles []common.Tile) bool { return tiles[0].Scene.SourceID == tiles[2].Scene.SourceID }}
var condEqualT1T2 = TileCondition{"equal_T1_T2", func(tiles []common.Tile) bool { return tiles[1].Scene.SourceID == tiles[2].Scene.SourceID }}
  • Example
{
  "condition": "pass"
}

Input Files

Input files necessary for the right execution of processing

  • Structure
// InFile describes an input file of the processing
type InFile struct {
    File
    Condition TileCondition `json:"condition"`
}
  • Example
{
   "in_files":[
      [
         {
            "layer":"product",
            "extension":"SAFE"
         }
      ],
      [],
      []
   ]
}

Infiles json block is an array of 3 in order to potentially reference current tile, previous and reference tile). For instance, it is useful in order to process coherence cf. Example with S1

Output Files

Output files generated by processing steps procedure.

  • Structure
// OutFile describes an output file of the processing
type OutFile struct {
    File
    dformatOut Arg           // argFixed or argConfig
    DType      DType         `json:"datatype"`
    NoData     float64       `json:"nodata"`
    Min        float64       `json:"min_value"`
    Max        float64       `json:"max_value"`
    ExtMin     float64       `json:"ext_min_value"`
    ExtMax     float64       `json:"ext_max_value"`
    Exponent   float64       `json:"exponent"` // JSON default: 1
    Nbands     int           `json:"nbands"`   // JSON default: 1
    Action     OutFileAction `json:"action"`
    Condition  TileCondition `json:"condition"` // JSON default: pass
}
  • Available action outFile
// OutFileAction
const (
    ToIgnore OutFileAction = iota
    ToCreate
    ToIndex
    ToDelete
)
  • Example
{
   "out_files":[
      [
         {
            "layer":"img",
            "extension":"tif",
            "action":"to_index",
            "dformat_out":{
               "type":"fixed",
               "value":"uint16,0,0,30000"
            },
            "ext_min_value":0,
            "ext_max_value":3,
            "nbands":4
         },
         {
            "layer":"clcsh",
            "extension":"tif",
            "action":"to_index",
            "dformat_out":{
               "type":"fixed",
               "value":"int16,-1,0,10000"
            },
            "ext_min_value":0,
            "ext_max_value":1
         }
      ],
      [],
      []
   ]
}

Example Processing Sentinel1

{
  "config": {
    "bs_erode_iterations": "10",
    "coh_erode_iterations": "10",
    "coherence_azimuth": "4",
    "coherence_range": "16",
    "dem_egm_correction": "True",
    "dem_file": "",
    "dem_name": "SRTM 3Sec",
    "dem_nodata": "0",
    "dem_resampling": "BILINEAR_INTERPOLATION",
    "dformat_out": "float32,0,0,1",
    "img_resampling": "BICUBIC_INTERPOLATION",
    "bkg_resampling": "BISINC_21_POINT_INTERPOLATION",
    "projection": "EPSG:4326",
    "resolution": "20",
    "snap_cpu_parallelism": "1",
    "terrain_correction_azimuth": "1",
    "terrain_correction_range": "4"
  },
  "processing_steps": [
    {
      "engine": "snap",
      "command": "snap/S1_SLC_BkG.xml",
      "args": {
        "dem_file": { "type": "config", "value": "dem_file" },
        "dem_name": { "type": "config", "value": "dem_name" },
        "dem_nodata": { "type": "config", "value": "dem_nodata" },
        "dem_resampling": { "type": "config", "value": "dem_resampling" },
        "resampling": { "type": "config", "value": "bkg_resampling" },
        "master": { "type": "in", "tile_index": 2, "layer": "preprocessed", "extension": "dim" },
        "output": { "type": "out", "layer": "coregistred", "extension": "dim" },
        "slave": { "type": "in", "tile_index": 0, "layer": "preprocessed", "extension": "dim" }
      },
      "condition": "pass"
    },
    {
      "engine": "snap",
      "command": "snap/S1_SLC_SlvExtract.xml",
      "args": {
        "input": { "type": "in", "tile_index": 0, "layer": "coregistred", "extension": "dim" },
        "output": { "type": "out", "layer": "coregextract", "extension": "dim" }
      },
      "condition": "different_T0_T1"
    },
    {
      "engine": "snap",
      "command": "snap/S1_SLC_Deb_BetaSigma_ML_TC_RNKELL.xml",
      "args": {
        "azimuth_multilook": { "type": "config", "value": "terrain_correction_azimuth" },
        "band": { "type": "fixed", "value": "Sigma0" },
        "dem_egm": { "type": "config", "value": "dem_egm_correction" },
        "dem_file": { "type": "config", "value": "dem_file" },
        "dem_name": { "type": "config", "value": "dem_name" },
        "dem_nodata": { "type": "config", "value": "dem_nodata" },
        "dem_resampling": { "type": "config", "value": "dem_resampling" },
        "grid_align": { "type": "fixed", "value": "true" },
        "img_resampling": { "type": "config", "value": "img_resampling" },
        "img_suffix": { "type": "tile", "value": "date" },
        "input": { "type": "in", "tile_index": 0, "layer": "coregistred", "extension": "dim" },
        "outputVH": { "type": "out", "layer": "sigma0_VH", "extension": "tif" },
        "outputVV": { "type": "out", "layer": "sigma0_VV", "extension": "tif" },
        "projection": { "type": "config", "value": "projection" },
        "range_multilook": { "type": "config", "value": "terrain_correction_range" },
        "resolution": { "type": "config", "value": "resolution" },
        "swath": { "type": "tile", "value": "swath" },
        "trig": { "type": "fixed", "value": "sin" }
      },
      "condition": "pass"
    },
    {
      "engine": "python",
      "command": "python/erodeMask.py",
      "args": {
        "file-in": { "type": "out", "layer": "sigma0_VV", "extension": "tif" },
        "file-out": { "type": "out", "layer": "sigma0_VV", "extension": "tif" },
        "iterations": { "type": "config", "value": "bs_erode_iterations" },
        "no-data": { "type": "fixed", "value": "0" }
      },
      "condition": "pass"
    },
    {
      "engine": "python",
      "command": "python/convert.py",
      "args": {
        "dformat-out": { "type": "config", "value": "dformat_out" },
        "file-in": { "type": "out", "layer": "sigma0_VV", "extension": "tif" },
        "file-out": { "type": "out", "layer": "sigma0_VV", "extension": "tif" },
        "range-in": { "type": "fixed", "value": "0,1" }
      },
      "condition": "pass"
    },
    {
      "engine": "python",
      "command": "python/erodeMask.py",
      "args": {
        "file-in": { "type": "out", "layer": "sigma0_VH", "extension": "tif" },
        "file-out": { "type": "out", "layer": "sigma0_VH", "extension": "tif" },
        "iterations": { "type": "config", "value": "bs_erode_iterations" },
        "no-data": { "type": "fixed", "value": "0" }
      },
      "condition": "pass"
    },
    {
      "engine": "python",
      "command": "python/convert.py",
      "args": {
        "dformat-out": { "type": "config", "value": "dformat_out" },
        "file-in": { "type": "out", "layer": "sigma0_VH", "extension": "tif" },
        "file-out": { "type": "out", "layer": "sigma0_VH", "extension": "tif" },
        "range-in": { "type": "fixed", "value": "0,1" }
      },
      "condition": "pass"
    },
    {
      "engine": "snap",
      "command": "snap/S1_SLC_BkG.xml",
      "args": {
        "dem_file": { "type": "config", "value": "dem_file" },
        "dem_name": { "type": "config", "value": "dem_name" },
        "dem_nodata": { "type": "config", "value": "dem_nodata" },
        "dem_resampling": { "type": "config", "value": "dem_resampling" },
        "resampling": { "type": "config", "value": "bkg_resampling" },
        "master": { "type": "in", "tile_index": 0, "layer": "coregextract", "extension": "dim" },
        "output": { "type": "out", "layer": "coregistred", "extension": "dim" },
        "slave": { "type": "in", "tile_index": 1, "layer": "coregextract", "extension": "dim" }
      },
      "condition": "different_T1_T2"
    },
    {
      "engine": "snap",
      "command": "snap/S1_SLC_Coh_BSel_Deb_ML_TC.xml",
      "args": {
        "azimuth_multilook": { "type": "config", "value": "terrain_correction_azimuth" },
        "coherence_azimuth": { "type": "config", "value": "coherence_azimuth" },
        "coherence_range": { "type": "config", "value": "coherence_range" },
        "dem_egm": { "type": "config", "value": "dem_egm_correction" },
        "dem_file": { "type": "config", "value": "dem_file" },
        "dem_name": { "type": "config", "value": "dem_name" },
        "dem_nodata": { "type": "config", "value": "dem_nodata" },
        "dem_resampling": { "type": "config", "value": "dem_resampling" },
        "grid_align": { "type": "fixed", "value": "true" },
        "img_resampling": { "type": "config", "value": "img_resampling" },
        "input": { "type": "in", "tile_index": 0, "layer": "coregistred", "extension": "dim" },
        "outputVH": { "type": "out", "layer": "coh_VH", "extension": "tif" },
        "outputVV": { "type": "out", "layer": "coh_VV", "extension": "tif" },
        "projection": { "type": "config", "value": "projection" },
        "range_multilook": { "type": "config", "value": "terrain_correction_range" },
        "resolution": { "type": "config", "value": "resolution" },
        "sel_date": { "type": "tile", "value": "cohdate" }
      },
      "condition": "different_T0_T1"
    },
    {
      "engine": "python",
      "command": "python/erodeMask.py",
      "args": {
        "file-in": { "type": "out", "layer": "coh_VV", "extension": "tif" },
        "file-out": { "type": "out", "layer": "coh_VV", "extension": "tif" },
        "iterations": { "type": "config", "value": "coh_erode_iterations" },
        "no-data": { "type": "fixed", "value": "0" }
      },
      "condition": "different_T0_T1"
    },
    {
      "engine": "python",
      "command": "python/convert.py",
      "args": {
        "dformat-out": { "type": "config", "value": "dformat_out" },
        "file-in": { "type": "out", "layer": "coh_VV", "extension": "tif" },
        "file-out": { "type": "out", "layer": "coh_VV", "extension": "tif" },
        "range-in": { "type": "fixed", "value": "0,1" }
      },
      "condition": "different_T0_T1"
    },
    {
      "engine": "python",
      "command": "python/erodeMask.py",
      "args": {
        "file-in": { "type": "out", "layer": "coh_VH", "extension": "tif" },
        "file-out": { "type": "out", "layer": "coh_VH", "extension": "tif" },
        "iterations": { "type": "config", "value": "coh_erode_iterations" },
        "no-data": { "type": "fixed", "value": "0" }
      },
      "condition": "different_T0_T1"
    },
    {
      "engine": "python",
      "command": "python/convert.py",
      "args": {
        "dformat-out": { "type": "config", "value": "dformat_out" },
        "file-in": { "type": "out", "layer": "coh_VH", "extension": "tif" },
        "file-out": { "type": "out", "layer": "coh_VH", "extension": "tif" },
        "range-in": { "type": "fixed", "value": "0,1" }
      },
      "condition": "different_T0_T1"
    }
  ],
  "in_files": [
    [
      {
        "layer": "preprocessed",
        "extension": "dim"
      }
    ],
    [
      {
        "layer": "coregextract",
        "extension": "dim",
        "condition": "different_T1_T2"
      }
    ],
    [
      {
        "layer": "preprocessed",
        "extension": "dim",
        "condition": "different_T0_T2"
      }
    ]
  ],
  "out_files": [
    [
      {
        "layer": "sigma0_VV",
        "extension": "tif",
        "dformat_out": { "type": "config", "value": "dformat_out" },
        "ext_min_value": 0,
        "ext_max_value": 1,
        "action": "to_index"
      },
      {
        "layer": "sigma0_VH",
        "extension": "tif",
        "dformat_out": { "type": "config", "value": "dformat_out" },
        "ext_min_value": 0,
        "ext_max_value": 1,
        "action": "to_index"
      },
      {
        "layer": "coh_VV",
        "extension": "tif",
        "dformat_out": { "type": "config", "value": "dformat_out" },
        "ext_min_value": 0,
        "ext_max_value": 1,
        "action": "to_index",
        "condition": "different_T0_T1"
      },
      {
        "layer": "coh_VH",
        "extension": "tif",
        "dformat_out": { "type": "config", "value": "dformat_out" },
        "ext_min_value": 0,
        "ext_max_value": 1,
        "action": "to_index",
        "condition": "different_T0_T1"
      }
    ],
    [
    ],
    []
  ]
}

Example Processing Sentinel2

{
    "config": {
     "srtm-uri": "gs://d-vrd-lai-crop-monitoring-ovl-artifacts/srtm/,gs://forest-overland-srtm-cache-dev/{NS}{LAT}{EW}{LON}.SRTMGL1.hgt.zip,gs://forest-overland-srtm-cache-dev/GLSDEM_{ns}0{LAT}{ew}{LON}.tif.zip",
     "roadmap-robot": "/data/graph/cmd/roadmaps/robot_Mosaic-S2-VNIR4.xml",
     "roadmap-options": "/data/graph/cmd/roadmaps/options_Geocube-VNIR4.xml",
     "roadmap-params": "",
     "libs": "/data/graph/cmd/libs",
     "clod-optical-depth-x100": "200",
     "rekl-workers": "-1"
    },
    "processing_steps": [
     {
      "engine": "cmd",
      "command": "./cmd/overland",
      "args": {
        "workdir": { "type": "config", "value": "workdir" },
        "image": { "type": "in", "layer": "product", "extension": "SAFE" },
        "srtm-uri": { "type": "config", "value": "srtm-uri" },
        "roadmap": { "type": "config", "value": "roadmap-robot" },
        "options": { "type": "config", "value": "roadmap-options" },
        "roadmap-params": { "type": "config", "value": "roadmap-params" },
        "libs": { "type": "config", "value": "libs" },
        "constellation": { "type": "fixed", "value": "sentinel2" },
        "out-pattern": { "type": "out", "layer": "*", "extension": "tif" }
      },
      "condition": "pass"
     },
     {
      "engine": "cmd",
      "command": "./cmd/rekl",
      "args": {
        "workdir": { "type": "config", "value": "workdir" },
        "satellite-image": { "type": "in", "layer": "product", "extension": "SAFE" },
        "input": { "type": "fixed", "value": "*.tif"},
        "output": { "type": "fixed", "value": "." },
        "mask": { "type": "out", "layer": "clod", "extension": "tif" },
        "mask-threshold": { "type": "config", "value": "clod-optical-depth-x100" },
        "workers": { "type": "config", "value": "rekl-workers" },
        "constellation": { "type": "fixed", "value": "Sentinel2" }
      },
      "condition": "pass"
     }
    ],
    "in_files": [
     [
      {
        "layer": "product",
        "extension": "SAFE"
      }
     ],
     [],
     []
    ],
    "out_files": [
     [
      {
        "layer": "img",
        "extension": "tif",
        "action": "to_index",
        "dformat_out": { "type": "fixed", "value": "uint16,0,0,30000" },
        "ext_min_value": 0,
        "ext_max_value": 3,
        "nbands": 4
      },
      {
        "layer": "clcsh",
        "extension": "tif",
        "action": "to_index",
        "dformat_out": { "type": "fixed", "value": "int16,-1,0,10000" },
        "ext_min_value": 0,
        "ext_max_value": 1
      },
      {
        "layer": "clod",
        "extension": "tif",
        "action": "to_index",
        "dformat_out": { "type": "fixed", "value": "int16,-1,0,5000" },
        "ext_min_value": 0,
        "ext_max_value": 50
      },
      {
        "layer": "fcover",
        "extension": "tif",
        "action": "to_index",
        "dformat_out": { "type": "fixed", "value": "int16,-1,0,10000" },
        "ext_min_value": 0,
        "ext_max_value": 1
      },
      {
        "layer": "hod",
        "extension": "tif",
        "action": "to_index",
        "dformat_out": { "type": "fixed", "value": "int16,-1,0,5000" },
        "ext_min_value": 0,
        "ext_max_value": 5
      }
     ],
     [],
     []
    ]
   }

Docker Engine

Principle & Concept

Instead of using and runing one large image docker which contains every dependencies (Snap, Python, other binaries), the ingester processor can be a docker container orchestrator.

Docker Processor

Processor Replication controller can be configured to run two containers:

  • Generic container with processor Go binary and docker binary (Client)
  • Dind (docker in docker) container which contains docker daemon (Server)

Deployment

In order to deploy this kind of architecture, some configuration must be added in your k8s config file:

In client container (environment var):

- name: DOCKER_HOST
  value: tcp://localhost:2375

A certificate can be defined in order to secure your server or enable insecure trafic.

In Server container (environment var):

- name: DOCKER_TLS_CERTDIR
  value: ""

Private Registry

In order to use private registry, credentials must be configured in Client side container.

  • user (for gcp registry: _json_key)
  • password (for gcp registry: service account content)
  • server (for gcp registry e.g: https://europe-west1-docker.pkg.dev)

For images stored in public registry (ex. docker.hub), there is no need for credentials.

Credentials

Some containers need file credentials (to request storage for example).

docker-mount-volumes can be defined in client executable arguments to mount a volume containing credential file.

Variable environment must be also defined in graph file:

{
      "engine": "docker",
      "command": "myImage",
      "args": {
        "workdir": { "type": "config", "value": "workdir" },
        "image": { "type": "in", "layer": "product", "extension": "SAFE" },
        "srtm-uri": { "type": "config", "value": "srtm-uri" },
        "roadmap": { "type": "config", "value": "roadmap-robot" },
        "options": { "type": "config", "value": "roadmap-options" },
        "roadmap-params": { "type": "config", "value": "roadmap-params" },
        "libs": { "type": "config", "value": "libs" },
        "constellation": { "type": "fixed", "value": "sentinel2" },
        "out-pattern": { "type": "out", "layer": "*", "extension": "tif" }
      },
      "envs": ["GOOGLE_APPLICATION_CREDENTIALS=/myPath/myFile"],
      "condition": "pass"
}

Important: Path defined in args and path in graphFile should be equal.

Volume

In order to mount volume correctly, it is important to enable access to all containers (processor & docker-daemon)

Example:

  • If you want to use a workdir which is mount on /local-sdd in your processor kubernetes deployment.

  • If you want to mount also this workdir on your docker processing, you need to mount this volume on processor container and docker-dind container to enable docker-daemon access.