|
| 1 | +import json |
| 2 | +from pathlib import Path |
| 3 | +import openeo |
| 4 | +from openeo.api.process import Parameter |
| 5 | +from openeo.rest.udp import build_process_dict |
| 6 | + |
| 7 | + |
| 8 | +def generate() -> dict: |
| 9 | + # DEFINE PARAMETERS |
| 10 | + # define spatial_extent |
| 11 | + spatial_extent = Parameter.bounding_box( |
| 12 | + name="spatial_extent", default={"west": 5.0, "south": 51.2, "east": 5.1, "north": 51.3} |
| 13 | + ) |
| 14 | + # define temporal_extent |
| 15 | + temporal_extent = Parameter.temporal_interval(name="temporal_extent", default=["2021-01-01", "2021-12-31"]) |
| 16 | + |
| 17 | + # backend to connect and load |
| 18 | + backend_url = "openeo.dataspace.copernicus.eu/" |
| 19 | + conn = openeo.connect(backend_url).authenticate_oidc() |
| 20 | + |
| 21 | + # Compute cloud mask, and filter input data based on cloud mask. |
| 22 | + # compute cloud mask using the SCL band |
| 23 | + scl = conn.load_collection( |
| 24 | + "SENTINEL2_L2A", |
| 25 | + temporal_extent=temporal_extent, |
| 26 | + spatial_extent=spatial_extent, |
| 27 | + bands=["SCL"], |
| 28 | + max_cloud_cover=10, |
| 29 | + ) |
| 30 | + cloud_mask = scl.process( |
| 31 | + "to_scl_dilation_mask", |
| 32 | + data=scl, |
| 33 | + kernel1_size=17, |
| 34 | + kernel2_size=77, |
| 35 | + mask1_values=[2, 4, 5, 6, 7], |
| 36 | + mask2_values=[3, 8, 9, 10, 11], |
| 37 | + erosion_kernel_size=3, |
| 38 | + ) |
| 39 | + |
| 40 | + # Load s2 bands and set max cloud cover to be less than 10% |
| 41 | + s2_bands = conn.load_collection( |
| 42 | + collection_id="SENTINEL2_L2A", |
| 43 | + spatial_extent=spatial_extent, |
| 44 | + temporal_extent=temporal_extent, |
| 45 | + bands=["B04", "B08"], |
| 46 | + max_cloud_cover=10, |
| 47 | + ) |
| 48 | + # mask data with cloud mask |
| 49 | + s2_bands_masked = s2_bands.mask(cloud_mask) |
| 50 | + |
| 51 | + # The delineation will be estimated based on the NDVI. The `ndvi` process can be used for these calculations. |
| 52 | + ndviband = s2_bands_masked.ndvi(red="B04", nir="B08") |
| 53 | + |
| 54 | + # Apply ML algorithm |
| 55 | + # apply a neural network, requires 128x128 pixel 'chunks' as input. |
| 56 | + segment_udf = openeo.UDF.from_file("udf_segmentation.py") |
| 57 | + segmentationband = ndviband.apply_neighborhood( |
| 58 | + process=segment_udf, |
| 59 | + size=[{"dimension": "x", "value": 64, "unit": "px"}, {"dimension": "y", "value": 64, "unit": "px"}], |
| 60 | + overlap=[{"dimension": "x", "value": 32, "unit": "px"}, {"dimension": "y", "value": 32, "unit": "px"}], |
| 61 | + ) |
| 62 | + |
| 63 | + # Postprocess the output from the neural network using a sobel filter and |
| 64 | + # Felzenszwalb's algorithm, which are then merged. |
| 65 | + segment_postprocess_udf = openeo.UDF.from_file("udf_sobel_felzenszwalb.py") |
| 66 | + sobel_felzenszwalb = segmentationband.apply_neighborhood( |
| 67 | + process=segment_postprocess_udf, |
| 68 | + size=[{"dimension": "x", "value": 2048, "unit": "px"}, {"dimension": "y", "value": 2048, "unit": "px"}], |
| 69 | + overlap=[{"dimension": "x", "value": 0, "unit": "px"}, {"dimension": "y", "value": 0, "unit": "px"}], |
| 70 | + ) |
| 71 | + job_options = { |
| 72 | + "udf-dependency-archives": [ |
| 73 | + "https://artifactory.vgt.vito.be/auxdata-public/openeo/onnx_dependencies.zip#onnx_deps", |
| 74 | + "https://artifactory.vgt.vito.be/artifactory/auxdata-public/openeo/parcelDelination/BelgiumCropMap_unet_3BandsGenerator_Models.zip#onnx_models", |
| 75 | + ], |
| 76 | + "driver-memory": "500m", |
| 77 | + "driver-memoryOverhead": "1000m", |
| 78 | + "executor-memory": "1000m", |
| 79 | + "executor-memoryOverhead": "500m", |
| 80 | + "python-memory": "4200m", |
| 81 | + } |
| 82 | + |
| 83 | + # Build the process dictionary |
| 84 | + return build_process_dict( |
| 85 | + process_graph=sobel_felzenszwalb, |
| 86 | + process_id="parcel_delineation", |
| 87 | + summary="Parcel delineation using Sentinel-2 data retrieved from the CDSE and processed on openEO.", |
| 88 | + description="Parcel delineation using Sentinel-2", |
| 89 | + parameters=[spatial_extent, temporal_extent], |
| 90 | + default_job_options=job_options, |
| 91 | + ) |
| 92 | + |
| 93 | + |
| 94 | +if __name__ == "__main__": |
| 95 | + # save the generated process to a file |
| 96 | + output_path = Path(__file__).parent |
| 97 | + print(output_path) |
| 98 | + output_path.mkdir(parents=True, exist_ok=True) |
| 99 | + |
| 100 | + # Save the generated process to a file |
| 101 | + with open(output_path / "parcel_delineation.json", "w") as f: |
| 102 | + json.dump(generate(), f, indent=2) |
0 commit comments