diff --git a/examples/notebooks/Client_Side_Processing/client_side_processing_color_composites.ipynb b/examples/notebooks/Client_Side_Processing/client_side_processing_color_composites.ipynb new file mode 100644 index 000000000..f60c19a01 --- /dev/null +++ b/examples/notebooks/Client_Side_Processing/client_side_processing_color_composites.ipynb @@ -0,0 +1,443 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "3cfbd63f-f9fe-4521-af0a-5679216871df", + "metadata": { + "tags": [] + }, + "source": [ + "\"OpenEO\n", + "# OpenEO Platform - Client Side Processing\n" + ] + }, + { + "cell_type": "markdown", + "id": "739a4107-cc33-4948-ae72-74e41efc5128", + "metadata": {}, + "source": [ + "In this interactive notebook we will show some usage eaxample of the Client Side Processing functionality added recently to the openEO Python client." + ] + }, + { + "cell_type": "markdown", + "id": "827ec0c9-c38f-4d2f-a293-00d110ec70f6", + "metadata": { + "tags": [] + }, + "source": [ + "## Requirements" + ] + }, + { + "cell_type": "markdown", + "id": "51a9e53a-481c-4ea5-a175-652d6398828d", + "metadata": {}, + "source": [ + "To use this functionality, you need `3.9<=Python<3.11`.\n", + "\n", + "You can install it using:\n", + " `pip install openeo[localprocessing]`\n", + "\n", + "
\n", + "This functionality is still under development and the installation procedure might change.\n", + "Please refer to official documentation page for the most up to date instructions:\n", + " \n", + "https://open-eo.github.io/openeo-python-client/cookbook/localprocessing.html" + ] + }, + { + "cell_type": "markdown", + "id": "df8ae408-29f8-4262-90a6-b2935abda290", + "metadata": { + "tags": [] + }, + "source": [ + "## Sample Datasets" + ] + }, + { + "cell_type": "markdown", + "id": "d89a7e7e-53a6-4e51-970f-61be7dbf69cc", + "metadata": {}, + "source": [ + "Clone the repository containing sample datasets provided by openEO Platform:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7413f5bd-542e-4efa-a3e0-f338c254f725", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "if not os.path.exists('./openeo-localprocessing-data'):\n", + " !git clone https://github.com/Open-EO/openeo-localprocessing-data.git" + ] + }, + { + "cell_type": "markdown", + "id": "9294b308-4e6b-4324-b213-b18dde0e29fb", + "metadata": {}, + "source": [ + "Initialize the local collections and inspect them:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c1a6e679-22f6-48db-81a6-43c023d08947", + "metadata": {}, + "outputs": [], + "source": [ + "from openeo.local import LocalConnection\n", + "local_conn = LocalConnection(['./openeo-localprocessing-data'])\n", + "local_conn.list_collections()" + ] + }, + { + "cell_type": "markdown", + "id": "c5a25488-504d-4238-98f2-521b2942e856", + "metadata": { + "tags": [] + }, + "source": [ + "## Example 1: Create color composites based on Sentinel-2 data" + ] + }, + { + "cell_type": "markdown", + "id": "ba92c429-ea0d-4c0a-b881-0927ce7157cb", + "metadata": {}, + "source": [ + "The first step, as usual in an openEO pipeline, is `load_collection`. In this case, the collection id is the same as the file path we want to use:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dcd2fa52-4ebe-4588-afc8-713d7dc15739", + "metadata": {}, + "outputs": [], + "source": [ + "cube = local_conn.load_collection('openeo-localprocessing-data/sample_netcdf/S2_L2A_sample.nc').filter_bands(['B04','B03','B02'])" + ] + }, + { + "cell_type": "markdown", + "id": "d1889e5c-232b-41dc-9de9-f41af66fa2f2", + "metadata": {}, + "source": [ + "We would like to create a color composite based on multiple acquisitions over time.\n", + "\n", + "For doing so, we will use the `reduce_dimension` process, which can reduce a dimension given a reducer process.\n", + "\n", + "In this case the dimension is the temporal (`t`) and the reducer is `mean`, to get a temporal average." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a2742291-708a-4af9-b568-7a4989665abe", + "metadata": {}, + "outputs": [], + "source": [ + "cube_avg = cube.reduce_dimension(dimension='t',reducer='mean')" + ] + }, + { + "cell_type": "markdown", + "id": "46e405a2-0dae-479b-9777-31eb753cb52f", + "metadata": {}, + "source": [ + "Since we would like to visualize the result as an image, we scale it to values between 0 and 255." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2292e43f-9512-4bfe-b548-a8088c5f10fd", + "metadata": {}, + "outputs": [], + "source": [ + "cube_scaled = cube_avg.linear_scale_range(0,2000,0,255)" + ] + }, + { + "cell_type": "markdown", + "id": "a6278deb-96ed-4543-a5d9-002172aed0b0", + "metadata": {}, + "source": [ + "We can now visualize the openEO process graph that we just created:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7690cf2d-0e75-4efc-aefd-df720fa9ae67", + "metadata": {}, + "outputs": [], + "source": [ + "cube_scaled" + ] + }, + { + "cell_type": "markdown", + "id": "b4d0867d-c7be-41bb-b055-78140d86cae4", + "metadata": {}, + "source": [ + "By calling `.execute()`, the process graph will be locally executed and the result will be returned as an Xarray object:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6494d6b1-e67d-492f-8432-2492c07d2d30", + "metadata": {}, + "outputs": [], + "source": [ + "result = cube_scaled.execute()\n", + "result" + ] + }, + { + "cell_type": "markdown", + "id": "b8360362-95e0-4368-940b-ba1028b63eb7", + "metadata": {}, + "source": [ + "Now that we have the result, we can plot the color composites:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0622fb0a-828f-490b-abed-b0ee6a2160d5", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "fig, ax = plt.subplots(1,1,figsize=(7.5,5))\n", + "result.plot.imshow(ax=ax,cmap='brg',vmin=0,vmax=255)\n", + "ax.set_title('Mean RGB composite - June 2022 - Bolzano, Italy - Sentinel-2')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "bc35d1ac-35d5-4f2d-b406-fe7ced85a99f", + "metadata": {}, + "source": [ + "We immediately notice that something is wrong. Probably some days where cloudy! Therefore, the average image is cloudy as well.\n", + "\n", + "It is possible can solve this issue with two different approaches:\n", + "1. Trying to use a different reducer. We will try to use `median` instead of `mean`.\n", + "2. By applying a cloud mask, based on the Sentinel-2 SCL (Scene Classification Layer) band. (Not possible yet locally, some processes missing)" + ] + }, + { + "cell_type": "markdown", + "id": "590bf7e8-5ccc-4439-b2f4-8c73eba6e015", + "metadata": {}, + "source": [ + "Let's try the first approach, using `median` instead of `mean`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bd63a070-1e7e-4164-a2cf-da6541686e6b", + "metadata": {}, + "outputs": [], + "source": [ + "cube = local_conn.load_collection('openeo-localprocessing-data/sample_netcdf/S2_L2A_sample.nc').filter_bands(['B04','B03','B02'])\n", + "cube_med = cube.reduce_dimension(dimension='t',reducer='median')\n", + "cube_scaled = cube_med.linear_scale_range(0,2000,0,255)\n", + "result_median = cube_scaled.execute()" + ] + }, + { + "cell_type": "markdown", + "id": "bb97ce07-ee94-4c9b-9ece-aa20641f4118", + "metadata": {}, + "source": [ + "We can now compare the results:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "62eff36b-d2a2-4457-9c4e-5c08bb1baf52", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "fig, ax = plt.subplots(1,2,figsize=(15,5))\n", + "result.plot.imshow(ax=ax[0],cmap='brg',vmin=0,vmax=255)\n", + "ax[0].set_title('Mean RGB composite - June 2022 - Bolzano, Italy - Sentinel-2')\n", + "result_median.plot.imshow(ax=ax[1],cmap='brg',vmin=0,vmax=255)\n", + "ax[1].set_title('Median RGB composite - June 2022 - Bolzano, Italy - Sentinel-2')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "fe2edb24-8492-4d07-a797-89a1f6288ad6", + "metadata": {}, + "source": [ + "We could now try to more advanced method, based on the SCL layer:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8d4ca63e-3341-4690-83f4-382e37a049e3", + "metadata": {}, + "outputs": [], + "source": [ + "cube = local_conn.load_collection('openeo-localprocessing-data/sample_netcdf/S2_L2A_sample.nc')" + ] + }, + { + "cell_type": "markdown", + "id": "9e9182ad-95be-4823-8487-00138fb1d790", + "metadata": {}, + "source": [ + "We now want to mask out the SCL class 9 (CLOUD_HIGH_PROBABILITY):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c09df575-77dc-4b80-b0d0-cdd9b5fd401f", + "metadata": {}, + "outputs": [], + "source": [ + "scl = cube.band('SCL')\n", + "cloud_mask_high = scl != 9\n", + "cloud_mask_medium = scl != 8\n", + "cloud_mask = (cloud_mask_high + cloud_mask_medium) == 2" + ] + }, + { + "cell_type": "markdown", + "id": "9d96bc29-ce75-4121-b5be-529fd0715d57", + "metadata": {}, + "source": [ + "We can now apply the cloud mask to the input data:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c0a0f9e5-2e03-4a33-8742-0c50f01dd051", + "metadata": {}, + "outputs": [], + "source": [ + "cube_masked = cube.filter_bands(['B04','B03','B02']).merge_cubes(cloud_mask,overlap_resolver='multiply')" + ] + }, + { + "cell_type": "markdown", + "id": "97e3259b-bf79-45fc-ade5-ec0a0234d845", + "metadata": {}, + "source": [ + "Check if the cloud mask is applied correctly:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2cbee926-8871-4ab1-b19e-8f3c6b8ff14b", + "metadata": {}, + "outputs": [], + "source": [ + "cube_masked.execute()[:,0].plot.imshow(vmin=0,vmax=2500)" + ] + }, + { + "cell_type": "markdown", + "id": "ffd02812-e4fc-4e20-bc56-f3d0cd499636", + "metadata": {}, + "source": [ + "And finally take the mean over time, rescale and execute:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13b8d369-f716-4a34-b2f9-7b9e4d9001f6", + "metadata": {}, + "outputs": [], + "source": [ + "cube_masked = cube_masked.reduce_dimension(dimension='t',reducer='median').linear_scale_range(0,2000,0,255)\n", + "cube_masked" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cb1bfd40-2bf3-4aaf-8c16-ef22c413e26b", + "metadata": {}, + "outputs": [], + "source": [ + "result_masked = cube_masked.execute()" + ] + }, + { + "cell_type": "markdown", + "id": "684ec65b-fb23-42ca-99f6-05be00ececc5", + "metadata": {}, + "source": [ + "Finally visualize the result and compare with the median version with no cloud filter:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d9b180b3-6788-46e9-8376-1bff39a5ebeb", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "fig, ax = plt.subplots(1,2,figsize=(15,5))\n", + "result_median.plot.imshow(ax=ax[0],cmap='brg',vmin=0,vmax=255)\n", + "ax[0].set_title('Median RGB composite - June 2022 - Bolzano, Italy - Sentinel-2')\n", + "result_masked.plot.imshow(ax=ax[1],cmap='brg',vmin=0,vmax=255)\n", + "ax[1].set_title('Median RGB composite cloud masked - June 2022 - Bolzano, Italy - Sentinel-2')\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d5e4f267-19e1-4401-830f-c96ecc0606d1", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/notebooks/Client_Side_Processing/client_side_processing_data_fusion.ipynb b/examples/notebooks/Client_Side_Processing/client_side_processing_data_fusion.ipynb new file mode 100644 index 000000000..d253f1a9a --- /dev/null +++ b/examples/notebooks/Client_Side_Processing/client_side_processing_data_fusion.ipynb @@ -0,0 +1,262 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "3cfbd63f-f9fe-4521-af0a-5679216871df", + "metadata": { + "tags": [] + }, + "source": [ + "\"OpenEO\n", + "# OpenEO Platform - Client Side Processing\n", + "\n", + "## Data Fusion\n" + ] + }, + { + "cell_type": "markdown", + "id": "739a4107-cc33-4948-ae72-74e41efc5128", + "metadata": {}, + "source": [ + "In this interactive notebook we will show some usage eaxample of the Client Side Processing functionality added recently to the openEO Python client." + ] + }, + { + "cell_type": "markdown", + "id": "827ec0c9-c38f-4d2f-a293-00d110ec70f6", + "metadata": { + "tags": [] + }, + "source": [ + "## Requirements" + ] + }, + { + "cell_type": "markdown", + "id": "51a9e53a-481c-4ea5-a175-652d6398828d", + "metadata": {}, + "source": [ + "To use this functionality, you need `3.9<=Python<3.11`.\n", + "\n", + "You can install it using:\n", + " `pip install openeo[localprocessing]`\n", + "\n", + "
\n", + "This functionality is still under development and the installation procedure might change.\n", + "Please refer to official documentation page for the most up to date instructions:\n", + " \n", + "https://open-eo.github.io/openeo-python-client/cookbook/localprocessing.html" + ] + }, + { + "cell_type": "markdown", + "id": "df8ae408-29f8-4262-90a6-b2935abda290", + "metadata": { + "tags": [] + }, + "source": [ + "## Sample Datasets" + ] + }, + { + "cell_type": "markdown", + "id": "d89a7e7e-53a6-4e51-970f-61be7dbf69cc", + "metadata": {}, + "source": [ + "Clone the repository containing sample datasets provided by openEO Platform:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7413f5bd-542e-4efa-a3e0-f338c254f725", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "if not os.path.exists('./openeo-localprocessing-data'):\n", + " !git clone https://github.com/Open-EO/openeo-localprocessing-data.git" + ] + }, + { + "cell_type": "markdown", + "id": "9294b308-4e6b-4324-b213-b18dde0e29fb", + "metadata": {}, + "source": [ + "Initialize the local collections and inspect them:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c1a6e679-22f6-48db-81a6-43c023d08947", + "metadata": {}, + "outputs": [], + "source": [ + "from openeo.local import LocalConnection\n", + "local_conn = LocalConnection(['./openeo-localprocessing-data'])\n", + "local_conn.list_collections()" + ] + }, + { + "cell_type": "markdown", + "id": "2f1ee39b-4e69-48c1-8866-8f891a2f4e28", + "metadata": {}, + "source": [ + "**We are going to work with the sample Sentinel-2 and Sentinel-1 data.**\n", + "\n", + "Let's look at them step by step:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "89e4c013-02a4-4cfe-b1cd-7759205ab303", + "metadata": {}, + "outputs": [], + "source": [ + "s2 = local_conn.load_collection('openeo-localprocessing-data/sample_netcdf/S2_L2A_sample.nc')\n", + "s2_dataarray = s2.execute()\n", + "s2_dataarray" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ad29b529-03b0-4b36-bdac-7c2562eb772a", + "metadata": {}, + "outputs": [], + "source": [ + "s1 = local_conn.load_collection('openeo-localprocessing-data/sample_netcdf/S1_GRD_sample.nc')\n", + "s1_dataarray = s1.execute()\n", + "s1_dataarray" + ] + }, + { + "cell_type": "markdown", + "id": "c3e0440b-ef10-45f6-8019-f8c45fe26007", + "metadata": {}, + "source": [ + "We notice that the Sentinel-1 data covers a bigger area than the one covered by Sentinel-2. This is also visible in the local collections metadata.\n", + "\n", + "In this case the two datacubes have the same resolution and are probably also aligned. However, sometimes we might need to combine data coming from different satellite sources with different projections and resolution.\n", + "\n", + "In this scenario we could use the `resample_cube_spatial` openEO process that allows to reproject and crop the source to match a target datacube in one shot.\n", + "\n", + "Have a look at the official process description if you want to know more details: https://processes.openeo.org/#resample_cube_spatial" + ] + }, + { + "cell_type": "markdown", + "id": "a7497996-2998-4ddb-b4e0-dc26dd1d14bc", + "metadata": {}, + "source": [ + "We now want to resample the S1 data to match S2, for being able to merge them later:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9828c925-6e40-449f-8d7b-eefc48bccbca", + "metadata": {}, + "outputs": [], + "source": [ + "s1_resampled = s1.resample_cube_spatial(target=s2,method=\"bilinear\")\n", + "s1_resampled.execute()" + ] + }, + { + "cell_type": "markdown", + "id": "43636dc9-87f7-4c71-b73c-4e623a17fa59", + "metadata": {}, + "source": [ + "Now our Sentinel-1 data has the same spatial extent and resolution. However, the two datacubes have different number of time steps and we have to align that dimension as well to make a meaningful merge.\n", + "\n", + "We could compute a temporal aggregation using the openEO process `aggregate_temporal_period`, to compute the monthly median values.\n", + "Have a look at the official process description if you want to know more details: https://processes.openeo.org/#aggregate_temporal_period" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a758cc8c-648c-41c1-af05-6ac02b48cc7b", + "metadata": {}, + "outputs": [], + "source": [ + "s2_monthly_median = s2.aggregate_temporal_period(period=\"month\",reducer=\"median\")\n", + "s1_monthly_median = s1_resampled.aggregate_temporal_period(period=\"month\",reducer=\"median\")" + ] + }, + { + "cell_type": "markdown", + "id": "c2631aab-05c8-43af-b81c-1eb52466301a", + "metadata": {}, + "source": [ + "Now that spatial and temporal dimensions are aligned, we can proceed merging the two datacubes and look at the generated openEO process graph:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2430403d-4824-4871-827d-82b2a78c8b3f", + "metadata": {}, + "outputs": [], + "source": [ + "s2_s1_monthly_median = s2_monthly_median.merge_cubes(s1_monthly_median)\n", + "s2_s1_monthly_median" + ] + }, + { + "cell_type": "markdown", + "id": "682e8534-243c-451e-8610-414caeb4f516", + "metadata": {}, + "source": [ + "Finally execute the process and check the output:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "47f44b4b-274e-45df-aaf9-8c9c335c2951", + "metadata": {}, + "outputs": [], + "source": [ + "s2_s1_monthly_median = s2_s1_monthly_median.execute()\n", + "s2_s1_monthly_median" + ] + }, + { + "cell_type": "markdown", + "id": "28fab2fa-aae4-4820-9abe-5a6d700a16b6", + "metadata": {}, + "source": [ + "The output now contains the optical bands of Sentinel-2 and the two polarizations from Sentinel-1.\n", + "\n", + "The datacube is aligned spatially and temporally, which was possible using the `resample_cube_spatial` and `aggregate_temporal_period` processes." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/openeo/local/processing.py b/openeo/local/processing.py index 0442a9b50..9f38dccab 100644 --- a/openeo/local/processing.py +++ b/openeo/local/processing.py @@ -2,11 +2,13 @@ import importlib import logging import xarray as xr +import rasterio import rioxarray from pathlib import Path from openeo_pg_parser_networkx import ProcessRegistry from openeo_processes_dask.process_implementations.core import process +from openeo_processes_dask.process_implementations.data_model import RasterCube import openeo_processes_dask.specs import openeo_processes_dask.process_implementations from openeo_pg_parser_networkx.process_registry import Process @@ -23,30 +25,45 @@ def init_process_registry(): ) ] - specs = { - func.__name__: getattr(openeo_processes_dask.specs, func.__name__) - for func in processes_from_module - } - + specs = {} for func in processes_from_module: - process_registry[func.__name__] = Process( + try: + specs[func.__name__] = getattr(openeo_processes_dask.specs, func.__name__) + except: + continue + + for func in processes_from_module: + try: + process_registry[func.__name__] = Process( spec=specs[func.__name__], implementation=func - ) + ) + except: + continue return process_registry PROCESS_REGISTRY = init_process_registry() _log = logging.getLogger(__name__) def load_local_collection(*args, **kwargs): - pretty_args = {k: type(v) for k, v in kwargs.items()} - _log.debug(f"Running process load_collection") - _log.debug(f"kwargs: {pretty_args}") - _log.debug("-" * 80) + pretty_args = {k: repr(v)[:80] for k, v in kwargs.items()} + _log.info(f"Running process load_collection") + _log.debug( + f"Running process load_collection with resolved parameters: {pretty_args}" + ) collection = Path(kwargs['id']) if '.zarr' in collection.suffixes: data = xr.open_dataset(kwargs['id'],chunks={},engine='zarr') elif '.nc' in collection.suffixes: - data = xr.open_dataset(kwargs['id'],chunks={},decode_coords='all').to_array(dim='bands') # Add decode_coords='all' if the crs as a band gives some issues + data = xr.open_dataset(kwargs['id'],chunks={},decode_coords='all') # Add decode_coords='all' if the crs as a band gives some issues + crs = None + if 'crs' in data.coords: + if 'spatial_ref' in data.crs.attrs: + crs = data.crs.attrs['spatial_ref'] + elif 'crs_wkt' in data.crs.attrs: + crs = data.crs.attrs['crs_wkt'] + data = data.to_array(dim='bands') + if crs is not None: + data.rio.write_crs(crs,inplace=True) elif '.tiff' in collection.suffixes or '.tif' in collection.suffixes: data = rioxarray.open_rasterio(kwargs['id'],chunks={},band_as_variable=True) for d in data.data_vars: @@ -59,4 +76,68 @@ def load_local_collection(*args, **kwargs): PROCESS_REGISTRY["load_collection"] = Process( spec=openeo_processes_dask.specs.load_collection, implementation=load_local_collection, +) + +def resample_cube_spatial_rioxarray(data: RasterCube, target: RasterCube, method: str = "near") -> RasterCube: + _log.info(f"Running process resample_cube_spatial") + methods_dict = { + "near": rasterio.enums.Resampling.nearest, + "bilinear": rasterio.enums.Resampling.bilinear, + "cubic": rasterio.enums.Resampling.cubic, + "cubicspline": rasterio.enums.Resampling.cubic_spline, + "lanczos": rasterio.enums.Resampling.lanczos, + "average": rasterio.enums.Resampling.average, + "mode": rasterio.enums.Resampling.mode, + "gauss": rasterio.enums.Resampling.gauss, + "max": rasterio.enums.Resampling.max, + "min": rasterio.enums.Resampling.min, + "med": rasterio.enums.Resampling.med, + "q1": rasterio.enums.Resampling.q1, + "q3": rasterio.enums.Resampling.q3, + "sum": rasterio.enums.Resampling.sum, + "rms": rasterio.enums.Resampling.rms + } + + # The target doesn't need to have the same dimensions as the data + input_data_dims = ( + data.openeo.band_dims + data.openeo.temporal_dims + data.openeo.spatial_dims + data.openeo.other_dims + ) + + if method not in methods_dict: + raise ValueError( + f'Selected resampling method "{method}" is not available! Please select one of ' + f"[{', '.join(methods_dict.keys())}]" + ) + if len(data.openeo.temporal_dims) > 0: + for i,t in enumerate(data[data.openeo.temporal_dims[0]]): + if i == 0: + resampled_data = data.loc[{data.openeo.temporal_dims[0]:t}].rio.reproject_match( + target, resampling=methods_dict[method] + ) + resampled_data = resampled_data.assign_coords({data.openeo.temporal_dims[0]:t}).expand_dims(data.openeo.temporal_dims[0]) + else: + tmp = data.loc[{data.openeo.temporal_dims[0]:t}].rio.reproject_match( + target, resampling=methods_dict[method] + ) + tmp = tmp.assign_coords({data.openeo.temporal_dims[0]:t}).expand_dims(data.openeo.temporal_dims[0]) + resampled_data = xr.concat([resampled_data,tmp],dim=data.openeo.temporal_dims[0]) + else: + resampled_data = data.rio.reproject_match( + target, resampling=methods_dict[method] + ) + resampled_data.rio.write_crs(target.rio.crs, inplace=True) + + # Order axes back to how they were before + resampled_data = resampled_data.transpose(*data.dims) + + # Ensure that attrs except crs are copied over + for k, v in data.attrs.items(): + if k.lower() != "crs": + resampled_data.attrs[k] = v + return resampled_data + + +PROCESS_REGISTRY["resample_cube_spatial"] = Process( + spec=openeo_processes_dask.specs.resample_cube_spatial, + implementation=resample_cube_spatial_rioxarray, ) \ No newline at end of file diff --git a/setup.py b/setup.py index 90adc89ee..1b86ef1a7 100644 --- a/setup.py +++ b/setup.py @@ -39,8 +39,8 @@ localprocessing_require = [ "rioxarray>=0.13.0", "pyproj", - "openeo_pg_parser_networkx>=2023.3.1", - "openeo_processes_dask[implementations]>=2023.3.2", + "openeo_pg_parser_networkx>=2023.5.1", + "openeo_processes_dask[implementations]>=2023.5.1", ]