[libcamera-devel,v3,02/12] utils: tuning: libtuning: Implement math helpers
diff mbox series

Message ID 20221110173154.488445-3-paul.elder@ideasonboard.com
State Accepted
Headers show
Series
  • utils: tuning: Add a new tuning infrastructure
Related show

Commit Message

Paul Elder Nov. 10, 2022, 5:31 p.m. UTC
Implement math helpers for libtuning. This includes:
- Average, a wrapper class for numpy averaging functions
- Gradient, a class that represents gradients, for distributing and
  mapping
- Smoothing, a wrapper class for cv2 smoothing functions

Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>

---
Changes in v3:
- Newly split from the first patch "utils: tuning: libtuning: Implement
  the core of libtuning"
  - See changelog from that patch
---
 utils/tuning/libtuning/average.py   | 21 ++++++++
 utils/tuning/libtuning/gradient.py  | 75 +++++++++++++++++++++++++++++
 utils/tuning/libtuning/smoothing.py | 24 +++++++++
 3 files changed, 120 insertions(+)
 create mode 100644 utils/tuning/libtuning/average.py
 create mode 100644 utils/tuning/libtuning/gradient.py
 create mode 100644 utils/tuning/libtuning/smoothing.py

Comments

Laurent Pinchart Nov. 23, 2022, 1:35 a.m. UTC | #1
Hi Paul,

Thank you for the patch.

On Fri, Nov 11, 2022 at 02:31:44AM +0900, Paul Elder via libcamera-devel wrote:
> Implement math helpers for libtuning. This includes:
> - Average, a wrapper class for numpy averaging functions
> - Gradient, a class that represents gradients, for distributing and
>   mapping
> - Smoothing, a wrapper class for cv2 smoothing functions
> 
> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> 
> ---
> Changes in v3:
> - Newly split from the first patch "utils: tuning: libtuning: Implement
>   the core of libtuning"
>   - See changelog from that patch
> ---
>  utils/tuning/libtuning/average.py   | 21 ++++++++
>  utils/tuning/libtuning/gradient.py  | 75 +++++++++++++++++++++++++++++
>  utils/tuning/libtuning/smoothing.py | 24 +++++++++
>  3 files changed, 120 insertions(+)
>  create mode 100644 utils/tuning/libtuning/average.py
>  create mode 100644 utils/tuning/libtuning/gradient.py
>  create mode 100644 utils/tuning/libtuning/smoothing.py
> 
> diff --git a/utils/tuning/libtuning/average.py b/utils/tuning/libtuning/average.py
> new file mode 100644
> index 00000000..e28770d7
> --- /dev/null
> +++ b/utils/tuning/libtuning/average.py
> @@ -0,0 +1,21 @@
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +#
> +# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
> +#
> +# average.py - Wrapper for numpy averaging functions to enable duck-typing
> +
> +import numpy as np
> +
> +
> +# @brief Wrapper for np averaging functions so that they can be duck-typed
> +class Average(object):
> +    def __init__(self):
> +        pass
> +
> +    def average(self, np_array):
> +        raise NotImplementedError
> +
> +
> +class Mean(Average):
> +    def average(self, np_array):
> +        return np.mean(np_array)
> diff --git a/utils/tuning/libtuning/gradient.py b/utils/tuning/libtuning/gradient.py
> new file mode 100644
> index 00000000..64a96369
> --- /dev/null
> +++ b/utils/tuning/libtuning/gradient.py
> @@ -0,0 +1,75 @@
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +#
> +# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
> +#
> +# gradient.py - Gradients that can be used to distribute or map numbers
> +
> +import libtuning as lt
> +
> +import math
> +from numbers import Number
> +
> +
> +# @brief Gradient for how to allocate pixels to sectors
> +# @description There are no parameters for the gradients as the domain is the
> +#              number of pixels and the range is the number of sectors, and
> +#              there is only one curve that has a startpoint and endpoint at
> +#              (0, 0) and at (#pixels, #sectors). The exception is for curves
> +#              that *do* have multiple solutions for only two points, such as
> +#              gaussian, and curves of higher polynomial orders if we had them.
> +#
> +# \todo There will probably be a helper in the Gradient class, as I have a
> +# feeling that all the other curves (besides Linear and Gaussian) can be
> +# implemented in the same way.
> +class Gradient(object):
> +    def __init__(self):
> +        pass
> +
> +    # @brief Distribute pixels into sectors (only in one dimension)
> +    # @param domain Number of pixels
> +    # @param sectors Number of sectors
> +    # @return A list of number of pixels in each sector
> +    def distribute(self, domain: list, sectors: list, ) -> list:

Is the training ', ' a Python syntax I don't know about ?

Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

> +        raise NotImplementedError
> +
> +    # @brief Map a number on a curve
> +    # @param domain Domain of the curve
> +    # @param rang Range of the curve
> +    # @param x Input on the domain of the curve
> +    # @return y from the range of the curve
> +    def map(self, domain: tuple, rang: tuple, x: Number) -> Number:
> +        raise NotImplementedError
> +
> +
> +class Linear(Gradient):
> +    # @param remainder Mode of handling remainder
> +    def __init__(self, remainder: lt.Remainder = lt.Remainder.Float):
> +        self.remainder = remainder
> +
> +    def distribute(self, domain: list, sectors: list) -> list:
> +        size = domain / sectors
> +        rem = domain % sectors
> +
> +        if rem == 0:
> +            return [int(size)] * sectors
> +
> +        size = math.ceil(size)
> +        rem = domain % size
> +        output_sectors = [int(size)] * (sectors - 1)
> +
> +        if self.remainder == lt.Remainder.Float:
> +            size = domain / sectors
> +            output_sectors = [size] * sectors
> +        elif self.remainder == lt.Remainder.DistributeFront:
> +            output_sectors.append(int(rem))
> +        elif self.remainder == lt.Remainder.DistributeBack:
> +            output_sectors.insert(0, int(rem))
> +        else:
> +            raise ValueError
> +
> +        return output_sectors
> +
> +    def map(self, domain: tuple, rang: tuple, x: Number) -> Number:
> +        m = (rang[1] - rang[0]) / (domain[1] - domain[0])
> +        b = rang[0] - m * domain[0]
> +        return m * x + b
> diff --git a/utils/tuning/libtuning/smoothing.py b/utils/tuning/libtuning/smoothing.py
> new file mode 100644
> index 00000000..b8a5a242
> --- /dev/null
> +++ b/utils/tuning/libtuning/smoothing.py
> @@ -0,0 +1,24 @@
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +#
> +# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
> +#
> +# smoothing.py - Wrapper for cv2 smoothing functions to enable duck-typing
> +
> +import cv2
> +
> +
> +# @brief Wrapper for cv2 smoothing functions so that they can be duck-typed
> +class Smoothing(object):
> +    def __init__(self):
> +        pass
> +
> +    def smoothing(self, src):
> +        raise NotImplementedError
> +
> +
> +class MedianBlur(Smoothing):
> +    def __init__(self, ksize):
> +        self.ksize = ksize
> +
> +    def smoothing(self, src):
> +        return cv2.medianBlur(src.astype('float32'), self.ksize).astype('float64')
Paul Elder Nov. 23, 2022, 10:24 a.m. UTC | #2
On Wed, Nov 23, 2022 at 03:35:46AM +0200, Laurent Pinchart wrote:
> Hi Paul,
> 
> Thank you for the patch.
> 
> On Fri, Nov 11, 2022 at 02:31:44AM +0900, Paul Elder via libcamera-devel wrote:
> > Implement math helpers for libtuning. This includes:
> > - Average, a wrapper class for numpy averaging functions
> > - Gradient, a class that represents gradients, for distributing and
> >   mapping
> > - Smoothing, a wrapper class for cv2 smoothing functions
> > 
> > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> > 
> > ---
> > Changes in v3:
> > - Newly split from the first patch "utils: tuning: libtuning: Implement
> >   the core of libtuning"
> >   - See changelog from that patch
> > ---
> >  utils/tuning/libtuning/average.py   | 21 ++++++++
> >  utils/tuning/libtuning/gradient.py  | 75 +++++++++++++++++++++++++++++
> >  utils/tuning/libtuning/smoothing.py | 24 +++++++++
> >  3 files changed, 120 insertions(+)
> >  create mode 100644 utils/tuning/libtuning/average.py
> >  create mode 100644 utils/tuning/libtuning/gradient.py
> >  create mode 100644 utils/tuning/libtuning/smoothing.py
> > 
> > diff --git a/utils/tuning/libtuning/average.py b/utils/tuning/libtuning/average.py
> > new file mode 100644
> > index 00000000..e28770d7
> > --- /dev/null
> > +++ b/utils/tuning/libtuning/average.py
> > @@ -0,0 +1,21 @@
> > +# SPDX-License-Identifier: GPL-2.0-or-later
> > +#
> > +# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
> > +#
> > +# average.py - Wrapper for numpy averaging functions to enable duck-typing
> > +
> > +import numpy as np
> > +
> > +
> > +# @brief Wrapper for np averaging functions so that they can be duck-typed
> > +class Average(object):
> > +    def __init__(self):
> > +        pass
> > +
> > +    def average(self, np_array):
> > +        raise NotImplementedError
> > +
> > +
> > +class Mean(Average):
> > +    def average(self, np_array):
> > +        return np.mean(np_array)
> > diff --git a/utils/tuning/libtuning/gradient.py b/utils/tuning/libtuning/gradient.py
> > new file mode 100644
> > index 00000000..64a96369
> > --- /dev/null
> > +++ b/utils/tuning/libtuning/gradient.py
> > @@ -0,0 +1,75 @@
> > +# SPDX-License-Identifier: GPL-2.0-or-later
> > +#
> > +# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
> > +#
> > +# gradient.py - Gradients that can be used to distribute or map numbers
> > +
> > +import libtuning as lt
> > +
> > +import math
> > +from numbers import Number
> > +
> > +
> > +# @brief Gradient for how to allocate pixels to sectors
> > +# @description There are no parameters for the gradients as the domain is the
> > +#              number of pixels and the range is the number of sectors, and
> > +#              there is only one curve that has a startpoint and endpoint at
> > +#              (0, 0) and at (#pixels, #sectors). The exception is for curves
> > +#              that *do* have multiple solutions for only two points, such as
> > +#              gaussian, and curves of higher polynomial orders if we had them.
> > +#
> > +# \todo There will probably be a helper in the Gradient class, as I have a
> > +# feeling that all the other curves (besides Linear and Gaussian) can be
> > +# implemented in the same way.
> > +class Gradient(object):
> > +    def __init__(self):
> > +        pass
> > +
> > +    # @brief Distribute pixels into sectors (only in one dimension)
> > +    # @param domain Number of pixels
> > +    # @param sectors Number of sectors
> > +    # @return A list of number of pixels in each sector
> > +    def distribute(self, domain: list, sectors: list, ) -> list:
> 
> Is the training ', ' a Python syntax I don't know about ?

Yeah I think it's a new feature called a typo :p

> 
> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>


Thanks,

Paul

> 
> > +        raise NotImplementedError
> > +
> > +    # @brief Map a number on a curve
> > +    # @param domain Domain of the curve
> > +    # @param rang Range of the curve
> > +    # @param x Input on the domain of the curve
> > +    # @return y from the range of the curve
> > +    def map(self, domain: tuple, rang: tuple, x: Number) -> Number:
> > +        raise NotImplementedError
> > +
> > +
> > +class Linear(Gradient):
> > +    # @param remainder Mode of handling remainder
> > +    def __init__(self, remainder: lt.Remainder = lt.Remainder.Float):
> > +        self.remainder = remainder
> > +
> > +    def distribute(self, domain: list, sectors: list) -> list:
> > +        size = domain / sectors
> > +        rem = domain % sectors
> > +
> > +        if rem == 0:
> > +            return [int(size)] * sectors
> > +
> > +        size = math.ceil(size)
> > +        rem = domain % size
> > +        output_sectors = [int(size)] * (sectors - 1)
> > +
> > +        if self.remainder == lt.Remainder.Float:
> > +            size = domain / sectors
> > +            output_sectors = [size] * sectors
> > +        elif self.remainder == lt.Remainder.DistributeFront:
> > +            output_sectors.append(int(rem))
> > +        elif self.remainder == lt.Remainder.DistributeBack:
> > +            output_sectors.insert(0, int(rem))
> > +        else:
> > +            raise ValueError
> > +
> > +        return output_sectors
> > +
> > +    def map(self, domain: tuple, rang: tuple, x: Number) -> Number:
> > +        m = (rang[1] - rang[0]) / (domain[1] - domain[0])
> > +        b = rang[0] - m * domain[0]
> > +        return m * x + b
> > diff --git a/utils/tuning/libtuning/smoothing.py b/utils/tuning/libtuning/smoothing.py
> > new file mode 100644
> > index 00000000..b8a5a242
> > --- /dev/null
> > +++ b/utils/tuning/libtuning/smoothing.py
> > @@ -0,0 +1,24 @@
> > +# SPDX-License-Identifier: GPL-2.0-or-later
> > +#
> > +# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
> > +#
> > +# smoothing.py - Wrapper for cv2 smoothing functions to enable duck-typing
> > +
> > +import cv2
> > +
> > +
> > +# @brief Wrapper for cv2 smoothing functions so that they can be duck-typed
> > +class Smoothing(object):
> > +    def __init__(self):
> > +        pass
> > +
> > +    def smoothing(self, src):
> > +        raise NotImplementedError
> > +
> > +
> > +class MedianBlur(Smoothing):
> > +    def __init__(self, ksize):
> > +        self.ksize = ksize
> > +
> > +    def smoothing(self, src):
> > +        return cv2.medianBlur(src.astype('float32'), self.ksize).astype('float64')

Patch
diff mbox series

diff --git a/utils/tuning/libtuning/average.py b/utils/tuning/libtuning/average.py
new file mode 100644
index 00000000..e28770d7
--- /dev/null
+++ b/utils/tuning/libtuning/average.py
@@ -0,0 +1,21 @@ 
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
+#
+# average.py - Wrapper for numpy averaging functions to enable duck-typing
+
+import numpy as np
+
+
+# @brief Wrapper for np averaging functions so that they can be duck-typed
+class Average(object):
+    def __init__(self):
+        pass
+
+    def average(self, np_array):
+        raise NotImplementedError
+
+
+class Mean(Average):
+    def average(self, np_array):
+        return np.mean(np_array)
diff --git a/utils/tuning/libtuning/gradient.py b/utils/tuning/libtuning/gradient.py
new file mode 100644
index 00000000..64a96369
--- /dev/null
+++ b/utils/tuning/libtuning/gradient.py
@@ -0,0 +1,75 @@ 
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
+#
+# gradient.py - Gradients that can be used to distribute or map numbers
+
+import libtuning as lt
+
+import math
+from numbers import Number
+
+
+# @brief Gradient for how to allocate pixels to sectors
+# @description There are no parameters for the gradients as the domain is the
+#              number of pixels and the range is the number of sectors, and
+#              there is only one curve that has a startpoint and endpoint at
+#              (0, 0) and at (#pixels, #sectors). The exception is for curves
+#              that *do* have multiple solutions for only two points, such as
+#              gaussian, and curves of higher polynomial orders if we had them.
+#
+# \todo There will probably be a helper in the Gradient class, as I have a
+# feeling that all the other curves (besides Linear and Gaussian) can be
+# implemented in the same way.
+class Gradient(object):
+    def __init__(self):
+        pass
+
+    # @brief Distribute pixels into sectors (only in one dimension)
+    # @param domain Number of pixels
+    # @param sectors Number of sectors
+    # @return A list of number of pixels in each sector
+    def distribute(self, domain: list, sectors: list, ) -> list:
+        raise NotImplementedError
+
+    # @brief Map a number on a curve
+    # @param domain Domain of the curve
+    # @param rang Range of the curve
+    # @param x Input on the domain of the curve
+    # @return y from the range of the curve
+    def map(self, domain: tuple, rang: tuple, x: Number) -> Number:
+        raise NotImplementedError
+
+
+class Linear(Gradient):
+    # @param remainder Mode of handling remainder
+    def __init__(self, remainder: lt.Remainder = lt.Remainder.Float):
+        self.remainder = remainder
+
+    def distribute(self, domain: list, sectors: list) -> list:
+        size = domain / sectors
+        rem = domain % sectors
+
+        if rem == 0:
+            return [int(size)] * sectors
+
+        size = math.ceil(size)
+        rem = domain % size
+        output_sectors = [int(size)] * (sectors - 1)
+
+        if self.remainder == lt.Remainder.Float:
+            size = domain / sectors
+            output_sectors = [size] * sectors
+        elif self.remainder == lt.Remainder.DistributeFront:
+            output_sectors.append(int(rem))
+        elif self.remainder == lt.Remainder.DistributeBack:
+            output_sectors.insert(0, int(rem))
+        else:
+            raise ValueError
+
+        return output_sectors
+
+    def map(self, domain: tuple, rang: tuple, x: Number) -> Number:
+        m = (rang[1] - rang[0]) / (domain[1] - domain[0])
+        b = rang[0] - m * domain[0]
+        return m * x + b
diff --git a/utils/tuning/libtuning/smoothing.py b/utils/tuning/libtuning/smoothing.py
new file mode 100644
index 00000000..b8a5a242
--- /dev/null
+++ b/utils/tuning/libtuning/smoothing.py
@@ -0,0 +1,24 @@ 
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
+#
+# smoothing.py - Wrapper for cv2 smoothing functions to enable duck-typing
+
+import cv2
+
+
+# @brief Wrapper for cv2 smoothing functions so that they can be duck-typed
+class Smoothing(object):
+    def __init__(self):
+        pass
+
+    def smoothing(self, src):
+        raise NotImplementedError
+
+
+class MedianBlur(Smoothing):
+    def __init__(self, ksize):
+        self.ksize = ksize
+
+    def smoothing(self, src):
+        return cv2.medianBlur(src.astype('float32'), self.ksize).astype('float64')