From 3439719fb2d4275627990e6e5ed440b60a5f0679 Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@inrae.fr> Date: Wed, 16 Aug 2023 10:21:19 +0200 Subject: [PATCH 1/8] COMP: fix #45 --- Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 711dc4ba..22f9bb19 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,9 @@ RUN ln -s /usr/bin/python3 /usr/local/bin/python && ln -s /usr/bin/pip3 /usr/loc RUN pip install --no-cache-dir pip --upgrade # NumPy version is conflicting with system's gdal dep and may require venv ARG NUMPY_SPEC="==1.22.*" -RUN pip install --no-cache-dir -U wheel mock six future tqdm deprecated "numpy$NUMPY_SPEC" packaging requests \ +# This is to avoid https://github.com/tensorflow/tensorflow/issues/61551 +ARG PROTO_SPEC="==4.23.*" +RUN pip install --no-cache-dir -U wheel mock six future tqdm deprecated "numpy$NUMPY_SPEC" "protobuf$PROTO_SPEC" packaging requests \ && pip install --no-cache-dir --no-deps keras_applications keras_preprocessing # ---------------------------------------------------------------------------- -- GitLab From 31d94342fc658f7d13b8d559b4252ac6d2766745 Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@inrae.fr> Date: Wed, 16 Aug 2023 10:21:51 +0200 Subject: [PATCH 2/8] DOC: tensor typing --- otbtf/model.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/otbtf/model.py b/otbtf/model.py index b3ee7b92..9958510b 100644 --- a/otbtf/model.py +++ b/otbtf/model.py @@ -28,7 +28,8 @@ import abc import logging import tensorflow as tf -TensorsDict = Dict[str, Any] +Tensor = Any +TensorsDict = Dict[str, Tensor] class ModelBase(abc.ABC): -- GitLab From 761c0a56d317396a3b76f9cffe8e1d08f706f9b4 Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@inrae.fr> Date: Wed, 16 Aug 2023 10:22:47 +0200 Subject: [PATCH 3/8] ADD: keras layers for argmax, max, binary dilated mask --- otbtf/layers.py | 190 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 otbtf/layers.py diff --git a/otbtf/layers.py b/otbtf/layers.py new file mode 100644 index 00000000..6da80bd7 --- /dev/null +++ b/otbtf/layers.py @@ -0,0 +1,190 @@ +# -*- coding: utf-8 -*- +# ========================================================================== +# +# Copyright 2018-2019 IRSTEA +# Copyright 2020-2023 INRAE +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ==========================================================================*/ +""" +[Source code :fontawesome-brands-github:](https://github.com/remicres/otbtf/ +tree/master/otbtf/ops.py){ .md-button } + +The utils module provides some useful keras layers to build deep nets. +""" +from typing import List, Tuple, Any +import tensorflow as tf + + +Tensor = Any +Scalars = List[float] | Tuple[Float] + + +class DilatedMask(keras.layers.Layer): + """Layer to dilate a binary mask.""" + def __init__(self, nodata_value: float, radius: int, name: str = None): + """ + Params: + nodata_value: the no-data value of the binary mask + radius: dilatation radius + name: layer name + + """ + self.nodata_value = nodata_value + self.radius = radius + super().__init__(name=name) + + def call(self, inp: Tensor): + """ + Params: + inp: input layer + + """ + # Compute a binary mask from the input + nodata_mask = tf.cast(tf.math.equal(inp, self.nodata_value), tf.uint8) + + se_size = 1 + 2 * self.radius + # Create a morphological kernel suitable for binary dilatation, see + # https://stackoverflow.com/q/54686895/13711499 + kernel = tf.zeros((se_size, se_size, 1), dtype=tf.uint8) + conv2d_out = tf.nn.dilation2d( + input=nodata_mask, + filters=kernel, + strides=[1, 1, 1, 1], + padding="SAME", + data_format="NHWC", + dilations=[1, 1, 1, 1], + name="dilatation_conv2d" + ) + return tf.cast(conv2d_out, tf.uint8) + + +class ApplyMask(keras.layers.Layer): + """Layer to apply a binary mask to one input.""" + def __init__(self, out_nodata: float, name: str = None): + """ + Params: + out_nodata: output no-data value, set when the mask is 1 + name: layer name + + """ + super().__init__(name=name) + self.out_nodata = out_nodata + + def call(self, inputs: Tuple[Tensor] | List[Tensor]): + """ + Params: + inputs: (mask, input). list or tuple of size 2. First element is + the binary mask, second element is the input. In the binary + mask, values at 1 indicate where to replace input values with + no-data. + + """ + mask, inp = inputs + return tf.where(mask == 1, float(self.out_nodata), inp) + + +class ScalarsTile(keras.layers.Layer): + """ + Layer to duplicate some scalars in a whole array. + Simple example with only one scalar = 0.152: + output [[0.152, 0.152, 0.152], + [0.152, 0.152, 0.152], + [0.152, 0.152, 0.152]] + + """ + def __init__(self, name: str = None): + """ + Params: + name: layer name + + """ + super().__init__(name=name) + + def call(self, inputs: List[Tensor | Scalars] | Tuple[Tensor | Scalars]): + """ + Params: + inputs: [reference, scalar inputs]. Reference is the tensor whose + shape has to be matched, is expected to be of shape [x, y, n]. + scalar inputs are expected to be of shape [1] or [n] so that + they fill the last dimension of the output. + + """ + ref, scalar_inputs = inputs + inp = tf.stack(scalar_inputs, axis=-1) + inp = tf.expand_dims(tf.expand_dims(inp, axis=1), axis=1) + return tf.tile(inp, [1, tf.shape(ref)[1], tf.shape(ref)[2], 1]) + + +class Argmax(keras.layers.Layer): + """ + Layer to compute the argmax of a tensor. + + For example, for a vector A=[0.1, 0.3, 0.6], the output is 2 because + A[2] is the max. + Useful to transform a softmax into a "categorical" map for instance. + + """ + def __init__(self, name: str = None): + """ + Params: + name: layer name + + """ + super().__init__(name=name) + + def call(self, inputs): + """ + Params: + inputs: softmax tensor, or any tensor with last dimension of + size nb_classes + + Returns: + Index of the maximum value, in the last dimension. Int32. + The output tensor has same shape length as input, but with last + dimension of size 1. Contains integer values ranging from 0 to + (nb_classes - 1). + + """ + return tf.expand_dims(tf.math.argmax(inputs, axis=-1), axis=-1) + + +class Max(keras.layers.Layer): + """ + Layer to compute the max of a tensor. + + For example, for a vector [0.1, 0.3, 0.6], the output is 0.6 + Useful to transform a softmax into a "confidence" map for instance + + """ + def __init__(self, name=None): + """ + Params: + name: layer name + + """ + super().__init__(name=name) + + def call(self, inputs): + """ + Params: + inputs: softmax tensor + + Returns: + Maximum value along the last axis of the input. + The output tensor has same shape length as input, but with last + dimension of size 1. + + """ + return tf.expand_dims(tf.math.reduce_max(inputs, axis=-1), axis=-1) -- GitLab From 6a46c034a21fdab2f258e971ee5a1da7cafb56cb Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@inrae.fr> Date: Wed, 16 Aug 2023 10:23:08 +0200 Subject: [PATCH 4/8] ADD: easy one-hot encoding --- otbtf/ops.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 otbtf/ops.py diff --git a/otbtf/ops.py b/otbtf/ops.py new file mode 100644 index 00000000..3c65b4b7 --- /dev/null +++ b/otbtf/ops.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# ========================================================================== +# +# Copyright 2018-2019 IRSTEA +# Copyright 2020-2023 INRAE +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ==========================================================================*/ +""" +[Source code :fontawesome-brands-github:](https://github.com/remicres/otbtf/ +tree/master/otbtf/ops.py){ .md-button } + +The utils module provides some useful Tensorflow ad keras operators to build +and train deep nets. +""" +from typing import List, Tuple, Any +import tensorflow as tf + + +Tensor = Any +Scalars = List[float] | Tuple[Float] +def one_hot(labels: Tensor, nb_classes: int): + """ + Converts labels values into one-hot vector. + + Params: + labels: tensor of label values (shape [x, y, 1]) + nb_classes: number of classes + + Returns: + one-hot encoded vector (shape [x, y, nb_classes]) + + """ + labels_xy = tf.squeeze(tf.cast(labels, tf.int32), axis=-1) # shape [x, y] + return tf.one_hot(labels_xy, depth=nb_classes) # shape [x, y, nb_classes] \ No newline at end of file -- GitLab From b6e3d2b60a9d40c385f87b8e7ac606f58e1cfa5f Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@inrae.fr> Date: Wed, 16 Aug 2023 10:28:55 +0200 Subject: [PATCH 5/8] ADD: easy one-hot encoding --- otbtf/ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/otbtf/ops.py b/otbtf/ops.py index 3c65b4b7..4a8d0b96 100644 --- a/otbtf/ops.py +++ b/otbtf/ops.py @@ -29,7 +29,7 @@ import tensorflow as tf Tensor = Any -Scalars = List[float] | Tuple[Float] +Scalars = List[float] | Tuple[float] def one_hot(labels: Tensor, nb_classes: int): """ Converts labels values into one-hot vector. -- GitLab From 39c85aff0e3f3c88a83587e25b93a310c397cbc4 Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@inrae.fr> Date: Wed, 16 Aug 2023 10:30:11 +0200 Subject: [PATCH 6/8] ADD: keras layers for argmax, max, binary dilated mask --- otbtf/layers.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/otbtf/layers.py b/otbtf/layers.py index 6da80bd7..38f50f9f 100644 --- a/otbtf/layers.py +++ b/otbtf/layers.py @@ -28,10 +28,10 @@ import tensorflow as tf Tensor = Any -Scalars = List[float] | Tuple[Float] +Scalars = List[float] | Tuple[float] -class DilatedMask(keras.layers.Layer): +class DilatedMask(tf.keras.layers.Layer): """Layer to dilate a binary mask.""" def __init__(self, nodata_value: float, radius: int, name: str = None): """ @@ -70,7 +70,7 @@ class DilatedMask(keras.layers.Layer): return tf.cast(conv2d_out, tf.uint8) -class ApplyMask(keras.layers.Layer): +class ApplyMask(tf.keras.layers.Layer): """Layer to apply a binary mask to one input.""" def __init__(self, out_nodata: float, name: str = None): """ @@ -95,7 +95,7 @@ class ApplyMask(keras.layers.Layer): return tf.where(mask == 1, float(self.out_nodata), inp) -class ScalarsTile(keras.layers.Layer): +class ScalarsTile(tf.keras.layers.Layer): """ Layer to duplicate some scalars in a whole array. Simple example with only one scalar = 0.152: @@ -127,7 +127,7 @@ class ScalarsTile(keras.layers.Layer): return tf.tile(inp, [1, tf.shape(ref)[1], tf.shape(ref)[2], 1]) -class Argmax(keras.layers.Layer): +class Argmax(tf.keras.layers.Layer): """ Layer to compute the argmax of a tensor. @@ -160,7 +160,7 @@ class Argmax(keras.layers.Layer): return tf.expand_dims(tf.math.argmax(inputs, axis=-1), axis=-1) -class Max(keras.layers.Layer): +class Max(tf.keras.layers.Layer): """ Layer to compute the max of a tensor. -- GitLab From 0c8cc020ac2186c244187d1aa4c74a0e597e0130 Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@inrae.fr> Date: Wed, 16 Aug 2023 10:30:44 +0200 Subject: [PATCH 7/8] ADD: keras layers for argmax, max, binary dilated mask --- otbtf/layers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/otbtf/layers.py b/otbtf/layers.py index 38f50f9f..a3680421 100644 --- a/otbtf/layers.py +++ b/otbtf/layers.py @@ -19,7 +19,7 @@ # ==========================================================================*/ """ [Source code :fontawesome-brands-github:](https://github.com/remicres/otbtf/ -tree/master/otbtf/ops.py){ .md-button } +tree/master/otbtf/layers.py){ .md-button } The utils module provides some useful keras layers to build deep nets. """ -- GitLab From 9ca8ad35480477b3e136315f9fd51bed6ca2465b Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@inrae.fr> Date: Wed, 16 Aug 2023 10:43:32 +0200 Subject: [PATCH 8/8] ADD: otbtf.ops, otbtf.layers --- otbtf/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/otbtf/__init__.py b/otbtf/__init__.py index 04ac11db..cfbcecb4 100644 --- a/otbtf/__init__.py +++ b/otbtf/__init__.py @@ -33,4 +33,5 @@ except ImportError: from otbtf.tfrecords import TFRecords # noqa from otbtf.model import ModelBase # noqa +from otbtf import layers, ops # noqa __version__ = pkg_resources.require("otbtf")[0].version -- GitLab