{"id":13441,"url":"https://patchwork.libcamera.org/api/1.1/patches/13441/?format=json","web_url":"https://patchwork.libcamera.org/patch/13441/","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":"<20210823124937.253539-7-jeanmichel.hautbois@ideasonboard.com>","date":"2021-08-23T12:49:36","name":"[libcamera-devel,v1,6/7] ipa: ipu3: Introduce a new AGC algorithm","commit_ref":null,"pull_url":null,"state":"superseded","archived":false,"hash":"71c2f7c7c275d8962766c0203bc9a4717b029ab3","submitter":{"id":75,"url":"https://patchwork.libcamera.org/api/1.1/people/75/?format=json","name":"Jean-Michel Hautbois","email":"jeanmichel.hautbois@ideasonboard.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/13441/mbox/","series":[{"id":2385,"url":"https://patchwork.libcamera.org/api/1.1/series/2385/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=2385","date":"2021-08-23T12:49:30","name":"IPU3: AWB and AGC improvements","version":1,"mbox":"https://patchwork.libcamera.org/series/2385/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/13441/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/13441/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 9EA9BC3241\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 23 Aug 2021 12:49:51 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 5CB7468909;\n\tMon, 23 Aug 2021 14:49:51 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id C1EC0688A2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 23 Aug 2021 14:49:43 +0200 (CEST)","from tatooine.ideasonboard.com (unknown\n\t[IPv6:2a01:e0a:169:7140:b920:776:a08c:1d1f])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 7EC1B2A5;\n\tMon, 23 Aug 2021 14:49:43 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"El0e0CTF\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1629722983;\n\tbh=MDGzhbsKcWFUHzJNmt/iJyqMDBA2mMekCD/YATXQXEw=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=El0e0CTFZFb9HhjV7SYNrbLZH5n6lu29seh9oQVnXBuLedLR61qIYPfoebBUoKZma\n\tRdIDyLEIdmeunQ3OgtWeSQjQgfRIInIyIOnw8WrbFnNzDA+J8juB0hFrIuS49T3AgA\n\tBEkRaaOX8fngakpALCrs923FLw4J53tK5Aj9uRro=","From":"Jean-Michel Hautbois <jeanmichel.hautbois@ideasonboard.com>","To":"libcamera-devel@lists.libcamera.org","Date":"Mon, 23 Aug 2021 14:49:36 +0200","Message-Id":"<20210823124937.253539-7-jeanmichel.hautbois@ideasonboard.com>","X-Mailer":"git-send-email 2.30.2","In-Reply-To":"<20210823124937.253539-1-jeanmichel.hautbois@ideasonboard.com>","References":"<20210823124937.253539-1-jeanmichel.hautbois@ideasonboard.com>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","Subject":"[libcamera-devel] [PATCH v1 6/7] ipa: ipu3: Introduce a new AGC\n\talgorithm","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":"The algorithm used until then is a simple one, let's introduce a new\none, based on the one used by the Raspberry Pi code. We can keep both\ncompiled, and chose to instanciate only one, which demonstrates the\nmodularity and ease to add functionnalities to the IPA.\n\nThis algorithm uses the IPAFrameContext to get the latest AWB gains\napplied and use them to estimate the next shutter time and gain values\nto set.\n\nSigned-off-by: Jean-Michel Hautbois <jeanmichel.hautbois@ideasonboard.com>\n---\n src/ipa/ipu3/algorithms/agc_metering.cpp | 336 +++++++++++++++++++++++\n src/ipa/ipu3/algorithms/agc_metering.h   |  81 ++++++\n src/ipa/ipu3/algorithms/awb.cpp          |   4 +-\n src/ipa/ipu3/algorithms/meson.build      |   1 +\n src/ipa/ipu3/ipa_context.h               |   8 +\n src/ipa/ipu3/ipu3.cpp                    |  12 +-\n 6 files changed, 438 insertions(+), 4 deletions(-)\n create mode 100644 src/ipa/ipu3/algorithms/agc_metering.cpp\n create mode 100644 src/ipa/ipu3/algorithms/agc_metering.h","diff":"diff --git a/src/ipa/ipu3/algorithms/agc_metering.cpp b/src/ipa/ipu3/algorithms/agc_metering.cpp\nnew file mode 100644\nindex 00000000..3479c269\n--- /dev/null\n+++ b/src/ipa/ipu3/algorithms/agc_metering.cpp\n@@ -0,0 +1,336 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Based on the implementation from the Raspberry Pi IPA,\n+ * Copyright (C) 2019-2021, Raspberry Pi (Trading) Ltd.\n+ * Copyright (C) 2021, Ideas On Board\n+ *\n+ * agc_metering.cpp - AGC/AEC control algorithm\n+ */\n+\n+#include \"agc_metering.h\"\n+#include \"awb.h\"\n+\n+#include <algorithm>\n+#include <cmath>\n+#include <numeric>\n+#include <stdint.h>\n+\n+#include <linux/v4l2-controls.h>\n+\n+#include <libcamera/base/log.h>\n+#include <libcamera/base/utils.h>\n+\n+#include \"libipa/histogram.h\"\n+\n+namespace libcamera {\n+\n+using namespace std::literals::chrono_literals;\n+\n+namespace ipa::ipu3::algorithms {\n+\n+LOG_DEFINE_CATEGORY(IPU3AgcMetering)\n+\n+/* Histogram constants */\n+static constexpr uint32_t knumHistogramBins = 256;\n+\n+/* seems to be a 10-bit pipeline */\n+static constexpr uint8_t kPipelineBits = 10;\n+\n+/* width of the AGC stats grid */\n+static constexpr uint32_t kAgcStatsSizeX = 7;\n+/* height of the AGC stats grid */\n+static constexpr uint32_t kAgcStatsSizeY = 5;\n+/* size of the AGC stats grid */\n+static constexpr uint32_t kAgcStatsSize = kAgcStatsSizeX * kAgcStatsSizeY;\n+\n+/**\n+ * The AGC algorithm uses region-based metering.\n+ * The image is divided up into regions as:\n+ *\n+ *\t+--+--------------+--+\n+ *\t|11|     9        |12|\n+ *\t+--+--+--------+--+--+\n+ *\t|  |  |   3    |  |  |\n+ *\t|  |  +--+--+--+  |  |\n+ *\t|7 |5 |1 |0 |2 |6 |8 |\n+ *\t|  |  +--+--+--+  |  |\n+ *\t|  |  |   4    |  |  |\n+ *\t+--+--+--------+--+--+\n+ *\t|13|     10       |14|\n+ *\t+--+--------------+--+\n+ * An average luminance value for the image is calculated according to:\n+ * \\f$Y = \\frac{\\sum_{i=0}^{i=kNumAgcWeightedZones}{kCenteredWeights_{i}Y_{i}}}\n+ * {\\sum_{i=0}^{i=kNumAgcWeightedZones}{w_{i}}}\\f$\n+ */\n+\n+/* Weight applied on each region */\n+static constexpr double kCenteredWeights[kNumAgcWeightedZones] = { 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 };\n+/* Region number repartition in the image */\n+static constexpr uint32_t kAgcStatsRegions[kAgcStatsSize] = {\n+\t11, 9, 9, 9, 9, 9, 12,\n+\t7, 5, 3, 3, 3, 6, 8,\n+\t7, 5, 1, 0, 2, 6, 8,\n+\t7, 5, 4, 4, 4, 6, 8,\n+\t13, 10, 10, 10, 10, 10, 14\n+};\n+\n+AgcMetering::AgcMetering()\n+\t: iqMean_(0.0), prevExposure_(0s), prevExposureNoDg_(0s),\n+\t  currentExposure_(0s), currentExposureNoDg_(0s), currentShutter_(1.0s),\n+\t  currentAnalogueGain_(1.0)\n+{\n+}\n+\n+int AgcMetering::configure(IPAContext &context, const IPAConfigInfo &configInfo)\n+{\n+\tcontext.configuration.agc.lineDuration = configInfo.sensorInfo.lineLength\n+\t\t\t\t\t       * (1.0s / configInfo.sensorInfo.pixelRate);\n+\n+\t/* \\todo: those values need to be extracted from a configuration file */\n+\tshutterConstraints_.push_back(100us);\n+\tshutterConstraints_.push_back(10ms);\n+\tshutterConstraints_.push_back(33ms);\n+\tgainConstraints_.push_back(1.0);\n+\tgainConstraints_.push_back(4.0);\n+\tgainConstraints_.push_back(16.0);\n+\n+\tfixedShutter_ = 0s;\n+\tfixedAnalogueGain_ = 0.0;\n+\n+\treturn 0;\n+}\n+\n+/* Translate the IPU3 statistics into the default statistics region array */\n+void AgcMetering::generateStats(const ipu3_uapi_stats_3a *stats,\n+\t\t\t\tconst ipu3_uapi_grid_config &grid)\n+{\n+\tuint32_t regionWidth = round(grid.width / static_cast<double>(kAgcStatsSizeX));\n+\tuint32_t regionHeight = round(grid.height / static_cast<double>(kAgcStatsSizeY));\n+\tuint32_t hist[knumHistogramBins] = { 0 };\n+\n+\tLOG(IPU3AgcMetering, Debug) << \"[\" << (int)grid.width\n+\t\t\t\t    << \"x\" << (int)grid.height << \"] regions\"\n+\t\t\t\t    << \" scaled to [\" << regionWidth\n+\t\t\t\t    << \"x\" << regionHeight << \"] AGC stats\";\n+\n+\t/*\n+\t * Generate a (kAgcStatsSizeX x kAgcStatsSizeY) array from the IPU3 grid\n+\t * which is (grid.width x grid.height).\n+\t */\n+\tfor (unsigned int j = 0; j < kAgcStatsSizeY * regionHeight; j++) {\n+\t\tfor (unsigned int i = 0; i < kAgcStatsSizeX * regionWidth; i++) {\n+\t\t\tuint32_t cellPosition = j * grid.width + i;\n+\t\t\tuint32_t cellX = (cellPosition / regionWidth)\n+\t\t\t\t       % kAgcStatsSizeX;\n+\t\t\tuint32_t cellY = ((cellPosition / grid.width) / regionHeight)\n+\t\t\t\t       % kAgcStatsSizeY;\n+\n+\t\t\tuint32_t agcRegionPosition = kAgcStatsRegions[cellY * kAgcStatsSizeX + cellX];\n+\t\t\tweights_[agcRegionPosition] = kCenteredWeights[agcRegionPosition];\n+\t\t\tcellPosition *= sizeof(Ipu3AwbCell);\n+\n+\t\t\t/* Cast the initial IPU3 structure to simplify the reading */\n+\t\t\tIpu3AwbCell *currentCell = reinterpret_cast<Ipu3AwbCell *>(const_cast<uint8_t *>(&stats->awb_raw_buffer.meta_data[cellPosition]));\n+\t\t\tif (currentCell->satRatio == 0) {\n+\t\t\t\t/* The cell is not saturated, use the current cell */\n+\t\t\t\tagcStats_[agcRegionPosition].counted++;\n+\t\t\t\tuint32_t greenValue = currentCell->greenRedAvg + currentCell->greenBlueAvg;\n+\t\t\t\thist[greenValue / 2]++;\n+\t\t\t\tagcStats_[agcRegionPosition].gSum += greenValue / 2;\n+\t\t\t\tagcStats_[agcRegionPosition].rSum += currentCell->redAvg;\n+\t\t\t\tagcStats_[agcRegionPosition].bSum += currentCell->blueAvg;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\t/* Estimate the quantile mean of the top 2% of the histogram */\n+\tiqMean_ = Histogram(Span<uint32_t>(hist)).interQuantileMean(0.98, 1.0);\n+}\n+\n+void AgcMetering::clearStats()\n+{\n+\tfor (unsigned int i = 0; i < kNumAgcWeightedZones; i++) {\n+\t\tagcStats_[i].bSum = 0;\n+\t\tagcStats_[i].rSum = 0;\n+\t\tagcStats_[i].gSum = 0;\n+\t\tagcStats_[i].counted = 0;\n+\t\tagcStats_[i].uncounted = 0;\n+\t}\n+}\n+\n+void AgcMetering::filterExposure()\n+{\n+\tdouble speed = 0.08;\n+\tif (prevExposure_ == 0s) {\n+\t\t/* DG stands for digital gain.*/\n+\t\tprevExposure_ = currentExposure_;\n+\t\tprevExposureNoDg_ = currentExposureNoDg_;\n+\t} else {\n+\t\t/*\n+\t\t * If we are close to the desired result, go faster to avoid\n+\t\t * making multiple micro-adjustments.\n+\t\t * \\ todo: Make this customisable?\n+\t\t */\n+\t\tif (prevExposure_ < 1.2 * currentExposure_ &&\n+\t\t    prevExposure_ > 0.8 * currentExposure_)\n+\t\t\tspeed = sqrt(speed);\n+\n+\t\tprevExposure_ = speed * currentExposure_ +\n+\t\t\t\tprevExposure_ * (1.0 - speed);\n+\t\tprevExposureNoDg_ = speed * currentExposureNoDg_ +\n+\t\t\t\tprevExposureNoDg_ * (1.0 - speed);\n+\t}\n+\t/*\n+\t * We can't let the no_dg exposure deviate too far below the\n+\t * total exposure, as there might not be enough digital gain available\n+\t * in the ISP to hide it (which will cause nasty oscillation).\n+\t */\n+\tdouble fastReduceThreshold = 0.3;\n+\tif (prevExposureNoDg_ <\n+\t    prevExposure_ * fastReduceThreshold)\n+\t\tprevExposureNoDg_ = prevExposure_ * fastReduceThreshold;\n+\tLOG(IPU3AgcMetering, Debug) << \"After filtering, total_exposure \" << prevExposure_;\n+}\n+\n+double AgcMetering::computeInitialY(double gain)\n+{\n+\t/*\n+\t * Note how the calculation below means that equal weights_ give you\n+\t * \"average\" metering (i.e. all pixels equally important).\n+\t */\n+\tdouble redSum = 0, greenSum = 0, blueSum = 0, pixelSum = 0;\n+\tfor (unsigned int i = 0; i < kNumAgcWeightedZones; i++) {\n+\t\tdouble counted = agcStats_[i].counted;\n+\t\tdouble rSum = std::min(agcStats_[i].rSum * gain, ((1 << kPipelineBits) - 1) * counted);\n+\t\tdouble gSum = std::min(agcStats_[i].gSum * gain, ((1 << kPipelineBits) - 1) * counted);\n+\t\tdouble bSum = std::min(agcStats_[i].bSum * gain, ((1 << kPipelineBits) - 1) * counted);\n+\t\tredSum += rSum * weights_[i];\n+\t\tgreenSum += gSum * weights_[i];\n+\t\tblueSum += bSum * weights_[i];\n+\t\tpixelSum += counted * weights_[i];\n+\t}\n+\tif (pixelSum == 0.0) {\n+\t\tLOG(IPU3AgcMetering, Warning) << \"computeInitialY: pixel_sum is zero\";\n+\t\treturn 0;\n+\t}\n+\tdouble Y_sum = redSum * awbStatus_.redGain * .299 +\n+\t\t       greenSum * awbStatus_.greenGain * .587 +\n+\t\t       blueSum * awbStatus_.blueGain * .114;\n+\n+\treturn Y_sum / pixelSum / (1 << kPipelineBits);\n+}\n+\n+void AgcMetering::computeTargetExposure(double gain)\n+{\n+\tcurrentExposure_ = currentExposureNoDg_ * gain;\n+\t/* \\todo: have a list of shutter speeds */\n+\tDuration maxShutterSpeed = shutterConstraints_.back();\n+\tDuration maxTotalExposure = maxShutterSpeed * gainConstraints_.back();\n+\n+\tcurrentExposure_ = std::min(currentExposure_, maxTotalExposure);\n+\tLOG(IPU3AgcMetering, Debug) << \"Target total_exposure \" << currentExposure_;\n+}\n+\n+void AgcMetering::divideUpExposure()\n+{\n+\tDuration exposureValue = prevExposure_;\n+\tDuration shutterTime;\n+\tdouble analogueGain;\n+\tshutterTime = shutterConstraints_[0];\n+\tshutterTime = std::min(shutterTime, shutterConstraints_.back());\n+\tanalogueGain = gainConstraints_[0];\n+\n+\tif (shutterTime * analogueGain < exposureValue) {\n+\t\tfor (unsigned int stage = 1;\n+\t\t     stage < gainConstraints_.size(); stage++) {\n+\t\t\tif (fixedShutter_ == 0s) {\n+\t\t\t\tDuration stageShutter =\n+\t\t\t\t\tstd::min(shutterConstraints_[stage], shutterConstraints_.back());\n+\t\t\t\tif (stageShutter * analogueGain >=\n+\t\t\t\t    exposureValue) {\n+\t\t\t\t\tshutterTime =\n+\t\t\t\t\t\texposureValue / analogueGain;\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t\tshutterTime = stageShutter;\n+\t\t\t}\n+\t\t\tif (fixedAnalogueGain_ == 0.0) {\n+\t\t\t\tif (gainConstraints_[stage] * shutterTime >= exposureValue) {\n+\t\t\t\t\tanalogueGain = exposureValue / shutterTime;\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t\tanalogueGain = gainConstraints_[stage];\n+\t\t\t}\n+\t\t}\n+\t}\n+\tLOG(IPU3AgcMetering, Debug) << \"Divided up shutter and gain are \"\n+\t\t\t\t    << shutterTime << \" and \" << analogueGain;\n+\n+\t/* \\todo: flickering avoidance ? */\n+\tfilteredShutter_ = shutterTime;\n+\tfilteredAnalogueGain_ = analogueGain;\n+}\n+\n+void AgcMetering::computeGain(double &currentGain)\n+{\n+\tcurrentGain = 1.0;\n+\t/* \\todo: the target Y needs to be grabbed from a configuration */\n+\tdouble targetY = 0.162;\n+\tfor (int i = 0; i < 8; i++) {\n+\t\tdouble initialY = computeInitialY(currentGain);\n+\t\tdouble extra_gain = std::min(10.0, targetY / (initialY + .001));\n+\n+\t\tcurrentGain *= extra_gain;\n+\t\tLOG(IPU3AgcMetering, Debug) << \"Initial Y \" << initialY\n+\t\t\t\t\t    << \" target \" << targetY\n+\t\t\t\t\t    << \" gives gain \" << currentGain;\n+\t\tif (extra_gain < 1.01)\n+\t\t\tbreak;\n+\t}\n+\n+\t/*\n+\t * Require the top 2% of pixels to lie at or below 0.8 in the pixel\n+\t * range (for a range from 0 to 255, it is 205). This lowers the\n+\t * exposure to stop pixels saturating.\n+\t */\n+\tdouble newGain = (0.8 * knumHistogramBins) / iqMean_;\n+\tLOG(IPU3AgcMetering, Debug) << \"gain: \" << currentGain\n+\t\t\t\t    << \" new gain: \" << newGain;\n+\n+\t/* Are we at the upper bound ? */\n+\tif (newGain < currentGain)\n+\t\tcurrentGain = newGain;\n+}\n+\n+void AgcMetering::process(IPAContext &context, const ipu3_uapi_stats_3a *stats)\n+{\n+\tASSERT(stats->stats_3a_status.awb_en);\n+\tclearStats();\n+\tgenerateStats(stats, context.configuration.grid.bdsGrid);\n+\n+\tcurrentShutter_ = context.frameContext.agc.exposure\n+\t\t\t* context.configuration.agc.lineDuration;\n+\tcurrentAnalogueGain_ = context.frameContext.agc.gain;\n+\n+\t/* Estimate the current exposure value */\n+\tcurrentExposureNoDg_ = currentShutter_ * currentAnalogueGain_;\n+\n+\t/* Get the current awb gains from IPAFrameContext */\n+\tawbStatus_.redGain = context.frameContext.awb.gains.red;\n+\tawbStatus_.greenGain = context.frameContext.awb.gains.green;\n+\tawbStatus_.blueGain = context.frameContext.awb.gains.blue;\n+\n+\tdouble currentGain = 1.0;\n+\tcomputeGain(currentGain);\n+\tcomputeTargetExposure(currentGain);\n+\tfilterExposure();\n+\tdivideUpExposure();\n+\n+\tcontext.frameContext.agc.exposure = filteredShutter_\n+\t\t\t\t\t  / context.configuration.agc.lineDuration;\n+\tcontext.frameContext.agc.gain = filteredAnalogueGain_;\n+}\n+\n+} /* namespace ipa::ipu3::algorithms */\n+\n+} /* namespace libcamera */\ndiff --git a/src/ipa/ipu3/algorithms/agc_metering.h b/src/ipa/ipu3/algorithms/agc_metering.h\nnew file mode 100644\nindex 00000000..c10846e7\n--- /dev/null\n+++ b/src/ipa/ipu3/algorithms/agc_metering.h\n@@ -0,0 +1,81 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Based on the implementation from the Raspberry Pi IPA,\n+ * Copyright (C) 2019-2021, Raspberry Pi (Trading) Ltd.\n+ * Copyright (C) 2021, Ideas On Board\n+ *\n+ * agc_metering.h - IPU3 AGC/AEC control algorithm\n+ */\n+#ifndef __LIBCAMERA_IPU3_AGC_H__\n+#define __LIBCAMERA_IPU3_AGC_H__\n+\n+#include <linux/intel-ipu3.h>\n+\n+#include <libcamera/base/utils.h>\n+\n+#include <libcamera/geometry.h>\n+\n+#include \"algorithm.h\"\n+#include \"awb.h\"\n+\n+namespace libcamera {\n+\n+struct IPACameraSensorInfo;\n+\n+namespace ipa::ipu3::algorithms {\n+\n+using utils::Duration;\n+\n+/* Number of weighted zones for metering */\n+static constexpr uint32_t kNumAgcWeightedZones = 15;\n+\n+class AgcMetering : public Algorithm\n+{\n+public:\n+\tAgcMetering();\n+\t~AgcMetering() = default;\n+\n+\tint configure(IPAContext &context, const IPAConfigInfo &configInfo) override;\n+\tvoid process(IPAContext &context, const ipu3_uapi_stats_3a *stats) override;\n+\n+private:\n+\tvoid processBrightness(const ipu3_uapi_stats_3a *stats);\n+\tvoid filterExposure();\n+\tvoid lockExposureGain(uint32_t &exposure, double &gain);\n+\tvoid generateStats(const ipu3_uapi_stats_3a *stats,\n+\t\t\t   const ipu3_uapi_grid_config &grid);\n+\tvoid clearStats();\n+\tvoid generateZones(std::vector<RGB> &zones);\n+\tdouble computeInitialY(double gain);\n+\tvoid computeTargetExposure(double currentGain);\n+\tvoid divideUpExposure();\n+\tvoid computeGain(double &currentGain);\n+\n+\tdouble weights_[kNumAgcWeightedZones];\n+\tstruct IspStatsRegion agcStats_[kNumAgcWeightedZones];\n+\n+\tdouble iqMean_;\n+\n+\tDuration prevExposure_;\n+\tDuration prevExposureNoDg_;\n+\tDuration currentExposure_;\n+\tDuration currentExposureNoDg_;\n+\n+\tDuration currentShutter_;\n+\tstd::vector<Duration> shutterConstraints_;\n+\tDuration fixedShutter_;\n+\tDuration filteredShutter_;\n+\n+\tdouble currentAnalogueGain_;\n+\tstd::vector<double> gainConstraints_;\n+\tdouble fixedAnalogueGain_;\n+\tdouble filteredAnalogueGain_;\n+\n+\tstruct AwbStatus awbStatus_;\n+};\n+\n+} /* namespace ipa::ipu3::algorithms */\n+\n+} /* namespace libcamera */\n+\n+#endif /* __LIBCAMERA_IPU3_AGC_H__ */\ndiff --git a/src/ipa/ipu3/algorithms/awb.cpp b/src/ipa/ipu3/algorithms/awb.cpp\nindex 8e4230b5..294871b1 100644\n--- a/src/ipa/ipu3/algorithms/awb.cpp\n+++ b/src/ipa/ipu3/algorithms/awb.cpp\n@@ -138,7 +138,7 @@ static const struct ipu3_uapi_ccm_mat_config imguCssCcmDefault = {\n };\n \n /* Minimum level of green in a given zone */\n-static constexpr uint32_t kMinGreenLevelInZone = 16;\n+static constexpr uint32_t kMinGreenLevelInZone = 32;\n \n Awb::Awb()\n \t: Algorithm()\n@@ -213,7 +213,7 @@ void Awb::generateAwbStats(const ipu3_uapi_stats_3a *stats,\n \t * for it to be relevant.\n \t * \\todo This proportion could be configured.\n \t */\n-\tminZonesCounted_ = (regionWidth * regionHeight) * 80 / 100;\n+\tminZonesCounted_ = (regionWidth * regionHeight) * 50 / 100;\n \n \t/*\n \t * Generate a (kAwbStatsSizeX x kAwbStatsSizeY) array from the IPU3 grid which is\ndiff --git a/src/ipa/ipu3/algorithms/meson.build b/src/ipa/ipu3/algorithms/meson.build\nindex 807b53ea..f31b2070 100644\n--- a/src/ipa/ipu3/algorithms/meson.build\n+++ b/src/ipa/ipu3/algorithms/meson.build\n@@ -2,6 +2,7 @@\n \n ipu3_ipa_algorithms = files([\n     'agc_mean.cpp',\n+    'agc_metering.cpp',\n     'algorithm.cpp',\n     'awb.cpp',\n     'tone_mapping.cpp',\ndiff --git a/src/ipa/ipu3/ipa_context.h b/src/ipa/ipu3/ipa_context.h\nindex 9d9444dc..0a987da4 100644\n--- a/src/ipa/ipu3/ipa_context.h\n+++ b/src/ipa/ipu3/ipa_context.h\n@@ -10,13 +10,21 @@\n \n #include <linux/intel-ipu3.h>\n \n+#include <libcamera/base/utils.h>\n+\n #include <libcamera/geometry.h>\n \n namespace libcamera {\n \n namespace ipa::ipu3 {\n \n+using utils::Duration;\n+\n struct IPASessionConfiguration {\n+\tstruct {\n+\t\tDuration lineDuration;\n+\t} agc;\n+\n \tstruct {\n \t\tipu3_uapi_grid_config bdsGrid;\n \t\tSize bdsOutputSize;\ndiff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp\nindex b73c4f7b..1425b344 100644\n--- a/src/ipa/ipu3/ipu3.cpp\n+++ b/src/ipa/ipu3/ipu3.cpp\n@@ -29,7 +29,7 @@\n \n #include \"libcamera/internal/mapped_framebuffer.h\"\n \n-#include \"algorithms/agc_mean.h\"\n+#include \"algorithms/agc_metering.h\"\n #include \"algorithms/algorithm.h\"\n #include \"algorithms/awb.h\"\n #include \"algorithms/tone_mapping.h\"\n@@ -81,6 +81,14 @@\n  * are run. This needs to be turned into real per-frame data storage.\n  */\n \n+/**\n+ * \\struct IPASessionConfiguration::agc\n+ * \\brief AGC configuration of the IPA\n+ *\n+ * \\var IPASessionConfiguration::agc::lineDuration\n+ * \\brief Duration of one line dependant on the sensor configuration\n+ */\n+\n /**\n  * \\struct IPASessionConfiguration::grid\n  * \\brief Grid configuration of the IPA\n@@ -266,7 +274,7 @@ int IPAIPU3::init(const IPASettings &settings,\n \t*ipaControls = ControlInfoMap(std::move(controls), controls::controls);\n \n \t/* Construct our Algorithms */\n-\talgorithms_.push_back(std::make_unique<algorithms::AgcMean>());\n+\talgorithms_.push_back(std::make_unique<algorithms::AgcMetering>());\n \talgorithms_.push_back(std::make_unique<algorithms::Awb>());\n \talgorithms_.push_back(std::make_unique<algorithms::ToneMapping>());\n \n","prefixes":["libcamera-devel","v1","6/7"]}