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; }