{
  "id": "corsa_compression",
  "links": [
    {
      "href": "https://openeo-dev.vito.be/openeo/1.2/processes/u:63d26122aec1fc5ae2c16a045570e4d90e89fb3a51081c0d9d6047c270f72940@egi.eu/corsa_compression",
      "rel": "canonical",
      "title": "Public URL for user-defined process 'corsa_compression'"
    }
  ],
  "parameters": [
    {
      "description": "A bounding box, which may include a vertical axis (see `base` and `height`).",
      "name": "spatial_extent",
      "schema": {
        "properties": {
          "base": {
            "default": null,
            "description": "Base (optional, lower left corner, coordinate axis 3).",
            "type": [
              "number",
              "null"
            ]
          },
          "crs": {
            "anyOf": [
              {
                "examples": [
                  3857
                ],
                "minimum": 1000,
                "subtype": "epsg-code",
                "title": "EPSG Code",
                "type": "integer"
              },
              {
                "subtype": "wkt2-definition",
                "title": "WKT2",
                "type": "string"
              }
            ],
            "default": 4326,
            "description": "Coordinate reference system of the extent, specified as as [EPSG code](http://www.epsg-registry.org/) or [WKT2 CRS string](http://docs.opengeospatial.org/is/18-010r7/18-010r7.html). Defaults to `4326` (EPSG code 4326) unless the client explicitly requests a different coordinate reference system."
          },
          "east": {
            "description": "East (upper right corner, coordinate axis 1).",
            "type": "number"
          },
          "height": {
            "default": null,
            "description": "Height (optional, upper right corner, coordinate axis 3).",
            "type": [
              "number",
              "null"
            ]
          },
          "north": {
            "description": "North (upper right corner, coordinate axis 2).",
            "type": "number"
          },
          "south": {
            "description": "South (lower left corner, coordinate axis 2).",
            "type": "number"
          },
          "west": {
            "description": "West (lower left corner, coordinate axis 1).",
            "type": "number"
          }
        },
        "required": [
          "west",
          "south",
          "east",
          "north"
        ],
        "subtype": "bounding-box",
        "type": "object"
      }
    },
    {
      "default": [
        "2023-05-01",
        "2023-05-01"
      ],
      "description": "Date expressed as an interval, all dates for which data is available will be processed.",
      "name": "temporal_extent",
      "optional": true,
      "schema": {
        "subtype": "temporal-interval",
        "type": "array"
      }
    }
  ],
  "process_graph": {
    "applyneighborhood1": {
      "arguments": {
        "data": {
          "from_node": "loadcollection1"
        },
        "overlap": [
          {
            "dimension": "x",
            "unit": "px",
            "value": 0
          },
          {
            "dimension": "y",
            "unit": "px",
            "value": 0
          }
        ],
        "process": {
          "process_graph": {
            "runudf1": {
              "arguments": {
                "context": {
                  "bands": [
                    "B02",
                    "B03",
                    "B04",
                    "B05",
                    "B06",
                    "B07",
                    "B08",
                    "B8A",
                    "B11",
                    "B12"
                  ],
                  "model_dir": "/data/users/Public/luytsa/corsa-compression/pretrain_BEN_10-20mbands_bicubic_512-128_onnx"
                },
                "data": {
                  "from_parameter": "data"
                },
                "runtime": "Python",
                "udf": "import functools\nimport requests, zipfile, io\nimport os\nimport numpy as np\nimport logging\nimport xarray\nimport joblib\nfrom openeo.udf import XarrayDataCube\nfrom openeo.udf.debug import inspect\nfrom openeo.metadata import CollectionMetadata\n\nfrom typing import Dict\nimport traceback\nimport sys\n\n@functools.lru_cache(maxsize=25)\ndef load_onnx_deps():\n    url  = \"https://artifactory.vgt.vito.be:443/auxdata-public/openeo/onnx_dependencies_1.16.3.zip\"\n    r = requests.get(url)\n    z = zipfile.ZipFile(io.BytesIO(r.content))\n    z.extractall(\"./onnx_deps\")\n\nload_onnx_deps()\nsys.path.insert(0, \"./onnx_deps\")\nimport onnxruntime as ort\n\n@functools.lru_cache(maxsize=25)\ndef load_ort_session(model_dir, session_options=None):\n    session =  ort.InferenceSession(f'{model_dir}/encoder.onnx', session_options=session_options)\n    ort_input_name = session.get_inputs()[0].name\n    return session, ort_input_name\n\n\n@functools.lru_cache(maxsize=25)\ndef load_scalers(scaler_dir, ordered_bands):\n    scalers = {}\n    for band in tuple(ordered_bands):\n        scalers[band] = joblib.load(\n            open(\n                os.path.join(scaler_dir, f\"{band}_scaler.pkl\"), \"rb\"\n            )\n        )\n        inspect(message=\"ULOGGING: loaded scaler band\", data=[band])\n    return scalers\n\n# # # seems to have no effect :'(\ndef apply_metadata(input_metadata:CollectionMetadata, context:dict) -> CollectionMetadata:\n    xstep = input_metadata.get('x','step')\n    ystep = input_metadata.get('y','step')\n    inspect(message=\"ULOGGING: x,y step\", data=[xstep, ystep])\n    bands_out = ['level_0', 'level_1']\n    renamed_bands = bands_out + [\"\"] * 8\n    return input_metadata.rename_labels(dimension=\"bands\", target=renamed_bands)\n\n\n\n    # xstep = input_metadata.get('x','step')\n    # ystep = input_metadata.get('y','step')\n    # inspect(message=\"ULOGGING: x,y step\", data=[xstep, ystep])\n    # # # inspect(message=\"ULOGGING: metadata\", data=[input_metadata])\n    # # print(f\"Metadata: {input_metadata}\")\n\n\n    # new_metadata = {\n    #       \"x\": {\"type\": \"spatial\", \"axis\": \"x\", \"step\": xstep},\n    #       \"y\": {\"type\": \"spatial\", \"axis\": \"y\", \"step\": ystep},\n    #       \"t\": {\"type\": \"temporal\"}\n    # }\n    # return CollectionMetadata(new_metadata)\n\ndef process_window_onnx(input_data, model_dir, ort_sess_opt=None):\n    \"\"\" Apply the model. Model expects [1, n_bands, 120, 120] input (already preprocessed) and returns [codebook_ids_level_0, codebook_ids_level_1]\"\"\"\n    if ort_sess_opt is None:\n        ort_sess_opt = ort.SessionOptions()\n        ort_sess_opt.intra_op_num_threads = 1\n    \n    ort_session, ort_input_name = load_ort_session(model_dir, session_options=ort_sess_opt)\n    ## apply model\n    ort_inputs = {ort_input_name: input_data}\n    ort_l0_vec, ort_l1_vec, ort_l0_ids, ort_l1_ids = ort_session.run(None, ort_inputs) # run() requires [output_names] and inputs arguments\n\n    ort_l0_ids = ort_l0_ids.reshape(1,1,60,60)\n    ort_l0_ids = ort_l0_ids.repeat(2, axis=2).repeat(2, axis=3)\n\n    ort_l1_ids = ort_l1_ids.reshape(1,1,30,30)\n    ort_l1_ids = ort_l1_ids.repeat(4, axis=2).repeat(4, axis=3)\n\n    return np.concatenate([ort_l0_ids, ort_l1_ids], axis=1).astype(np.uint16)\n\ndef preprocess_datacube(input_data, scaler_dir, ordered_bands):\n    scalers = load_scalers(scaler_dir, tuple(ordered_bands))\n\n    input_data = input_data.data.astype(np.float32) #.squeeze(0)\n    im_width = input_data.shape[2]\n    im_height = input_data.shape[1]\n    n_bands = input_data.shape[0]\n    assert n_bands == len(scalers), f'found {len(scalers)} scalers, but {n_bands} bands were given as input. Should be equal.'\n\n    for k, band in enumerate(ordered_bands):\n        input_data[k] = np.clip(\n            scalers[band]\n            .transform(input_data[k].ravel().reshape(-1, 1))\n            .reshape(im_height, im_width)\n        ,-100,18000)\n        \n    # inspect(message=\"ULOGGING: after scaling\", data=[input_data])\n    return input_data[np.newaxis, ...].astype(np.float32)\n\n\ndef apply_datacube(cube: XarrayDataCube, context: Dict) -> XarrayDataCube:\n\n    \"\"\" main function \"\"\"\n\n    try:\n        ## get path to model, falling back to default value\n        if len(context) > 0:\n            model_dir = context.get(\"model_dir\", '/data/users/Public/luytsa/corsa-compression/onnx_ben_allbands')\n            bands = context.get(\"bands\", ('B01', 'B02', 'B03', 'B04', 'B05', 'B06', 'B07', 'B08', 'B8A', 'B09', 'B11', 'B12'))\n            inspect(message=\"ULOGGING: path_model from context\", data=[model_dir, os.path.exists(model_dir)])\n        else: # for local debugging\n            model_dir = '/home/vito/luytsa/openmmv1x/trained_models/openeo/pretrain_BEN_10-20m_512-128/onnx' # for local debugging\n            bands = ( 'B02', 'B03', 'B04', 'B05', 'B06', 'B07', 'B08', 'B8A', 'B11', 'B12')\n            inspect(message=\"ULOGGING: model_dir set to default\", data=[model_dir, os.path.exists(model_dir)])\n        print(f\"Path model: {model_dir}\")\n\n\n        library_versions = {'python':sys.version, 'numpy': np.__version__, 'xarray': xarray.__version__, 'joblib': joblib.__version__, 'onnx_runtime': ort.__version__}\n        library_versions = [f\"{k}: {v}\" for k,v in library_versions.items()]\n        inspect(message=\"ULOGGING: library versions\", data=library_versions)\n\n\n        ## get the array\n        cubearray:xarray.DataArray = cube.get_array() # bands, y, x\n        nan_mask = np.isnan(cubearray.data)\n        nan_count = np.count_nonzero(nan_mask,axis=(1,2))\n\n        inspect(message=\"ULOGGING: cubearray\", data=cubearray)\n        inspect(message=\"ULOGGING: cubearray NaNs per band\", data= nan_count)\n\n        print(f\"Cube array shape: {cubearray.shape}\")\n        print(f\"Cube array NaNs: {np.count_nonzero(np.isnan(cubearray.data),axis=(1,2))}\")\n\n        # replace NaNs with 0 so inference works\n        if  np.count_nonzero(nan_mask)>0:\n            print(\"Replacing NaNs with 0\")\n            cubearray = cubearray.interpolate_na(dim='y', method='linear', use_coordinate=False,limit=2 )/2 + cubearray.interpolate_na(dim='x', method='linear', use_coordinate=False, limit=2)/2\n            cubearray = cubearray.fillna(0)\n            \n        # # Pixel size of the original image\n        # init_pixel_size_x = cubearray.coords['x'][-1] - cubearray.coords['x'][-2]\n        # init_pixel_size_y = cubearray.coords['y'][-1] - cubearray.coords['y'][-2]\n        \n        ## normalize data\n        cubearray_normalized = preprocess_datacube(cubearray, scaler_dir=f'{model_dir}/scalers', ordered_bands=tuple(bands))\n\n        ## inference\n        result = process_window_onnx(cubearray_normalized, model_dir)\n        # inspect(message=\"ULOGGING: result\", data=result)\n        print(f\"Result shape: {result[0].shape}\")\n\n        # coord_x = np.linspace(start=cube.get_array().coords['x'].min(), stop=cube.get_array().coords['x'].max() + init_pixel_size_x,\n        #                   num=result.shape[-2], endpoint=False)\n        # coord_y = np.linspace(start=cube.get_array().coords['y'].min(), stop=cube.get_array().coords['y'].max() + init_pixel_size_y,\n        #                     num=result.shape[-1], endpoint=False)\n        # print(cubearray.coords['x'].min())\n        # print(cubearray.coords['y'].max())\n        # print(init_pixel_size_x)\n        \n        # result_xarray = xarray.DataArray(result[0], dims=['bands', 'x', 'y'], coords=dict(x=coord_x, y=coord_y, bands=['level_0' ,'level_1']))\n\n        ## transform numpy array predictions into an xarray with right dimension order for openEO XarrayDataCube\n        result_xarray = xarray.DataArray(result[0], dims=[\"bands\", \"y\", \"x\"], coords=dict(bands=['level_0' ,'level_1'], x=cubearray.coords[\"x\"], y=cubearray.coords[\"y\"]))\n        # inspect(message=\"ULOGGING: result_xarray\", data=result_xarray)\n        return XarrayDataCube(result_xarray)\n    \n    except Exception as e:\n        inspect(message=\"ULOGGING: Error in UDF!\",\n                data=traceback.format_exc())\n        exc_type, exc_obj, exc_tb = sys.exc_info()\n        fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]\n        print(exc_type, fname, exc_tb.tb_lineno)\n        print(e)\n        raise e\n"
              },
              "process_id": "run_udf",
              "result": true
            }
          }
        },
        "size": [
          {
            "dimension": "x",
            "unit": "px",
            "value": 120
          },
          {
            "dimension": "y",
            "unit": "px",
            "value": 120
          },
          {
            "dimension": "t",
            "value": "P1D"
          }
        ]
      },
      "process_id": "apply_neighborhood"
    },
    "filterbands1": {
      "arguments": {
        "bands": [
          "level_0",
          "level_1"
        ],
        "data": {
          "from_node": "renamelabels1"
        }
      },
      "process_id": "filter_bands"
    },
    "filterbands2": {
      "arguments": {
        "bands": [
          "level_1"
        ],
        "data": {
          "from_node": "filterbands1"
        }
      },
      "process_id": "filter_bands"
    },
    "filterbands3": {
      "arguments": {
        "bands": [
          "level_0"
        ],
        "data": {
          "from_node": "filterbands1"
        }
      },
      "process_id": "filter_bands"
    },
    "loadcollection1": {
      "arguments": {
        "bands": [
          "B02",
          "B03",
          "B04",
          "B05",
          "B06",
          "B07",
          "B08",
          "B8A",
          "B11",
          "B12"
        ],
        "featureflags": {
          "tilesize": 120
        },
        "id": "SENTINEL2_L2A",
        "properties": {
          "eo:cloud_cover": {
            "process_graph": {
              "lte1": {
                "arguments": {
                  "x": {
                    "from_parameter": "value"
                  },
                  "y": 10
                },
                "process_id": "lte",
                "result": true
              }
            }
          }
        },
        "spatial_extent": {
          "from_parameter": "spatial_extent"
        },
        "temporal_extent": {
          "from_parameter": "temporal_extent"
        }
      },
      "process_id": "load_collection"
    },
    "renamelabels1": {
      "arguments": {
        "data": {
          "from_node": "applyneighborhood1"
        },
        "dimension": "bands",
        "target": [
          "level_0",
          "level_1",
          "",
          "",
          "",
          "",
          "",
          "",
          "",
          ""
        ]
      },
      "process_id": "rename_labels"
    },
    "resamplespatial1": {
      "arguments": {
        "align": "upper-left",
        "data": {
          "from_node": "filterbands2"
        },
        "method": "near",
        "projection": null,
        "resolution": 40
      },
      "process_id": "resample_spatial"
    },
    "resamplespatial2": {
      "arguments": {
        "align": "upper-left",
        "data": {
          "from_node": "filterbands3"
        },
        "method": "near",
        "projection": null,
        "resolution": 20
      },
      "process_id": "resample_spatial"
    },
    "saveresult1": {
      "arguments": {
        "data": {
          "from_node": "resamplespatial1"
        },
        "format": "GTiff",
        "options": {
          "filename_prefix": "level1_40m"
        }
      },
      "process_id": "save_result"
    },
    "saveresult2": {
      "arguments": {
        "data": {
          "from_node": "resamplespatial2"
        },
        "format": "GTiff",
        "options": {
          "filename_prefix": "level0_20m"
        }
      },
      "process_id": "save_result",
      "result": true
    }
  },
  "public": true
}