[{"id":31200,"web_url":"https://patchwork.libcamera.org/comment/31200/","msgid":"<172621809379.3474483.4624174744870673702@ping.linuxembedded.co.uk>","date":"2024-09-13T09:01:33","subject":"Re: [PATCH v2 1/9] ipa: libipa: Add generic Interpolator class","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"Quoting Stefan Klug (2024-09-13 08:57:19)\n> The MatrixInterpolator is great for interpolation of matrices for\n> different color temperatures. It has however one limitation - it can\n> only handle matrices. For LSC it would be great to interpolate the LSC\n> tables (or even polynomials) using the same approach. Add a generic\n> Interpolator class based on the existing MatrixInterpolator. This calss\n\ns/calss/class/\n\n> can be adapted to any other type using partial template specialization.\n> \n> Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>\n> ---\n>  src/ipa/libipa/interpolator.cpp | 156 ++++++++++++++++++++++++++++++++\n>  src/ipa/libipa/interpolator.h   | 131 +++++++++++++++++++++++++++\n>  src/ipa/libipa/meson.build      |   2 +\n>  3 files changed, 289 insertions(+)\n>  create mode 100644 src/ipa/libipa/interpolator.cpp\n>  create mode 100644 src/ipa/libipa/interpolator.h\n> \n> diff --git a/src/ipa/libipa/interpolator.cpp b/src/ipa/libipa/interpolator.cpp\n> new file mode 100644\n> index 000000000000..05a4af984f9a\n> --- /dev/null\n> +++ b/src/ipa/libipa/interpolator.cpp\n> @@ -0,0 +1,156 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>\n> + *\n> + * Helper class for interpolating objects\n> + */\n> +#include \"interpolator.h\"\n> +\n> +#include <algorithm>\n> +#include <string>\n> +\n> +#include <libcamera/base/log.h>\n> +\n> +#include \"libcamera/internal/yaml_parser.h\"\n> +\n> +#include \"interpolator.h\"\n> +\n> +/**\n> + * \\file interpolator.h\n> + * \\brief Helper class for linear interpolating a set of objects\n> + */\n> +\n> +namespace libcamera {\n> +\n> +LOG_DEFINE_CATEGORY(Interpolator)\n> +\n> +namespace ipa {\n> +\n> +/**\n> + * \\class Interpolator\n> + * \\brief Class for storing, retrieving, and interpolating objects\n> + * \\tparam T Type of objects stored in the interpolator\n> + *\n> + * The main use case is to pass a map from color temperatures to corresponding\n> + * objects (eg. matrices for color correction), and then requesting a\n> + * interpolated object for a specific color temperature. This class will\n> + * abstract away the interpolation portion.\n> + */\n> +\n> +/**\n> + * \\fn Interpolator::Interpolator()\n> + * \\brief Construct a empty interpolator\n> + */\n> +\n> +/**\n> + * \\fn Interpolator::Interpolator(const std::map<unsigned int, T> &data)\n> + * \\brief Construct a interpolator from a map of objects\n> + * \\param data Map from which to construct the interpolator\n> + */\n> +\n> +/**\n> + * \\fn Interpolator::Interpolator(std::map<unsigned int, T> &&data)\n> + * \\brief Construct a interpolator from a map of objects\n\ns/a interpolator/an interpolator/\n\n> + * \\param data Map from which to construct the interpolator\n> + */\n> +\n> +/**\n> + * \\fn int Interpolator<T>::readYaml(const libcamera::YamlObject &yaml,\n> +                                    const std::string &key_name,\n> +                                    const std::string &value_name)\n> + * \\brief Initialize an Interpolator instance from yaml\n> + * \\tparam T Type of data stored in the interpolator\n> + * \\param[in] yaml The yaml object that contains the map of unsigned integers to objects\n> + * \\param[in] key_name The name of the key in the yaml object\n> + * \\param[in] value_name The name of the value in the yaml object\n> + *\n> + * The yaml object is expected to be a list of maps. Each map has two or more\n> + * pairs: one of \\a key_name to the key value (usually color temperature), and\n> + * one or more of \\a value_name to the object. This is a bit difficult to\n> + * explain, so here is an example (in python, as it is easier to parse than\n> + * yaml):\n> + *       [\n> + *               {\n> + *                   'ct': 2860,\n> + *                   'ccm': [ 2.12089, -0.52461, -0.59629,\n> + *                           -0.85342,  2.80445, -0.95103,\n> + *                           -0.26897, -1.14788,  2.41685 ],\n> + *                   'offsets': [ 0, 0, 0 ]\n> + *               },\n> + *\n> + *               {\n> + *                   'ct': 2960,\n> + *                   'ccm': [ 2.26962, -0.54174, -0.72789,\n> + *                           -0.77008,  2.60271, -0.83262,\n> + *                           -0.26036, -1.51254,  2.77289 ],\n> + *                   'offsets': [ 0, 0, 0 ]\n> + *               },\n> + *\n> + *               {\n> + *                   'ct': 3603,\n> + *                   'ccm': [ 2.18644, -0.66148, -0.52496,\n> + *                           -0.77828,  2.69474, -0.91645,\n> + *                           -0.25239, -0.83059,  2.08298 ],\n> + *                   'offsets': [ 0, 0, 0 ]\n> + *               },\n> + *       ]\n> + *\n> + * In this case, \\a key_name would be 'ct', and \\a value_name can be either\n> + * 'ccm' or 'offsets'. This way multiple interpolators can be defined in\n> + * one set of color temperature ranges in the tuning file, and they can be\n> + * retrieved separately with the \\a value_name parameter.\n> + *\n> + * \\return Zero on success, negative error code otherwise\n> + */\n> +\n> +/**\n> + * \\fn void Interpolator<T>::setQuantization(const unsigned int q)\n> + * \\brief Set the quantization value\n> + * \\param[in] q The quantization value\n> + *\n> + * Sets the quantization value. When this is set, 'key' gets quantized to this\n> + * size, before doing the interpolation. This can help in reducing the number of\n> + * updated pushed to the hardware.\n\ns/updated/updates/\n\n\nAnd that's all I can find so ...\n\n\nReviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n\n> + *\n> + * Note that normally a threshold needs to be combined with quantization.\n> + * Otherwise a value that swings around the edge of the quantization step will\n> + * lead to constant updates.\n> + */\n> +\n> +/**\n> + * \\fn void Interpolator<T>::setData(std::map<unsigned int, T> &&data)\n> + * \\brief Set the internal map\n> + *\n> + * Overwrites the internal map using move semantics.\n> + */\n> +\n> +/**\n> + * \\fn const T& Interpolator<T>::getInterpolated(unsigned int key, unsigned int *quantizedKey = nullptr)\n> + * \\brief Retrieve a a interpolated value for the given key\n> + * \\param[in] key The unsigned integer key of the object to retrieve\n> + * \\param[out] quantizedKey If provided, the key value after quantization\n> + * \\return The object corresponding to the key. The object is cached internally,\n> + * so on successive calls with the same key (after quantization) interpolation\n> + * is not recalculated.\n> + */\n> +\n> +/**\n> + * \\fn void Interpolator<T>::interpolate(const T &a, const T &b, T &dest, double\n> + * lambda)\n> + * \\brief Interpolate between two instances of T\n> + * \\param a The first value to interpolate\n> + * \\param b The second value to interpolate\n> + * \\param dest The destination for the interpolated value\n> + * \\param lambda The interpolation factor (0..1)\n> + *\n> + * Interpolates between a and b according to lambda. It calculates dest = a *\n> + * (1.0 - lambda) + b * lambda;\n> + *\n> + * If T supports multiplication with double and addition, this function can be\n> + * used as is. For other types this function can be overwritten using partial\n> + * template specialization.\n> + */\n> +\n> +} /* namespace ipa */\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/ipa/libipa/interpolator.h b/src/ipa/libipa/interpolator.h\n> new file mode 100644\n> index 000000000000..fffce21465fe\n> --- /dev/null\n> +++ b/src/ipa/libipa/interpolator.h\n> @@ -0,0 +1,131 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>\n> + *\n> + * Helper class for interpolating maps of objects\n> + */\n> +\n> +#pragma once\n> +\n> +#include <algorithm>\n> +#include <cmath>\n> +#include <map>\n> +#include <string>\n> +#include <tuple>\n> +\n> +#include <libcamera/base/log.h>\n> +\n> +#include \"libcamera/internal/yaml_parser.h\"\n> +\n> +namespace libcamera {\n> +\n> +LOG_DECLARE_CATEGORY(Interpolator)\n> +\n> +namespace ipa {\n> +\n> +template<typename T>\n> +class Interpolator\n> +{\n> +public:\n> +       Interpolator() = default;\n> +       Interpolator(const std::map<unsigned int, T> &data)\n> +               : data_(data)\n> +       {\n> +       }\n> +       Interpolator(std::map<unsigned int, T> &&data)\n> +               : data_(std::move(data))\n> +       {\n> +       }\n> +\n> +       ~Interpolator() = default;\n> +\n> +       int readYaml(const libcamera::YamlObject &yaml,\n> +                    const std::string &key_name,\n> +                    const std::string &value_name)\n> +       {\n> +               data_.clear();\n> +               lastInterpolatedKey_.reset();\n> +\n> +               if (!yaml.isList()) {\n> +                       LOG(Interpolator, Error) << \"yaml object must be a list\";\n> +                       return -EINVAL;\n> +               }\n> +\n> +               for (const auto &value : yaml.asList()) {\n> +                       unsigned int ct = std::stoul(value[key_name].get<std::string>(\"\"));\n> +                       std::optional<T> data =\n> +                               value[value_name].get<T>();\n> +                       if (!data) {\n> +                               return -EINVAL;\n> +                       }\n> +\n> +                       data_[ct] = *data;\n> +               }\n> +\n> +               if (data_.size() < 1) {\n> +                       LOG(Interpolator, Error) << \"Need at least one element\";\n> +                       return -EINVAL;\n> +               }\n> +\n> +               return 0;\n> +       }\n> +\n> +       void setQuantization(const unsigned int q)\n> +       {\n> +               quantization_ = q;\n> +       }\n> +\n> +       void setData(std::map<unsigned int, T> &&data)\n> +       {\n> +               data_ = std::move(data);\n> +               lastInterpolatedKey_.reset();\n> +       }\n> +\n> +       const T &getInterpolated(unsigned int key, unsigned int *quantizedKey = nullptr)\n> +       {\n> +               ASSERT(data_.size() > 0);\n> +\n> +               if (quantization_ > 0)\n> +                       key = std::lround(key / static_cast<double>(quantization_)) * quantization_;\n> +\n> +               if (quantizedKey)\n> +                       *quantizedKey = key;\n> +\n> +               if (lastInterpolatedKey_.has_value() &&\n> +                   *lastInterpolatedKey_ == key)\n> +                       return lastInterpolatedValue_;\n> +\n> +               auto it = data_.lower_bound(key);\n> +\n> +               if (it == data_.begin())\n> +                       return it->second;\n> +\n> +               if (it == data_.end())\n> +                       return std::prev(it)->second;\n> +\n> +               if (it->first == key)\n> +                       return it->second;\n> +\n> +               auto it2 = std::prev(it);\n> +               double lambda = (key - it2->first) / static_cast<double>(it->first - it2->first);\n> +               interpolate(it2->second, it->second, lastInterpolatedValue_, lambda);\n> +               lastInterpolatedKey_ = key;\n> +\n> +               return lastInterpolatedValue_;\n> +       }\n> +\n> +       void interpolate(const T &a, const T &b, T &dest, double lambda)\n> +       {\n> +               dest = a * (1.0 - lambda) + b * lambda;\n> +       }\n> +\n> +private:\n> +       std::map<unsigned int, T> data_;\n> +       T lastInterpolatedValue_;\n> +       std::optional<unsigned int> lastInterpolatedKey_;\n> +       unsigned int quantization_ = 0;\n> +};\n> +\n> +} /* namespace ipa */\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build\n> index eff8ce2660f1..2c2712a7d252 100644\n> --- a/src/ipa/libipa/meson.build\n> +++ b/src/ipa/libipa/meson.build\n> @@ -7,6 +7,7 @@ libipa_headers = files([\n>      'exposure_mode_helper.h',\n>      'fc_queue.h',\n>      'histogram.h',\n> +    'interpolator.h',\n>      'matrix.h',\n>      'matrix_interpolator.h',\n>      'module.h',\n> @@ -21,6 +22,7 @@ libipa_sources = files([\n>      'exposure_mode_helper.cpp',\n>      'fc_queue.cpp',\n>      'histogram.cpp',\n> +    'interpolator.cpp',\n>      'matrix.cpp',\n>      'matrix_interpolator.cpp',\n>      'module.cpp',\n> -- \n> 2.43.0\n>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id A1EDDC324C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 13 Sep 2024 09:01:41 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 167A4634FC;\n\tFri, 13 Sep 2024 11:01:40 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id D25BC634F4\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 13 Sep 2024 11:01:37 +0200 (CEST)","from pendragon.ideasonboard.com\n\t(cpc89244-aztw30-2-0-cust6594.18-1.cable.virginm.net [86.31.185.195])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 2535C13D7;\n\tFri, 13 Sep 2024 11:00:18 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"MJBC+o0F\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1726218018;\n\tbh=c5zZuwWhQcvPCRSzzmG71MGz2eXWRIpwCiN4ZUtHfEw=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=MJBC+o0FwgCL2eZ8ePk1eZebtK6udcgXTSWG0Tx2npKywJV+fso7D3v8IutKDZUMF\n\t/pQ9QG9AMDSo+xgdQwYwzin7WAn0gfLZ2DEAPyRP/LaAFLjOUoPzm+nuPc5vEuSg27\n\tugX/7kQdDBvkKZIzP+U37Cm8ZQ4zjOIhWBrUZNP0=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<20240913075750.35115-2-stefan.klug@ideasonboard.com>","References":"<20240913075750.35115-1-stefan.klug@ideasonboard.com>\n\t<20240913075750.35115-2-stefan.klug@ideasonboard.com>","Subject":"Re: [PATCH v2 1/9] ipa: libipa: Add generic Interpolator class","From":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Cc":"Stefan Klug <stefan.klug@ideasonboard.com>","To":"Stefan Klug <stefan.klug@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","Date":"Fri, 13 Sep 2024 10:01:33 +0100","Message-ID":"<172621809379.3474483.4624174744870673702@ping.linuxembedded.co.uk>","User-Agent":"alot/0.10","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":31201,"web_url":"https://patchwork.libcamera.org/comment/31201/","msgid":"<ZuP_kPYN7LQuP9k_@pyrite.rasen.tech>","date":"2024-09-13T09:02:08","subject":"Re: [PATCH v2 1/9] ipa: libipa: Add generic Interpolator class","submitter":{"id":17,"url":"https://patchwork.libcamera.org/api/people/17/","name":"Paul Elder","email":"paul.elder@ideasonboard.com"},"content":"On Fri, Sep 13, 2024 at 09:57:19AM +0200, Stefan Klug wrote:\n> The MatrixInterpolator is great for interpolation of matrices for\n> different color temperatures. It has however one limitation - it can\n> only handle matrices. For LSC it would be great to interpolate the LSC\n> tables (or even polynomials) using the same approach. Add a generic\n> Interpolator class based on the existing MatrixInterpolator. This calss\n> can be adapted to any other type using partial template specialization.\n> \n> Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>\n\nKieran beat me to it but \"an interpolator\".\n\nOther than that,\n\nReviewed-by: Paul Elder <paul.elder@ideasonboard.com>\n\n> ---\n>  src/ipa/libipa/interpolator.cpp | 156 ++++++++++++++++++++++++++++++++\n>  src/ipa/libipa/interpolator.h   | 131 +++++++++++++++++++++++++++\n>  src/ipa/libipa/meson.build      |   2 +\n>  3 files changed, 289 insertions(+)\n>  create mode 100644 src/ipa/libipa/interpolator.cpp\n>  create mode 100644 src/ipa/libipa/interpolator.h\n> \n> diff --git a/src/ipa/libipa/interpolator.cpp b/src/ipa/libipa/interpolator.cpp\n> new file mode 100644\n> index 000000000000..05a4af984f9a\n> --- /dev/null\n> +++ b/src/ipa/libipa/interpolator.cpp\n> @@ -0,0 +1,156 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>\n> + *\n> + * Helper class for interpolating objects\n> + */\n> +#include \"interpolator.h\"\n> +\n> +#include <algorithm>\n> +#include <string>\n> +\n> +#include <libcamera/base/log.h>\n> +\n> +#include \"libcamera/internal/yaml_parser.h\"\n> +\n> +#include \"interpolator.h\"\n> +\n> +/**\n> + * \\file interpolator.h\n> + * \\brief Helper class for linear interpolating a set of objects\n> + */\n> +\n> +namespace libcamera {\n> +\n> +LOG_DEFINE_CATEGORY(Interpolator)\n> +\n> +namespace ipa {\n> +\n> +/**\n> + * \\class Interpolator\n> + * \\brief Class for storing, retrieving, and interpolating objects\n> + * \\tparam T Type of objects stored in the interpolator\n> + *\n> + * The main use case is to pass a map from color temperatures to corresponding\n> + * objects (eg. matrices for color correction), and then requesting a\n> + * interpolated object for a specific color temperature. This class will\n> + * abstract away the interpolation portion.\n> + */\n> +\n> +/**\n> + * \\fn Interpolator::Interpolator()\n> + * \\brief Construct a empty interpolator\n> + */\n> +\n> +/**\n> + * \\fn Interpolator::Interpolator(const std::map<unsigned int, T> &data)\n> + * \\brief Construct a interpolator from a map of objects\n> + * \\param data Map from which to construct the interpolator\n> + */\n> +\n> +/**\n> + * \\fn Interpolator::Interpolator(std::map<unsigned int, T> &&data)\n> + * \\brief Construct a interpolator from a map of objects\n> + * \\param data Map from which to construct the interpolator\n> + */\n> +\n> +/**\n> + * \\fn int Interpolator<T>::readYaml(const libcamera::YamlObject &yaml,\n> +\t\t                     const std::string &key_name,\n> +\t\t                     const std::string &value_name)\n> + * \\brief Initialize an Interpolator instance from yaml\n> + * \\tparam T Type of data stored in the interpolator\n> + * \\param[in] yaml The yaml object that contains the map of unsigned integers to objects\n> + * \\param[in] key_name The name of the key in the yaml object\n> + * \\param[in] value_name The name of the value in the yaml object\n> + *\n> + * The yaml object is expected to be a list of maps. Each map has two or more\n> + * pairs: one of \\a key_name to the key value (usually color temperature), and\n> + * one or more of \\a value_name to the object. This is a bit difficult to\n> + * explain, so here is an example (in python, as it is easier to parse than\n> + * yaml):\n> + *       [\n> + *               {\n> + *                   'ct': 2860,\n> + *                   'ccm': [ 2.12089, -0.52461, -0.59629,\n> + *                           -0.85342,  2.80445, -0.95103,\n> + *                           -0.26897, -1.14788,  2.41685 ],\n> + *                   'offsets': [ 0, 0, 0 ]\n> + *               },\n> + *\n> + *               {\n> + *                   'ct': 2960,\n> + *                   'ccm': [ 2.26962, -0.54174, -0.72789,\n> + *                           -0.77008,  2.60271, -0.83262,\n> + *                           -0.26036, -1.51254,  2.77289 ],\n> + *                   'offsets': [ 0, 0, 0 ]\n> + *               },\n> + *\n> + *               {\n> + *                   'ct': 3603,\n> + *                   'ccm': [ 2.18644, -0.66148, -0.52496,\n> + *                           -0.77828,  2.69474, -0.91645,\n> + *                           -0.25239, -0.83059,  2.08298 ],\n> + *                   'offsets': [ 0, 0, 0 ]\n> + *               },\n> + *       ]\n> + *\n> + * In this case, \\a key_name would be 'ct', and \\a value_name can be either\n> + * 'ccm' or 'offsets'. This way multiple interpolators can be defined in\n> + * one set of color temperature ranges in the tuning file, and they can be\n> + * retrieved separately with the \\a value_name parameter.\n> + *\n> + * \\return Zero on success, negative error code otherwise\n> + */\n> +\n> +/**\n> + * \\fn void Interpolator<T>::setQuantization(const unsigned int q)\n> + * \\brief Set the quantization value\n> + * \\param[in] q The quantization value\n> + *\n> + * Sets the quantization value. When this is set, 'key' gets quantized to this\n> + * size, before doing the interpolation. This can help in reducing the number of\n> + * updated pushed to the hardware.\n> + *\n> + * Note that normally a threshold needs to be combined with quantization.\n> + * Otherwise a value that swings around the edge of the quantization step will\n> + * lead to constant updates.\n> + */\n> +\n> +/**\n> + * \\fn void Interpolator<T>::setData(std::map<unsigned int, T> &&data)\n> + * \\brief Set the internal map\n> + *\n> + * Overwrites the internal map using move semantics.\n> + */\n> +\n> +/**\n> + * \\fn const T& Interpolator<T>::getInterpolated(unsigned int key, unsigned int *quantizedKey = nullptr)\n> + * \\brief Retrieve a a interpolated value for the given key\n> + * \\param[in] key The unsigned integer key of the object to retrieve\n> + * \\param[out] quantizedKey If provided, the key value after quantization\n> + * \\return The object corresponding to the key. The object is cached internally,\n> + * so on successive calls with the same key (after quantization) interpolation\n> + * is not recalculated.\n> + */\n> +\n> +/**\n> + * \\fn void Interpolator<T>::interpolate(const T &a, const T &b, T &dest, double\n> + * lambda)\n> + * \\brief Interpolate between two instances of T\n> + * \\param a The first value to interpolate\n> + * \\param b The second value to interpolate\n> + * \\param dest The destination for the interpolated value\n> + * \\param lambda The interpolation factor (0..1)\n> + *\n> + * Interpolates between a and b according to lambda. It calculates dest = a *\n> + * (1.0 - lambda) + b * lambda;\n> + *\n> + * If T supports multiplication with double and addition, this function can be\n> + * used as is. For other types this function can be overwritten using partial\n> + * template specialization.\n> + */\n> +\n> +} /* namespace ipa */\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/ipa/libipa/interpolator.h b/src/ipa/libipa/interpolator.h\n> new file mode 100644\n> index 000000000000..fffce21465fe\n> --- /dev/null\n> +++ b/src/ipa/libipa/interpolator.h\n> @@ -0,0 +1,131 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>\n> + *\n> + * Helper class for interpolating maps of objects\n> + */\n> +\n> +#pragma once\n> +\n> +#include <algorithm>\n> +#include <cmath>\n> +#include <map>\n> +#include <string>\n> +#include <tuple>\n> +\n> +#include <libcamera/base/log.h>\n> +\n> +#include \"libcamera/internal/yaml_parser.h\"\n> +\n> +namespace libcamera {\n> +\n> +LOG_DECLARE_CATEGORY(Interpolator)\n> +\n> +namespace ipa {\n> +\n> +template<typename T>\n> +class Interpolator\n> +{\n> +public:\n> +\tInterpolator() = default;\n> +\tInterpolator(const std::map<unsigned int, T> &data)\n> +\t\t: data_(data)\n> +\t{\n> +\t}\n> +\tInterpolator(std::map<unsigned int, T> &&data)\n> +\t\t: data_(std::move(data))\n> +\t{\n> +\t}\n> +\n> +\t~Interpolator() = default;\n> +\n> +\tint readYaml(const libcamera::YamlObject &yaml,\n> +\t\t     const std::string &key_name,\n> +\t\t     const std::string &value_name)\n> +\t{\n> +\t\tdata_.clear();\n> +\t\tlastInterpolatedKey_.reset();\n> +\n> +\t\tif (!yaml.isList()) {\n> +\t\t\tLOG(Interpolator, Error) << \"yaml object must be a list\";\n> +\t\t\treturn -EINVAL;\n> +\t\t}\n> +\n> +\t\tfor (const auto &value : yaml.asList()) {\n> +\t\t\tunsigned int ct = std::stoul(value[key_name].get<std::string>(\"\"));\n> +\t\t\tstd::optional<T> data =\n> +\t\t\t\tvalue[value_name].get<T>();\n> +\t\t\tif (!data) {\n> +\t\t\t\treturn -EINVAL;\n> +\t\t\t}\n> +\n> +\t\t\tdata_[ct] = *data;\n> +\t\t}\n> +\n> +\t\tif (data_.size() < 1) {\n> +\t\t\tLOG(Interpolator, Error) << \"Need at least one element\";\n> +\t\t\treturn -EINVAL;\n> +\t\t}\n> +\n> +\t\treturn 0;\n> +\t}\n> +\n> +\tvoid setQuantization(const unsigned int q)\n> +\t{\n> +\t\tquantization_ = q;\n> +\t}\n> +\n> +\tvoid setData(std::map<unsigned int, T> &&data)\n> +\t{\n> +\t\tdata_ = std::move(data);\n> +\t\tlastInterpolatedKey_.reset();\n> +\t}\n> +\n> +\tconst T &getInterpolated(unsigned int key, unsigned int *quantizedKey = nullptr)\n> +\t{\n> +\t\tASSERT(data_.size() > 0);\n> +\n> +\t\tif (quantization_ > 0)\n> +\t\t\tkey = std::lround(key / static_cast<double>(quantization_)) * quantization_;\n> +\n> +\t\tif (quantizedKey)\n> +\t\t\t*quantizedKey = key;\n> +\n> +\t\tif (lastInterpolatedKey_.has_value() &&\n> +\t\t    *lastInterpolatedKey_ == key)\n> +\t\t\treturn lastInterpolatedValue_;\n> +\n> +\t\tauto it = data_.lower_bound(key);\n> +\n> +\t\tif (it == data_.begin())\n> +\t\t\treturn it->second;\n> +\n> +\t\tif (it == data_.end())\n> +\t\t\treturn std::prev(it)->second;\n> +\n> +\t\tif (it->first == key)\n> +\t\t\treturn it->second;\n> +\n> +\t\tauto it2 = std::prev(it);\n> +\t\tdouble lambda = (key - it2->first) / static_cast<double>(it->first - it2->first);\n> +\t\tinterpolate(it2->second, it->second, lastInterpolatedValue_, lambda);\n> +\t\tlastInterpolatedKey_ = key;\n> +\n> +\t\treturn lastInterpolatedValue_;\n> +\t}\n> +\n> +\tvoid interpolate(const T &a, const T &b, T &dest, double lambda)\n> +\t{\n> +\t\tdest = a * (1.0 - lambda) + b * lambda;\n> +\t}\n> +\n> +private:\n> +\tstd::map<unsigned int, T> data_;\n> +\tT lastInterpolatedValue_;\n> +\tstd::optional<unsigned int> lastInterpolatedKey_;\n> +\tunsigned int quantization_ = 0;\n> +};\n> +\n> +} /* namespace ipa */\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build\n> index eff8ce2660f1..2c2712a7d252 100644\n> --- a/src/ipa/libipa/meson.build\n> +++ b/src/ipa/libipa/meson.build\n> @@ -7,6 +7,7 @@ libipa_headers = files([\n>      'exposure_mode_helper.h',\n>      'fc_queue.h',\n>      'histogram.h',\n> +    'interpolator.h',\n>      'matrix.h',\n>      'matrix_interpolator.h',\n>      'module.h',\n> @@ -21,6 +22,7 @@ libipa_sources = files([\n>      'exposure_mode_helper.cpp',\n>      'fc_queue.cpp',\n>      'histogram.cpp',\n> +    'interpolator.cpp',\n>      'matrix.cpp',\n>      'matrix_interpolator.cpp',\n>      'module.cpp',\n> -- \n> 2.43.0\n>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id C3DBCC324C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 13 Sep 2024 09:02:14 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 59DD6634F5;\n\tFri, 13 Sep 2024 11:02:14 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id B16D0634F5\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 13 Sep 2024 11:02:12 +0200 (CEST)","from pyrite.rasen.tech (213-229-8-243.static.upcbusiness.at\n\t[213.229.8.243])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id D30C8E0D;\n\tFri, 13 Sep 2024 11:00:53 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"pMuZ4uTx\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1726218054;\n\tbh=PWCDxo60PPBz0tGgR+Jp/4ic5gPBQZxg3u0WTuY1tQc=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=pMuZ4uTxekqkyFxr52DxBq2+TJGXnxxxv49eqq7lDmNKNIUnsac4abRHZTEsCObTI\n\tG99xsNSQvb7mLSa3njFEWbQU619awf8nI3StVYbDjHU5MkaCU2TCWvCtLXvNAx4SUX\n\tGiM0SLd57vuDIByuGEEVlJRH0kiQHdG9df1tmGv0=","Date":"Fri, 13 Sep 2024 11:02:08 +0200","From":"Paul Elder <paul.elder@ideasonboard.com>","To":"Stefan Klug <stefan.klug@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Subject":"Re: [PATCH v2 1/9] ipa: libipa: Add generic Interpolator class","Message-ID":"<ZuP_kPYN7LQuP9k_@pyrite.rasen.tech>","References":"<20240913075750.35115-1-stefan.klug@ideasonboard.com>\n\t<20240913075750.35115-2-stefan.klug@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=us-ascii","Content-Disposition":"inline","In-Reply-To":"<20240913075750.35115-2-stefan.klug@ideasonboard.com>","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]