Show a patch.

GET /api/1.1/patches/12742/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 12742,
    "url": "https://patchwork.libcamera.org/api/1.1/patches/12742/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/12742/",
    "project": {
        "id": 1,
        "url": "https://patchwork.libcamera.org/api/1.1/projects/1/?format=api",
        "name": "libcamera",
        "link_name": "libcamera",
        "list_id": "libcamera_core",
        "list_email": "libcamera-devel@lists.libcamera.org",
        "web_url": "",
        "scm_url": "",
        "webscm_url": ""
    },
    "msgid": "<20210628202255.138874-8-jeanmichel.hautbois@ideasonboard.com>",
    "date": "2021-06-28T20:22:55",
    "name": "[libcamera-devel,v1,7/7] ipa: ipu3: Implement a new AGC algorithm",
    "commit_ref": null,
    "pull_url": null,
    "state": "changes-requested",
    "archived": false,
    "hash": "d84c62322b137cd5beb0f655fd0d08b2fb8755a9",
    "submitter": {
        "id": 75,
        "url": "https://patchwork.libcamera.org/api/1.1/people/75/?format=api",
        "name": "Jean-Michel Hautbois",
        "email": "jeanmichel.hautbois@ideasonboard.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/12742/mbox/",
    "series": [
        {
            "id": 2190,
            "url": "https://patchwork.libcamera.org/api/1.1/series/2190/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=2190",
            "date": "2021-06-28T20:22:48",
            "name": "ipa: Introduce a new open AGC",
            "version": 1,
            "mbox": "https://patchwork.libcamera.org/series/2190/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/12742/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/12742/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 8BFD4C321F\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 28 Jun 2021 20:23:09 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 398C4684E8;\n\tMon, 28 Jun 2021 22:23:09 +0200 (CEST)",
            "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 1B428684DC\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 28 Jun 2021 22:23:01 +0200 (CEST)",
            "from tatooine.ideasonboard.com (unknown\n\t[IPv6:2a01:e0a:169:7140:c3ad:78d0:405e:fc33])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id BE5F5E1A;\n\tMon, 28 Jun 2021 22:23:00 +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=\"cpGDCb8V\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1624911780;\n\tbh=Lsk0agF3Lgm+Jm+zpWoPiJqfgdBJjIlJsKNRe5UV9fw=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=cpGDCb8VRNaRDVNq8A+BQOGFgI6/3Wt3ZINre4IjPPYxUiyfOR3HCwD/lt5QSNK7O\n\trbFgB6DccbIKRWz+CdpP6vMkMnO64WnZ1d/ui1qxnWQKnbo/NEnpLOF3mDiUPqlJbL\n\tGY0eXcdFHdiIgcEhS0fFCwLbSp3LUE6PLVYHFO84=",
        "From": "Jean-Michel Hautbois <jeanmichel.hautbois@ideasonboard.com>",
        "To": "libcamera-devel@lists.libcamera.org",
        "Date": "Mon, 28 Jun 2021 22:22:55 +0200",
        "Message-Id": "<20210628202255.138874-8-jeanmichel.hautbois@ideasonboard.com>",
        "X-Mailer": "git-send-email 2.30.2",
        "In-Reply-To": "<20210628202255.138874-1-jeanmichel.hautbois@ideasonboard.com>",
        "References": "<20210628202255.138874-1-jeanmichel.hautbois@ideasonboard.com>",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit",
        "Subject": "[libcamera-devel] [PATCH v1 7/7] ipa: ipu3: Implement 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": "This one comes from RPi for most if it, except that we are not\nexchanging any metadata between algorithms for now.\nWhen process() is called, the current analogue gain and shutter time are\ncalculated. The AWB stats from IPU3 are then parsed to generate new\nstatistics dedicated to AGC. This new grid is used to estimate the\nluminance and each region is weighted. A default centered metering is\nused as is should be the most used one.\n\nAfter calculating weighted regions, analogue gain and shutter time are\ndivided up and the values are then sent back to the IPAIPU3.\n\nSigned-off-by: Jean-Michel Hautbois <jeanmichel.hautbois@ideasonboard.com>\n---\n src/ipa/ipu3/ipu3_agc.cpp | 276 ++++++++++++++++++++++++--------------\n src/ipa/ipu3/ipu3_agc.h   |  23 +++-\n 2 files changed, 196 insertions(+), 103 deletions(-)",
    "diff": "diff --git a/src/ipa/ipu3/ipu3_agc.cpp b/src/ipa/ipu3/ipu3_agc.cpp\nindex 042d67fa..0d421404 100644\n--- a/src/ipa/ipu3/ipu3_agc.cpp\n+++ b/src/ipa/ipu3/ipu3_agc.cpp\n@@ -27,37 +27,19 @@ namespace ipa::ipu3 {\n \n LOG_DEFINE_CATEGORY(IPU3Agc)\n \n-/* Number of frames to wait before calculating stats on minimum exposure */\n-static constexpr uint32_t kInitialFrameMinAECount = 4;\n-/* Number of frames to wait between new gain/exposure estimations */\n-static constexpr uint32_t kFrameSkipCount = 6;\n-\n-/* Maximum ISO value for analogue gain */\n-static constexpr uint32_t kMinISO = 100;\n-static constexpr uint32_t kMaxISO = 1500;\n-\n-/* Maximum analogue gain value\n- * \\todo grab it from a camera helper */\n-static constexpr uint32_t kMinGain = kMinISO / 100;\n-static constexpr uint32_t kMaxGain = kMaxISO / 100;\n-\n-/* \\todo use calculated value based on sensor */\n-static constexpr uint32_t kMinExposure = 1;\n-static constexpr uint32_t kMaxExposure = 1976;\n-\n /* Histogram constants */\n static constexpr uint32_t knumHistogramBins = 256;\n-static constexpr double kEvGainTarget = 0.5;\n \n-/* A cell is 8 bytes and contains averages for RGB values and saturation ratio */\n-static constexpr uint8_t kCellSize = 8;\n+/* seems to be a 8-bit pipeline */\n+static constexpr uint8_t kPipelineBits = 8;\n \n IPU3Agc::IPU3Agc()\n \t: frameCount_(0), lastFrame_(0), converged_(false),\n \t  updateControls_(false), iqMean_(0.0), gamma_(1.0),\n \t  lineDuration_(0s), maxExposureTime_(0s),\n \t  prevExposure_(0s), prevExposureNoDg_(0s),\n-\t  currentExposure_(0s), currentExposureNoDg_(0s)\n+\t  currentExposure_(0s), currentExposureNoDg_(0s),\n+\t  currentShutter_(1.0s), currentAnalogueGain_(1.0)\n {\n }\n \n@@ -83,55 +65,79 @@ void IPU3Agc::initialise(struct ipu3_uapi_grid_config &bdsGrid, const IPAConfigI\n \t}\n \tminGain_ = std::max(itGain->second.min().get<int32_t>(), 1);\n \tmaxGain_ = itGain->second.max().get<int32_t>();\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 \n-void IPU3Agc::processBrightness(const ipu3_uapi_stats_3a *stats)\n+/* Translate the IPU3 statistics into the default statistics region array */\n+void IPU3Agc::generateStats(const ipu3_uapi_stats_3a *stats)\n {\n-\tconst struct ipu3_uapi_grid_config statsAeGrid = stats->stats_4a_config.awb_config.grid;\n-\tRectangle aeRegion = { statsAeGrid.x_start,\n-\t\t\t       statsAeGrid.y_start,\n-\t\t\t       static_cast<unsigned int>(statsAeGrid.x_end - statsAeGrid.x_start) + 1,\n-\t\t\t       static_cast<unsigned int>(statsAeGrid.y_end - statsAeGrid.y_start) + 1 };\n-\tPoint topleft = aeRegion.topLeft();\n-\tint topleftX = topleft.x >> aeGrid_.block_width_log2;\n-\tint topleftY = topleft.y >> aeGrid_.block_height_log2;\n-\n-\t/* Align to the grid cell width and height */\n-\tuint32_t startX = topleftX << aeGrid_.block_width_log2;\n-\tuint32_t startY = topleftY * aeGrid_.width << aeGrid_.block_width_log2;\n-\tuint32_t endX = (startX + (aeRegion.size().width >> aeGrid_.block_width_log2)) << aeGrid_.block_width_log2;\n-\tuint32_t i, j;\n-\tuint32_t count = 0;\n-\n+\tuint32_t regionWidth = round(aeGrid_.width / static_cast<double>(kAgcStatsSizeX));\n+\tuint32_t regionHeight = round(aeGrid_.height / static_cast<double>(kAgcStatsSizeY));\n \tuint32_t hist[knumHistogramBins] = { 0 };\n-\tfor (j = topleftY;\n-\t     j < topleftY + (aeRegion.size().height >> aeGrid_.block_height_log2);\n-\t     j++) {\n-\t\tfor (i = startX + startY; i < endX + startY; i += kCellSize) {\n-\t\t\t/*\n-\t\t\t * The grid width (and maybe height) is not reliable.\n-\t\t\t * We observed a bit shift which makes the value 160 to be 32 in the stats grid.\n-\t\t\t * Use the one passed at init time.\n-\t\t\t */\n-\t\t\tif (stats->awb_raw_buffer.meta_data[i + 4 + j * aeGrid_.width] == 0) {\n-\t\t\t\tuint8_t Gr = stats->awb_raw_buffer.meta_data[i + 0 + j * aeGrid_.width];\n-\t\t\t\tuint8_t Gb = stats->awb_raw_buffer.meta_data[i + 3 + j * aeGrid_.width];\n-\t\t\t\thist[(Gr + Gb) / 2]++;\n-\t\t\t\tcount++;\n+\n+\tLOG(IPU3Agc, Debug) << \"[\" << (int)aeGrid_.width << \"x\" << (int)aeGrid_.height << \"] regions\"\n+\t\t\t    << \" scaled to [\" << regionWidth << \"x\" << regionHeight << \"] AGC stats\";\n+\n+\t/*\n+\t * Generate a (kAgcStatsSizeX x kAgcStatsSizeY) array from the IPU3 grid which is\n+\t * (aeGrid_.width x aeGrid_.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 * aeGrid_.width + i;\n+\t\t\tuint32_t cellX = (cellPosition / regionWidth) % kAgcStatsSizeX;\n+\t\t\tuint32_t cellY = ((cellPosition / aeGrid_.width) / regionHeight) % 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/* Limit the gamma effect for now */\n-\tgamma_ = 1.1;\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 IPU3Agc::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+\tawb_.blueGain = 1.0;\n+\tawb_.greenGain = 1.0;\n+\tawb_.redGain = 1.0;\n+}\n+\n void IPU3Agc::filterExposure()\n {\n-\tdouble speed = 0.2;\n+\tdouble speed = 0.08;\n \tif (prevExposure_ == 0s) {\n \t\t/* DG stands for digital gain.*/\n \t\tprevExposure_ = currentExposure_;\n@@ -156,65 +162,131 @@ void IPU3Agc::filterExposure()\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.4;\n+\tdouble fastReduceThreshold = 0.3;\n \tif (prevExposureNoDg_ <\n \t    prevExposure_ * fastReduceThreshold)\n \t\tprevExposureNoDg_ = prevExposure_ * fastReduceThreshold;\n \tLOG(IPU3Agc, Debug) << \"After filtering, total_exposure \" << prevExposure_;\n }\n \n-void IPU3Agc::lockExposureGain(uint32_t &exposure, double &gain)\n+double IPU3Agc::computeInitialY(IspStatsRegion regions[], AwbStatus const &awb,\n+\t\t\t\tdouble weights[], double gain)\n {\n-\tupdateControls_ = false;\n+\t/* Note how the calculation below means that equal weights give you\n+\t * \"average\" metering (i.e. all pixels equally important). */\n+\tdouble redSum = 0, greenSum = 0, blueSum = 0, pixelSum = 0;\n+\tfor (unsigned int i = 0; i < kNumAgcWeightedZones; i++) {\n+\t\tdouble counted = regions[i].counted;\n+\t\tdouble rSum = std::min(regions[i].rSum * gain, ((1 << kPipelineBits) - 1) * counted);\n+\t\tdouble gSum = std::min(regions[i].gSum * gain, ((1 << kPipelineBits) - 1) * counted);\n+\t\tdouble bSum = std::min(regions[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(IPU3Agc, Warning) << \"computeInitialY: pixel_sum is zero\";\n+\t\treturn 0;\n+\t}\n+\tdouble Y_sum = redSum * awb.redGain * .299 +\n+\t\t       greenSum * awb.greenGain * .587 +\n+\t\t       blueSum * awb.blueGain * .114;\n \n-\t/* Algorithm initialization should wait for first valid frames */\n-\t/* \\todo - have a number of frames given by DelayedControls ?\n-\t * - implement a function for IIR */\n-\tif ((frameCount_ < kInitialFrameMinAECount) || (frameCount_ - lastFrame_ < kFrameSkipCount))\n-\t\treturn;\n+\treturn Y_sum / pixelSum / (1 << kPipelineBits);\n+}\n \n-\t/* Are we correctly exposed ? */\n-\tif (std::abs(iqMean_ - kEvGainTarget * knumHistogramBins) <= 1) {\n-\t\tLOG(IPU3Agc, Debug) << \"!!! Good exposure with iqMean = \" << iqMean_;\n-\t\tconverged_ = true;\n-\t} else {\n-\t\tdouble newGain = kEvGainTarget * knumHistogramBins / iqMean_;\n-\n-\t\t/* extracted from Rpi::Agc::computeTargetExposure */\n-\t\tlibcamera::utils::Duration currentShutter = exposure * lineDuration_;\n-\t\tcurrentExposureNoDg_ = currentShutter * gain;\n-\t\tLOG(IPU3Agc, Debug) << \"Actual total exposure \" << currentExposureNoDg_\n-\t\t\t\t    << \" Shutter speed \" << currentShutter\n-\t\t\t\t    << \" Gain \" << gain;\n-\t\tcurrentExposure_ = currentExposureNoDg_ * newGain;\n-\t\tlibcamera::utils::Duration maxTotalExposure = maxExposureTime_ * kMaxGain;\n-\t\tcurrentExposure_ = std::min(currentExposure_, maxTotalExposure);\n-\t\tLOG(IPU3Agc, Debug) << \"Target total exposure \" << currentExposure_;\n-\n-\t\t/* \\todo: estimate if we need to desaturate */\n-\t\tfilterExposure();\n-\n-\t\tlibcamera::utils::Duration newExposure = 0.0s;\n-\t\tif (currentShutter < maxExposureTime_) {\n-\t\t\texposure = std::clamp(static_cast<uint32_t>(exposure * currentExposure_ / currentExposureNoDg_), kMinExposure, kMaxExposure);\n-\t\t\tnewExposure = currentExposure_ / exposure;\n-\t\t\tgain = std::clamp(static_cast<uint32_t>(gain * currentExposure_ / newExposure), kMinGain, kMaxGain);\n-\t\t\tupdateControls_ = true;\n-\t\t} else if (currentShutter >= maxExposureTime_) {\n-\t\t\tgain = std::clamp(static_cast<uint32_t>(gain * currentExposure_ / currentExposureNoDg_), kMinGain, kMaxGain);\n-\t\t\tnewExposure = currentExposure_ / gain;\n-\t\t\texposure = std::clamp(static_cast<uint32_t>(exposure * currentExposure_ / newExposure), kMinExposure, kMaxExposure);\n-\t\t\tupdateControls_ = true;\n+void IPU3Agc::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(IPU3Agc, Debug) << \"Target total_exposure \" << currentExposure_;\n+}\n+\n+void IPU3Agc::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\tLOG(IPU3Agc, Debug) << \"Adjust exposure \" << exposure * lineDuration_ << \" and gain \" << gain;\n \t}\n-\tlastFrame_ = frameCount_;\n+\tLOG(IPU3Agc, Debug) << \"Divided up shutter and gain are \" << shutterTime << \" and \"\n+\t\t\t    << analogueGain;\n+\n+\t/* \\todo: flickering avoidance ? */\n+\tfilteredShutter_ = shutterTime;\n+\tfilteredAnalogueGain_ = analogueGain;\n+}\n+\n+void IPU3Agc::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(agcStats_, awb_, weights_, currentGain);\n+\t\tdouble extra_gain = std::min(10.0, targetY / (initialY + .001));\n+\n+\t\tcurrentGain *= extra_gain;\n+\t\tLOG(IPU3Agc, Debug) << \"Initial Y \" << initialY << \" target \" << targetY\n+\t\t\t\t    << \" gives gain \" << currentGain;\n+\t\tif (extra_gain < 1.01)\n+\t\t\tbreak;\n+\t}\n+\n+\tdouble newGain = 128 / iqMean_;\n+\tLOG(IPU3Agc, Debug) << \"gain: \" << currentGain << \" new gain: \" << newGain;\n }\n \n-void IPU3Agc::process(const ipu3_uapi_stats_3a *stats, uint32_t &exposure, double &gain)\n+void IPU3Agc::process(const ipu3_uapi_stats_3a *stats, uint32_t &exposure, double &analogueGain)\n {\n-\tprocessBrightness(stats);\n-\tlockExposureGain(exposure, gain);\n+\tASSERT(stats->stats_3a_status.awb_en);\n+\tclearStats();\n+\tgenerateStats(stats);\n+\tcurrentShutter_ = exposure * lineDuration_;\n+\t/* \\todo: the gain needs to be calculated based on sensor informations */\n+\tcurrentAnalogueGain_ = analogueGain;\n+\tcurrentExposureNoDg_ = currentShutter_ * currentAnalogueGain_;\n+\n+\tdouble currentGain = 1;\n+\tcomputeGain(currentGain);\n+\tcomputeTargetExposure(currentGain);\n+\tfilterExposure();\n+\tdivideUpExposure();\n+\n+\texposure = filteredShutter_ / lineDuration_;\n+\tanalogueGain = filteredAnalogueGain_;\n+\n+\tupdateControls_ = true;\n \tframeCount_++;\n }\n \ndiff --git a/src/ipa/ipu3/ipu3_agc.h b/src/ipa/ipu3/ipu3_agc.h\nindex ce43c534..f1b1157b 100644\n--- a/src/ipa/ipu3/ipu3_agc.h\n+++ b/src/ipa/ipu3/ipu3_agc.h\n@@ -35,8 +35,8 @@ public:\n \tIPU3Agc();\n \t~IPU3Agc() = default;\n \n-\tvoid process(const ipu3_uapi_stats_3a *stats, uint32_t &exposure, double &gain);\n \tvoid initialise(struct ipu3_uapi_grid_config &bdsGrid, const IPAConfigInfo &configInfo);\n+\tvoid process(const ipu3_uapi_stats_3a *stats, uint32_t &exposure, double &analogueGain);\n \tbool converged() { return converged_; }\n \tbool updateControls() { return updateControls_; }\n \t/* \\todo Use a metadata exchange between IPAs */\n@@ -46,6 +46,17 @@ 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+\tvoid clearStats();\n+\tvoid generateZones(std::vector<RGB> &zones);\n+\tdouble computeInitialY(IspStatsRegion regions[], AwbStatus const &awb, double weights[], double gain);\n+\tvoid computeTargetExposure(double currentGain);\n+\tvoid divideUpExposure();\n+\tvoid computeGain(double &currentGain);\n+\n+\tAwbStatus awb_;\n+\tdouble weights_[kNumAgcWeightedZones];\n+\tIspStatsRegion agcStats_[kNumAgcWeightedZones];\n \n \tstruct ipu3_uapi_grid_config aeGrid_;\n \tControlInfoMap ctrls_;\n@@ -72,6 +83,16 @@ private:\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 \n } /* namespace ipa::ipu3 */\n",
    "prefixes": [
        "libcamera-devel",
        "v1",
        "7/7"
    ]
}