From patchwork Wed Nov 19 13:22:10 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Klug X-Patchwork-Id: 25081 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 DBBF7BD80A for ; Wed, 19 Nov 2025 13:22:30 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 997BE60A85; Wed, 19 Nov 2025 14:22:30 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ShQSPrJ4"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 9E5C4609E0 for ; Wed, 19 Nov 2025 14:22:28 +0100 (CET) Received: from ideasonboard.com (unknown [IPv6:2a00:6020:448c:6c00:5419:7bb:83a3:3d7a]) by perceval.ideasonboard.com (Postfix) with UTF8SMTPSA id 19CB0E7C; Wed, 19 Nov 2025 14:20:24 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1763558424; bh=CdWXJ5t7eaOtaT/R2RO/4FKGUFV2VxnNYGIYdhgd6gk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ShQSPrJ4/F5T2qfzerkyRUWfqMWJytfYULLYzBIJKcmxRfDGYsvCk1GYBPY102Iti dcoUMucrOxahJbuk/iZ5SKpPsjC8XjN1bUxaNuCiX3oOtfF4BbhrCbSUEztDvAjLD8 nWkKEzFqd7VR/h8t6hqKWuBV215m2Vt09gwBxkV0= From: Stefan Klug To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug , Kieran Bingham , Daniel Scally Subject: [PATCH v3 1/4] ipa: libipa: pwl: Allow to parse a plain yaml value as single point PWL Date: Wed, 19 Nov 2025 14:22:10 +0100 Message-ID: <20251119132221.2088013-2-stefan.klug@ideasonboard.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251119132221.2088013-1-stefan.klug@ideasonboard.com> References: <20251119132221.2088013-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 the tuning files, it is useful to specify some values as PWL that produces a different output value depending on an input value. For example it is useful to specify the relativeLuminanceTarget depending on the lux level, so that the regulation regulates a bit darker in low light scenes. For simple setups this is not necessary and a single value is sufficient. This patch extends the yaml loading code, so that either case is supported transparently without the need for additional external code. This way the following yaml expressions are all valid: yTarget: [ 1000, 0.15, 2000, 0.17 ] # Regular PWL yTarget: [ 0, 0.17 ] # Single point PWL yTarget: 0.17 # Same as above For cases (I'm not aware of any) where a single point Pwl is not allowed there is no change as that must be checked externally anyways. Signed-off-by: Stefan Klug Reviewed-by: Kieran Bingham Reviewed-by: Daniel Scally --- Changes in v3: - Collected tags Changes in v2: - Added this patch --- src/ipa/libipa/pwl.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/ipa/libipa/pwl.cpp b/src/ipa/libipa/pwl.cpp index 69a9334112e8..78ba43e39b77 100644 --- a/src/ipa/libipa/pwl.cpp +++ b/src/ipa/libipa/pwl.cpp @@ -437,6 +437,15 @@ template<> std::optional YamlObject::Getter::get(const YamlObject &obj) const { + /* Treat a single value as single point PWL */ + if (obj.isValue()) { + auto v = obj.get(); + if (!v) + return std::nullopt; + + return ipa::Pwl({ { { 0.0, *v } } }); + } + if (!obj.size() || obj.size() % 2) return std::nullopt; From patchwork Wed Nov 19 13:22:11 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Klug X-Patchwork-Id: 25082 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 E4EF3BD80A for ; Wed, 19 Nov 2025 13:22:33 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 949F260AA3; Wed, 19 Nov 2025 14:22:33 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="CbKFEkx0"; 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 8089E606A0 for ; Wed, 19 Nov 2025 14:22:31 +0100 (CET) Received: from ideasonboard.com (unknown [IPv6:2a00:6020:448c:6c00:5419:7bb:83a3:3d7a]) by perceval.ideasonboard.com (Postfix) with UTF8SMTPSA id D284E195E; Wed, 19 Nov 2025 14:20:26 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1763558426; bh=qGvT3BW9ZL4aP9kR1bgRb+4Ux+dAaMHhmM0jb17WS7w=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=CbKFEkx0uQmc5O8rr04I9yBkopw8PTBLeYs1Kp94llrq3cZ6FxupBlh0Kr++etiW+ Xd6hFYf2e1vx3wJjXIxfZapfYiyqLxeyyCUPeoFDFjKmRsShJZW5LVXmKZQT30V6a9 f4SObOQWNMaM0Z4b+am9/SmP6MBU7A82PrBrBvM0= From: Stefan Klug To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug Subject: [PATCH v3 2/4] ipa: rkisp1: lux: Properly handle frame context and active state Date: Wed, 19 Nov 2025 14:22:11 +0100 Message-ID: <20251119132221.2088013-3-stefan.klug@ideasonboard.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251119132221.2088013-1-stefan.klug@ideasonboard.com> References: <20251119132221.2088013-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" WIth the upcoming regulation rework the processing order in the IPA is updated a bit so that process() updates the active state with new measurements and fills the metadata with the data from the corresponding frame context. In prepare() all parameters for one frame are tied together using the most up to date values from active state. Change the lux algorithm to support that order of events. Also prepare for cases where stats can be null which can happen with the upcoming regulation rework. While at it fix a formatting issue reported by checkstyle and drop a unnecessary local variable. Signed-off-by: Stefan Klug Reviewed-by: Kieran Bingham --- Changes in v3: - Added this patch in response to the comment from Kieran in https://patchwork.libcamera.org/patch/24982/#36740 --- src/ipa/rkisp1/algorithms/lux.cpp | 23 ++++++++++++++++++----- src/ipa/rkisp1/algorithms/lux.h | 3 +++ src/ipa/rkisp1/ipa_context.h | 4 ++++ 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/ipa/rkisp1/algorithms/lux.cpp b/src/ipa/rkisp1/algorithms/lux.cpp index e8da69810008..bc714b7a82c6 100644 --- a/src/ipa/rkisp1/algorithms/lux.cpp +++ b/src/ipa/rkisp1/algorithms/lux.cpp @@ -46,6 +46,16 @@ int Lux::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData return lux_.parseTuningData(tuningData); } +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void Lux::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + [[maybe_unused]] RkISP1Params *params) +{ + frameContext.lux.lux = context.activeState.lux.lux; +} + /** * \copydoc libcamera::ipa::Algorithm::process */ @@ -55,8 +65,13 @@ void Lux::process(IPAContext &context, const rkisp1_stat_buffer *stats, ControlList &metadata) { - utils::Duration exposureTime = context.configuration.sensor.lineDuration - * frameContext.sensor.exposure; + metadata.set(controls::Lux, frameContext.lux.lux); + + if (!stats) + return; + + utils::Duration exposureTime = context.configuration.sensor.lineDuration * + frameContext.sensor.exposure; double gain = frameContext.sensor.gain; /* \todo Deduplicate the histogram calculation from AGC */ @@ -64,9 +79,7 @@ void Lux::process(IPAContext &context, Histogram yHist({ params->hist.hist_bins, context.hw.numHistogramBins }, [](uint32_t x) { return x >> 4; }); - double lux = lux_.estimateLux(exposureTime, gain, 1.0, yHist); - frameContext.lux.lux = lux; - metadata.set(controls::Lux, lux); + context.activeState.lux.lux = lux_.estimateLux(exposureTime, gain, 1.0, yHist); } REGISTER_IPA_ALGORITHM(Lux, "Lux") diff --git a/src/ipa/rkisp1/algorithms/lux.h b/src/ipa/rkisp1/algorithms/lux.h index 8dcadc284a84..e0239848e252 100644 --- a/src/ipa/rkisp1/algorithms/lux.h +++ b/src/ipa/rkisp1/algorithms/lux.h @@ -23,6 +23,9 @@ public: Lux(); int init(IPAContext &context, const YamlObject &tuningData) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + RkISP1Params *params) override; void process(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, const rkisp1_stat_buffer *stats, diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index f85a130d9c23..b257cee55379 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -133,6 +133,10 @@ struct IPAActiveState { double gamma; } goc; + struct { + double lux; + } lux; + struct { controls::WdrModeEnum mode; AgcMeanLuminance::AgcConstraint constraint; From patchwork Wed Nov 19 13:22:12 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Klug X-Patchwork-Id: 25083 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 B680CBD80A for ; Wed, 19 Nov 2025 13:22:35 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 64CA260A8A; Wed, 19 Nov 2025 14:22:35 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="vvVs+p2j"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id D2D7460AA6 for ; Wed, 19 Nov 2025 14:22:33 +0100 (CET) Received: from ideasonboard.com (unknown [IPv6:2a00:6020:448c:6c00:5419:7bb:83a3:3d7a]) by perceval.ideasonboard.com (Postfix) with UTF8SMTPSA id 586BA195E; Wed, 19 Nov 2025 14:20:29 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1763558429; bh=Nu7ekdtDTkFc9AlZtqRWG3n4bm6C+ZbPzmrwgSzVUUk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=vvVs+p2jjc2q7Eo/VlpfFrc/m+r9RDJQdt0eSDBQkE0RVUXReZd5tMifLApQGjnUy hPzTCYsfXRUUHraMOb20UKEJHhdlfADcIRGpKMeadMs5X9CBPdbxfC9nS/mCPf0Mgz GTBjrAx8LCbCDNI7K71mZ0deGtl74/vB8IKSUPVE= From: Stefan Klug To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug , Paul Elder , Daniel Scally Subject: [PATCH v3 3/4] ipa: libipa: agc_mean_luminance: Change luminance target to piecewise linear function Date: Wed, 19 Nov 2025 14:22:12 +0100 Message-ID: <20251119132221.2088013-4-stefan.klug@ideasonboard.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251119132221.2088013-1-stefan.klug@ideasonboard.com> References: <20251119132221.2088013-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 some situations it is necessary to specify the target brightness value depending on the overall lux level. Replace the float relativeLuminanceTraget by a PWL. As the PWL loading code loads a plain value as single point PWL, backwards compatibility to existing tuning files is ensured. While at it, order the class members in reverse xmas tree notation. Signed-off-by: Paul Elder Signed-off-by: Stefan Klug Reviewed-by: Daniel Scally Reviewed-by: Kieran Bingham --- Changes in v3: - Dropped reference to original patch from commit message. For the record, the old patch was https://patchwork.libcamera.org/patch/20231/ - Ordered member variables in reverse xmas tree order - Fallback to kDefaultRelativeLuminanceTarget if relativeLuminanceTraget is missing in the tuning file - Warn once if lux level is not available after a few frames Changes in v2: - Collected tags - Dropped special handling of plain values, as this is now part of the yaml PWL loader. --- src/ipa/libipa/agc_mean_luminance.cpp | 67 ++++++++++++++++++++++++--- src/ipa/libipa/agc_mean_luminance.h | 14 ++++-- src/ipa/rkisp1/algorithms/agc.cpp | 1 + 3 files changed, 72 insertions(+), 10 deletions(-) diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp index 64f36bd75dd2..4f2b5fad2082 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -54,6 +54,14 @@ static constexpr double kDefaultRelativeLuminanceTarget = 0.16; */ static constexpr double kMaxRelativeLuminanceTarget = 0.95; +/* + * Default lux level + * + * If no lux level or a zero lux level is specified, but PWLs are used to + * specify luminance targets, this default level is used. + */ +static constexpr unsigned int kDefaultLuxLevel = 500; + /** * \struct AgcMeanLuminance::AgcConstraint * \brief The boundaries and target for an AeConstraintMode constraint @@ -144,17 +152,30 @@ static constexpr double kMaxRelativeLuminanceTarget = 0.95; */ AgcMeanLuminance::AgcMeanLuminance() - : exposureCompensation_(1.0), frameCount_(0), filteredExposure_(0s), - relativeLuminanceTarget_(0) + : filteredExposure_(0s), luxWarningEnabled_(true), + exposureCompensation_(1.0), frameCount_(0), lux_(0) { } AgcMeanLuminance::~AgcMeanLuminance() = default; -void AgcMeanLuminance::parseRelativeLuminanceTarget(const YamlObject &tuningData) +int AgcMeanLuminance::parseRelativeLuminanceTarget(const YamlObject &tuningData) { - relativeLuminanceTarget_ = - tuningData["relativeLuminanceTarget"].get(kDefaultRelativeLuminanceTarget); + auto &target = tuningData["relativeLuminanceTarget"]; + if (!target) { + relativeLuminanceTarget_ = { { { { 0.0, kDefaultRelativeLuminanceTarget } } } }; + return 0; + } + + auto pwl = target.get(); + if (!pwl) { + LOG(AgcMeanLuminance, Error) + << "Failed to load relative luminance target."; + return -EINVAL; + } + + relativeLuminanceTarget_ = std::move(*pwl); + return 0; } void AgcMeanLuminance::parseConstraint(const YamlObject &modeDict, int32_t id) @@ -325,6 +346,8 @@ void AgcMeanLuminance::configure(utils::Duration lineDuration, { for (auto &[id, helper] : exposureModeHelpers_) helper->configure(lineDuration, sensorHelper); + + luxWarningEnabled_ = true; } /** @@ -385,7 +408,9 @@ int AgcMeanLuminance::parseTuningData(const YamlObject &tuningData) { int ret; - parseRelativeLuminanceTarget(tuningData); + ret = parseRelativeLuminanceTarget(tuningData); + if (ret) + return ret; ret = parseConstraintModes(tuningData); if (ret) @@ -403,6 +428,16 @@ int AgcMeanLuminance::parseTuningData(const YamlObject &tuningData) * AGC calculations. It is expressed as gain instead of EV. */ +/** + * \fn AgcMeanLuminance::setLux(int lux) + * \brief Set the lux level + * \param[in] lux The lux level + * + * This function sets the lux level to be used in the AGC calculations. A value + * of 0 means no measurement and a default value of \a kDefaultLuxLevel is used + * if necessary. + */ + /** * \brief Set the ExposureModeHelper limits for this class * \param[in] minExposureTime Minimum exposure time to allow @@ -538,7 +573,25 @@ double AgcMeanLuminance::constraintClampGain(uint32_t constraintModeIndex, */ double AgcMeanLuminance::effectiveYTarget() const { - return std::min(relativeLuminanceTarget_ * exposureCompensation_, + double lux = lux_; + if (relativeLuminanceTarget_.size() > 1 && lux_ == 0) { + if (frameCount_ > 10 && luxWarningEnabled_) { + luxWarningEnabled_ = false; + LOG(AgcMeanLuminance, Warning) + << "Missing lux value for luminance target " + "calculation, default to " + << kDefaultLuxLevel + << ". Note that the Lux algorithm must be " + "included before the Agc algorithm."; + } + + lux = kDefaultLuxLevel; + } + + double luminanceTarget = relativeLuminanceTarget_.eval( + relativeLuminanceTarget_.domain().clamp(lux)); + + return std::min(luminanceTarget * exposureCompensation_, kMaxRelativeLuminanceTarget); } diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h index e5f164c3186b..746b97b16ffe 100644 --- a/src/ipa/libipa/agc_mean_luminance.h +++ b/src/ipa/libipa/agc_mean_luminance.h @@ -20,6 +20,7 @@ #include "exposure_mode_helper.h" #include "histogram.h" +#include "pwl.h" namespace libcamera { @@ -50,6 +51,11 @@ public: exposureCompensation_ = gain; } + void setLux(unsigned int lux) + { + lux_ = lux; + } + void setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime, double minGain, double maxGain, std::vector constraints); @@ -82,7 +88,7 @@ public: private: virtual double estimateLuminance(const double gain) const = 0; - void parseRelativeLuminanceTarget(const YamlObject &tuningData); + int parseRelativeLuminanceTarget(const YamlObject &tuningData); void parseConstraint(const YamlObject &modeDict, int32_t id); int parseConstraintModes(const YamlObject &tuningData); int parseExposureModes(const YamlObject &tuningData); @@ -92,10 +98,12 @@ private: double gain); utils::Duration filterExposure(utils::Duration exposureValue); + utils::Duration filteredExposure_; + mutable bool luxWarningEnabled_; double exposureCompensation_; + Pwl relativeLuminanceTarget_; uint64_t frameCount_; - utils::Duration filteredExposure_; - double relativeLuminanceTarget_; + unsigned int lux_; std::vector additionalConstraints_; std::map> constraintModes_; diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index f5a3c917cb69..1ecaff680978 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -618,6 +618,7 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, effectiveExposureValue *= frameContext.agc.quantizationGain; setExposureCompensation(pow(2.0, frameContext.agc.exposureValue)); + setLux(frameContext.lux.lux); utils::Duration newExposureTime; double aGain, qGain, dGain; From patchwork Wed Nov 19 13:22:13 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Klug X-Patchwork-Id: 25084 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 95CD5BD80A for ; Wed, 19 Nov 2025 13:22:39 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 62200609E0; Wed, 19 Nov 2025 14:22:39 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="CRLBErnM"; 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 DA345609D8 for ; Wed, 19 Nov 2025 14:22:36 +0100 (CET) Received: from ideasonboard.com (unknown [IPv6:2a00:6020:448c:6c00:5419:7bb:83a3:3d7a]) by perceval.ideasonboard.com (Postfix) with UTF8SMTPSA id 5B9EE19D6; Wed, 19 Nov 2025 14:20:32 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1763558432; bh=0ymmwBIURbUy5zTGGM8TkGdKNfvMKdg7OajTgSrXI0I=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=CRLBErnMSBfiTlF8ZkuWRBH3ybcEmcFfwxxirKyBWM/cb9Psqh+fK69VkNdZ3It6t VXdW8XryxqFoOy5DoigIG3h/BEF7aPIVJtVl4lc7ljzcQujWhb7jAe5RN3eJjBqwK/ JbC3nUgoWlyZ7Jg0ycNniG9q4teSr5Fp8Y/hX6ho= From: Stefan Klug To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug , Daniel Scally , Kieran Bingham , Jacopo Mondi Subject: [PATCH v3 4/4] ipa: libipa: agc_mean_luminance: Fix yTarget handling in constraints Date: Wed, 19 Nov 2025 14:22:13 +0100 Message-ID: <20251119132221.2088013-5-stefan.klug@ideasonboard.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251119132221.2088013-1-stefan.klug@ideasonboard.com> References: <20251119132221.2088013-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 yTarget loading code is broken and works neither for plain values nor for arrays of values to form a PWL. Fix this by supporting both cases. If a list is provided in the tuning file construct a PWL, otherwise construct a single point PWL with the given value. Fixes: 24247a12c7d3 ("ipa: libipa: Add AgcMeanLuminance base class") Signed-off-by: Stefan Klug Reviewed-by: Daniel Scally Reviewed-by: Kieran Bingham Reviewed-by: Jacopo Mondi --- Changes in v3: - Collected tags Changes in v2: - Collected tag - Drop special logic to handle single values as that was added generically to the yaml Pwl loader --- src/ipa/libipa/agc_mean_luminance.cpp | 41 +++++++++++++++++++-------- src/ipa/libipa/agc_mean_luminance.h | 4 +-- src/ipa/rkisp1/algorithms/wdr.cpp | 3 +- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp index 4f2b5fad2082..ef1644cdbf30 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -178,7 +178,7 @@ int AgcMeanLuminance::parseRelativeLuminanceTarget(const YamlObject &tuningData) return 0; } -void AgcMeanLuminance::parseConstraint(const YamlObject &modeDict, int32_t id) +int AgcMeanLuminance::parseConstraint(const YamlObject &modeDict, int32_t id) { for (const auto &[boundName, content] : modeDict.asDict()) { if (boundName != "upper" && boundName != "lower") { @@ -191,10 +191,14 @@ void AgcMeanLuminance::parseConstraint(const YamlObject &modeDict, int32_t id) AgcConstraint::Bound bound = static_cast(idx); double qLo = content["qLo"].get().value_or(0.98); double qHi = content["qHi"].get().value_or(1.0); - double yTarget = - content["yTarget"].getList().value_or(std::vector{ 0.5 }).at(0); + auto yTarget = content["yTarget"].get(); + if (!yTarget) { + LOG(AgcMeanLuminance, Error) + << "Failed to parse yTarget"; + return -EINVAL; + } - AgcConstraint constraint = { bound, qLo, qHi, yTarget }; + AgcConstraint constraint = { bound, qLo, qHi, std::move(*yTarget) }; if (!constraintModes_.count(id)) constraintModes_[id] = {}; @@ -204,6 +208,8 @@ void AgcMeanLuminance::parseConstraint(const YamlObject &modeDict, int32_t id) else constraintModes_[id].insert(constraintModes_[id].begin(), constraint); } + + return 0; } int AgcMeanLuminance::parseConstraintModes(const YamlObject &tuningData) @@ -226,8 +232,11 @@ int AgcMeanLuminance::parseConstraintModes(const YamlObject &tuningData) return -EINVAL; } - parseConstraint(modeDict, - AeConstraintModeNameValueMap.at(modeName)); + int ret = parseConstraint(modeDict, + AeConstraintModeNameValueMap.at(modeName)); + if (ret) + return ret; + availableConstraintModes.push_back( AeConstraintModeNameValueMap.at(modeName)); } @@ -244,7 +253,7 @@ int AgcMeanLuminance::parseConstraintModes(const YamlObject &tuningData) AgcConstraint::Bound::Lower, 0.98, 1.0, - 0.5 + Pwl({ { { 0.0, 0.5 } } }) }; constraintModes_[controls::ConstraintNormal].insert( @@ -359,8 +368,9 @@ void AgcMeanLuminance::configure(utils::Duration lineDuration, * the data in a specific format; the Agc algorithm's tuning data should contain * a dictionary called AeConstraintMode containing per-mode setting dictionaries * with the key being a value from \ref controls::AeConstraintModeNameValueMap. - * Each mode dict may contain either a "lower" or "upper" key or both, for - * example: + * The yTarget can either be provided as single value or as array in which case + * it is interpreted as a PWL mapping lux levels to yTarget values. Each mode + * dict may contain either a "lower" or "upper" key or both, for example: * * \code{.unparsed} * algorithms: @@ -379,7 +389,7 @@ void AgcMeanLuminance::configure(utils::Duration lineDuration, * upper: * qLo: 0.98 * qHi: 1.0 - * yTarget: 0.8 + * yTarget: [ 100, 0.8, 20000, 0.5 ] * * \endcode * @@ -535,8 +545,15 @@ double AgcMeanLuminance::constraintClampGain(uint32_t constraintModeIndex, const Histogram &hist, double gain) { - auto applyConstraint = [&gain, &hist](const AgcConstraint &constraint) { - double newGain = constraint.yTarget * hist.bins() / + auto applyConstraint = [this, &gain, &hist](const AgcConstraint &constraint) { + double lux = lux_; + + if (relativeLuminanceTarget_.size() > 1 && lux_ == 0) + lux = kDefaultLuxLevel; + + double target = constraint.yTarget.eval( + constraint.yTarget.domain().clamp(lux)); + double newGain = target * hist.bins() / hist.interQuantileMean(constraint.qLo, constraint.qHi); if (constraint.bound == AgcConstraint::Bound::Lower && diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h index 746b97b16ffe..0df97b27332d 100644 --- a/src/ipa/libipa/agc_mean_luminance.h +++ b/src/ipa/libipa/agc_mean_luminance.h @@ -40,7 +40,7 @@ public: Bound bound; double qLo; double qHi; - double yTarget; + Pwl yTarget; }; void configure(utils::Duration lineDuration, const CameraSensorHelper *sensorHelper); @@ -89,7 +89,7 @@ private: virtual double estimateLuminance(const double gain) const = 0; int parseRelativeLuminanceTarget(const YamlObject &tuningData); - void parseConstraint(const YamlObject &modeDict, int32_t id); + int parseConstraint(const YamlObject &modeDict, int32_t id); int parseConstraintModes(const YamlObject &tuningData); int parseExposureModes(const YamlObject &tuningData); double estimateInitialGain() const; diff --git a/src/ipa/rkisp1/algorithms/wdr.cpp b/src/ipa/rkisp1/algorithms/wdr.cpp index 45144913dcd8..ed81628c032c 100644 --- a/src/ipa/rkisp1/algorithms/wdr.cpp +++ b/src/ipa/rkisp1/algorithms/wdr.cpp @@ -175,7 +175,8 @@ int WideDynamicRange::configure(IPAContext &context, constraint.bound = AgcMeanLuminance::AgcConstraint::Bound::Upper; constraint.qHi = 1.0; constraint.qLo = 1.0 - exposureConstraintMaxBrightPixels_; - constraint.yTarget = exposureConstraintY_; + constraint.yTarget.clear(); + constraint.yTarget.append(0, exposureConstraintY_); return 0; }