[{"id":19007,"web_url":"https://patchwork.libcamera.org/comment/19007/","msgid":"<YSPYyTGWT13oCWpl@pendragon.ideasonboard.com>","date":"2021-08-23T17:20:09","subject":"Re: [libcamera-devel] [PATCH v1 6/7] ipa: ipu3: Introduce a new AGC\n\talgorithm","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Jean-Michel,\n\nThank you for the patch.\n\nOn Mon, Aug 23, 2021 at 02:49:36PM +0200, Jean-Michel Hautbois wrote:\n> The algorithm used until then is a simple one, let's introduce a new\n> one, based on the one used by the Raspberry Pi code. We can keep both\n> compiled, and chose to instanciate only one, which demonstrates the\n> modularity and ease to add functionnalities to the IPA.\n> \n> This algorithm uses the IPAFrameContext to get the latest AWB gains\n> applied and use them to estimate the next shutter time and gain values\n> to set.\n> \n> Signed-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\n> \n> diff --git a/src/ipa/ipu3/algorithms/agc_metering.cpp b/src/ipa/ipu3/algorithms/agc_metering.cpp\n> new file mode 100644\n> index 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\nThat's a good start, it's only missing the explanation of how the\nalgorithm works :-) It can be done with an overview here and additional\ncomments through the code.\n\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\nWould\n\n\t11,  9,  9,  9,  9,  9, 12,\n\t 7,  5,  3,  3,  3,  6,  8,\n\t 7,  5,  1,  0,  2,  6,  8,\n\t 7,  5,  4,  4,  4,  6,  8,\n\t13, 10, 10, 10, 10, 10, 14\n\nbe more readable ?\n\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\nYou can move this to the beginning of AgcMetering::generateStats().\n\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\nExtra space before todo.\n\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\nconstexpr\n\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 */\n> diff --git a/src/ipa/ipu3/algorithms/agc_metering.h b/src/ipa/ipu3/algorithms/agc_metering.h\n> new file mode 100644\n> index 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__ */\n> diff --git a/src/ipa/ipu3/algorithms/awb.cpp b/src/ipa/ipu3/algorithms/awb.cpp\n> index 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\nWhy ?\n\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\nSame here.\n\n>  \n>  \t/*\n>  \t * Generate a (kAwbStatsSizeX x kAwbStatsSizeY) array from the IPU3 grid which is\n> diff --git a/src/ipa/ipu3/algorithms/meson.build b/src/ipa/ipu3/algorithms/meson.build\n> index 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',\n> diff --git a/src/ipa/ipu3/ipa_context.h b/src/ipa/ipu3/ipa_context.h\n> index 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\nOr\n\n\t\tutils::Duration lineDuration;\n\nand no using directive.\n\nOn a side note, Duration should likely be moved out from utils.h, it's\nnot a base helper.\n\n> +\t} agc;\n> +\n>  \tstruct {\n>  \t\tipu3_uapi_grid_config bdsGrid;\n>  \t\tSize bdsOutputSize;\n> diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp\n> index 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>","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 015A1BD87D\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 23 Aug 2021 17:20:21 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 7678A688A3;\n\tMon, 23 Aug 2021 19:20:21 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id B45E96025B\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 23 Aug 2021 19:20:19 +0200 (CEST)","from pendragon.ideasonboard.com (62-78-145-57.bb.dnainternet.fi\n\t[62.78.145.57])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 24A0D2A5;\n\tMon, 23 Aug 2021 19:20:19 +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=\"eKl24TUm\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1629739219;\n\tbh=8sfy+DKt2Ibrxvkr/zLBM6P/8du21f3sRgS36IS4Opo=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=eKl24TUmLDBWvaJk+K60cufHqecqKspXI8DneSOTqQJwpctZ5O3vD+kXaZf7Js5IL\n\tlWQV1H+PWUR3g3vGVQ4OgjP4rNJuaq7qtIYepCtk4IGtOmNYTT3je2tJpyufZ3+S+m\n\t2DYVfJuqk9DPMQ/0/Rxgo+PjPocZTqRFMCi3ilb0=","Date":"Mon, 23 Aug 2021 20:20:09 +0300","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Jean-Michel Hautbois <jeanmichel.hautbois@ideasonboard.com>","Message-ID":"<YSPYyTGWT13oCWpl@pendragon.ideasonboard.com>","References":"<20210823124937.253539-1-jeanmichel.hautbois@ideasonboard.com>\n\t<20210823124937.253539-7-jeanmichel.hautbois@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20210823124937.253539-7-jeanmichel.hautbois@ideasonboard.com>","Subject":"Re: [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>","Cc":"libcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]