From d62fe6acd5754590165fa405495df33423bf4ae7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Dr=C3=B6nner?= Date: Tue, 26 Nov 2024 11:25:10 +0100 Subject: [PATCH 01/14] add ml model shape --- geoengine/ml.py | 72 ++++++++++++++++++++++++++++++++++++++++-------- setup.cfg | 2 +- tests/test_ml.py | 32 +++++++++++++++------ 3 files changed, 85 insertions(+), 21 deletions(-) diff --git a/geoengine/ml.py b/geoengine/ml.py index d9ef6c01..26806922 100644 --- a/geoengine/ml.py +++ b/geoengine/ml.py @@ -5,16 +5,17 @@ from pathlib import Path import tempfile from dataclasses import dataclass +import geoengine_openapi_client.models from onnx import TypeProto, TensorProto, ModelProto from onnx.helper import tensor_dtype_to_string -from geoengine_openapi_client.models import MlModelMetadata, MlModel, RasterDataType +from geoengine_openapi_client.models import MlModelMetadata, MlModel, RasterDataType, TensorShape3D import geoengine_openapi_client from geoengine.auth import get_session from geoengine.datasets import UploadId from geoengine.error import InputException -@dataclass +@ dataclass class MlModelConfig: '''Configuration for an ml model''' name: str @@ -34,7 +35,8 @@ def register_ml_model(onnx_model: ModelProto, onnx_model, input_type=model_config.metadata.input_type, output_type=model_config.metadata.output_type, - num_input_bands=model_config.metadata.num_input_bands, + input_shape=model_config.metadata.input_shape, + out_shape=model_config.metadata.output_shape ) session = get_session() @@ -62,7 +64,8 @@ def register_ml_model(onnx_model: ModelProto, def validate_model_config(onnx_model: ModelProto, *, input_type: RasterDataType, output_type: RasterDataType, - num_input_bands: int): + input_shape: TensorShape3D, + out_shape: TensorShape3D): '''Validates the model config. Raises an exception if the model config is invalid''' def check_data_type(data_type: TypeProto, expected_type: RasterDataType, prefix: 'str'): @@ -80,6 +83,13 @@ def check_data_type(data_type: TypeProto, expected_type: RasterDataType, prefix: if domain.version != 9: raise InputException('Only ONNX models with opset version 9 are supported') + if input_shape.x != input_shape.y: + raise InputException('Currently only input shapes with x==y are allowed') + if out_shape.x != out_shape.y: + raise InputException('Currently only output shapes with x==y are allowed') + if out_shape.attributes != 1: + raise InputException('Currently only output shapes with one attribute/band allowed') + model_inputs = onnx_model.graph.input model_outputs = onnx_model.graph.output @@ -87,18 +97,58 @@ def check_data_type(data_type: TypeProto, expected_type: RasterDataType, prefix: raise InputException('Models with multiple inputs are not supported') check_data_type(model_inputs[0].type, input_type, 'input') - dims = model_inputs[0].type.tensor_type.shape.dim - if len(dims) != 2: - raise InputException('Only 2D input tensors are supported') - if not dims[1].dim_value: - raise InputException('Dimension 1 of the input tensor must have a length') - if dims[1].dim_value != num_input_bands: - raise InputException(f'Model input has {dims[1].dim_value} bands, but {num_input_bands} bands are expected') + dim = model_inputs[0].type.tensor_type.shape.dim + + if len(dim) == 2: + if not dim[1].dim_value: + raise InputException('Dimension 1 of a 1D input tensor must have a length') + if dim[1].dim_value != input_shape.attributes: + raise InputException(f'Model input has {dim[1].dim_value} bands, but {input_shape.attributes} are expected') + elif len(dim) == 4: + if not dim[1].dim_value: + raise InputException('Dimension 1 of the a 3D input tensor must have a length') + if not dim[2].dim_value: + raise InputException('Dimension 2 of the a 3D input tensor must have a length') + if not dim[3].dim_value: + raise InputException('Dimension 3 of the a 3D input tensor must have a length') + if dim[1].dim_value != input_shape.attributes: + raise InputException(f'Model input has {dim[1].dim_value} y size, but {input_shape.y} are expected') + if dim[2].dim_value != input_shape.attributes: + raise InputException(f'Model input has {dim[2].dim_value} x size, but {input_shape.x} are expected') + if dim[3].dim_value != input_shape.attributes: + raise InputException(f'Model input has {dim[3].dim_value} bands, but {input_shape.attributes} are expected') + else: + raise InputException('Only 1D and 3D input tensors are supported') if len(model_outputs) < 1: raise InputException('Models with no outputs are not supported') check_data_type(model_outputs[0].type, output_type, 'output') + dim = model_outputs[0].type.tensor_type.shape.dim + + if len(dim) == 1: + pass # this is a happens if there is only a single out? so shape would be [-1] + elif len(dim) == 2: + if not dim[1].dim_value: + raise InputException('Dimension 1 of a 1D input tensor must have a length') + if dim[1].dim_value != 1: + raise InputException(f'Model output has {dim[1].dim_value} bands, but {out_shape.attributes} are expected') + elif len(dim) == 4: + if not dim[1].dim_value: + raise InputException('Dimension 1 of the a 3D input tensor must have a length') + if not dim[2].dim_value: + raise InputException('Dimension 2 of the a 3D input tensor must have a length') + if not dim[3].dim_value: + raise InputException('Dimension 3 of the a 3D input tensor must have a length') + if dim[1].dim_value != out_shape.attributes: + raise InputException(f'Model output has {dim[1].dim_value} y size, but {out_shape.y} are expected') + if dim[2].dim_value != out_shape.attributes: + raise InputException(f'Model output has {dim[2].dim_value} x size, but {out_shape.x} are expected') + if dim[3].dim_value != out_shape.attributes: + raise InputException(f'Model output has {dim[3].dim_value} bands, but {out_shape.attributes} are expected') + else: + raise InputException('Only 1D and 3D output tensors are supported') + RASTER_TYPE_TO_ONNX_TYPE = { RasterDataType.F32: TensorProto.FLOAT, diff --git a/setup.cfg b/setup.cfg index 675ed7c4..6cdc4ea7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -18,7 +18,7 @@ package_dir = packages = find: python_requires = >=3.9 install_requires = - geoengine-openapi-client == 0.0.17 + geoengine-openapi-client @ git+https://github.com/geo-engine/openapi-client@ml-model-input-output-shape#subdirectory=python geopandas >=0.9,<0.15 matplotlib >=3.5,<3.8 numpy >=1.21,<2 diff --git a/tests/test_ml.py b/tests/test_ml.py index 88a7865c..d5ef9e20 100644 --- a/tests/test_ml.py +++ b/tests/test_ml.py @@ -4,7 +4,7 @@ from sklearn.ensemble import RandomForestClassifier from skl2onnx import to_onnx import numpy as np -from geoengine_openapi_client.models import MlModelMetadata, RasterDataType +from geoengine_openapi_client.models import MlModelMetadata, RasterDataType, TensorShape3D import geoengine as ge from . import UrllibMocker @@ -48,8 +48,17 @@ def test_uploading_onnx_model(self): "metadata": { "fileName": "model.onnx", "inputType": "F32", - "numInputBands": 2, - "outputType": "I64" + "outputType": "I64", + "inputShape": { + "y": 1, + "x": 1, + "attributes": 2 + }, + "outputShape": { + "y": 1, + "x": 1, + "attributes": 1 + } }, "name": "foo", "upload": upload_id @@ -65,8 +74,9 @@ def test_uploading_onnx_model(self): metadata=MlModelMetadata( file_name="model.onnx", input_type=RasterDataType.F32, - num_input_bands=2, output_type=RasterDataType.I64, + input_shape=TensorShape3D(y=1, x=1, attributes=2), + output_shape=TensorShape3D(y=1, x=1, attributes=1) ), display_name="Decision Tree", description="A simple decision tree model", @@ -81,8 +91,9 @@ def test_uploading_onnx_model(self): metadata=MlModelMetadata( file_name="model.onnx", input_type=RasterDataType.F32, - num_input_bands=4, output_type=RasterDataType.I64, + input_shape=TensorShape3D(y=1, x=1, attributes=4), + output_shape=TensorShape3D(y=1, x=1, attributes=1) ), display_name="Decision Tree", description="A simple decision tree model", @@ -90,7 +101,7 @@ def test_uploading_onnx_model(self): ) self.assertEqual( str(exception.exception), - 'Model input has 2 bands, but 4 bands are expected' + 'Model input has 2 bands, but 4 are expected' ) with self.assertRaises(ge.InputException) as exception: @@ -101,8 +112,9 @@ def test_uploading_onnx_model(self): metadata=MlModelMetadata( file_name="model.onnx", input_type=RasterDataType.F64, - num_input_bands=2, output_type=RasterDataType.I64, + input_shape=TensorShape3D(y=1, x=1, attributes=2), + output_shape=TensorShape3D(y=1, x=1, attributes=1) ), display_name="Decision Tree", description="A simple decision tree model", @@ -121,8 +133,9 @@ def test_uploading_onnx_model(self): metadata=MlModelMetadata( file_name="model.onnx", input_type=RasterDataType.F32, - num_input_bands=2, output_type=RasterDataType.I32, + input_shape=TensorShape3D(y=1, x=1, attributes=2), + output_shape=TensorShape3D(y=1, x=1, attributes=1) ), display_name="Decision Tree", description="A simple decision tree model", @@ -141,8 +154,9 @@ def test_uploading_onnx_model(self): metadata=MlModelMetadata( file_name="model.onnx", input_type=RasterDataType.F32, - num_input_bands=2, output_type=RasterDataType.I64, + input_shape=TensorShape3D(y=1, x=1, attributes=2), + output_shape=TensorShape3D(y=1, x=1, attributes=1) ), display_name="Decision Tree", description="A simple decision tree model", From b7bd11201dc457c4631139cc122572b40f1c1ee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Dr=C3=B6nner?= Date: Tue, 26 Nov 2024 11:47:57 +0100 Subject: [PATCH 02/14] update ml_pipeline example --- examples/ml_pipeline.ipynb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/examples/ml_pipeline.ipynb b/examples/ml_pipeline.ipynb index 1472eb5e..adf52094 100644 --- a/examples/ml_pipeline.ipynb +++ b/examples/ml_pipeline.ipynb @@ -9,14 +9,14 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import geoengine as ge\n", "from geoengine.ml import MlModelConfig\n", "\n", - "from geoengine_openapi_client.models import MlModelMetadata, RasterDataType\n", + "from geoengine_openapi_client.models import MlModelMetadata, RasterDataType, TensorShape3D\n", "\n", "from sklearn.tree import DecisionTreeClassifier\n", "import numpy as np\n", @@ -25,7 +25,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -41,7 +41,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -77,7 +77,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -88,8 +88,9 @@ "metadata = MlModelMetadata(\n", " file_name=\"model.onnx\",\n", " input_type=RasterDataType.F32,\n", - " num_input_bands=2,\n", " output_type=RasterDataType.I64,\n", + " input_shape=TensorShape3D(y=1, x=1, attributes=2),\n", + " output_shape=TensorShape3D(y=1, x=1, attributes=1)\n", ")\n", "\n", "model_config = MlModelConfig(\n", @@ -113,12 +114,12 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 5, "metadata": {}, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAioAAAHHCAYAAACRAnNyAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8WgzjOAAAACXBIWXMAAA9hAAAPYQGoP6dpAACE4UlEQVR4nO2deXgURfrHv5NAEiAkXCEJEu7LAAFBjWFVUG4RUVE8UA75IbJ4IHiQ3VVQ5PCG9eBwMaCiKCrquiqCHKIcAhKNgCBIJAIhukjCmUCmfn/gzM50+u45enq+n+fpJ5nu6qrqq+rb7/tWtUsIIUAIIYQQYkNiwl0BQgghhBAlKFQIIYQQYlsoVAghhBBiWyhUCCGEEGJbKFQIIYQQYlsoVAghhBBiWyhUCCGEEGJbKFQIIYQQYlsoVAghhBBiWyhUwkizZs0wYsSIcFeDRDk9evRAhw4dwl0NP0aMGIFmzZqFuxqOw8p5DVd7dfjwYdxwww2oX78+XC4XZs2aFfI6kPBCoRJk1q9fjylTpuDo0aPhrkpI2bx5M+6++260b98etWrVQpMmTTBkyBDs3r1bNv3OnTvRr18/JCYmol69erj99tvx22+/VUk3bdo0XHPNNUhNTYXL5cKUKVN01ad3795wuVy4++67dR+D2+3GU089hebNmyMhIQFZWVl46623VPc5c+YMMjMz4XK58Mwzz+guq7y8HA8//DAaNWqEGjVqIDs7GytWrKiS7vPPP8eoUaPQoUMHxMbGsjMPMAcOHMCQIUNQp04dJCUlYdCgQfj555/DXS1DHDx4EFOmTEF+fn64qxIQ7r//fixfvhy5ubl4/fXX0a9fv5CVvX79elx66aWoWbMm0tLScO+99+L48eMhK5/8iSBB5emnnxYAxL59+6psO336tKioqAh9pULA4MGDRVpamrjnnnvEK6+8IqZOnSpSU1NFrVq1REFBgV/aoqIi0aBBA9GyZUsxe/ZsMW3aNFG3bl3RqVMnUV5e7pcWgEhLSxN9+/YVAMTkyZM16/Lee++JWrVqCQBi3Lhxuo9h0qRJAoAYPXq0mD9/vhgwYIAAIN566y3FfZ599llvWU8//bTusm6++WZRrVo18cADD4h58+aJnJwcUa1aNbFu3Tq/dMOHDxcJCQmiW7duonHjxqJp06a6y1Cie/fuon379pbzCSTDhw8PyLEZ4dixY6J169aiYcOG4sknnxTPPfecyMjIEI0bNxa///57SOtihc2bNwsAIi8vr8q2iooKcfr0aVP5Nm3aVAwfPtxa5UyQmpoqhg4dGvJyt23bJhISEsQFF1wg5syZI/7+97+L+Ph40a9fv5DXJdqhUAkyakLFyXz99ddVRMbu3btFfHx8lUZn7NixokaNGuKXX37xrluxYoUAIObNm+eX1nMef/vtN11C5dSpU6JZs2bi8ccfNyRUfv31V1G9enW/9G63W1x22WWicePG4uzZs1X2OXz4sEhOTvaWpVeobNq0qUr6U6dOiZYtW4qcnBy/tAcOHPCK2wEDBlCoBJAnn3xSABDffPONd93OnTtFbGysyM3NDWldrKAmVKwQCKFSWVkpTp06ZWgfl8tl6AUjUPTv31+kp6eL0tJS77pXXnlFABDLly8PeX2iGbp+gsiUKVPw4IMPAgCaN28Ol8sFl8uFwsJCAFV9vgsXLoTL5cJXX32Fe++9FykpKahTpw7GjBmDiooKHD16FMOGDUPdunVRt25dPPTQQxCSj1+73W7MmjUL7du3R0JCAlJTUzFmzBj88ccfoTpsAEC3bt0QFxfnt65169Zo3749du7c6bf+vffew9VXX40mTZp41/Xq1Qtt2rTBO++845fWqKvjqaeegtvtxgMPPGBovw8//BBnzpzBX//6V+86l8uFsWPH4tdff8WGDRuq7DNp0iS0bdsWt912m6Gy3n33XcTGxuLOO+/0rktISMCoUaOwYcMGFBUVedc3atQI1atXN5S/XrZu3Ypu3bqhRo0aaN68OebOneu3vaKiAo8++ii6du2K5ORk1KpVC5dddhlWr17tl66wsNDr+po/fz5atmyJ+Ph4XHTRRdi8eXOVcj/44AN06NABCQkJ6NChA5YtWxaU49Pi3XffxUUXXYSLLrrIu65du3bo2bNnlftQL8XFxRg5ciQaN26M+Ph4pKenY9CgQd42ADh3T1999dX4/PPP0blzZyQkJCAzMxPvv/++X15HjhzBAw88gI4dOyIxMRFJSUno378/vvvuO2+aNWvWeOs/cuRIb5uzcOFCAPIxKs888wy6deuG+vXro0aNGujatSveffddU8crxeNuXbx4Mdq3b4/4+Hh89tlnAM652e644w6kpqYiPj4e7du3x6uvvurd19MeCiHw0ksveY8lFJSVlWHFihW47bbbkJSU5F0/bNgwJCYmmr4fiDmqhbsCTub666/H7t278dZbb+H5559HgwYNAAApKSmq+91zzz1IS0vDY489ho0bN2L+/PmoU6cO1q9fjyZNmmD69On45JNP8PTTT6NDhw4YNmyYd98xY8Zg4cKFGDlyJO69917s27cPL774IrZt24avv/5atZMrLy/HsWPHdB2b51iMIITA4cOH0b59e++6AwcOoKSkBBdeeGGV9BdffDE++eQTw+V42L9/P2bOnIlXX30VNWrUMLTvtm3bUKtWLZx//vlV6uTZfumll3rXf/PNN1i0aBG++uorw43ptm3b0KZNG78G0bes/Px8ZGRkGMrTKH/88QeuuuoqDBkyBLfccgveeecdjB07FnFxcbjjjjsAnGu8//Wvf+GWW27B6NGjcezYMSxYsAB9+/bFN998g86dO/vl+eabb+LYsWMYM2YMXC4XnnrqKVx//fX4+eefvffh559/jsGDByMzMxMzZszAf//7X2/Hrofjx4/j9OnTmumqV6+O5ORkxe1utxvff/+991h9ufjii/H555/j2LFjqF27tq56eRg8eDC2b9+Oe+65B82aNUNJSQlWrFiB/fv3+wmGn376CTfddBPuuusuDB8+HHl5ebjxxhvx2WefoXfv3gCAn3/+GR988AFuvPFGNG/eHIcPH8a8efPQvXt37NixA40aNcL555+Pxx9/HI8++ijuvPNOXHbZZQDOvTgoMXv2bFxzzTUYOnQoKioqsGTJEtx44434+OOPMWDAAEPHK8eqVavwzjvv4O6770aDBg3QrFkzHD58GJdccolXyKSkpODTTz/FqFGjUFZWhvHjx+Pyyy/H66+/jttvvx29e/f2a+eU+OOPP1BZWamZrmbNmqhZs6bi9oKCApw9e7ZKuxQXF4fOnTtj27Zt2gdOAkeYLTqOR831IzWl5uXlCQCib9++wu12e9fn5OQIl8sl7rrrLu+6s2fPisaNG4vu3bt7161bt04AEIsXL/Yr57PPPpNdL8VTvp7FDK+//roAIBYsWOBd5zFTv/baa1XSP/jggwKArE9dj+vnhhtuEN26dfP+hgHXz4ABA0SLFi2qrD9x4oQAICZNmuRd53a7xcUXXyxuueUWIcQ59xQMuH7at28vrrzyyirrt2/fLgCIuXPnKtYxUK4fAOLZZ5/1risvLxedO3cW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THx56KGHRPv27f3W3XTTTaJv374BrKE6SvX/5JNPRExMjCguLvaumzNnjkhKSvJeFzvU35emTZuK559/XnG7nvsqnFx88cVi3Lhx3t+VlZWiUaNGYsaMGWGslTyTJ08WnTp1kt129OhRUb16dbF06VLvup07dwoAYsOGDSGqoTrSZ9Ptdou0tDTx9NNPe9cdPXpUxMfHi7feeksIIcSOHTsEALF582Zvmk8//VS4XC5x4MCBkNVdCPm2Zfjw4WLQoEGK+9ip/kIIUVJSIgCItWvXCiH03Td62iWiDF0/FigtLUW9evW8vzds2IDLL7/c7xPWffv2xa5du/DHH3940/Tq1csvn759+2LDhg2hqbROxo0bhwYNGuDiiy/Gq6++6veJcjsfw4YNG9CxY0ekpqZ61/Xt2xdlZWXYvn27N43d6j9z5kzUr18fF1xwAZ5++mk/k7Ce+ypcVFRUYOvWrX7nMyYmBr169bLF/SDHTz/9hEaNGqFFixYYOnQo9u/fDwDYunUrzpw543cs7dq1Q5MmTWx7LPv27UNxcbFfnZOTk5Gdne2t84YNG1CnTh1ceOGF3jS9evVCTEwMNm3aFPI6y7FmzRo0bNgQbdu2xdixY/Hf//7Xu81u9S8tLQUAb9uv577R0y4RZTgzrUn27NmDF154Ac8884x3XXFxMZo3b+6XznNjFhcXo27duiguLva7WT1piouLg19pnTz++OO48sorUbNmTXz++ef461//iuPHj+Pee+8FAMVjKCsrw6lTp1CjRo1wVBuAct0829TShKv+9957L7p06YJ69eph/fr1yM3NxaFDh/Dcc89566t1X4WL33//HZWVlbLn88cffwxTrZTJzs7GwoUL0bZtWxw6dAiPPfYYLrvsMvzwww8oLi5GXFxcldgnuz2fvnjqpdamFBcXV5myvFq1aqhXr54tjqtfv364/vrr0bx5c+zduxd/+9vf0L9/f2zYsAGxsbG2qr/b7cb48ePxl7/8BR06dAAAXfeNnnaJKBP1QmXSpEl48sknVdPs3LnTL+DuwIED6NevH2688UaMHj062FXUxMwxqPHII494/7/gggtw4sQJPP30016hEmgCXX87YOSYJkyY4F2XlZWFuLg4jBkzBjNmzIiaqbhDRf/+/b3/Z2VlITs7G02bNsU777wTVoEdzdx8883e/zt27IisrCy0bNkSa9asQc+ePcNYs6qMGzcOP/zwA7766qtwVyWqiHqhMnHiRIwYMUI1TYsWLbz/Hzx4EFdccQW6deuG+fPn+6VLS0urEunt+Z2WlqaaxrPdDEaPwSjZ2dmYOnUqysvLER8fr3gMSUlJphr7QNY/LS2tyogTvdfAbP3lsHJM2dnZOHv2LAoLC9G2bVtd91W4aNCgAWJjYwN+T4eKOnXqoE2bNtizZw969+6NiooKHD161O/t2M7H4qnX4cOHkZ6e7l1/+PBhdO7c2ZumpKTEb7+zZ8/iyJEjtjyuFi1aoEGDBtizZw969uxpm/rffffd+Pjjj/Hll1+icePG3vVpaWma942edomoEO4gmUji119/Fa1btxY333yzOHv2bJXtnqDHiooK77rc3NwqwbRXX3213345OTm2CqaV8sQTT4i6det6fz/00EOiQ4cOfmluueUWWwXT+o44mTdvnkhKShKnT58WQtij/mq88cYbIiYmxjvyQc99FU4uvvhicffdd3t/V1ZWivPOO8+WwbRSjh07JurWrStmz57tDYp89913vdt//PHHiAimfeaZZ7zrSktLZYNpt2zZ4k2zfPly2wTTSikqKhIul0t8+OGHQojw19/tdotx48aJRo0aid27d1fZrue+0dMuEWUoVHTy66+/ilatWomePXuKX3/91W8onYejR4+K1NRUcfvtt4sffvhBLFmyRNSsWbPK8ORq1aqJZ555RuzcuVNMnjw5pMOTf/nlF7Ft2zbx2GOPicTERLFt2zaxbds2cezYMSGEEB999JF45ZVXREFBgfjpp5/Eyy+/LGrWrCkeffRRbx6e4b0PPvig2Llzp3jppZdCNrxXq/6eYYB9+vQR+fn54rPPPhMpKSmyw5PDUX8p69evF88//7zIz88Xe/fuFW+88YZISUkRw4YN86bRc1+FkyVLloj4+HixcOFCsWPHDnHnnXeKOnXq+I1wsAsTJ04Ua9asEfv27RNff/216NWrl2jQoIEoKSkRQpwbZtqkSROxatUqsWXLFpGTkyNycnLCWudjx45573MA4rnnnhPbtm0Tv/zyixDi3PDkOnXqiA8//FB8//33YtCgQbLDky+44AKxadMm8dVXX4nWrVuHbHivWv2PHTsmHnjgAbFhwwaxb98+sXLlStGlSxfRunVrvw48nPUfO3asSE5OFmvWrPFr90+ePOlNo3Xf6GmXiDIUKjrJy8sTAGQXX7777jtx6aWXivj4eHHeeeeJmTNnVsnrnXfeEW3atBFxcXGiffv24j//+U+oDkMMHz5c9hhWr14thDg37K9z584iMTFR1KpVS3Tq1EnMnTtXVFZW+uWzevVq0blzZxEXFydatGgh8vLybFF/IYQoLCwU/fv3FzVq1BANGjQQEydOFGfOnLFF/aVs3bpVZGdni+TkZJGQkCDOP/98MX369CpvWXruq3DywgsviCZNmoi4uDhx8cUXi40bN4a7SrLcdNNNIj09XcTFxYnzzjtP3HTTTWLPnj3e7adOnRJ//etfRd26dUXNmjXFdddd5/cyEg5Wr14te88PHz5cCHHujf+RRx4RqampIj4+XvTs2VPs2rXLL4///ve/4pZbbhGJiYkiKSlJjBw50ivuw1n/kydPij59+oiUlBRRvXp10bRpUzF69OgqIjec9Vdq933bDD33jZ52icjjEsJn3CkhhBBCiI3gPCqEEEIIsS0UKoQQQgixLRQqhBBCCLEtFCqEEEIIsS0UKoQQQgixLRQqhBBCCLEtFCqEEEIIsS0UKoQQQgixLRQqhBBCCLEtFCqEEEIIsS0UKoQQ0/z2229IS0vD9OnTvevWr1+PuLg4fPHFF2GsGSHEKfBbP4QQS3zyySe49tprsX79erRt2xadO3fGoEGD8Nxzz4W7aoQQB0ChQgixzLhx47By5UpceOGFKCgowObNmxEfHx/uahFCHACFCiHEMqdOnUKHDh1QVFSErVu3omPHjuGuEiHEITBGhRBimb179+LgwYNwu90oLCwMd3UIIQ6CFhVCiCUqKipw8cUXo3Pnzmjbti1mzZqFgoICNGzYMNxVI4Q4AAoVQoglHnzwQbz77rv47rvvkJiYiO7duyM5ORkff/xxuKtGCHEAdP0QQkyzZs0azJo1C6+//jqSkpIQExOD119/HevWrcOcOXPCXT1CiAOgRYUQQgghtoUWFUIIIYTYFgoVQgghhNgWChVCCCGE2BYKFUIIIYTYFgoVQgghhNgWChVCCCGE2BYKFUIIIYTYFgoVQgghhNgWChVCCCGE2BYKFUIIIYTYFgoVQgghhNgWChVCCCGE2Jb/B4Yz8pQphxrgAAAAAElFTkSuQmCC", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAioAAAHHCAYAAACRAnNyAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuNSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/xnp5ZAAAACXBIWXMAAA9hAAAPYQGoP6dpAACE4UlEQVR4nO2deXgURfrHv5NAEiAkXCEJEu7LAAFBjWFVUG4RUVE8UA75IbJ4IHiQ3VVQ5PCG9eBwMaCiKCrquiqCHKIcAhKNgCBIJAIhukjCmUCmfn/gzM50+u45enq+n+fpJ5nu6qrqq+rb7/tWtUsIIUAIIYQQYkNiwl0BQgghhBAlKFQIIYQQYlsoVAghhBBiWyhUCCGEEGJbKFQIIYQQYlsoVAghhBBiWyhUCCGEEGJbKFQIIYQQYlsoVAghhBBiWyhUwkizZs0wYsSIcFeDRDk9evRAhw4dwl0NP0aMGIFmzZqFuxqOw8p5DVd7dfjwYdxwww2oX78+XC4XZs2aFfI6kPBCoRJk1q9fjylTpuDo0aPhrkpI2bx5M+6++260b98etWrVQpMmTTBkyBDs3r1bNv3OnTvRr18/JCYmol69erj99tvx22+/VUk3bdo0XHPNNUhNTYXL5cKUKVN01ad3795wuVy4++67dR+D2+3GU089hebNmyMhIQFZWVl46623VPc5c+YMMjMz4XK58Mwzz+guq7y8HA8//DAaNWqEGjVqIDs7GytWrKiS7vPPP8eoUaPQoUMHxMbGsjMPMAcOHMCQIUNQp04dJCUlYdCgQfj555/DXS1DHDx4EFOmTEF+fn64qxIQ7r//fixfvhy5ubl4/fXX0a9fv5CVvX79elx66aWoWbMm0tLScO+99+L48eMhK5/8iSBB5emnnxYAxL59+6psO336tKioqAh9pULA4MGDRVpamrjnnnvEK6+8IqZOnSpSU1NFrVq1REFBgV/aoqIi0aBBA9GyZUsxe/ZsMW3aNFG3bl3RqVMnUV5e7pcWgEhLSxN9+/YVAMTkyZM16/Lee++JWrVqCQBi3Lhxuo9h0qRJAoAYPXq0mD9/vhgwYIAAIN566y3FfZ599llvWU8//bTusm6++WZRrVo18cADD4h58+aJnJwcUa1aNbFu3Tq/dMOHDxcJCQmiW7duonHjxqJp06a6y1Cie/fuon379pbzCSTDhw8PyLEZ4dixY6J169aiYcOG4sknnxTPPfecyMjIEI0bNxa///57SOtihc2bNwsAIi8vr8q2iooKcfr0aVP5Nm3aVAwfPtxa5UyQmpoqhg4dGvJyt23bJhISEsQFF1wg5syZI/7+97+L+Ph40a9fv5DXJdqhUAkyakLFyXz99ddVRMbu3btFfHx8lUZn7NixokaNGuKXX37xrluxYoUAIObNm+eX1nMef/vtN11C5dSpU6JZs2bi8ccfNyRUfv31V1G9enW/9G63W1x22WWicePG4uzZs1X2OXz4sEhOTvaWpVeobNq0qUr6U6dOiZYtW4qcnBy/tAcOHPCK2wEDBlCoBJAnn3xSABDffPONd93OnTtFbGysyM3NDWldrKAmVKwQCKFSWVkpTp06ZWgfl8tl6AUjUPTv31+kp6eL0tJS77pXXnlFABDLly8PeX2iGbp+gsiUKVPw4IMPAgCaN28Ol8sFl8uFwsJCAFV9vgsXLoTL5cJXX32Fe++9FykpKahTpw7GjBmDiooKHD16FMOGDUPdunVRt25dPPTQQxCSj1+73W7MmjUL7du3R0JCAlJTUzFmzBj88ccfoTpsAEC3bt0QFxfnt65169Zo3749du7c6bf+vffew9VXX40mTZp41/Xq1Qtt2rTBO++845fWqKvjqaeegtvtxgMPPGBovw8//BBnzpzBX//6V+86l8uFsWPH4tdff8WGDRuq7DNp0iS0bdsWt912m6Gy3n33XcTGxuLOO+/0rktISMCoUaOwYcMGFBUVedc3atQI1atXN5S/XrZu3Ypu3bqhRo0aaN68OebOneu3vaKiAo8++ii6du2K5ORk1KpVC5dddhlWr17tl66wsNDr+po/fz5atmyJ+Ph4XHTRRdi8eXOVcj/44AN06NABCQkJ6NChA5YtWxaU49Pi3XffxUUXXYSLLrrIu65du3bo2bNnlftQL8XFxRg5ciQaN26M+Ph4pKenY9CgQd42ADh3T1999dX4/PPP0blzZyQkJCAzMxPvv/++X15HjhzBAw88gI4dOyIxMRFJSUno378/vvvuO2+aNWvWeOs/cuRIb5uzcOFCAPIxKs888wy6deuG+vXro0aNGujatSveffddU8crxeNuXbx4Mdq3b4/4+Hh89tlnAM652e644w6kpqYiPj4e7du3x6uvvurd19MeCiHw0ksveY8lFJSVlWHFihW47bbbkJSU5F0/bNgwJCYmmr4fiDmqhbsCTub666/H7t278dZbb+H5559HgwYNAAApKSmq+91zzz1IS0vDY489ho0bN2L+/PmoU6cO1q9fjyZNmmD69On45JNP8PTTT6NDhw4YNmyYd98xY8Zg4cKFGDlyJO69917s27cPL774IrZt24avv/5atZMrLy/HsWPHdB2b51iMIITA4cOH0b59e++6AwcOoKSkBBdeeGGV9BdffDE++eQTw+V42L9/P2bOnIlXX30VNWrUMLTvtm3bUKtWLZx//vlV6uTZfumll3rXf/PNN1i0aBG++uorw43ptm3b0KZNG78G0bes/Px8ZGRkGMrTKH/88QeuuuoqDBkyBLfccgveeecdjB07FnFxcbjjjjsAnGu8//Wvf+GWW27B6NGjcezYMSxYsAB9+/bFN998g86dO/vl+eabb+LYsWMYM2YMXC4XnnrqKVx//fX4+eefvffh559/jsGDByMzMxMzZszAf//7X2/Hrofjx4/j9OnTmumqV6+O5ORkxe1utxvff/+991h9ufjii/H555/j2LFjqF27tq56eRg8eDC2b9+Oe+65B82aNUNJSQlWrFiB/fv3+wmGn376CTfddBPuuusuDB8+HHl5ebjxxhvx2WefoXfv3gCAn3/+GR988AFuvPFGNG/eHIcPH8a8efPQvXt37NixA40aNcL555+Pxx9/HI8++ijuvPNOXHbZZQDOvTgoMXv2bFxzzTUYOnQoKioqsGTJEtx44434+OOPMWDAAEPHK8eqVavwzjvv4O6770aDBg3QrFkzHD58GJdccolXyKSkpODTTz/FqFGjUFZWhvHjx+Pyyy/H66+/jttvvx29e/f2a+eU+OOPP1BZWamZrmbNmqhZs6bi9oKCApw9e7ZKuxQXF4fOnTtj27Zt2gdOAkeYLTqOR831IzWl5uXlCQCib9++wu12e9fn5OQIl8sl7rrrLu+6s2fPisaNG4vu3bt7161bt04AEIsXL/Yr57PPPpNdL8VTvp7FDK+//roAIBYsWOBd5zFTv/baa1XSP/jggwKArE9dj+vnhhtuEN26dfP+hgHXz4ABA0SLFi2qrD9x4oQAICZNmuRd53a7xcUXXyxuueUWIcQ59xQMuH7at28vrrzyyirrt2/fLgCIuXPnKtYxUK4fAOLZZ5/1risvLxedO3cW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THx56KGHRPv27f3W3XTTTaJv374BrKE6SvX/5JNPRExMjCguLvaumzNnjkhKSvJeFzvU35emTZuK559/XnG7nvsqnFx88cVi3Lhx3t+VlZWiUaNGYsaMGWGslTyTJ08WnTp1kt129OhRUb16dbF06VLvup07dwoAYsOGDSGqoTrSZ9Ptdou0tDTx9NNPe9cdPXpUxMfHi7feeksIIcSOHTsEALF582Zvmk8//VS4XC5x4MCBkNVdCPm2Zfjw4WLQoEGK+9ip/kIIUVJSIgCItWvXCiH03Td62iWiDF0/FigtLUW9evW8vzds2IDLL7/c7xPWffv2xa5du/DHH3940/Tq1csvn759+2LDhg2hqbROxo0bhwYNGuDiiy/Gq6++6veJcjsfw4YNG9CxY0ekpqZ61/Xt2xdlZWXYvn27N43d6j9z5kzUr18fF1xwAZ5++mk/k7Ce+ypcVFRUYOvWrX7nMyYmBr169bLF/SDHTz/9hEaNGqFFixYYOnQo9u/fDwDYunUrzpw543cs7dq1Q5MmTWx7LPv27UNxcbFfnZOTk5Gdne2t84YNG1CnTh1ceOGF3jS9evVCTEwMNm3aFPI6y7FmzRo0bNgQbdu2xdixY/Hf//7Xu81u9S8tLQUAb9uv577R0y4RZTgzrUn27NmDF154Ac8884x3XXFxMZo3b+6XznNjFhcXo27duiguLva7WT1piouLg19pnTz++OO48sorUbNmTXz++ef461//iuPHj+Pee+8FAMVjKCsrw6lTp1CjRo1wVBuAct0829TShKv+9957L7p06YJ69eph/fr1yM3NxaFDh/Dcc89566t1X4WL33//HZWVlbLn88cffwxTrZTJzs7GwoUL0bZtWxw6dAiPPfYYLrvsMvzwww8oLi5GXFxcldgnuz2fvnjqpdamFBcXV5myvFq1aqhXr54tjqtfv364/vrr0bx5c+zduxd/+9vf0L9/f2zYsAGxsbG2qr/b7cb48ePxl7/8BR06dAAAXfeNnnaJKBP1QmXSpEl48sknVdPs3LnTL+DuwIED6NevH2688UaMHj062FXUxMwxqPHII494/7/gggtw4sQJPP30016hEmgCXX87YOSYJkyY4F2XlZWFuLg4jBkzBjNmzIiaqbhDRf/+/b3/Z2VlITs7G02bNsU777wTVoEdzdx8883e/zt27IisrCy0bNkSa9asQc+ePcNYs6qMGzcOP/zwA7766qtwVyWqiHqhMnHiRIwYMUI1TYsWLbz/Hzx4EFdccQW6deuG+fPn+6VLS0urEunt+Z2WlqaaxrPdDEaPwSjZ2dmYOnUqysvLER8fr3gMSUlJphr7QNY/LS2tyogTvdfAbP3lsHJM2dnZOHv2LAoLC9G2bVtd91W4aNCgAWJjYwN+T4eKOnXqoE2bNtizZw969+6NiooKHD161O/t2M7H4qnX4cOHkZ6e7l1/+PBhdO7c2ZumpKTEb7+zZ8/iyJEjtjyuFi1aoEGDBtizZw969uxpm/rffffd+Pjjj/Hll1+icePG3vVpaWma942edomoEO4gmUji119/Fa1btxY333yzOHv2bJXtnqDHiooK77rc3NwqwbRXX3213345OTm2CqaV8sQTT4i6det6fz/00EOiQ4cOfmluueUWWwXT+o44mTdvnkhKShKnT58WQtij/mq88cYbIiYmxjvyQc99FU4uvvhicffdd3t/V1ZWivPOO8+WwbRSjh07JurWrStmz57tDYp89913vdt//PHHiAimfeaZZ7zrSktLZYNpt2zZ4k2zfPly2wTTSikqKhIul0t8+OGHQojw19/tdotx48aJRo0aid27d1fZrue+0dMuEWUoVHTy66+/ilatWomePXuKX3/91W8onYejR4+K1NRUcfvtt4sffvhBLFmyRNSsWbPK8ORq1aqJZ555RuzcuVNMnjw5pMOTf/nlF7Ft2zbx2GOPicTERLFt2zaxbds2cezYMSGEEB999JF45ZVXREFBgfjpp5/Eyy+/LGrWrCkeffRRbx6e4b0PPvig2Llzp3jppZdCNrxXq/6eYYB9+vQR+fn54rPPPhMpKSmyw5PDUX8p69evF88//7zIz88Xe/fuFW+88YZISUkRw4YN86bRc1+FkyVLloj4+HixcOFCsWPHDnHnnXeKOnXq+I1wsAsTJ04Ua9asEfv27RNff/216NWrl2jQoIEoKSkRQpwbZtqkSROxatUqsWXLFpGTkyNycnLCWudjx45573MA4rnnnhPbtm0Tv/zyixDi3PDkOnXqiA8//FB8//33YtCgQbLDky+44AKxadMm8dVXX4nWrVuHbHivWv2PHTsmHnjgAbFhwwaxb98+sXLlStGlSxfRunVrvw48nPUfO3asSE5OFmvWrPFr90+ePOlNo3Xf6GmXiDIUKjrJy8sTAGQXX7777jtx6aWXivj4eHHeeeeJmTNnVsnrnXfeEW3atBFxcXGiffv24j//+U+oDkMMHz5c9hhWr14thDg37K9z584iMTFR1KpVS3Tq1EnMnTtXVFZW+uWzevVq0blzZxEXFydatGgh8vLybFF/IYQoLCwU/fv3FzVq1BANGjQQEydOFGfOnLFF/aVs3bpVZGdni+TkZJGQkCDOP/98MX369CpvWXruq3DywgsviCZNmoi4uDhx8cUXi40bN4a7SrLcdNNNIj09XcTFxYnzzjtP3HTTTWLPnj3e7adOnRJ//etfRd26dUXNmjXFdddd5/cyEg5Wr14te88PHz5cCHHujf+RRx4RqampIj4+XvTs2VPs2rXLL4///ve/4pZbbhGJiYkiKSlJjBw50ivuw1n/kydPij59+oiUlBRRvXp10bRpUzF69OgqIjec9Vdq933bDD33jZ52icjjEsJn3CkhhBBCiI3gPCqEEEIIsS0UKoQQQgixLRQqhBBCCLEtFCqEEEIIsS0UKoQQQgixLRQqhBBCCLEtFCqEEEIIsS0UKoQQQgixLRQqhBBCCLEtFCqEEEIIsS0UKoQQ0/z2229IS0vD9OnTvevWr1+PuLg4fPHFF2GsGSHEKfBbP4QQS3zyySe49tprsX79erRt2xadO3fGoEGD8Nxzz4W7aoQQB0ChQgixzLhx47By5UpceOGFKCgowObNmxEfHx/uahFCHACFCiHEMqdOnUKHDh1QVFSErVu3omPHjuGuEiHEITBGhRBimb179+LgwYNwu90oLCwMd3UIIQ6CFhVCiCUqKipw8cUXo3Pnzmjbti1mzZqFgoICNGzYMNxVI4Q4AAoVQoglHnzwQbz77rv47rvvkJiYiO7duyM5ORkff/xxuKtGCHEAdP0QQkyzZs0azJo1C6+//jqSkpIQExOD119/HevWrcOcOXPCXT1CiAOgRYUQQgghtoUWFUIIIYTYFgoVQgghhNgWChVCCCGE2BYKFUIIIYTYFgoVQgghhNgWChVCCCGE2BYKFUIIIYTYFgoVQgghhNgWChVCCCGE2BYKFUIIIYTYFgoVQgghhNgWChVCCCGE2Jb/B4Yz8pQphxrgAAAAAElFTkSuQmCC", "text/plain": [ "
" ] From d5bc65e043f638316b99f87e1821db2c84e5c9f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Dr=C3=B6nner?= Date: Tue, 26 Nov 2024 12:49:14 +0100 Subject: [PATCH 03/14] ml model validierung also allow x,y shaped 1d output --- geoengine/ml.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/geoengine/ml.py b/geoengine/ml.py index 679ab4a9..2b4488e7 100644 --- a/geoengine/ml.py +++ b/geoengine/ml.py @@ -98,9 +98,9 @@ def check_data_type(data_type: TypeProto, expected_type: RasterDataType, prefix: raise InputException('Dimension 2 of the a 3D input tensor must have a length') if not dim[3].dim_value: raise InputException('Dimension 3 of the a 3D input tensor must have a length') - if dim[1].dim_value != input_shape.attributes: + if dim[1].dim_value != input_shape.y: raise InputException(f'Model input has {dim[1].dim_value} y size, but {input_shape.y} are expected') - if dim[2].dim_value != input_shape.attributes: + if dim[2].dim_value != input_shape.x: raise InputException(f'Model input has {dim[2].dim_value} x size, but {input_shape.x} are expected') if dim[3].dim_value != input_shape.attributes: raise InputException(f'Model input has {dim[3].dim_value} bands, but {input_shape.attributes} are expected') @@ -112,7 +112,6 @@ def check_data_type(data_type: TypeProto, expected_type: RasterDataType, prefix: check_data_type(model_outputs[0].type, output_type, 'output') dim = model_outputs[0].type.tensor_type.shape.dim - if len(dim) == 1: pass # this is a happens if there is only a single out? so shape would be [-1] elif len(dim) == 2: @@ -120,6 +119,15 @@ def check_data_type(data_type: TypeProto, expected_type: RasterDataType, prefix: raise InputException('Dimension 1 of a 1D input tensor must have a length') if dim[1].dim_value != 1: raise InputException(f'Model output has {dim[1].dim_value} bands, but {out_shape.attributes} are expected') + elif len(dim) == 3: + if not dim[1].dim_value: + raise InputException('Dimension 1 of a 3D input tensor must have a length') + if not dim[2].dim_value: + raise InputException('Dimension 2 of a 3D input tensor must have a length') + if dim[1].dim_value != out_shape.y: + raise InputException(f'Model output has {dim[1].dim_value} y size, but {out_shape.y} are expected') + if dim[2].dim_value != out_shape.x: + raise InputException(f'Model output has {dim[2].dim_value} x size, but {out_shape.x} are expected') elif len(dim) == 4: if not dim[1].dim_value: raise InputException('Dimension 1 of the a 3D input tensor must have a length') @@ -127,9 +135,9 @@ def check_data_type(data_type: TypeProto, expected_type: RasterDataType, prefix: raise InputException('Dimension 2 of the a 3D input tensor must have a length') if not dim[3].dim_value: raise InputException('Dimension 3 of the a 3D input tensor must have a length') - if dim[1].dim_value != out_shape.attributes: + if dim[1].dim_value != out_shape.y: raise InputException(f'Model output has {dim[1].dim_value} y size, but {out_shape.y} are expected') - if dim[2].dim_value != out_shape.attributes: + if dim[2].dim_value != out_shape.x: raise InputException(f'Model output has {dim[2].dim_value} x size, but {out_shape.x} are expected') if dim[3].dim_value != out_shape.attributes: raise InputException(f'Model output has {dim[3].dim_value} bands, but {out_shape.attributes} are expected') From 1a5600bd41192caf79fad19088c3c14da7df4bd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Dr=C3=B6nner?= Date: Fri, 7 Feb 2025 10:08:34 +0100 Subject: [PATCH 04/14] rename MlModel 3DTensorShape attrbutes to bands --- examples/ml_pipeline.ipynb | 4 ++-- geoengine/ml.py | 14 +++++++------- tests/test_ml.py | 12 ++++++------ 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/examples/ml_pipeline.ipynb b/examples/ml_pipeline.ipynb index adf52094..c955137f 100644 --- a/examples/ml_pipeline.ipynb +++ b/examples/ml_pipeline.ipynb @@ -89,8 +89,8 @@ " file_name=\"model.onnx\",\n", " input_type=RasterDataType.F32,\n", " output_type=RasterDataType.I64,\n", - " input_shape=TensorShape3D(y=1, x=1, attributes=2),\n", - " output_shape=TensorShape3D(y=1, x=1, attributes=1)\n", + " input_shape=TensorShape3D(y=1, x=1, bands=2),\n", + " output_shape=TensorShape3D(y=1, x=1, bands=1)\n", ")\n", "\n", "model_config = MlModelConfig(\n", diff --git a/geoengine/ml.py b/geoengine/ml.py index ff3d7d0d..118de4bf 100644 --- a/geoengine/ml.py +++ b/geoengine/ml.py @@ -123,8 +123,8 @@ def check_data_type(data_type: TypeProto, expected_type: RasterDataType, prefix: if len(dim) == 2: if not dim[1].dim_value: raise InputException('Dimension 1 of a 1D input tensor must have a length') - if dim[1].dim_value != input_shape.attributes: - raise InputException(f'Model input has {dim[1].dim_value} bands, but {input_shape.attributes} are expected') + if dim[1].dim_value != input_shape.bands: + raise InputException(f'Model input has {dim[1].dim_value} bands, but {input_shape.bands} are expected') elif len(dim) == 4: if not dim[1].dim_value: raise InputException('Dimension 1 of the a 3D input tensor must have a length') @@ -136,8 +136,8 @@ def check_data_type(data_type: TypeProto, expected_type: RasterDataType, prefix: raise InputException(f'Model input has {dim[1].dim_value} y size, but {input_shape.y} are expected') if dim[2].dim_value != input_shape.x: raise InputException(f'Model input has {dim[2].dim_value} x size, but {input_shape.x} are expected') - if dim[3].dim_value != input_shape.attributes: - raise InputException(f'Model input has {dim[3].dim_value} bands, but {input_shape.attributes} are expected') + if dim[3].dim_value != input_shape.bands: + raise InputException(f'Model input has {dim[3].dim_value} bands, but {input_shape.bands} are expected') else: raise InputException('Only 1D and 3D input tensors are supported') @@ -152,7 +152,7 @@ def check_data_type(data_type: TypeProto, expected_type: RasterDataType, prefix: if not dim[1].dim_value: raise InputException('Dimension 1 of a 1D input tensor must have a length') if dim[1].dim_value != 1: - raise InputException(f'Model output has {dim[1].dim_value} bands, but {out_shape.attributes} are expected') + raise InputException(f'Model output has {dim[1].dim_value} bands, but {out_shape.bands} are expected') elif len(dim) == 3: if not dim[1].dim_value: raise InputException('Dimension 1 of a 3D input tensor must have a length') @@ -173,8 +173,8 @@ def check_data_type(data_type: TypeProto, expected_type: RasterDataType, prefix: raise InputException(f'Model output has {dim[1].dim_value} y size, but {out_shape.y} are expected') if dim[2].dim_value != out_shape.x: raise InputException(f'Model output has {dim[2].dim_value} x size, but {out_shape.x} are expected') - if dim[3].dim_value != out_shape.attributes: - raise InputException(f'Model output has {dim[3].dim_value} bands, but {out_shape.attributes} are expected') + if dim[3].dim_value != out_shape.bands: + raise InputException(f'Model output has {dim[3].dim_value} bands, but {out_shape.bands} are expected') else: raise InputException('Only 1D and 3D output tensors are supported') diff --git a/tests/test_ml.py b/tests/test_ml.py index 7c0c603f..df9fe7c2 100644 --- a/tests/test_ml.py +++ b/tests/test_ml.py @@ -79,8 +79,8 @@ def test_uploading_onnx_model(self): file_name="model.onnx", input_type=RasterDataType.F32, output_type=RasterDataType.I64, - input_shape=TensorShape3D(y=1, x=1, attributes=4), - output_shape=TensorShape3D(y=1, x=1, attributes=1) + input_shape=TensorShape3D(y=1, x=1, bands=4), + output_shape=TensorShape3D(y=1, x=1, bands=1) ), display_name="Decision Tree", description="A simple decision tree model", @@ -100,8 +100,8 @@ def test_uploading_onnx_model(self): file_name="model.onnx", input_type=RasterDataType.F64, output_type=RasterDataType.I64, - input_shape=TensorShape3D(y=1, x=1, attributes=2), - output_shape=TensorShape3D(y=1, x=1, attributes=1) + input_shape=TensorShape3D(y=1, x=1, bands=2), + output_shape=TensorShape3D(y=1, x=1, bands=1) ), display_name="Decision Tree", description="A simple decision tree model", @@ -121,8 +121,8 @@ def test_uploading_onnx_model(self): file_name="model.onnx", input_type=RasterDataType.F32, output_type=RasterDataType.I32, - input_shape=TensorShape3D(y=1, x=1, attributes=2), - output_shape=TensorShape3D(y=1, x=1, attributes=1) + input_shape=TensorShape3D(y=1, x=1, bands=2), + output_shape=TensorShape3D(y=1, x=1, bands=1) ), display_name="Decision Tree", description="A simple decision tree model", From b884da95b0731b5b4810fc89a70ac6aee838eab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Dr=C3=B6nner?= Date: Thu, 8 May 2025 17:12:06 +0200 Subject: [PATCH 05/14] update openapi branch --- .github/.backend_git_ref | 2 +- examples/ml_pipeline.ipynb | 6 +++--- geoengine/ml.py | 6 +++--- setup.cfg | 2 +- tests/test_ml.py | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/.backend_git_ref b/.github/.backend_git_ref index ba2906d0..476ae44d 100644 --- a/.github/.backend_git_ref +++ b/.github/.backend_git_ref @@ -1 +1 @@ -main +e07f65bba004f23fe5bb957042bc71232b1b1c21 diff --git a/examples/ml_pipeline.ipynb b/examples/ml_pipeline.ipynb index c955137f..b88bf5c8 100644 --- a/examples/ml_pipeline.ipynb +++ b/examples/ml_pipeline.ipynb @@ -9,14 +9,14 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import geoengine as ge\n", "from geoengine.ml import MlModelConfig\n", "\n", - "from geoengine_openapi_client.models import MlModelMetadata, RasterDataType, TensorShape3D\n", + "from geoengine_openapi_client.models import MlModelMetadata, RasterDataType, MlTensorShape3D as TensorShape3D\n", "\n", "from sklearn.tree import DecisionTreeClassifier\n", "import numpy as np\n", @@ -180,7 +180,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.12" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/geoengine/ml.py b/geoengine/ml.py index 118de4bf..a3b7e16d 100644 --- a/geoengine/ml.py +++ b/geoengine/ml.py @@ -9,7 +9,7 @@ import geoengine_openapi_client.models from onnx import TypeProto, TensorProto, ModelProto from onnx.helper import tensor_dtype_to_string -from geoengine_openapi_client.models import MlModelMetadata, MlModel, RasterDataType, TensorShape3D +from geoengine_openapi_client.models import MlModelMetadata, MlModel, RasterDataType, MlTensorShape3D import geoengine_openapi_client from geoengine.auth import get_session from geoengine.datasets import UploadId @@ -98,8 +98,8 @@ def register_ml_model(onnx_model: ModelProto, def validate_model_config(onnx_model: ModelProto, *, input_type: RasterDataType, output_type: RasterDataType, - input_shape: TensorShape3D, - out_shape: TensorShape3D): + input_shape: MlTensorShape3D, + out_shape: MlTensorShape3D): '''Validates the model config. Raises an exception if the model config is invalid''' def check_data_type(data_type: TypeProto, expected_type: RasterDataType, prefix: 'str'): diff --git a/setup.cfg b/setup.cfg index 34557edc..c02d44f6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -18,7 +18,7 @@ package_dir = packages = find: python_requires = >=3.9 install_requires = - geoengine-openapi-client @ git+https://github.com/geo-engine/openapi-client@ml-model-input-output-shape#subdirectory=python + geoengine-openapi-client @ git+https://github.com/geo-engine/openapi-client@ml-model-input-outpt-shape-2#subdirectory=python geopandas >=0.9,<0.15 matplotlib >=3.5,<3.8 numpy >=1.21,<2.1 diff --git a/tests/test_ml.py b/tests/test_ml.py index df9fe7c2..45f3914a 100644 --- a/tests/test_ml.py +++ b/tests/test_ml.py @@ -4,7 +4,7 @@ from sklearn.ensemble import RandomForestClassifier from skl2onnx import to_onnx import numpy as np -from geoengine_openapi_client.models import MlModelMetadata, RasterDataType, TensorShape3D +from geoengine_openapi_client.models import MlModelMetadata, RasterDataType, MlTensorShape3D as TensorShape3D import geoengine as ge from tests.ge_test import GeoEngineTestInstance From c45bf1324ed20219ebbf4c9e135c0a2af2d9eb6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Dr=C3=B6nner?= Date: Wed, 14 May 2025 22:09:55 +0200 Subject: [PATCH 06/14] change ml model verification --- .github/.backend_git_ref | 2 +- examples/expression.ipynb | 2 +- geoengine/ml.py | 101 ++++++++++++++++++-------------------- 3 files changed, 51 insertions(+), 54 deletions(-) diff --git a/.github/.backend_git_ref b/.github/.backend_git_ref index 476ae44d..76003954 100644 --- a/.github/.backend_git_ref +++ b/.github/.backend_git_ref @@ -1 +1 @@ -e07f65bba004f23fe5bb957042bc71232b1b1c21 +a8d61cd3e5d0cddf82d96609ace7812e58899db2 diff --git a/examples/expression.ipynb b/examples/expression.ipynb index ccbf9140..572e3529 100644 --- a/examples/expression.ipynb +++ b/examples/expression.ipynb @@ -397,7 +397,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.12" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/geoengine/ml.py b/geoengine/ml.py index 7ea8739e..4ac87a4b 100644 --- a/geoengine/ml.py +++ b/geoengine/ml.py @@ -38,6 +38,7 @@ def register_ml_model(onnx_model: ModelProto, input_shape=model_config.metadata.input_shape, out_shape=model_config.metadata.output_shape ) + check_backend_constraints(model_config.metadata.input_shape, model_config.metadata.output_shape) session = get_session() @@ -62,6 +63,48 @@ def register_ml_model(onnx_model: ModelProto, return MlModelName.from_response(res_name) +def model_dim_to_tensorshape(model_dims): + '''Transform an ONNX dimension into a MlTensorShape3D''' + mts = MlTensorShape3D(x=1, y=1, bands=1) + if len(model_dims) == 1 and model_dims[0] > 0: + mts.bands = model_dims[0] + elif len(model_dims) == 2: + if model_dims[0] in (-1, 1): + mts.bands = model_dims[1] + else: + mts.y = model_dims[1] + mts.x = model_dims[2] + elif len(model_dims) == 3: + if model_dims[0] in (-1, 1): + mts.y = model_dims[1] + mts.x = model_dims[2] + else: + mts.y = model_dims[0] + mts.x = model_dims[1] + mts.bands = model_dims[2] + elif len(model_dims) == 4 and model_dims[0] in (-1, 1): + mts.y = model_dims[1] + mts.x = model_dims[2] + mts.bands = model_dims[3] + else: + raise InputException('Only 1D and 3D input tensors are supported. Got model dim {model_dims}') + return mts + + +def check_backend_constraints(input_shape: MlTensorShape3D, output_shape: MlTensorShape3D, ge_tile_size=(512, 512)): + ''' Checks that the shapes match the constraintsof the backend''' + + if not ( + input_shape.x in [1, ge_tile_size[0]] and input_shape.y in [1, ge_tile_size[1]] and input_shape.bands > 0 + ): + raise InputException('Backend currently supports single pixel and full tile shaped input! Got {input_shape}!') + + if not ( + output_shape.x in [1, ge_tile_size[0]] and output_shape.y in [1, ge_tile_size[1]] and output_shape.bands > 0 + ): + raise InputException('Backend currently supports single pixel and full tile shaped Output! Got {input_shape}!') + + # pylint: disable=too-many-branches,too-many-statements def validate_model_config(onnx_model: ModelProto, *, input_type: RasterDataType, @@ -89,64 +132,18 @@ def check_data_type(data_type: TypeProto, expected_type: RasterDataType, prefix: check_data_type(model_inputs[0].type, input_type, 'input') dim = model_inputs[0].type.tensor_type.shape.dim - - if len(dim) == 2: - if not dim[1].dim_value: - raise InputException('Dimension 1 of a 1D input tensor must have a length') - if dim[1].dim_value != input_shape.bands: - raise InputException(f'Model input has {dim[1].dim_value} bands, but {input_shape.bands} are expected') - elif len(dim) == 4: - if not dim[1].dim_value: - raise InputException('Dimension 1 of the a 3D input tensor must have a length') - if not dim[2].dim_value: - raise InputException('Dimension 2 of the a 3D input tensor must have a length') - if not dim[3].dim_value: - raise InputException('Dimension 3 of the a 3D input tensor must have a length') - if dim[1].dim_value != input_shape.y: - raise InputException(f'Model input has {dim[1].dim_value} y size, but {input_shape.y} are expected') - if dim[2].dim_value != input_shape.x: - raise InputException(f'Model input has {dim[2].dim_value} x size, but {input_shape.x} are expected') - if dim[3].dim_value != input_shape.bands: - raise InputException(f'Model input has {dim[3].dim_value} bands, but {input_shape.bands} are expected') - else: - raise InputException('Only 1D and 3D input tensors are supported') + in_ts3d = model_dim_to_tensorshape(dim) + if not in_ts3d == input_shape: + raise InputException("Input shape {in_ts3d} and metadata {input_shape} not equal!") if len(model_outputs) < 1: raise InputException('Models with no outputs are not supported') check_data_type(model_outputs[0].type, output_type, 'output') dim = model_outputs[0].type.tensor_type.shape.dim - if len(dim) == 1: - pass # this is a happens if there is only a single out? so shape would be [-1] - elif len(dim) == 2: - if not dim[1].dim_value: - raise InputException('Dimension 1 of a 1D input tensor must have a length') - if dim[1].dim_value != 1: - raise InputException(f'Model output has {dim[1].dim_value} bands, but {out_shape.bands} are expected') - elif len(dim) == 3: - if not dim[1].dim_value: - raise InputException('Dimension 1 of a 3D input tensor must have a length') - if not dim[2].dim_value: - raise InputException('Dimension 2 of a 3D input tensor must have a length') - if dim[1].dim_value != out_shape.y: - raise InputException(f'Model output has {dim[1].dim_value} y size, but {out_shape.y} are expected') - if dim[2].dim_value != out_shape.x: - raise InputException(f'Model output has {dim[2].dim_value} x size, but {out_shape.x} are expected') - elif len(dim) == 4: - if not dim[1].dim_value: - raise InputException('Dimension 1 of the a 3D input tensor must have a length') - if not dim[2].dim_value: - raise InputException('Dimension 2 of the a 3D input tensor must have a length') - if not dim[3].dim_value: - raise InputException('Dimension 3 of the a 3D input tensor must have a length') - if dim[1].dim_value != out_shape.y: - raise InputException(f'Model output has {dim[1].dim_value} y size, but {out_shape.y} are expected') - if dim[2].dim_value != out_shape.x: - raise InputException(f'Model output has {dim[2].dim_value} x size, but {out_shape.x} are expected') - if dim[3].dim_value != out_shape.bands: - raise InputException(f'Model output has {dim[3].dim_value} bands, but {out_shape.bands} are expected') - else: - raise InputException('Only 1D and 3D output tensors are supported') + out_ts3d = model_dim_to_tensorshape(dim) + if not out_ts3d == out_shape: + raise InputException("Output shape {out_ts3d} and metadata {out_shape} not equal!") RASTER_TYPE_TO_ONNX_TYPE = { From 89c54df8ec5cde98c50323630c8adbe51ad00329 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Dr=C3=B6nner?= Date: Thu, 15 May 2025 23:06:28 +0200 Subject: [PATCH 07/14] add more cases to model shape converer --- geoengine/ml.py | 46 +++++++++++++++++++++++++--------------------- tests/test_ml.py | 20 ++++++++++---------- 2 files changed, 35 insertions(+), 31 deletions(-) diff --git a/geoengine/ml.py b/geoengine/ml.py index 4ac87a4b..9b77cb14 100644 --- a/geoengine/ml.py +++ b/geoengine/ml.py @@ -65,29 +65,32 @@ def register_ml_model(onnx_model: ModelProto, def model_dim_to_tensorshape(model_dims): '''Transform an ONNX dimension into a MlTensorShape3D''' + mts = MlTensorShape3D(x=1, y=1, bands=1) - if len(model_dims) == 1 and model_dims[0] > 0: - mts.bands = model_dims[0] + if len(model_dims) == 1 and model_dims[0].dim_value in (-1, 0): + pass # in this case, the model will produce as many outs as inputs + elif len(model_dims) == 1 and model_dims[0].dim_value > 0: + mts.bands = model_dims[0].dim_value elif len(model_dims) == 2: - if model_dims[0] in (-1, 1): - mts.bands = model_dims[1] + if model_dims[0].dim_value in (None, -1, 0, 1): + mts.bands = model_dims[1].dim_value else: - mts.y = model_dims[1] - mts.x = model_dims[2] + mts.y = model_dims[0].dim_value + mts.x = model_dims[1].dim_value elif len(model_dims) == 3: - if model_dims[0] in (-1, 1): - mts.y = model_dims[1] - mts.x = model_dims[2] + if model_dims[0].dim_value in (None, -1, 0, 1): + mts.y = model_dims[1].dim_value + mts.x = model_dims[2].dim_value else: - mts.y = model_dims[0] - mts.x = model_dims[1] - mts.bands = model_dims[2] - elif len(model_dims) == 4 and model_dims[0] in (-1, 1): - mts.y = model_dims[1] - mts.x = model_dims[2] - mts.bands = model_dims[3] + mts.y = model_dims[0].dim_value + mts.x = model_dims[1].dim_value + mts.bands = model_dims[2].dim_value + elif len(model_dims) == 4 and model_dims[0].dim_value in (None, -1, 0, 1): + mts.y = model_dims[1].dim_value + mts.x = model_dims[2].dim_value + mts.bands = model_dims[3].dim_value else: - raise InputException('Only 1D and 3D input tensors are supported. Got model dim {model_dims}') + raise InputException(f'Only 1D and 3D input tensors are supported. Got model dim {model_dims}') return mts @@ -97,12 +100,12 @@ def check_backend_constraints(input_shape: MlTensorShape3D, output_shape: MlTens if not ( input_shape.x in [1, ge_tile_size[0]] and input_shape.y in [1, ge_tile_size[1]] and input_shape.bands > 0 ): - raise InputException('Backend currently supports single pixel and full tile shaped input! Got {input_shape}!') + raise InputException(f'Backend currently supports single pixel and full tile shaped input! Got {input_shape}!') if not ( output_shape.x in [1, ge_tile_size[0]] and output_shape.y in [1, ge_tile_size[1]] and output_shape.bands > 0 ): - raise InputException('Backend currently supports single pixel and full tile shaped Output! Got {input_shape}!') + raise InputException(f'Backend currently supports single pixel and full tile shaped Output! Got {input_shape}!') # pylint: disable=too-many-branches,too-many-statements @@ -132,9 +135,10 @@ def check_data_type(data_type: TypeProto, expected_type: RasterDataType, prefix: check_data_type(model_inputs[0].type, input_type, 'input') dim = model_inputs[0].type.tensor_type.shape.dim + in_ts3d = model_dim_to_tensorshape(dim) if not in_ts3d == input_shape: - raise InputException("Input shape {in_ts3d} and metadata {input_shape} not equal!") + raise InputException(f"Input shape {in_ts3d} and metadata {input_shape} not equal!") if len(model_outputs) < 1: raise InputException('Models with no outputs are not supported') @@ -143,7 +147,7 @@ def check_data_type(data_type: TypeProto, expected_type: RasterDataType, prefix: dim = model_outputs[0].type.tensor_type.shape.dim out_ts3d = model_dim_to_tensorshape(dim) if not out_ts3d == out_shape: - raise InputException("Output shape {out_ts3d} and metadata {out_shape} not equal!") + raise InputException(f"Output shape {out_ts3d} and metadata {out_shape} not equal!") RASTER_TYPE_TO_ONNX_TYPE = { diff --git a/tests/test_ml.py b/tests/test_ml.py index 7bc6d30a..098e74b6 100644 --- a/tests/test_ml.py +++ b/tests/test_ml.py @@ -4,7 +4,7 @@ from sklearn.ensemble import RandomForestClassifier from skl2onnx import to_onnx import numpy as np -from geoengine_openapi_client.models import MlModelMetadata, RasterDataType, MlTensorShape3D as TensorShape3D +from geoengine_openapi_client.models import MlModelMetadata, RasterDataType, MlTensorShape3D import geoengine as ge from tests.ge_test import GeoEngineTestInstance @@ -41,8 +41,8 @@ def test_uploading_onnx_model(self): file_name="model.onnx", input_type=RasterDataType.F32, output_type=RasterDataType.I64, - input_shape=TensorShape3D(y=1, x=1, bands=2), - output_shape=TensorShape3D(y=1, x=1, bands=1) + input_shape=MlTensorShape3D(y=1, x=1, bands=2), + output_shape=MlTensorShape3D(y=1, x=1, bands=1) ), display_name="Decision Tree", description="A simple decision tree model", @@ -79,8 +79,8 @@ def test_uploading_onnx_model(self): file_name="model.onnx", input_type=RasterDataType.F32, output_type=RasterDataType.I64, - input_shape=TensorShape3D(y=1, x=1, bands=4), - output_shape=TensorShape3D(y=1, x=1, bands=1) + input_shape=MlTensorShape3D(y=1, x=1, bands=4), + output_shape=MlTensorShape3D(y=1, x=1, bands=1) ), display_name="Decision Tree", description="A simple decision tree model", @@ -88,7 +88,7 @@ def test_uploading_onnx_model(self): ) self.assertEqual( str(exception.exception), - 'Model input has 2 bands, but 4 are expected' + 'Input shape bands=2 x=1 y=1 and metadata bands=4 x=1 y=1 not equal!' ) with self.assertRaises(ge.InputException) as exception: @@ -100,8 +100,8 @@ def test_uploading_onnx_model(self): file_name="model.onnx", input_type=RasterDataType.F64, output_type=RasterDataType.I64, - input_shape=TensorShape3D(y=1, x=1, bands=2), - output_shape=TensorShape3D(y=1, x=1, bands=1) + input_shape=MlTensorShape3D(y=1, x=1, bands=2), + output_shape=MlTensorShape3D(y=1, x=1, bands=1) ), display_name="Decision Tree", description="A simple decision tree model", @@ -121,8 +121,8 @@ def test_uploading_onnx_model(self): file_name="model.onnx", input_type=RasterDataType.F32, output_type=RasterDataType.I32, - input_shape=TensorShape3D(y=1, x=1, bands=2), - output_shape=TensorShape3D(y=1, x=1, bands=1) + input_shape=MlTensorShape3D(y=1, x=1, bands=2), + output_shape=MlTensorShape3D(y=1, x=1, bands=1) ), display_name="Decision Tree", description="A simple decision tree model", From 96547dcd4da16f7c32170d5d6f112ee91ad765c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Dr=C3=B6nner?= Date: Thu, 15 May 2025 23:15:46 +0200 Subject: [PATCH 08/14] update backend ref --- .github/.backend_git_ref | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/.backend_git_ref b/.github/.backend_git_ref index 76003954..f27dfe59 100644 --- a/.github/.backend_git_ref +++ b/.github/.backend_git_ref @@ -1 +1 @@ -a8d61cd3e5d0cddf82d96609ace7812e58899db2 +e85582875c0d476c5239d44853b0301e800b0fb2 From dde2c0d42b0906e0a7bd17a7e17f3f3433f7144d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Dr=C3=B6nner?= Date: Thu, 15 May 2025 23:45:26 +0200 Subject: [PATCH 09/14] fix onnx to 0.17 --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 9a5cc53b..927c254b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -36,6 +36,7 @@ install_requires = urllib3 >= 2.1, < 2.4 pydantic >= 2.10.6, < 2.11 skl2onnx >=1.17,<2 + onnx == 1.17 [options.extras_require] dev = From b71c8f202312979b3e8eb1565e6d54f2382704c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Dr=C3=B6nner?= Date: Fri, 16 May 2025 12:49:18 +0200 Subject: [PATCH 10/14] update backend ref --- .github/.backend_git_ref | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/.backend_git_ref b/.github/.backend_git_ref index f27dfe59..3450ef68 100644 --- a/.github/.backend_git_ref +++ b/.github/.backend_git_ref @@ -1 +1 @@ -e85582875c0d476c5239d44853b0301e800b0fb2 +cdb162df11bff5a3ae5854126d15538c77a2cabb From da6ee108a83cc1fbbbbdbab1f37bb4c4128257c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Dr=C3=B6nner?= Date: Fri, 16 May 2025 17:14:14 +0200 Subject: [PATCH 11/14] more tests --- tests/test_ml.py | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/tests/test_ml.py b/tests/test_ml.py index 098e74b6..2ed0b69d 100644 --- a/tests/test_ml.py +++ b/tests/test_ml.py @@ -1,20 +1,63 @@ '''Tests ML functionality''' +from typing import List import unittest +from onnx import TensorShapeProto as TSP from sklearn.ensemble import RandomForestClassifier from skl2onnx import to_onnx import numpy as np from geoengine_openapi_client.models import MlModelMetadata, RasterDataType, MlTensorShape3D import geoengine as ge +from geoengine.ml import model_dim_to_tensorshape from tests.ge_test import GeoEngineTestInstance -class WorkflowStorageTests(unittest.TestCase): - '''Test methods for storing workflows as datasets''' +class MlModelTests(unittest.TestCase): + '''Test methods for MlModels''' def setUp(self) -> None: ge.reset(False) + def test_model_dim_to_tensorshape(self): + ''' Test model_dim_to_tensorshape ''' + + dim_1d: List[TSP.Dimension] = [TSP.Dimension(dim_value=7)] + mts_1d = MlTensorShape3D(bands=7, y=1, x=1) + self.assertEqual(model_dim_to_tensorshape(dim_1d), mts_1d) + + dim_1d_v: List[TSP.Dimension] = [TSP.Dimension(dim_value=None), TSP.Dimension(dim_value=7)] + mts_1d_v = MlTensorShape3D(bands=7, y=1, x=1) + self.assertEqual(model_dim_to_tensorshape(dim_1d_v), mts_1d_v) + + dim_2d_t: List[TSP.Dimension] = [TSP.Dimension(dim_value=512), TSP.Dimension(dim_value=512)] + mts_2d_t = MlTensorShape3D(bands=1, y=512, x=512) + self.assertEqual(model_dim_to_tensorshape(dim_2d_t), mts_2d_t) + + dim_2d_1: List[TSP.Dimension] = [TSP.Dimension(dim_value=1), TSP.Dimension(dim_value=7)] + mts_2d_1 = MlTensorShape3D(bands=7, y=1, x=1) + self.assertEqual(model_dim_to_tensorshape(dim_2d_1), mts_2d_1) + + dim_3d_t: List[TSP.Dimension] = [ + TSP.Dimension(dim_value=512), TSP.Dimension(dim_value=512), TSP.Dimension(dim_value=7) + ] + mts_3d_t = MlTensorShape3D(bands=7, y=512, x=512) + self.assertEqual(model_dim_to_tensorshape(dim_3d_t), mts_3d_t) + + dim_3d_v: List[TSP.Dimension] = [ + TSP.Dimension(dim_value=None), TSP.Dimension(dim_value=512), TSP.Dimension(dim_value=512) + ] + mts_3d_v = MlTensorShape3D(bands=1, y=512, x=512) + self.assertEqual(model_dim_to_tensorshape(dim_3d_v), mts_3d_v) + + dim_4d_v: List[TSP.Dimension] = [ + TSP.Dimension(dim_value=None), + TSP.Dimension(dim_value=512), + TSP.Dimension(dim_value=512), + TSP.Dimension(dim_value=4) + ] + mts_4d_v = MlTensorShape3D(bands=4, y=512, x=512) + self.assertEqual(model_dim_to_tensorshape(dim_4d_v), mts_4d_v) + def test_uploading_onnx_model(self): clf = RandomForestClassifier(random_state=42) From 7c0c6c5ae47bb424ffa209798748128b38680d93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Dr=C3=B6nner?= Date: Tue, 3 Jun 2025 09:43:27 +0200 Subject: [PATCH 12/14] update backend ref and openapi version --- .github/.backend_git_ref | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/.backend_git_ref b/.github/.backend_git_ref index 3450ef68..d242a7cf 100644 --- a/.github/.backend_git_ref +++ b/.github/.backend_git_ref @@ -1 +1 @@ -cdb162df11bff5a3ae5854126d15538c77a2cabb +7aadfa3 diff --git a/setup.cfg b/setup.cfg index 6c96276d..38448a5b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -18,7 +18,7 @@ package_dir = packages = find: python_requires = >=3.10 install_requires = - geoengine-openapi-client @ git+https://github.com/geo-engine/openapi-client@ml-model-input-outpt-shape-2#subdirectory=python + geoengine-openapi-client == 0.0.25 geopandas >=1.0,<2.0 matplotlib >=3.5,<3.11 numpy >=1.21,<2.3 From a0216cdaae2dfd115cdc3f24cd0c6e8dcae04db0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Dr=C3=B6nner?= Date: Tue, 3 Jun 2025 09:51:31 +0200 Subject: [PATCH 13/14] update backend ref --- .github/.backend_git_ref | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/.backend_git_ref b/.github/.backend_git_ref index d242a7cf..62fd2b5b 100644 --- a/.github/.backend_git_ref +++ b/.github/.backend_git_ref @@ -1 +1 @@ -7aadfa3 +7aadfa37aadfa383e6eee63442e366890dfb1160114caed From 48c6fee912bbe2ec4c15471fe3e8aeee8e5c3b14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Dr=C3=B6nner?= Date: Tue, 3 Jun 2025 10:32:58 +0200 Subject: [PATCH 14/14] update backend ref --- .github/.backend_git_ref | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/.backend_git_ref b/.github/.backend_git_ref index 62fd2b5b..a2296491 100644 --- a/.github/.backend_git_ref +++ b/.github/.backend_git_ref @@ -1 +1 @@ -7aadfa37aadfa383e6eee63442e366890dfb1160114caed +7aadfa383e6eee63442e366890dfb1160114caed