From patchwork Mon Aug 26 15:21:59 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Klug X-Patchwork-Id: 21011 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 5F052C323E for ; Mon, 26 Aug 2024 15:22:35 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 07DEA63417; Mon, 26 Aug 2024 17:22:35 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="V7WPcPLE"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2CC5B633D3 for ; Mon, 26 Aug 2024 17:22:32 +0200 (CEST) Received: from ideasonboard.com (unknown [IPv6:2a00:6020:448c:6c00:58b7:f3d:c9d4:defa]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id DB207480; Mon, 26 Aug 2024 17:21:25 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1724685686; bh=Qh1aXpOeDQjgR7o+JU8yS86Fx1ZfxJemb36DeYS2v8A=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=V7WPcPLEdqCbquQoegjRgwhNjA7JY+jgNN3NxQwqADVOp00wlJF6oGD4czcRDOrCc Dyja5GHXxxy843PyFApg0+/fIbS5zQ3GxSN/bwiIj73rWo37iMdqVzMjtTSbz+2wor en+uJCyUb+pvshedaOLY2ZjPUoQVXwr+gV/sAok8= From: Stefan Klug To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug Subject: [PATCH v1 1/8] ipa: libipa: Add generic Interpolator class Date: Mon, 26 Aug 2024 17:21:59 +0200 Message-ID: <20240826152224.362773-2-stefan.klug@ideasonboard.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20240826152224.362773-1-stefan.klug@ideasonboard.com> References: <20240826152224.362773-1-stefan.klug@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" The MatrixInterpolator is great for interpolation of matrices for different color temperatures. It has however one limitation - it can only handle matrices. For LSC it would be great to interpolate the LSC tables (or even polynomials) using the same approach. Add a generic Interpolator class based on the existing MatrixInterpolator. This calss can be adapted to any other type using partial template specialization. Signed-off-by: Stefan Klug --- src/ipa/libipa/interpolator.cpp | 139 ++++++++++++++++++++++++++++++++ src/ipa/libipa/interpolator.h | 139 ++++++++++++++++++++++++++++++++ src/ipa/libipa/meson.build | 2 + 3 files changed, 280 insertions(+) create mode 100644 src/ipa/libipa/interpolator.cpp create mode 100644 src/ipa/libipa/interpolator.h diff --git a/src/ipa/libipa/interpolator.cpp b/src/ipa/libipa/interpolator.cpp new file mode 100644 index 000000000000..98462d1509da --- /dev/null +++ b/src/ipa/libipa/interpolator.cpp @@ -0,0 +1,139 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Paul Elder + * + * Helper class for interpolating maps of matrices + */ +#include "interpolator.h" + +#include +#include + +#include + +#include "libcamera/internal/yaml_parser.h" + +#include "interpolator.h" + +/** + * \file interpolator.h + * \brief Helper class for linear interpolating a set of objects + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(Interpolator) + +namespace ipa { + +/** + * \class Interpolator + * \brief Class for storing, retrieving, and interpolating objects + * \tparam T Type of objects stored in the interpolator + * + * The main use case is to pass a map from color temperatures to corresponding + * objects (eg. matrices for color correction), and then requesting a + * interpolated object for a specific color temperature. This class will + * abstract away the interpolation portion. + */ + +/** + * \fn Interpolator::Interpolator() + * \brief Construct a empty interpolator + */ + +/** + * \fn Interpolator::Interpolator(const std::map &data) + * \brief Construct a interpolator from a map of objects + * \param data Map from which to construct the interpolator + */ + +/** + * \fn Interpolator::Interpolator(const std::map &&data) + * \brief Construct a interpolator from a map of objects + * \param data Map from which to construct the interpolator + */ + +/** + * \fn int Interpolator::readYaml(const libcamera::YamlObject &yaml, + const std::string &key_name, + const std::string &value_name) + * \brief Initialize an Interpolator instance from yaml + * \tparam T Type of data stored in the interpolator + * \param[in] yaml The yaml object that contains the map of unsigned integers to objects + * \param[in] key_name The name of the key in the yaml object + * \param[in] value_name The name of the matrix in the yaml object + * + * The yaml object is expected to be a list of maps. Each map has two or more + * pairs: one of \a key_name to the key value (usually color temperature), and + * one or more of \a value_name to the object. This is a bit difficult to + * explain, so here is an example (in python, as it is easier to parse than + * yaml): + * [ + * { + * 'ct': 2860, + * 'ccm': [ 2.12089, -0.52461, -0.59629, + * -0.85342, 2.80445, -0.95103, + * -0.26897, -1.14788, 2.41685 ], + * 'offsets': [ 0, 0, 0 ] + * }, + * + * { + * 'ct': 2960, + * 'ccm': [ 2.26962, -0.54174, -0.72789, + * -0.77008, 2.60271, -0.83262, + * -0.26036, -1.51254, 2.77289 ], + * 'offsets': [ 0, 0, 0 ] + * }, + * + * { + * 'ct': 3603, + * 'ccm': [ 2.18644, -0.66148, -0.52496, + * -0.77828, 2.69474, -0.91645, + * -0.25239, -0.83059, 2.08298 ], + * 'offsets': [ 0, 0, 0 ] + * }, + * ] + * + * In this case, \a key_name would be 'ct', and \a value_name can be either + * 'ccm' or 'offsets'. This way multiple interpolators can be defined in + * one set of color temperature ranges in the tuning file, and they can be + * retrieved separately with the \a value_name parameter. + * + * \return Zero on success, negative error code otherwise + */ + +/** + * \fn void setQuantization(const unsigned int q) + * \brief Set the quantization value + * \param[in] q The quantization value + * + * Sets the quantization value. When this is set key gets quantized to this size, + * before doing the interpolation. This can help in reducing the number of + * updated pushed to the hardware. + * + * Note that normally a threshold needs to be combined with quantization. + * Otherwise a value that swings around the edge of the quantization step will + * lead to constant updates. + */ + +/** + * \fn void setData(std::map &&data) + * \brief Set the internal map + * + * Overwrites the internal map using move semantics. + */ + +/** + * \fn const T &getInterpolated(unsigned int key, unsigned int *quantizedKey = nullptr) + * \brief Retrieve a a interpolated value for the given key + * \param[in] key The unsigned integer key of the object to retrieve + * \param[out] quantizedKey If provided, the key value after quantization + * \return The object corresponding to the key. The object is cached internally, + * so on successive calls with the same key (after quantization) interpolation + * is not recalculated. + */ + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/interpolator.h b/src/ipa/libipa/interpolator.h new file mode 100644 index 000000000000..b0241c35bbab --- /dev/null +++ b/src/ipa/libipa/interpolator.h @@ -0,0 +1,139 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Paul Elder + * + * Helper class for interpolating maps of objects + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include "libcamera/internal/yaml_parser.h" + +namespace libcamera { + +LOG_DECLARE_CATEGORY(Interpolator) + +namespace ipa { + +template +class Interpolator +{ +public: + Interpolator() + { + quantization_ = 0; + } + + Interpolator(const std::map &data) + { + quantization_ = 0; + data_ = data; + } + + Interpolator(std::map &&data) + { + quantization_ = 0; + data_ = std::move(data); + } + + ~Interpolator() {} + + int readYaml(const libcamera::YamlObject &yaml, + const std::string &key_name, + const std::string &value_name) + { + data_.clear(); + + if (!yaml.isList()) { + LOG(Interpolator, Error) << "yaml object must be a list"; + return -EINVAL; + } + + for (const auto &value : yaml.asList()) { + unsigned int ct = std::stoul(value[key_name].get("")); + std::optional data = + value[value_name].get(); + if (!data) { + return -EINVAL; + } + + data_[ct] = *data; + } + + if (data_.size() < 1) { + LOG(Interpolator, Error) << "Need at least one element"; + return -EINVAL; + } + + return 0; + } + + void setQuantization(const unsigned int q) + { + quantization_ = q; + } + + void setData(std::map &&data) + { + data_ = std::move(data); + } + + const T &getInterpolated(unsigned int key, unsigned int *quantizedKey = nullptr) + { + ASSERT(data_.size() > 0); + + if (quantization_ > 0) + key = std::lround(key / static_cast(quantization_)) * quantization_; + + if (quantizedKey) + *quantizedKey = key; + + if (lastInterpolatedKey_.has_value() && + *lastInterpolatedKey_ == key) + return lastInterpolatedValue_; + + if (data_.size() == 1 || + key <= data_.begin()->first) + return data_.begin()->second; + + if (key >= data_.rbegin()->first) + return data_.rbegin()->second; + + if (data_.find(key) != data_.end()) + return data_[key]; + + /* The above four guarantee that this will succeed */ + auto iter = data_.upper_bound(key); + unsigned int ctUpper = iter->first; + unsigned int ctLower = (--iter)->first; + + double lambda = (key - ctLower) / static_cast(ctUpper - ctLower); + interpolate(data_[ctLower], data_[ctUpper], lastInterpolatedValue_, lambda); + lastInterpolatedKey_ = key; + + return lastInterpolatedValue_; + } + + void interpolate(const T &a, const T &b, T &dest, double lambda) + { + dest = a * (1.0 - lambda) + b * lambda; + } + +private: + std::map data_; + T lastInterpolatedValue_; + std::optional lastInterpolatedKey_; + unsigned int quantization_; +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index eff8ce2660f1..2c2712a7d252 100644 --- a/src/ipa/libipa/meson.build +++ b/src/ipa/libipa/meson.build @@ -7,6 +7,7 @@ libipa_headers = files([ 'exposure_mode_helper.h', 'fc_queue.h', 'histogram.h', + 'interpolator.h', 'matrix.h', 'matrix_interpolator.h', 'module.h', @@ -21,6 +22,7 @@ libipa_sources = files([ 'exposure_mode_helper.cpp', 'fc_queue.cpp', 'histogram.cpp', + 'interpolator.cpp', 'matrix.cpp', 'matrix_interpolator.cpp', 'module.cpp', From patchwork Mon Aug 26 15:22:00 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Klug X-Patchwork-Id: 21012 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 00B8FC324C for ; Mon, 26 Aug 2024 15:22:37 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 43CB5633CF; Mon, 26 Aug 2024 17:22:37 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="gMpvL65Z"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 479CB633D3 for ; Mon, 26 Aug 2024 17:22:34 +0200 (CEST) Received: from ideasonboard.com (unknown [IPv6:2a00:6020:448c:6c00:58b7:f3d:c9d4:defa]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 04092741; Mon, 26 Aug 2024 17:21:27 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1724685688; bh=gGbKCUnL0zdjVvdtW3FyYzk6c9CQehRnjgwIVoMvyuM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=gMpvL65Z/pjq3XXSNHYQIfhteZ9vP9IWlPvZ60UMFoIT93ToLGoUJd/4xMjmzF4xM nvXO85Ff8Nrj3a+B9KKXa3KMvhzifF2vZmbId0dVWjkLmI/LIS+mrf5v9/pu26UTe2 VThqP8r1SjzwwjIe94wvqXvvAiKPHpyP4o0TVDhQ= From: Stefan Klug To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug Subject: [PATCH v1 2/8] ipa: rkisp1: Use generic Interpolator class Date: Mon, 26 Aug 2024 17:22:00 +0200 Message-ID: <20240826152224.362773-3-stefan.klug@ideasonboard.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20240826152224.362773-1-stefan.klug@ideasonboard.com> References: <20240826152224.362773-1-stefan.klug@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Replace all occurrences of the MatrixInterpolator with the generic one. Signed-off-by: Stefan Klug --- src/ipa/rkisp1/algorithms/awb.cpp | 4 ++-- src/ipa/rkisp1/algorithms/awb.h | 5 +++-- src/ipa/rkisp1/algorithms/ccm.cpp | 18 ++++++------------ src/ipa/rkisp1/algorithms/ccm.h | 6 +++--- 4 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp index d482eda5b541..9b181c163e0b 100644 --- a/src/ipa/rkisp1/algorithms/awb.cpp +++ b/src/ipa/rkisp1/algorithms/awb.cpp @@ -53,7 +53,7 @@ int Awb::init(IPAContext &context, const YamlObject &tuningData) kMaxColourTemperature, kDefaultColourTemperature); - MatrixInterpolator gains; + Interpolator> gains; int ret = gains.readYaml(tuningData["gains"], "ct", "gains"); if (ret < 0) LOG(RkISP1Awb, Warning) @@ -124,7 +124,7 @@ void Awb::queueRequest(IPAContext &context, const auto &colourTemperature = controls.get(controls::ColourTemperature); if (colourTemperature && !awb.autoEnabled && gains_ && !colourGains) { - Matrix gains = gains_->get(*colourTemperature); + Matrix gains = gains_->getInterpolated(*colourTemperature); awb.gains.manual.red = gains[0][0]; awb.gains.manual.blue = gains[1][0]; diff --git a/src/ipa/rkisp1/algorithms/awb.h b/src/ipa/rkisp1/algorithms/awb.h index a010e6d1cb3c..9e44f1c278ab 100644 --- a/src/ipa/rkisp1/algorithms/awb.h +++ b/src/ipa/rkisp1/algorithms/awb.h @@ -9,7 +9,8 @@ #include -#include "libipa/matrix_interpolator.h" +#include "libipa/interpolator.h" +#include "libipa/matrix.h" #include "algorithm.h" @@ -39,7 +40,7 @@ public: private: uint32_t estimateCCT(double red, double green, double blue); - std::optional> gains_; + std::optional>> gains_; bool rgbMode_; }; diff --git a/src/ipa/rkisp1/algorithms/ccm.cpp b/src/ipa/rkisp1/algorithms/ccm.cpp index fe7246f8b185..5a29d20e6cdb 100644 --- a/src/ipa/rkisp1/algorithms/ccm.cpp +++ b/src/ipa/rkisp1/algorithms/ccm.cpp @@ -23,7 +23,7 @@ #include "libcamera/internal/yaml_parser.h" #include "../utils.h" -#include "libipa/matrix_interpolator.h" +#include "libipa/interpolator.h" /** * \file ccm.h @@ -50,7 +50,7 @@ int Ccm::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData LOG(RkISP1Ccm, Warning) << "Failed to parse 'ccm' " << "parameter from tuning file; falling back to unit matrix"; - ccm_.reset(); + ccm_.setData({ { 0, Matrix::identity() } }); } ret = offsets_.readYaml(tuningData["ccms"], "ct", "offsets"); @@ -58,14 +58,8 @@ int Ccm::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData LOG(RkISP1Ccm, Warning) << "Failed to parse 'offsets' " << "parameter from tuning file; falling back to zero offsets"; - /* - * MatrixInterpolator::reset() resets to identity matrices - * while here we need zero matrices so we need to construct it - * ourselves. - */ - Matrix m({ 0, 0, 0 }); - std::map> matrices = { { 0, m } }; - offsets_ = MatrixInterpolator(matrices); + + offsets_.setData({ { 0, Matrix({ 0, 0, 0 }) } }); } return 0; @@ -117,8 +111,8 @@ void Ccm::prepare(IPAContext &context, const uint32_t frame, } ct_ = ct; - Matrix ccm = ccm_.get(ct); - Matrix offsets = offsets_.get(ct); + Matrix ccm = ccm_.getInterpolated(ct); + Matrix offsets = offsets_.getInterpolated(ct); context.activeState.ccm.ccm = ccm; frameContext.ccm.ccm = ccm; diff --git a/src/ipa/rkisp1/algorithms/ccm.h b/src/ipa/rkisp1/algorithms/ccm.h index 30cb882180cc..4efe9c00c3c9 100644 --- a/src/ipa/rkisp1/algorithms/ccm.h +++ b/src/ipa/rkisp1/algorithms/ccm.h @@ -9,8 +9,8 @@ #include +#include "libipa/interpolator.h" #include "libipa/matrix.h" -#include "libipa/matrix_interpolator.h" #include "algorithm.h" @@ -40,8 +40,8 @@ private: const Matrix &offsets); unsigned int ct_; - MatrixInterpolator ccm_; - MatrixInterpolator offsets_; + Interpolator> ccm_; + Interpolator> offsets_; }; } /* namespace ipa::rkisp1::algorithms */ From patchwork Mon Aug 26 15:22:01 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Klug X-Patchwork-Id: 21013 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 47574C323E for ; Mon, 26 Aug 2024 15:22:41 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A97506341B; Mon, 26 Aug 2024 17:22:40 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="uruhscea"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id B10176340B for ; Mon, 26 Aug 2024 17:22:37 +0200 (CEST) Received: from ideasonboard.com (unknown [IPv6:2a00:6020:448c:6c00:58b7:f3d:c9d4:defa]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 6C225741; Mon, 26 Aug 2024 17:21:31 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1724685691; bh=0OaQ5TWDi2rliaoRNih3aasVpf89ABAH7O6visGqhrc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=uruhsceai7oOQ0fLS3aX+nSxBaCXNbbr3pxWrnaBCYhcvnHoo7sjwACvq4MD3buCw yZPiFI/5lqzsLVojVJtao0hYLDYCaaBkgQdkg3WHk47disl4aQXrx5UxQFHrKlB5mi LjOWs2nrm4eknGTEz2VPbwfEYidzBXPmdkX2HKy8= From: Stefan Klug To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug Subject: [PATCH v1 3/8] ipa: rkisp1: Remove MatrixInterpolator Date: Mon, 26 Aug 2024 17:22:01 +0200 Message-ID: <20240826152224.362773-4-stefan.klug@ideasonboard.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20240826152224.362773-1-stefan.klug@ideasonboard.com> References: <20240826152224.362773-1-stefan.klug@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" The MatrixInterpolator is no longer used. Remove it. Signed-off-by: Stefan Klug --- src/ipa/libipa/matrix_interpolator.cpp | 110 ---------------------- src/ipa/libipa/matrix_interpolator.h | 122 ------------------------- src/ipa/libipa/meson.build | 2 - 3 files changed, 234 deletions(-) delete mode 100644 src/ipa/libipa/matrix_interpolator.cpp delete mode 100644 src/ipa/libipa/matrix_interpolator.h diff --git a/src/ipa/libipa/matrix_interpolator.cpp b/src/ipa/libipa/matrix_interpolator.cpp deleted file mode 100644 index 04ca177f72c6..000000000000 --- a/src/ipa/libipa/matrix_interpolator.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Copyright (C) 2024, Paul Elder - * - * Helper class for interpolating maps of matrices - */ -#include "matrix_interpolator.h" - -#include -#include - -#include - -#include "libcamera/internal/yaml_parser.h" - -#include "matrix.h" - -/** - * \file matrix_interpolator.h - * \brief Helper class for interpolating maps of matrices - */ - -namespace libcamera { - -LOG_DEFINE_CATEGORY(MatrixInterpolator) - -namespace ipa { - -/** - * \class MatrixInterpolator - * \brief Class for storing, retrieving, and interpolating matrices - * \tparam T Type of numerical values to be stored in the matrices - * \tparam R Number of rows in the matrices - * \tparam C Number of columns in the matrices - * - * The main use case is to pass a map from color temperatures to corresponding - * matrices (eg. color correction), and then requesting a matrix for a specific - * color temperature. This class will abstract away the interpolation portion. - */ - -/** - * \fn MatrixInterpolator::MatrixInterpolator(const std::map> &matrices) - * \brief Construct a matrix interpolator from a map of matrices - * \param matrices Map from which to construct the matrix interpolator - */ - -/** - * \fn MatrixInterpolator::reset() - * \brief Reset the matrix interpolator content to a single identity matrix - */ - -/** - * \fn int MatrixInterpolator::readYaml() - * \brief Initialize an MatrixInterpolator instance from yaml - * \tparam T Type of data stored in the matrices - * \tparam R Number of rows of the matrices - * \tparam C Number of columns of the matrices - * \param[in] yaml The yaml object that contains the map of unsigned integers to matrices - * \param[in] key_name The name of the key in the yaml object - * \param[in] matrix_name The name of the matrix in the yaml object - * - * The yaml object is expected to be a list of maps. Each map has two or more - * pairs: one of \a key_name to the key value (usually color temperature), and - * one or more of \a matrix_name to the matrix. This is a bit difficult to - * explain, so here is an example (in python, as it is easier to parse than - * yaml): - * [ - * { - * 'ct': 2860, - * 'ccm': [ 2.12089, -0.52461, -0.59629, - * -0.85342, 2.80445, -0.95103, - * -0.26897, -1.14788, 2.41685 ], - * 'offsets': [ 0, 0, 0 ] - * }, - * - * { - * 'ct': 2960, - * 'ccm': [ 2.26962, -0.54174, -0.72789, - * -0.77008, 2.60271, -0.83262, - * -0.26036, -1.51254, 2.77289 ], - * 'offsets': [ 0, 0, 0 ] - * }, - * - * { - * 'ct': 3603, - * 'ccm': [ 2.18644, -0.66148, -0.52496, - * -0.77828, 2.69474, -0.91645, - * -0.25239, -0.83059, 2.08298 ], - * 'offsets': [ 0, 0, 0 ] - * }, - * ] - * - * In this case, \a key_name would be 'ct', and \a matrix_name can be either - * 'ccm' or 'offsets'. This way multiple matrix interpolators can be defined in - * one set of color temperature ranges in the tuning file, and they can be - * retrieved separately with the \a matrix_name parameter. - * - * \return Zero on success, negative error code otherwise - */ - -/** - * \fn Matrix MatrixInterpolator::get(unsigned int key) - * \brief Retrieve a matrix from the list of matrices, interpolating if necessary - * \param[in] key The unsigned integer key of the matrix to retrieve - * \return The matrix corresponding to the color temperature - */ - -} /* namespace ipa */ - -} /* namespace libcamera */ diff --git a/src/ipa/libipa/matrix_interpolator.h b/src/ipa/libipa/matrix_interpolator.h deleted file mode 100644 index 087c4fd14e4f..000000000000 --- a/src/ipa/libipa/matrix_interpolator.h +++ /dev/null @@ -1,122 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Copyright (C) 2024, Paul Elder - * - * Helper class for interpolating maps of matrices - */ - -#pragma once - -#include -#include -#include -#include - -#include - -#include "libcamera/internal/yaml_parser.h" - -#include "matrix.h" - -namespace libcamera { - -LOG_DECLARE_CATEGORY(MatrixInterpolator) - -namespace ipa { - -#ifndef __DOXYGEN__ -template> * = nullptr> -#else -template -#endif /* __DOXYGEN__ */ -class MatrixInterpolator -{ -public: - MatrixInterpolator() - { - reset(); - } - - MatrixInterpolator(const std::map> &matrices) - { - for (const auto &pair : matrices) - matrices_[pair.first] = pair.second; - } - - ~MatrixInterpolator() {} - - void reset() - { - matrices_.clear(); - matrices_[0] = Matrix::identity(); - } - - int readYaml(const libcamera::YamlObject &yaml, - const std::string &key_name, - const std::string &matrix_name) - { - matrices_.clear(); - - if (!yaml.isList()) { - LOG(MatrixInterpolator, Error) << "yaml object must be a list"; - return -EINVAL; - } - - for (const auto &value : yaml.asList()) { - unsigned int ct = std::stoul(value[key_name].get("")); - std::optional> matrix = - value[matrix_name].get>(); - if (!matrix) { - LOG(MatrixInterpolator, Error) << "Failed to read matrix"; - return -EINVAL; - } - - matrices_[ct] = *matrix; - - LOG(MatrixInterpolator, Debug) - << "Read matrix '" << matrix_name << "' for key '" - << key_name << "' " << ct << ": " - << matrices_[ct].toString(); - } - - if (matrices_.size() < 1) { - LOG(MatrixInterpolator, Error) << "Need at least one matrix"; - return -EINVAL; - } - - return 0; - } - - Matrix get(unsigned int ct) - { - ASSERT(matrices_.size() > 0); - - if (matrices_.size() == 1 || - ct <= matrices_.begin()->first) - return matrices_.begin()->second; - - if (ct >= matrices_.rbegin()->first) - return matrices_.rbegin()->second; - - if (matrices_.find(ct) != matrices_.end()) - return matrices_[ct]; - - /* The above four guarantee that this will succeed */ - auto iter = matrices_.upper_bound(ct); - unsigned int ctUpper = iter->first; - unsigned int ctLower = (--iter)->first; - - double lambda = (ct - ctLower) / static_cast(ctUpper - ctLower); - Matrix ret = - lambda * matrices_[ctUpper] + (1.0 - lambda) * matrices_[ctLower]; - return ret; - } - -private: - std::map> matrices_; -}; - -} /* namespace ipa */ - -} /* namespace libcamera */ diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index 2c2712a7d252..3740178b2909 100644 --- a/src/ipa/libipa/meson.build +++ b/src/ipa/libipa/meson.build @@ -9,7 +9,6 @@ libipa_headers = files([ 'histogram.h', 'interpolator.h', 'matrix.h', - 'matrix_interpolator.h', 'module.h', 'pwl.h', 'vector.h', @@ -24,7 +23,6 @@ libipa_sources = files([ 'histogram.cpp', 'interpolator.cpp', 'matrix.cpp', - 'matrix_interpolator.cpp', 'module.cpp', 'pwl.cpp', 'vector.cpp', From patchwork Mon Aug 26 15:22:02 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Klug X-Patchwork-Id: 21014 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 5939BC324C for ; Mon, 26 Aug 2024 15:22:43 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E96106341D; Mon, 26 Aug 2024 17:22:42 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="rh0mq6hv"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 594DA633CF for ; Mon, 26 Aug 2024 17:22:40 +0200 (CEST) Received: from ideasonboard.com (unknown [IPv6:2a00:6020:448c:6c00:58b7:f3d:c9d4:defa]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 1B3C1741; Mon, 26 Aug 2024 17:21:34 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1724685694; bh=lHhfGRlfi0eYNmaaoLeXylpe9MOsrkRWUwyH1mYLFas=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=rh0mq6hvwnh43qZbGdJ31k8dVSN+i9+B/xXcuO3j4JEx8cUnNWxD7Q16U4elvixkM wxbhB7YuRoKTmwhyZq9uVl6b03E21bxQd26OHOSLglmOBT2O4GIe78bhO8hYIIB3M1 ZCllmm8O2mbPaeSVcPt3LnF3x7k8LpJg9K2Qa54M= From: Stefan Klug To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug Subject: [PATCH v1 4/8] ipa: rkisp1: Use interpolator in lsc Date: Mon, 26 Aug 2024 17:22:02 +0200 Message-ID: <20240826152224.362773-5-stefan.klug@ideasonboard.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20240826152224.362773-1-stefan.klug@ideasonboard.com> References: <20240826152224.362773-1-stefan.klug@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Now, that the generic interpolator is available, use it to do the interpolation of the lens shading tables. This makes the algorithm easier to read and remove some duplicate code. Signed-off-by: Stefan Klug --- src/ipa/rkisp1/algorithms/lsc.cpp | 163 +++++++++--------------------- src/ipa/rkisp1/algorithms/lsc.h | 13 +-- 2 files changed, 57 insertions(+), 119 deletions(-) diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index 161183fca352..7e0e65b22185 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -24,6 +24,34 @@ namespace libcamera { +namespace ipa { + +template +void interpolateVector(const std::vector &a, const std::vector &b, + std::vector &dest, double lambda) +{ + assert(a.size() == b.size()); + dest.resize(a.size()); + for (size_t i = 0; i < a.size(); i++) { + dest[i] = a[i] * (1.0 - lambda) + b[i] * lambda; + } +} + +template<> +void Interpolator:: + interpolate(const rkisp1::algorithms::LensShadingCorrection::Components &a, + const rkisp1::algorithms::LensShadingCorrection::Components &b, + rkisp1::algorithms::LensShadingCorrection::Components &dest, + double lambda) +{ + interpolateVector(a.r, b.r, dest.r, lambda); + interpolateVector(a.gr, b.gr, dest.gr, lambda); + interpolateVector(a.gb, b.gb, dest.gb, lambda); + interpolateVector(a.b, b.b, dest.b, lambda); +} + +} // namespace ipa + namespace ipa::rkisp1::algorithms { /** @@ -40,6 +68,8 @@ namespace ipa::rkisp1::algorithms { LOG_DEFINE_CATEGORY(RkISP1Lsc) +constexpr int kColourTemperatureChangeThreshhold = 10; + static std::vector parseSizes(const YamlObject &tuningData, const char *prop) { @@ -90,8 +120,9 @@ static std::vector parseTable(const YamlObject &tuningData, } LensShadingCorrection::LensShadingCorrection() - : lastCt_({ 0, 0 }) + : lastAppliedCt_(0), lastAppliedQuantizedCt_(0) { + sets_.setQuantization(kColourTemperatureChangeThreshhold); } /** @@ -115,17 +146,18 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, } const auto &sets = yamlSets.asList(); + std::map lscData; for (const auto &yamlSet : sets) { uint32_t ct = yamlSet["ct"].get(0); - if (sets_.count(ct)) { + if (lscData.count(ct)) { LOG(RkISP1Lsc, Error) << "Multiple sets found for color temperature " << ct; return -EINVAL; } - Components &set = sets_[ct]; + Components &set = lscData[ct]; set.ct = ct; set.r = parseTable(yamlSet, "r"); @@ -142,11 +174,13 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, } } - if (sets_.empty()) { + if (lscData.empty()) { LOG(RkISP1Lsc, Error) << "Failed to load any sets"; return -EINVAL; } + sets_.setData(std::move(lscData)); + return 0; } @@ -208,131 +242,34 @@ void LensShadingCorrection::copyTable(rkisp1_cif_isp_lsc_config &config, std::copy(set.b.begin(), set.b.end(), &config.b_data_tbl[0][0]); } -/* - * Interpolate LSC parameters based on color temperature value. - */ -void LensShadingCorrection::interpolateTable(rkisp1_cif_isp_lsc_config &config, - const Components &set0, - const Components &set1, - const uint32_t ct) -{ - double coeff0 = (set1.ct - ct) / static_cast(set1.ct - set0.ct); - double coeff1 = (ct - set0.ct) / static_cast(set1.ct - set0.ct); - - for (unsigned int i = 0; i < RKISP1_CIF_ISP_LSC_SAMPLES_MAX; ++i) { - for (unsigned int j = 0; j < RKISP1_CIF_ISP_LSC_SAMPLES_MAX; ++j) { - unsigned int sample = i * RKISP1_CIF_ISP_LSC_SAMPLES_MAX + j; - - config.r_data_tbl[i][j] = - set0.r[sample] * coeff0 + - set1.r[sample] * coeff1; - - config.gr_data_tbl[i][j] = - set0.gr[sample] * coeff0 + - set1.gr[sample] * coeff1; - - config.gb_data_tbl[i][j] = - set0.gb[sample] * coeff0 + - set1.gb[sample] * coeff1; - - config.b_data_tbl[i][j] = - set0.b[sample] * coeff0 + - set1.b[sample] * coeff1; - } - } -} - /** * \copydoc libcamera::ipa::Algorithm::prepare */ void LensShadingCorrection::prepare(IPAContext &context, - const uint32_t frame, + [[maybe_unused]] const uint32_t frame, [[maybe_unused]] IPAFrameContext &frameContext, rkisp1_params_cfg *params) { struct rkisp1_cif_isp_lsc_config &config = params->others.lsc_config; - /* - * If there is only one set, the configuration has already been done - * for first frame. - */ - if (sets_.size() == 1 && frame > 0) - return; - - /* - * If there is only one set, pick it. We can ignore lastCt_, as it will - * never be relevant. - */ - if (sets_.size() == 1) { - setParameters(params); - copyTable(config, sets_.cbegin()->second); - return; - } - uint32_t ct = context.activeState.awb.temperatureK; - ct = std::clamp(ct, sets_.cbegin()->first, sets_.crbegin()->first); - - /* - * If the original is the same, then it means the same adjustment would - * be made. If the adjusted is the same, then it means that it's the - * same as what was actually applied. Thus in these cases we can skip - * reprogramming the LSC. - * - * original == adjusted can only happen if an interpolation - * happened, or if original has an exact entry in sets_. This means - * that if original != adjusted, then original was adjusted to - * the nearest available entry in sets_, resulting in adjusted. - * Clearly, any ct value that is in between original and adjusted - * will be adjusted to the same adjusted value, so we can skip - * reprogramming the LSC table. - * - * We also skip updating the original value, as the last one had a - * larger bound and thus a larger range of ct values that will be - * adjusted to the same adjusted. - */ - if ((lastCt_.original <= ct && ct <= lastCt_.adjusted) || - (lastCt_.adjusted <= ct && ct <= lastCt_.original)) + if (std::abs(static_cast(ct) - static_cast(lastAppliedCt_)) < + kColourTemperatureChangeThreshhold) + return; + unsigned int quantizedCt; + const Components &set = sets_.getInterpolated(ct, &quantizedCt); + if (lastAppliedQuantizedCt_ == quantizedCt) return; setParameters(params); + copyTable(config, set); - /* - * The color temperature matches exactly one of the available LSC tables. - */ - if (sets_.count(ct)) { - copyTable(config, sets_[ct]); - lastCt_ = { ct, ct }; - return; - } + lastAppliedCt_ = ct; + lastAppliedQuantizedCt_ = quantizedCt; - /* No shortcuts left; we need to round or interpolate */ - auto iter = sets_.upper_bound(ct); - const Components &set1 = iter->second; - const Components &set0 = (--iter)->second; - uint32_t ct0 = set0.ct; - uint32_t ct1 = set1.ct; - uint32_t diff0 = ct - ct0; - uint32_t diff1 = ct1 - ct; - static constexpr double kThreshold = 0.1; - float threshold = kThreshold * (ct1 - ct0); - - if (diff0 < threshold || diff1 < threshold) { - const Components &set = diff0 < diff1 ? set0 : set1; - LOG(RkISP1Lsc, Debug) << "using LSC table for " << set.ct; - copyTable(config, set); - lastCt_ = { ct, set.ct }; - return; - } - - /* - * ct is not within 10% of the difference between the neighbouring - * color temperatures, so we need to interpolate. - */ LOG(RkISP1Lsc, Debug) - << "ct is " << ct << ", interpolating between " - << ct0 << " and " << ct1; - interpolateTable(config, set0, set1, ct); - lastCt_ = { ct, ct }; + << "ct is " << ct << ", quantized to " + << quantizedCt; } REGISTER_IPA_ALGORITHM(LensShadingCorrection, "LensShadingCorrection") diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h index 5baf592797a6..4d0ec5575550 100644 --- a/src/ipa/rkisp1/algorithms/lsc.h +++ b/src/ipa/rkisp1/algorithms/lsc.h @@ -9,6 +9,8 @@ #include +#include "libipa/interpolator.h" + #include "algorithm.h" namespace libcamera { @@ -27,7 +29,6 @@ public: IPAFrameContext &frameContext, rkisp1_params_cfg *params) override; -private: struct Components { uint32_t ct; std::vector r; @@ -36,23 +37,23 @@ private: std::vector b; }; +private: void setParameters(rkisp1_params_cfg *params); void copyTable(rkisp1_cif_isp_lsc_config &config, const Components &set0); void interpolateTable(rkisp1_cif_isp_lsc_config &config, const Components &set0, const Components &set1, const uint32_t ct); - std::map sets_; + ipa::Interpolator sets_; std::vector xSize_; std::vector ySize_; uint16_t xGrad_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE]; uint16_t yGrad_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE]; uint16_t xSizes_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE]; uint16_t ySizes_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE]; - struct { - uint32_t original; - uint32_t adjusted; - } lastCt_; + + unsigned int lastAppliedCt_; + unsigned int lastAppliedQuantizedCt_; }; } /* namespace ipa::rkisp1::algorithms */ From patchwork Mon Aug 26 15:22:03 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Klug X-Patchwork-Id: 21015 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 3099DC323E for ; Mon, 26 Aug 2024 15:22:47 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id AEBBF63417; Mon, 26 Aug 2024 17:22:46 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="tezfjt7I"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id E6AF96341B for ; Mon, 26 Aug 2024 17:22:42 +0200 (CEST) Received: from ideasonboard.com (unknown [IPv6:2a00:6020:448c:6c00:58b7:f3d:c9d4:defa]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A93C2741; Mon, 26 Aug 2024 17:21:36 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1724685696; bh=Sol+mcMBqj5cbBi+786nlOn9rj/Dv6QBpGCj8gp5HEM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=tezfjt7I288Ni0Izke4FslbZcF5EntZHch71UICOS0XTyxwK5v5TioSk+JGhkRH9b 97sm+Z7YPjSCaYbUFUf+Iljt1z0ZXFHSwmSmf4kTS910OFWi7Py9tbFgwlmsVSJ+tE uo2WWJHZ7L6HRPHE3sRG8a8I/6XaSbtlEV/YNIUI= From: Stefan Klug To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug Subject: [PATCH v1 5/8] ipa: rkisp1: Move loader functions into helper class Date: Mon, 26 Aug 2024 17:22:03 +0200 Message-ID: <20240826152224.362773-6-stefan.klug@ideasonboard.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20240826152224.362773-1-stefan.klug@ideasonboard.com> References: <20240826152224.362773-1-stefan.klug@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" In preparation to the polynomial LSC data, move the current loader into it's own helper class. Signed-off-by: Stefan Klug --- src/ipa/rkisp1/algorithms/lsc.cpp | 124 ++++++++++++++++++------------ 1 file changed, 75 insertions(+), 49 deletions(-) diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index 7e0e65b22185..930c188f49e7 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -68,6 +68,69 @@ namespace ipa::rkisp1::algorithms { LOG_DEFINE_CATEGORY(RkISP1Lsc) +class LscClassicLoader +{ +public: + int parseLscData(const YamlObject &yamlSets, + std::map &lscData) + { + const auto &sets = yamlSets.asList(); + for (const auto &yamlSet : sets) { + uint32_t ct = yamlSet["ct"].get(0); + + if (lscData.count(ct)) { + LOG(RkISP1Lsc, Error) + << "Multiple sets found for color temperature " + << ct; + return -EINVAL; + } + + LensShadingCorrection::Components &set = lscData[ct]; + + set.ct = ct; + set.r = parseTable(yamlSet, "r"); + set.gr = parseTable(yamlSet, "gr"); + set.gb = parseTable(yamlSet, "gb"); + set.b = parseTable(yamlSet, "b"); + + if (set.r.empty() || set.gr.empty() || + set.gb.empty() || set.b.empty()) { + LOG(RkISP1Lsc, Error) + << "Set for color temperature " << ct + << " is missing tables"; + return -EINVAL; + } + } + + if (lscData.empty()) { + LOG(RkISP1Lsc, Error) << "Failed to load any sets"; + return -EINVAL; + } + + return 0; + } + +private: + std::vector parseTable(const YamlObject &tuningData, + const char *prop) + { + static constexpr unsigned int kLscNumSamples = + RKISP1_CIF_ISP_LSC_SAMPLES_MAX * RKISP1_CIF_ISP_LSC_SAMPLES_MAX; + + std::vector table = + tuningData[prop].getList().value_or(std::vector{}); + if (table.size() != kLscNumSamples) { + LOG(RkISP1Lsc, Error) + << "Invalid '" << prop << "' values: expected " + << kLscNumSamples + << " elements, got " << table.size(); + return {}; + } + + return table; + } +}; + constexpr int kColourTemperatureChangeThreshhold = 10; static std::vector parseSizes(const YamlObject &tuningData, @@ -100,25 +163,6 @@ static std::vector parseSizes(const YamlObject &tuningData, return sizes; } -static std::vector parseTable(const YamlObject &tuningData, - const char *prop) -{ - static constexpr unsigned int kLscNumSamples = - RKISP1_CIF_ISP_LSC_SAMPLES_MAX * RKISP1_CIF_ISP_LSC_SAMPLES_MAX; - - std::vector table = - tuningData[prop].getList().value_or(std::vector{}); - if (table.size() != kLscNumSamples) { - LOG(RkISP1Lsc, Error) - << "Invalid '" << prop << "' values: expected " - << kLscNumSamples - << " elements, got " << table.size(); - return {}; - } - - return table; -} - LensShadingCorrection::LensShadingCorrection() : lastAppliedCt_(0), lastAppliedQuantizedCt_(0) { @@ -145,39 +189,21 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, return -EINVAL; } - const auto &sets = yamlSets.asList(); std::map lscData; - for (const auto &yamlSet : sets) { - uint32_t ct = yamlSet["ct"].get(0); - - if (lscData.count(ct)) { - LOG(RkISP1Lsc, Error) - << "Multiple sets found for color temperature " - << ct; - return -EINVAL; - } - - Components &set = lscData[ct]; - - set.ct = ct; - set.r = parseTable(yamlSet, "r"); - set.gr = parseTable(yamlSet, "gr"); - set.gb = parseTable(yamlSet, "gb"); - set.b = parseTable(yamlSet, "b"); - - if (set.r.empty() || set.gr.empty() || - set.gb.empty() || set.b.empty()) { - LOG(RkISP1Lsc, Error) - << "Set for color temperature " << ct - << " is missing tables"; - return -EINVAL; - } + int res = 0; + std::optional type = tuningData["type"].get(); + if (!type.has_value()) { + LOG(RkISP1Lsc, Warning) << "LSC data is in classic format. " + << "This will be deprecated soon."; + auto loader = LscClassicLoader(); + res = loader.parseLscData(yamlSets, lscData); + } else { + LOG(RkISP1Lsc, Error) << "Unsupported LSC type '" << *type << "'"; + res = -EINVAL; } - if (lscData.empty()) { - LOG(RkISP1Lsc, Error) << "Failed to load any sets"; - return -EINVAL; - } + if (res) + return res; sets_.setData(std::move(lscData)); From patchwork Mon Aug 26 15:22:04 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Stefan Klug X-Patchwork-Id: 21016 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id D3507C324C for ; Mon, 26 Aug 2024 15:22:47 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 63F466341D; Mon, 26 Aug 2024 17:22:47 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="cPRy9pvF"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 85B99633D4 for ; Mon, 26 Aug 2024 17:22:45 +0200 (CEST) Received: from ideasonboard.com (unknown [IPv6:2a00:6020:448c:6c00:58b7:f3d:c9d4:defa]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 4207E741; Mon, 26 Aug 2024 17:21:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1724685699; bh=AOKaijGCWZOCM1rIBzUsWumA2GsBFuu9vxGIx69KhO4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=cPRy9pvFnplQg1JUkkde5YK5iWoSmmDqB5K/+2moJ4rKEJA1BP4g7C9ssVyY1iDjs 7SJ7lJCGfiZ0FiiIDlYphjmE6YQF1wu2x5rHt9HL6kBaly3ECOshebjSECjduCTnQ6 C29QhI3vfcnTmwQe4Qp6WRH3ecGJ7GO2c6iLPvOE= From: Stefan Klug To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug Subject: [PATCH v1 6/8] ipa: libipa: Add lsc polynomial class Date: Mon, 26 Aug 2024 17:22:04 +0200 Message-ID: <20240826152224.362773-7-stefan.klug@ideasonboard.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20240826152224.362773-1-stefan.klug@ideasonboard.com> References: <20240826152224.362773-1-stefan.klug@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a basic class to represent polynomials as specified in the DNG spec for vignetting correction. Signed-off-by: Stefan Klug --- src/ipa/libipa/meson.build | 2 + src/ipa/libipa/polynomial.cpp | 23 ++++++++ src/ipa/libipa/polynomial.h | 107 ++++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 src/ipa/libipa/polynomial.cpp create mode 100644 src/ipa/libipa/polynomial.h diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index 3740178b2909..8b085750807a 100644 --- a/src/ipa/libipa/meson.build +++ b/src/ipa/libipa/meson.build @@ -10,6 +10,7 @@ libipa_headers = files([ 'interpolator.h', 'matrix.h', 'module.h', + 'polynomial.h', 'pwl.h', 'vector.h', ]) @@ -24,6 +25,7 @@ libipa_sources = files([ 'interpolator.cpp', 'matrix.cpp', 'module.cpp', + 'polynomial.cpp', 'pwl.cpp', 'vector.cpp', ]) diff --git a/src/ipa/libipa/polynomial.cpp b/src/ipa/libipa/polynomial.cpp new file mode 100644 index 000000000000..39b17b29314b --- /dev/null +++ b/src/ipa/libipa/polynomial.cpp @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board + * + * Polynomal class to represent lens shading correction + */ + +#include "polynomial.h" + +#include + +/** + * \file polynomial.h + * \brief Polynomial class + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(Polynomial) + +namespace ipa { +} +} // namespace libcamera diff --git a/src/ipa/libipa/polynomial.h b/src/ipa/libipa/polynomial.h new file mode 100644 index 000000000000..971be062297f --- /dev/null +++ b/src/ipa/libipa/polynomial.h @@ -0,0 +1,107 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board + * + * Vector and related operations + */ +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include "libcamera/internal/yaml_parser.h" + +namespace libcamera { + +LOG_DECLARE_CATEGORY(Polynomial) + +namespace ipa { + +constexpr int kNumCoefficients = 5; +class Polynomial +{ +public: + /** + * Note: cx and cy are relative to the image size + */ + Polynomial(double cx = 0.0, double cy = 0.0, double k0 = 0.0, + double k1 = 0.0, double k2 = 0.0, double k3 = 0.0, + double k4 = 0.0) + : cx_(cx), cy_(cy), + coefficients_({ k0, k1, k2, k3, k4 }) + { + } + + double sampleAtNormalizedPixelPos(double x, double y) const + { + double dx = x - cnx_; + double dy = y - cny_; + double r = sqrt(dx * dx + dy * dy); + double res = 1.0; + for (int i = 0; i < kNumCoefficients; i++) { + res += coefficients_[i] * std::pow(r, (i + 1) * 2); + } + return res; + } + + /** + * Returns m according to dng spec. 𝑚 represents the Euclidean distance + * (in pixels) from the optical center to the farthest pixel in the + * image. + */ + double getM() const + { + double cpx = imageSize_.width * cx_; + double cpy = imageSize_.height * cy_; + double mx = std::max(cpx, std::fabs(imageSize_.width - cpx)); + double my = std::max(cpy, std::fabs(imageSize_.height - cpy)); + + return sqrt(mx * mx + my * my); + } + + void setReferenceImageSize(const Size &size) + { + imageSize_ = size; + /* Calculate normalized centers */ + double m = getM(); + cnx_ = (size.width * cx_) / m; + cny_ = (size.height * cy_) / m; + } + +private: + double cx_; + double cy_; + double cnx_; + double cny_; + std::array coefficients_; + + Size imageSize_; +}; + +} /* namespace ipa */ + +template<> +struct YamlObject::Getter { + std::optional get(const YamlObject &obj) const + { + std::optional cx = obj["cx"].get(); + std::optional cy = obj["cy"].get(); + std::optional k0 = obj["k0"].get(); + std::optional k1 = obj["k1"].get(); + std::optional k2 = obj["k2"].get(); + std::optional k3 = obj["k3"].get(); + std::optional k4 = obj["k4"].get(); + + if (!(cx && cy && k0 && k1 && k2 && k3 && k4)) + LOG(Polynomial, Error) << "Polynomial is missing parameter "; + + return ipa::Polynomial(*cx, *cy, *k0, *k1, *k2, *k3, *k4); + } +}; + +} /* namespace libcamera */ From patchwork Mon Aug 26 15:22:05 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Klug X-Patchwork-Id: 21017 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 7421AC32D5 for ; Mon, 26 Aug 2024 15:22:51 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 076176341F; Mon, 26 Aug 2024 17:22:51 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="kkhgAput"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id B9BB663421 for ; Mon, 26 Aug 2024 17:22:48 +0200 (CEST) Received: from ideasonboard.com (unknown [IPv6:2a00:6020:448c:6c00:58b7:f3d:c9d4:defa]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 74181480; Mon, 26 Aug 2024 17:21:42 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1724685702; bh=pGM8kPhrhD0ndwxqtoZupK9hT5l70hRKV3008QELl2g=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=kkhgAputIdVrG/gRxBr+dGVdXJAbDDA/1p80n+/y3CPzAxL/y8xDr71gK2qw0Z+fU 5xsfNaTRgQR+tvqtw1UyFVmTtk82kXT/Gp7A7X/XKT7eaKUUyiLa3GXQVJjkNcFrk9 j7UYn0pxwqbOUfSKAJhyfXDXvroUdfF17BoTThZc= From: Stefan Klug To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug Subject: [PATCH v1 7/8] ipa: rkisp1: Add sensor info to context Date: Mon, 26 Aug 2024 17:22:05 +0200 Message-ID: <20240826152224.362773-8-stefan.klug@ideasonboard.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20240826152224.362773-1-stefan.klug@ideasonboard.com> References: <20240826152224.362773-1-stefan.klug@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" For the LSC algorithm to dynamically calculate the LSC tables based on the sensor size and the crop rectangle it needs access to that data. Provide access to it by adding the sensorInfo object to the context. Signed-off-by: Stefan Klug --- src/ipa/rkisp1/ipa_context.h | 2 ++ src/ipa/rkisp1/rkisp1.cpp | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index 061efc0c578e..18c294798524 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -178,6 +179,7 @@ struct IPAFrameContext : public FrameContext { struct IPAContext { const IPAHwSettings *hw; + IPACameraSensorInfo sensorInfo; IPASessionConfiguration configuration; IPAActiveState activeState; diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp index 23e0826cc335..c671629956c7 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -111,7 +111,7 @@ const ControlInfoMap::Map rkisp1Controls{ } /* namespace */ IPARkISP1::IPARkISP1() - : context_({ {}, {}, {}, { kMaxFrameContexts }, {}, {} }) + : context_({ {}, {}, {}, {}, { kMaxFrameContexts }, {}, {} }) { } @@ -143,6 +143,8 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision, LOG(IPARkISP1, Debug) << "Hardware revision is " << hwRevision; + context_.sensorInfo = sensorInfo; + context_.camHelper = CameraSensorHelperFactoryBase::create(settings.sensorModel); if (!context_.camHelper) { LOG(IPARkISP1, Error) From patchwork Mon Aug 26 15:22:06 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Klug X-Patchwork-Id: 21018 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id C61DEC323E for ; Mon, 26 Aug 2024 15:22:53 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 664046341D; Mon, 26 Aug 2024 17:22:53 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="wGd3OMdE"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 8B7656341D for ; Mon, 26 Aug 2024 17:22:51 +0200 (CEST) Received: from ideasonboard.com (unknown [IPv6:2a00:6020:448c:6c00:58b7:f3d:c9d4:defa]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 47413480; Mon, 26 Aug 2024 17:21:45 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1724685705; bh=lO9jUXi6lW5dLTIYXMb+hxZtCU9xWsv/WVw8q0dozcQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=wGd3OMdEGDrAZ0JyTJ5O3pj9GvpJMFSL3bWChzZSn9WjbjAajQVlyeVu2nD3LxtuY SWW/GVg/VEnnnwVHZucQxExYvNcLjCryWQMQotD4GYsy1qWCUacKS7b6ap8cKBMODI e42SdN7zzT4AJQp051mLeObRLfHNn72qOEPjpIV8= From: Stefan Klug To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug Subject: [PATCH v1 8/8] ipa: rkisp1: Add polynomial LSC loader Date: Mon, 26 Aug 2024 17:22:06 +0200 Message-ID: <20240826152224.362773-9-stefan.klug@ideasonboard.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20240826152224.362773-1-stefan.klug@ideasonboard.com> References: <20240826152224.362773-1-stefan.klug@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a loader that is capable to load polynomial coefficients from the tuning files. The polynoms are sampled at load time to reduce the computational overhead at runtime. Signed-off-by: Stefan Klug --- src/ipa/rkisp1/algorithms/lsc.cpp | 126 +++++++++++++++++++++++++++++- 1 file changed, 125 insertions(+), 1 deletion(-) diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index 930c188f49e7..573de302e685 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -16,6 +16,7 @@ #include "libcamera/internal/yaml_parser.h" +#include "libipa/polynomial.h" #include "linux/rkisp1-config.h" /** @@ -68,6 +69,123 @@ namespace ipa::rkisp1::algorithms { LOG_DEFINE_CATEGORY(RkISP1Lsc) +class LscPolynomialLoader +{ +public: + LscPolynomialLoader(const Size &sensorSize, + const Rectangle &cropRectangle, + const std::vector &xSizes, + const std::vector &ySizes) + : sensorSize_(sensorSize), + cropRectangle_(cropRectangle), + xSizes_(xSizes), + ySizes_(ySizes) + { + } + + int parseLscData(const YamlObject &yamlSets, + std::map &lscData) + { + const auto &sets = yamlSets.asList(); + for (const auto &yamlSet : sets) { + std::optional pr, pgr, pgb, pb; + uint32_t ct = yamlSet["ct"].get(0); + + if (lscData.count(ct)) { + LOG(RkISP1Lsc, Error) + << "Multiple sets found for " + << "color temperature " << ct; + return -EINVAL; + } + + LensShadingCorrection::Components &set = lscData[ct]; + pr = yamlSet["r"].get(); + pgr = yamlSet["gr"].get(); + pgb = yamlSet["gb"].get(); + pb = yamlSet["b"].get(); + + if (!(pr || pgr || pgb || pb)) { + LOG(RkISP1Lsc, Error) + << "Failed to parse polynomial for " + << "colour temperature " << ct; + return -EINVAL; + } + + set.ct = ct; + pr->setReferenceImageSize(sensorSize_); + pgr->setReferenceImageSize(sensorSize_); + pgb->setReferenceImageSize(sensorSize_); + pb->setReferenceImageSize(sensorSize_); + set.r = samplePolynomial(*pr); + set.gr = samplePolynomial(*pgr); + set.gb = samplePolynomial(*pgb); + set.b = samplePolynomial(*pb); + } + + if (lscData.empty()) { + LOG(RkISP1Lsc, Error) << "Failed to load any sets"; + return -EINVAL; + } + + return 0; + } + +private: + std::vector sizesListToPositions(const std::vector &sizes) + { + const int half = sizes.size(); + std::vector res(half * 2 + 1); + double x = 0.0; + + res[half] = 0.5; + for (int i = 1; i <= half; i++) { + x += sizes[half - i]; + res[half - i] = 0.5 - x; + res[half + i] = 0.5 + x; + } + + return res; + } + + std::vector samplePolynomial(const Polynomial &poly) + { + constexpr int k = RKISP1_CIF_ISP_LSC_SAMPLES_MAX; + + double m = poly.getM(); + double x0 = cropRectangle_.x / m; + double y0 = cropRectangle_.y / m; + double w = cropRectangle_.width / m; + double h = cropRectangle_.height / m; + std::vector res; + + assert(xSizes_.size() * 2 + 1 == k); + + res.reserve(k * k); + + std::vector xPos(sizesListToPositions(xSizes_)); + std::vector yPos(sizesListToPositions(ySizes_)); + + for (int x = 0; x < k; x++) { + for (int y = 0; y < k; y++) { + double xp = x0 + xPos[x] * w; + double yp = y0 + yPos[y] * h; + int v = static_cast( + poly.sampleAtNormalizedPixelPos(xp, yp) * + 1024); + + v = std::min(std::max(v, 1024), 4095); + res.push_back(v); + } + } + return res; + } + + Size sensorSize_; + Rectangle cropRectangle_; + const std::vector &xSizes_; + const std::vector &ySizes_; +}; + class LscClassicLoader { public: @@ -192,11 +310,17 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, std::map lscData; int res = 0; std::optional type = tuningData["type"].get(); - if (!type.has_value()) { + if (!tuningData.contains("type")) { LOG(RkISP1Lsc, Warning) << "LSC data is in classic format. " << "This will be deprecated soon."; auto loader = LscClassicLoader(); res = loader.parseLscData(yamlSets, lscData); + } else if (*type == "polynomial") { + auto loader = LscPolynomialLoader(context.sensorInfo.activeAreaSize, + context.sensorInfo.analogCrop, + xSize_, + ySize_); + res = loader.parseLscData(yamlSets, lscData); } else { LOG(RkISP1Lsc, Error) << "Unsupported LSC type '" << *type << "'"; res = -EINVAL;