From e42aebde2a133168bd88430a3dac8122743bd8c9 Mon Sep 17 00:00:00 2001 From: Manu Goudar Date: Wed, 12 Nov 2025 17:00:31 +0100 Subject: [PATCH 1/2] Add python3.11 udf support. --- .../vito/parcel_delineation/openeo_udp/generate.py | 6 +++--- .../openeo_udp/parcel_delineation.json | 14 +++++++------- .../openeo_udp/udf_sobel_felzenszwalb.py | 8 +++++++- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/algorithm_catalog/vito/parcel_delineation/openeo_udp/generate.py b/algorithm_catalog/vito/parcel_delineation/openeo_udp/generate.py index a3131ebd..33b9c889 100644 --- a/algorithm_catalog/vito/parcel_delineation/openeo_udp/generate.py +++ b/algorithm_catalog/vito/parcel_delineation/openeo_udp/generate.py @@ -53,7 +53,7 @@ def generate() -> dict: # Apply ML algorithm # apply a neural network, requires 128x128 pixel 'chunks' as input. - segment_udf = openeo.UDF.from_file("udf_segmentation.py", version="3.8") + segment_udf = openeo.UDF.from_file("udf_segmentation.py", version="3.11") segmentationband = ndviband.apply_neighborhood( process=segment_udf, size=[{"dimension": "x", "value": 64, "unit": "px"}, {"dimension": "y", "value": 64, "unit": "px"}], @@ -62,7 +62,7 @@ def generate() -> dict: # Postprocess the output from the neural network using a sobel filter and # Felzenszwalb's algorithm, which are then merged. - segment_postprocess_udf = openeo.UDF.from_file("udf_sobel_felzenszwalb.py") + segment_postprocess_udf = openeo.UDF.from_file("udf_sobel_felzenszwalb.py", version="3.11") sobel_felzenszwalb = segmentationband.apply_neighborhood( process=segment_postprocess_udf, size=[{"dimension": "x", "value": 2048, "unit": "px"}, {"dimension": "y", "value": 2048, "unit": "px"}], @@ -70,7 +70,7 @@ def generate() -> dict: ) job_options = { "udf-dependency-archives": [ - "https://artifactory.vgt.vito.be/auxdata-public/openeo/onnx_dependencies.zip#onnx_deps", + "https://s3.waw3-1.cloudferro.com/swift/v1/project_dependencies/onnx_deps_python311.zip#onnx_deps", "https://artifactory.vgt.vito.be/artifactory/auxdata-public/openeo/parcelDelination/BelgiumCropMap_unet_3BandsGenerator_Models.zip#onnx_models", ], "driver-memory": "500m", diff --git a/algorithm_catalog/vito/parcel_delineation/openeo_udp/parcel_delineation.json b/algorithm_catalog/vito/parcel_delineation/openeo_udp/parcel_delineation.json index 0896c0c7..7e0b4cf9 100644 --- a/algorithm_catalog/vito/parcel_delineation/openeo_udp/parcel_delineation.json +++ b/algorithm_catalog/vito/parcel_delineation/openeo_udp/parcel_delineation.json @@ -136,8 +136,8 @@ "from_parameter": "data" }, "runtime": "Python", - "version": "3.8", - "udf": "from functools import lru_cache\nimport gc\nimport sys\nfrom typing import Dict, Tuple\nfrom random import seed, sample\nfrom xarray import DataArray, zeros_like\nfrom openeo.udf import inspect\n\n# Add the onnx dependencies to the path\nsys.path.insert(1, \"onnx_deps\")\nimport onnxruntime as ort\n\n\nmodel_names = frozenset(\n [\n \"BelgiumCropMap_unet_3BandsGenerator_Network1.onnx\",\n \"BelgiumCropMap_unet_3BandsGenerator_Network2.onnx\",\n \"BelgiumCropMap_unet_3BandsGenerator_Network3.onnx\",\n ]\n)\n\n\n@lru_cache(maxsize=1)\ndef load_ort_sessions(names):\n \"\"\"\n Load the models and make the prediction functions.\n The lru_cache avoids loading the model multiple times on the same worker.\n\n @param modeldir: Model directory\n @return: Loaded model sessions\n \"\"\"\n # inspect(message=\"Loading convolutional neural networks as ONNX runtime sessions ...\")\n return [ort.InferenceSession(f\"onnx_models/{model_name}\") for model_name in names]\n\n\ndef process_window_onnx(ndvi_stack: DataArray, patch_size=128) -> DataArray:\n \"\"\"Compute prediction.\n\n Compute predictions using ML models. ML models takes three inputs images and predicts\n one image. Four predictions are made per model using three random images. Three images\n are considered to save computational time. Final result is median of these predictions.\n\n Parameters\n ----------\n ndvi_stack : DataArray\n ndvi data\n patch_size : Int\n Size of the sample\n\n Returns\n -------\n xr.DataArray\n Machine learning prediction.\n \"\"\"\n # Do 12 predictions: use 3 networks, and for each take 3 random NDVI images and repeat 4 times\n ort_sessions = load_ort_sessions(model_names) # get models\n\n predictions_per_model = 4\n no_rand_images = 3 # Number of random images that are needed for input\n no_images = ndvi_stack.t.shape[0]\n\n # Range of index of images for random index selection\n images_range = range(no_images)\n # List of all predictions\n prediction = []\n for ort_session in ort_sessions:\n # make 4 predictions per model\n for i in range(predictions_per_model):\n # initialize a predicter array\n # Seed to lead to a reproducible results.\n seed(i)\n # Random selection of 3 images for input\n idx = sample(images_range, k=no_rand_images)\n # log a message that the selected indices are not at least a week away\n if len(set((ndvi_stack.isel(t=idx)).t.dt.isocalendar().week.data)) != no_rand_images:\n inspect(message=\"Time difference is not larger than a week for good parcel delineation\")\n\n # re-shape the input data for ML input\n input_data = ndvi_stack.isel(t=idx).data.reshape(1, patch_size * patch_size, no_rand_images)\n ort_inputs = {ort_session.get_inputs()[0].name: input_data}\n\n # Run ML to predict\n ort_outputs = ort_session.run(None, ort_inputs)\n # reshape ort_outputs and append it to prediction list\n prediction.append(ort_outputs[0].reshape((patch_size, patch_size)))\n\n # free up some memory to avoid memory errors\n gc.collect()\n\n # Create a DataArray of all predictions\n all_predictions = DataArray(\n prediction,\n dims=[\"predict\", \"x\", \"y\"],\n coords={\n \"predict\": range(len(prediction)),\n \"x\": ndvi_stack.coords[\"x\"],\n \"y\": ndvi_stack.coords[\"y\"],\n },\n )\n # final prediction is the median of all predictions per pixel\n return all_predictions.median(dim=\"predict\")\n\n\ndef get_valid_ml_inputs(nvdi_stack_data: DataArray, sum_invalid, min_images: int) -> DataArray:\n \"\"\"Machine learning inputs\n\n Extract ML inputs based on how good the data is\n\n \"\"\"\n if (sum_invalid.data == 0).sum() >= min_images:\n good_data = nvdi_stack_data.sel(t=sum_invalid[sum_invalid.data == 0].t)\n else: # select the 4 best time samples with least amount of invalid pixels.\n good_data = nvdi_stack_data.sel(t=sum_invalid.sortby(sum_invalid).t[:min_images])\n return good_data\n\n\ndef preprocess_datacube(cubearray: DataArray, min_images: int) -> Tuple[bool, DataArray]:\n \"\"\"Preprocess data for machine learning.\n\n Preprocess data by clamping NVDI values and first check if the\n data is valid for machine learning and then check if there is good\n data to perform machine learning.\n\n Parameters\n ----------\n cubearray : xr.DataArray\n Input datacube\n min_images : int\n Minimum number of samples to consider for machine learning.\n\n Returns\n -------\n bool\n True refers to data is invalid for machine learning.\n xr.DataArray\n If above bool is False, return data for machine learning else returns a\n sample containing nan (similar to machine learning output).\n \"\"\"\n # Preprocessing data\n # check if bands is in the dims and select the first index\n if \"bands\" in cubearray.dims:\n nvdi_stack = cubearray.isel(bands=0)\n else:\n nvdi_stack = cubearray\n # Clamp out of range NDVI values\n nvdi_stack = nvdi_stack.where(lambda nvdi_stack: nvdi_stack < 0.92, 0.92)\n nvdi_stack = nvdi_stack.where(lambda nvdi_stack: nvdi_stack > -0.08)\n nvdi_stack += 0.08\n # Count the amount of invalid pixels in each time sample.\n sum_invalid = nvdi_stack.isnull().sum(dim=[\"x\", \"y\"])\n # Check % of invalid pixels in each time sample by using mean\n sum_invalid_mean = nvdi_stack.isnull().mean(dim=[\"x\", \"y\"])\n # Fill the invalid pixels with value 0\n nvdi_stack_data = nvdi_stack.fillna(0)\n\n # Check if data is valid for machine learning. If invalid, return True and\n # an DataArray of nan values (similar to the machine learning output)\n # The number of invalid time sample less then min images\n if (sum_invalid_mean.data < 1).sum() <= min_images:\n inspect(message=\"Input data is invalid for this window -> skipping!\")\n # create a nan dataset and return\n nan_data = zeros_like(nvdi_stack.sel(t=sum_invalid_mean.t[0], drop=True))\n nan_data = nan_data.where(lambda nan_data: nan_data > 1)\n return True, nan_data\n # Data selection: valid data for machine learning\n # select time samples where there are no invalid pixels\n good_data = get_valid_ml_inputs(nvdi_stack_data, sum_invalid, min_images)\n return False, good_data.transpose(\"x\", \"y\", \"t\")\n\n\ndef apply_datacube(cube: DataArray, context: Dict) -> DataArray:\n # select atleast best 4 temporal images of ndvi for ML\n min_images = 4\n # preprocess the datacube\n invalid_data, ndvi_stack = preprocess_datacube(cube, min_images)\n # If data is invalid, there is no need to run prediction algorithm so\n # return prediction as nan DataArray and reintroduce time and bands dimensions\n if invalid_data:\n return ndvi_stack.expand_dims(dim={\"t\": [(cube.t.dt.year.values[0])], \"bands\": [\"prediction\"]})\n # Machine learning prediction: process the window\n result = process_window_onnx(ndvi_stack)\n # Reintroduce time and bands dimensions\n result_xarray = result.expand_dims(dim={\"t\": [(cube.t.dt.year.values[0])], \"bands\": [\"prediction\"]})\n # Return the resulting xarray\n return result_xarray\n" + "udf": "from functools import lru_cache\nimport gc\nimport sys\nfrom typing import Dict, Tuple\nfrom random import seed, sample\nfrom xarray import DataArray, zeros_like\nfrom openeo.udf import inspect\n\n# Add the onnx dependencies to the path\nsys.path.insert(1, \"onnx_deps\")\nimport onnxruntime as ort\n\n\nmodel_names = frozenset(\n [\n \"BelgiumCropMap_unet_3BandsGenerator_Network1.onnx\",\n \"BelgiumCropMap_unet_3BandsGenerator_Network2.onnx\",\n \"BelgiumCropMap_unet_3BandsGenerator_Network3.onnx\",\n ]\n)\n\n\n@lru_cache(maxsize=1)\ndef load_ort_sessions(names):\n \"\"\"\n Load the models and make the prediction functions.\n The lru_cache avoids loading the model multiple times on the same worker.\n\n @param modeldir: Model directory\n @return: Loaded model sessions\n \"\"\"\n # inspect(message=\"Loading convolutional neural networks as ONNX runtime sessions ...\")\n return [ort.InferenceSession(f\"onnx_models/{model_name}\") for model_name in names]\n\n\ndef process_window_onnx(ndvi_stack: DataArray, patch_size=128) -> DataArray:\n \"\"\"Compute prediction.\n\n Compute predictions using ML models. ML models takes three inputs images and predicts\n one image. Four predictions are made per model using three random images. Three images\n are considered to save computational time. Final result is median of these predictions.\n\n Parameters\n ----------\n ndvi_stack : DataArray\n ndvi data\n patch_size : Int\n Size of the sample\n\n Returns\n -------\n xr.DataArray\n Machine learning prediction.\n \"\"\"\n # Do 12 predictions: use 3 networks, and for each take 3 random NDVI images and repeat 4 times\n ort_sessions = load_ort_sessions(model_names) # get models\n\n predictions_per_model = 4\n no_rand_images = 3 # Number of random images that are needed for input\n no_images = ndvi_stack.t.shape[0]\n\n # Range of index of images for random index selection\n images_range = range(no_images)\n # List of all predictions\n prediction = []\n for ort_session in ort_sessions:\n # make 4 predictions per model\n for i in range(predictions_per_model):\n # initialize a predicter array\n # Seed to lead to a reproducible results.\n seed(i)\n # Random selection of 3 images for input\n idx = sample(images_range, k=no_rand_images)\n # log a message that the selected indices are not at least a week away\n if len(set((ndvi_stack.isel(t=idx)).t.dt.isocalendar().week.data)) != no_rand_images:\n inspect(message=\"Time difference is not larger than a week for good parcel delineation\")\n\n # re-shape the input data for ML input\n input_data = ndvi_stack.isel(t=idx).data.reshape(1, patch_size * patch_size, no_rand_images)\n ort_inputs = {ort_session.get_inputs()[0].name: input_data}\n\n # Run ML to predict\n ort_outputs = ort_session.run(None, ort_inputs)\n # reshape ort_outputs and append it to prediction list\n prediction.append(ort_outputs[0].reshape((patch_size, patch_size)))\n\n # free up some memory to avoid memory errors\n gc.collect()\n\n # Create a DataArray of all predictions\n all_predictions = DataArray(\n prediction,\n dims=[\"predict\", \"x\", \"y\"],\n coords={\n \"predict\": range(len(prediction)),\n \"x\": ndvi_stack.coords[\"x\"],\n \"y\": ndvi_stack.coords[\"y\"],\n },\n )\n # final prediction is the median of all predictions per pixel\n return all_predictions.median(dim=\"predict\")\n\n\ndef get_valid_ml_inputs(nvdi_stack_data: DataArray, sum_invalid, min_images: int) -> DataArray:\n \"\"\"Machine learning inputs\n\n Extract ML inputs based on how good the data is\n\n \"\"\"\n if (sum_invalid.data == 0).sum() >= min_images:\n good_data = nvdi_stack_data.sel(t=sum_invalid[sum_invalid.data == 0].t)\n else: # select the 4 best time samples with least amount of invalid pixels.\n good_data = nvdi_stack_data.sel(t=sum_invalid.sortby(sum_invalid).t[:min_images])\n return good_data\n\n\ndef preprocess_datacube(cubearray: DataArray, min_images: int) -> Tuple[bool, DataArray]:\n \"\"\"Preprocess data for machine learning.\n\n Preprocess data by clamping NVDI values and first check if the\n data is valid for machine learning and then check if there is good\n data to perform machine learning.\n\n Parameters\n ----------\n cubearray : xr.DataArray\n Input datacube\n min_images : int\n Minimum number of samples to consider for machine learning.\n\n Returns\n -------\n bool\n True refers to data is invalid for machine learning.\n xr.DataArray\n If above bool is False, return data for machine learning else returns a\n sample containing nan (similar to machine learning output).\n \"\"\"\n # Preprocessing data\n # check if bands is in the dims and select the first index\n if \"bands\" in cubearray.dims:\n nvdi_stack = cubearray.isel(bands=0)\n else:\n nvdi_stack = cubearray\n # Clamp out of range NDVI values\n nvdi_stack = nvdi_stack.where(lambda nvdi_stack: nvdi_stack < 0.92, 0.92)\n nvdi_stack = nvdi_stack.where(lambda nvdi_stack: nvdi_stack > -0.08)\n nvdi_stack += 0.08\n # Count the amount of invalid pixels in each time sample.\n sum_invalid = nvdi_stack.isnull().sum(dim=[\"x\", \"y\"])\n # Check % of invalid pixels in each time sample by using mean\n sum_invalid_mean = nvdi_stack.isnull().mean(dim=[\"x\", \"y\"])\n # Fill the invalid pixels with value 0\n nvdi_stack_data = nvdi_stack.fillna(0)\n\n # Check if data is valid for machine learning. If invalid, return True and\n # an DataArray of nan values (similar to the machine learning output)\n # The number of invalid time sample less then min images\n if (sum_invalid_mean.data < 1).sum() <= min_images:\n inspect(message=\"Input data is invalid for this window -> skipping!\")\n # create a nan dataset and return\n nan_data = zeros_like(nvdi_stack.sel(t=sum_invalid_mean.t[0], drop=True))\n nan_data = nan_data.where(lambda nan_data: nan_data > 1)\n return True, nan_data\n # Data selection: valid data for machine learning\n # select time samples where there are no invalid pixels\n good_data = get_valid_ml_inputs(nvdi_stack_data, sum_invalid, min_images)\n return False, good_data.transpose(\"x\", \"y\", \"t\")\n\n\ndef apply_datacube(cube: DataArray, context: Dict) -> DataArray:\n # select atleast best 4 temporal images of ndvi for ML\n min_images = 4\n # preprocess the datacube\n invalid_data, ndvi_stack = preprocess_datacube(cube, min_images)\n # If data is invalid, there is no need to run prediction algorithm so\n # return prediction as nan DataArray and reintroduce time and bands dimensions\n if invalid_data:\n return ndvi_stack.expand_dims(dim={\"t\": [(cube.t.dt.year.values[0])], \"bands\": [\"prediction\"]})\n # Machine learning prediction: process the window\n result = process_window_onnx(ndvi_stack)\n # Reintroduce time and bands dimensions\n result_xarray = result.expand_dims(dim={\"t\": [(cube.t.dt.year.values[0])], \"bands\": [\"prediction\"]})\n # Return the resulting xarray\n return result_xarray\n", + "version": "3.11" }, "result": true } @@ -184,8 +184,8 @@ "from_parameter": "data" }, "runtime": "Python", - "version": "3.8", - "udf": "from xarray import DataArray\nfrom skimage import segmentation, graph\nfrom skimage.filters import sobel\nfrom typing import Dict\nfrom openeo.udf import inspect\n\n\ndef apply_datacube(cube: DataArray, context: Dict) -> DataArray:\n inspect(message=f\"Dimensions of the final datacube {cube.dims}\")\n # get the underlying array without the bands and t dimension\n image_data = cube.squeeze(\"t\", drop=True).squeeze(\"bands\", drop=True).values\n # compute edges\n edges = sobel(image_data)\n # Perform felzenszwalb segmentation\n segment = segmentation.felzenszwalb(image_data, scale=120, sigma=0.0, min_size=30, channel_axis=None)\n # Perform the rag boundary analysis and merge the segments\n bgraph = graph.rag_boundary(segment, edges)\n # merging segments\n mergedsegment = graph.cut_threshold(segment, bgraph, 0.15, in_place=False)\n # create a data cube and perform masking operations\n output_arr = DataArray(mergedsegment.reshape(cube.shape), dims=cube.dims, coords=cube.coords)\n output_arr = output_arr.where(cube >= 0.3) # Mask the output pixels based on the cube values <0.3\n output_arr = output_arr.where(output_arr >= 0) # Mask all values less than or equal to zero\n return output_arr\n" + "udf": "# /// script\n# dependencies = [\n# \"scikit-image\",\n# ]\n# ///\n\nfrom typing import Dict\nfrom xarray import DataArray\nfrom skimage import segmentation, graph\nfrom skimage.filters import sobel\nfrom openeo.udf import inspect\n\n\ndef apply_datacube(cube: DataArray, context: Dict) -> DataArray:\n inspect(message=f\"Dimensions of the final datacube {cube.dims}\")\n # get the underlying array without the bands and t dimension\n image_data = cube.squeeze(\"t\", drop=True).squeeze(\"bands\", drop=True).values\n # compute edges\n edges = sobel(image_data)\n # Perform felzenszwalb segmentation\n segment = segmentation.felzenszwalb(image_data, scale=120, sigma=0.0, min_size=30, channel_axis=None)\n # Perform the rag boundary analysis and merge the segments\n bgraph = graph.rag_boundary(segment, edges)\n # merging segments\n mergedsegment = graph.cut_threshold(segment, bgraph, 0.15, in_place=False)\n # create a data cube and perform masking operations\n output_arr = DataArray(mergedsegment.reshape(cube.shape), dims=cube.dims, coords=cube.coords)\n output_arr = output_arr.where(cube >= 0.3) # Mask the output pixels based on the cube values <0.3\n output_arr = output_arr.where(output_arr >= 0) # Mask all values less than or equal to zero\n return output_arr\n", + "version": "3.11" }, "result": true } @@ -209,10 +209,10 @@ }, "id": "parcel_delineation", "summary": "Parcel delineation using Sentinel-2 data retrieved from the CDSE and processed on openEO.", - "description": "# Parcel Delineation\nParcel delineation refers to the identification and marking of agricultural boundaries. \nThis process is *essential* for tasks such as crop yield estimation and land management. \nAccurate delineation also aids in classifying crop types and managing farmland more effectively.\n \n## Algorithm for Parcel Delineation Using Sentinel-2 Data \n\nThis algorithm performs parcel delineation using Sentinel-2 data and a pre-trained`U-Net machine learning model. The process involves the following steps:\n1. **Pre-processing Sentinel-2 Data:**\n 1. Filter data to ensure a maximum of 10% cloud coverage. \n 2. Apply a cloud mask based on the SCL layer. \n2. **Compute NDVI:**\n 1. The Normalized Difference Vegetation Index (NDVI) is calculated from the pre-processed data.\n 2. The NDVI serves as input to the U-Net model. \n3. **Predict Delineation:**\n 1. The U-Net model predicts parcel delineation boundaries. \n4. **Optimization and Labeling:**\n 1. Apply a Sobel filter to enhance edge detection. \n 2. Use Felzenszwalb's algorithm for segmentation and labeling of delineated parcels.\n ", + "description": "# Parcel Delineation\nParcel delineation refers to the identification and marking of agricultural boundaries. \nThis process is *essential* for tasks such as crop yield estimation and land management. \nAccurate delineation also aids in classifying crop types and managing farmland more effectively.\n \n## Algorithm for Parcel Delineation Using Sentinel-2 Data \n\nThis algorithm performs parcel delineation using Sentinel-2 data and a pre-trained U-Net machine learning model. The process involves the following steps:\n1. **Pre-processing Sentinel-2 Data:**\n 1. Filter data to ensure a maximum of 10% cloud coverage. \n 2. Apply a cloud mask based on the SCL layer. \n2. **Compute NDVI:**\n 1. The Normalized Difference Vegetation Index (NDVI) is calculated from the pre-processed data.\n 2. The NDVI serves as input to the U-Net model. \n3. **Predict Delineation:**\n 1. The U-Net model predicts parcel delineation boundaries. \n4. **Optimization and Labeling:**\n 1. Apply a Sobel filter to enhance edge detection. \n 2. Use Felzenszwalb's algorithm for segmentation and labeling of delineated parcels.\n ", "default_job_options": { "udf-dependency-archives": [ - "https://artifactory.vgt.vito.be/auxdata-public/openeo/onnx_dependencies.zip#onnx_deps", + "https://s3.waw3-1.cloudferro.com/swift/v1/project_dependencies/onnx_deps_python311.zip#onnx_deps", "https://artifactory.vgt.vito.be/artifactory/auxdata-public/openeo/parcelDelination/BelgiumCropMap_unet_3BandsGenerator_Models.zip#onnx_models" ], "driver-memory": "500m", @@ -312,4 +312,4 @@ "optional": true } ] -} +} \ No newline at end of file diff --git a/algorithm_catalog/vito/parcel_delineation/openeo_udp/udf_sobel_felzenszwalb.py b/algorithm_catalog/vito/parcel_delineation/openeo_udp/udf_sobel_felzenszwalb.py index 84202dab..645114e1 100644 --- a/algorithm_catalog/vito/parcel_delineation/openeo_udp/udf_sobel_felzenszwalb.py +++ b/algorithm_catalog/vito/parcel_delineation/openeo_udp/udf_sobel_felzenszwalb.py @@ -1,7 +1,13 @@ +# /// script +# dependencies = [ +# "scikit-image", +# ] +# /// + +from typing import Dict from xarray import DataArray from skimage import segmentation, graph from skimage.filters import sobel -from typing import Dict from openeo.udf import inspect From a1648509c5b0c6b9b27534608458d733cdf51690 Mon Sep 17 00:00:00 2001 From: Manu Goudar Date: Wed, 12 Nov 2025 17:11:16 +0100 Subject: [PATCH 2/2] Edit benchmark to point to new python311 version. --- .../benchmark_scenarios/parcel_delineation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/algorithm_catalog/vito/parcel_delineation/benchmark_scenarios/parcel_delineation.json b/algorithm_catalog/vito/parcel_delineation/benchmark_scenarios/parcel_delineation.json index 16457fbd..7e827b76 100644 --- a/algorithm_catalog/vito/parcel_delineation/benchmark_scenarios/parcel_delineation.json +++ b/algorithm_catalog/vito/parcel_delineation/benchmark_scenarios/parcel_delineation.json @@ -7,7 +7,7 @@ "process_graph": { "parcel_delineation1": { "process_id": "parcel_delineation", - "namespace": "https://raw.githubusercontent.com/ESA-APEx/apex_algorithms/1e3f985ec46c62086f3e4406a0f9f572c43f5865/algorithm_catalog/vito/parcel_delineation/openeo_udp/parcel_delineation.json", + "namespace": "https://raw.githubusercontent.com/ESA-APEx/apex_algorithms/e42aebde2a133168bd88430a3dac8122743bd8c9/algorithm_catalog/vito/parcel_delineation/openeo_udp/parcel_delineation.json", "arguments": { "spatial_extent": { "west": 5.0,