{"id":24079,"url":"https://patchwork.libcamera.org/api/1.1/patches/24079/?format=json","web_url":"https://patchwork.libcamera.org/patch/24079/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/1.1/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20250808141315.413839-9-stefan.klug@ideasonboard.com>","date":"2025-08-08T14:12:46","name":"[v2,08/16] libipa: exposure_mode_helper: Calculate quantization gain in splitExposure()","commit_ref":null,"pull_url":null,"state":"superseded","archived":false,"hash":"13ae3372216f08990cc0d76102fa4a19d43f5855","submitter":{"id":184,"url":"https://patchwork.libcamera.org/api/1.1/people/184/?format=json","name":"Stefan Klug","email":"stefan.klug@ideasonboard.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/24079/mbox/","series":[{"id":5364,"url":"https://patchwork.libcamera.org/api/1.1/series/5364/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=5364","date":"2025-08-08T14:12:38","name":"Implement WDR algorithm","version":2,"mbox":"https://patchwork.libcamera.org/series/5364/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/24079/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/24079/checks/","tags":{},"headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id B6CAFBDCC1\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri,  8 Aug 2025 14:14:08 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 7174769221;\n\tFri,  8 Aug 2025 16:14:08 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id D779F6921E\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri,  8 Aug 2025 16:14:06 +0200 (CEST)","from ideasonboard.com (unknown\n\t[IPv6:2a00:6020:448c:6c00:3ea1:35ac:90da:a221])\n\tby perceval.ideasonboard.com (Postfix) with UTF8SMTPSA id EE3C8208E; \n\tFri,  8 Aug 2025 16:13:16 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"i4uRovtf\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1754662397;\n\tbh=YWAo715sOGCFW3ImcQFI4ZffXYRkbNiSP01cyq3p3hg=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=i4uRovtf6oOfSqocOpGCYnIh1VZQhSbg86ePdysKJgmcWOidIKr45LeY8I2IePf7X\n\tuhOLcjrXdApcdMqIr+8JIv9JD+oiC3GdjoFuqLSkIiiVzoa5bfsd0loz7QnDOkuE/H\n\taFoEHNfOOqOPgRmK5vtdu/UEI/KwL7z6SyGUQwro=","From":"Stefan Klug <stefan.klug@ideasonboard.com>","To":"libcamera-devel@lists.libcamera.org","Cc":"Stefan Klug <stefan.klug@ideasonboard.com>","Subject":"[PATCH v2 08/16] libipa: exposure_mode_helper: Calculate\n\tquantization gain in splitExposure()","Date":"Fri,  8 Aug 2025 16:12:46 +0200","Message-ID":"<20250808141315.413839-9-stefan.klug@ideasonboard.com>","X-Mailer":"git-send-email 2.48.1","In-Reply-To":"<20250808141315.413839-1-stefan.klug@ideasonboard.com>","References":"<20250808141315.413839-1-stefan.klug@ideasonboard.com>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"},"content":"Calculate the error introduced by quantization as \"quantization gain\"\nand return it separately from splitExposure(). It is not included in the\ndigital gain, to not silently ignore the limits imposed by the AGC\nconfiguration.\n\nSigned-off-by: Stefan Klug <stefan.klug@ideasonboard.com>\n---\n src/ipa/ipu3/algorithms/agc.cpp         |  4 +--\n src/ipa/libipa/agc_mean_luminance.cpp   |  7 ++--\n src/ipa/libipa/agc_mean_luminance.h     |  2 +-\n src/ipa/libipa/exposure_mode_helper.cpp | 46 +++++++++++++++++--------\n src/ipa/libipa/exposure_mode_helper.h   |  2 +-\n src/ipa/rkisp1/algorithms/agc.cpp       |  9 ++---\n 6 files changed, 44 insertions(+), 26 deletions(-)","diff":"diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp\nindex 39d0aebb0838..da045640d569 100644\n--- a/src/ipa/ipu3/algorithms/agc.cpp\n+++ b/src/ipa/ipu3/algorithms/agc.cpp\n@@ -222,8 +222,8 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n \tutils::Duration effectiveExposureValue = exposureTime * analogueGain;\n \n \tutils::Duration newExposureTime;\n-\tdouble aGain, dGain;\n-\tstd::tie(newExposureTime, aGain, dGain) =\n+\tdouble aGain, qGain, dGain;\n+\tstd::tie(newExposureTime, aGain, qGain, dGain) =\n \t\tcalculateNewEv(context.activeState.agc.constraintMode,\n \t\t\t       context.activeState.agc.exposureMode, hist,\n \t\t\t       effectiveExposureValue);\ndiff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp\nindex 58c4dfc9add2..695453e7ceea 100644\n--- a/src/ipa/libipa/agc_mean_luminance.cpp\n+++ b/src/ipa/libipa/agc_mean_luminance.cpp\n@@ -566,11 +566,12 @@ utils::Duration AgcMeanLuminance::filterExposure(utils::Duration exposureValue)\n  *\n  * Calculate a new exposure value to try to obtain the target. The calculated\n  * exposure value is filtered to prevent rapid changes from frame to frame, and\n- * divided into exposure time, analogue and digital gain.\n+ * divided into exposure time, analogue, quantization and digital gain.\n  *\n- * \\return Tuple of exposure time, analogue gain, and digital gain\n+ * \\return Tuple of exposure time, analogue gain, quantization gain and digital\n+ * gain\n  */\n-std::tuple<utils::Duration, double, double>\n+std::tuple<utils::Duration, double, double, double>\n AgcMeanLuminance::calculateNewEv(uint32_t constraintModeIndex,\n \t\t\t\t uint32_t exposureModeIndex,\n \t\t\t\t const Histogram &yHist,\ndiff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h\nindex a7c7e006af42..741ccf986625 100644\n--- a/src/ipa/libipa/agc_mean_luminance.h\n+++ b/src/ipa/libipa/agc_mean_luminance.h\n@@ -68,7 +68,7 @@ public:\n \t\treturn controls_;\n \t}\n \n-\tstd::tuple<utils::Duration, double, double>\n+\tstd::tuple<utils::Duration, double, double, double>\n \tcalculateNewEv(uint32_t constraintModeIndex, uint32_t exposureModeIndex,\n \t\t       const Histogram &yHist, utils::Duration effectiveExposureValue);\n \ndiff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp\nindex 7d470ebe487c..79b8e6758faf 100644\n--- a/src/ipa/libipa/exposure_mode_helper.cpp\n+++ b/src/ipa/libipa/exposure_mode_helper.cpp\n@@ -178,14 +178,23 @@ double ExposureModeHelper::clampGain(double gain, double *quantizationGain) cons\n  * required exposure, the helper falls-back to simply maximising the exposure\n  * time first, followed by analogue gain, followed by digital gain.\n  *\n- * \\return Tuple of exposure time, analogue gain, and digital gain\n+ * During the calculations the gain missed due to quantization is recorded and\n+ * returned as quantization gain. The quantization gain is not included in the\n+ * digital gain. So to exactly apply the given exposure, both quantization gain\n+ * and digital gain must be applied.\n+ *\n+ * \\return Tuple of exposure time, analogue gain, quantization gain and digital\n+ * gain\n  */\n-std::tuple<utils::Duration, double, double>\n+std::tuple<utils::Duration, double, double, double>\n ExposureModeHelper::splitExposure(utils::Duration exposure) const\n {\n \tASSERT(maxExposureTime_);\n \tASSERT(maxGain_);\n \n+\tutils::Duration exposureTime;\n+\tdouble gain;\n+\tdouble quantGain;\n \tbool gainFixed = minGain_ == maxGain_;\n \tbool exposureTimeFixed = minExposureTime_ == maxExposureTime_;\n \n@@ -193,16 +202,20 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const\n \t * There's no point entering the loop if we cannot change either gain\n \t * nor exposure time anyway.\n \t */\n-\tif (exposureTimeFixed && gainFixed)\n-\t\treturn { minExposureTime_, minGain_, exposure / (minExposureTime_ * minGain_) };\n+\tif (exposureTimeFixed && gainFixed) {\n+\t\texposureTime = clampExposureTime(minExposureTime_, &quantGain);\n+\t\tgain = clampGain(minGain_ * quantGain, &quantGain);\n+\n+\t\treturn { exposureTime, gain, quantGain,\n+\t\t\t exposure / (minExposureTime_ * minGain_ * quantGain) };\n+\t}\n \n-\tutils::Duration exposureTime;\n \tdouble stageGain = clampGain(1.0);\n \tdouble lastStageGain = stageGain;\n-\tdouble gain;\n \n \tfor (unsigned int stage = 0; stage < gains_.size(); stage++) {\n-\t\tutils::Duration stageExposureTime = clampExposureTime(exposureTimes_[stage]);\n+\t\tutils::Duration stageExposureTime = clampExposureTime(exposureTimes_[stage],\n+\t\t\t\t\t\t\t\t      &quantGain);\n \t\tstageGain = clampGain(gains_[stage]);\n \n \t\t/*\n@@ -215,18 +228,20 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const\n \n \t\t/* Clamp the gain to lastStageGain and regulate exposureTime. */\n \t\tif (stageExposureTime * lastStageGain >= exposure) {\n-\t\t\texposureTime = clampExposureTime(exposure / lastStageGain);\n-\t\t\tgain = clampGain(exposure / exposureTime);\n+\t\t\texposureTime = clampExposureTime(exposure / lastStageGain, &quantGain);\n+\t\t\tgain = clampGain((exposure / exposureTime) * quantGain, &quantGain);\n \n-\t\t\treturn { exposureTime, gain, exposure / (exposureTime * gain) };\n+\t\t\treturn { exposureTime, gain, quantGain,\n+\t\t\t\t exposure / (exposureTime * gain * quantGain) };\n \t\t}\n \n \t\t/* Clamp the exposureTime to stageExposureTime and regulate gain. */\n \t\tif (stageExposureTime * stageGain >= exposure) {\n \t\t\texposureTime = stageExposureTime;\n-\t\t\tgain = clampGain(exposure / exposureTime);\n+\t\t\tgain = clampGain((exposure / exposureTime) * quantGain, &quantGain);\n \n-\t\t\treturn { exposureTime, gain, exposure / (exposureTime * gain) };\n+\t\t\treturn { exposureTime, gain, quantGain,\n+\t\t\t\t exposure / (exposureTime * gain * quantGain) };\n \t\t}\n \n \t\tlastStageGain = stageGain;\n@@ -239,10 +254,11 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const\n \t * stages to use then the default stageGain of 1.0 is used so that\n \t * exposure time is maxed before gain is touched at all.\n \t */\n-\texposureTime = clampExposureTime(exposure / stageGain);\n-\tgain = clampGain(exposure / exposureTime);\n+\texposureTime = clampExposureTime(exposure / stageGain, &quantGain);\n+\tgain = clampGain((exposure / exposureTime) * quantGain, &quantGain);\n \n-\treturn { exposureTime, gain, exposure / (exposureTime * gain) };\n+\treturn { exposureTime, gain, quantGain,\n+\t\t exposure / (exposureTime * gain * quantGain) };\n }\n \n /**\ndiff --git a/src/ipa/libipa/exposure_mode_helper.h b/src/ipa/libipa/exposure_mode_helper.h\nindex 8701fae9a26e..cc811e9fde18 100644\n--- a/src/ipa/libipa/exposure_mode_helper.h\n+++ b/src/ipa/libipa/exposure_mode_helper.h\n@@ -30,7 +30,7 @@ public:\n \tvoid setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime,\n \t\t       double minGain, double maxGain);\n \n-\tstd::tuple<utils::Duration, double, double>\n+\tstd::tuple<utils::Duration, double, double, double>\n \tsplitExposure(utils::Duration exposure) const;\n \n \tutils::Duration minExposureTime() const { return minExposureTime_; }\ndiff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp\nindex 35440b67e999..0a29326841fb 100644\n--- a/src/ipa/rkisp1/algorithms/agc.cpp\n+++ b/src/ipa/rkisp1/algorithms/agc.cpp\n@@ -567,15 +567,16 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n \tsetExposureCompensation(pow(2.0, frameContext.agc.exposureValue));\n \n \tutils::Duration newExposureTime;\n-\tdouble aGain, dGain;\n-\tstd::tie(newExposureTime, aGain, dGain) =\n+\tdouble aGain, qGain, dGain;\n+\tstd::tie(newExposureTime, aGain, qGain, dGain) =\n \t\tcalculateNewEv(frameContext.agc.constraintMode,\n \t\t\t       frameContext.agc.exposureMode,\n \t\t\t       hist, effectiveExposureValue);\n \n \tLOG(RkISP1Agc, Debug)\n-\t\t<< \"Divided up exposure time, analogue gain and digital gain are \"\n-\t\t<< newExposureTime << \", \" << aGain << \" and \" << dGain;\n+\t\t<< \"Divided up exposure time, analogue gain, quantization gain\"\n+\t\t<< \" and digital gain are \" << newExposureTime << \", \" << aGain\n+\t\t<< \", \" << qGain << \" and \" << dGain;\n \n \tIPAActiveState &activeState = context.activeState;\n \t/* Update the estimated exposure and gain. */\n","prefixes":["v2","08/16"]}