Show a patch.

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

{
    "id": 21190,
    "url": "https://patchwork.libcamera.org/api/1.1/patches/21190/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/21190/",
    "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": "<20240906120927.4071508-18-mzamazal@redhat.com>",
    "date": "2024-09-06T12:09:26",
    "name": "[v6,17/18] libcamera: software_isp: Move exposure+gain to an algorithm module",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": false,
    "hash": "14f151f2361eae8d1124a2df25a33e0e5f0e96b4",
    "submitter": {
        "id": 177,
        "url": "https://patchwork.libcamera.org/api/1.1/people/177/?format=api",
        "name": "Milan Zamazal",
        "email": "mzamazal@redhat.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/21190/mbox/",
    "series": [
        {
            "id": 4566,
            "url": "https://patchwork.libcamera.org/api/1.1/series/4566/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=4566",
            "date": "2024-09-06T12:09:09",
            "name": "Software ISP refactoring",
            "version": 6,
            "mbox": "https://patchwork.libcamera.org/series/4566/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/21190/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/21190/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 E373EBF415\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri,  6 Sep 2024 12:10:55 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 200B463508;\n\tFri,  6 Sep 2024 14:10:54 +0200 (CEST)",
            "from us-smtp-delivery-124.mimecast.com\n\t(us-smtp-delivery-124.mimecast.com [170.10.133.124])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 5E0EA634F2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri,  6 Sep 2024 14:10:34 +0200 (CEST)",
            "from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com\n\t(ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63])\n\tby relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3,\n\tcipher=TLS_AES_256_GCM_SHA384) id us-mta-368-aEzC773UPjyl__zDUtdJyA-1;\n\tFri, 06 Sep 2024 08:10:32 -0400",
            "from mx-prod-int-02.mail-002.prod.us-west-2.aws.redhat.com\n\t(mx-prod-int-02.mail-002.prod.us-west-2.aws.redhat.com\n\t[10.30.177.15])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\tkey-exchange X25519 server-signature RSA-PSS (2048 bits)\n\tserver-digest SHA256) (No client certificate requested)\n\tby mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix)\n\twith ESMTPS id 1BC4E1955E99; Fri,  6 Sep 2024 12:10:31 +0000 (UTC)",
            "from nuthatch.redhat.com (unknown [10.45.224.65])\n\tby mx-prod-int-02.mail-002.prod.us-west-2.aws.redhat.com (Postfix)\n\twith ESMTP id B26851956086; Fri,  6 Sep 2024 12:10:28 +0000 (UTC)"
        ],
        "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=redhat.com header.i=@redhat.com\n\theader.b=\"U+tBmYQe\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n\ts=mimecast20190719; t=1725624633;\n\th=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n\tto:to:cc:cc:mime-version:mime-version:content-type:content-type:\n\tcontent-transfer-encoding:content-transfer-encoding:\n\tin-reply-to:in-reply-to:references:references;\n\tbh=UtWW8gYIye/v7M4umWI8owvCfaPQ64KoDOp52JRvbR8=;\n\tb=U+tBmYQeRC0t5cWrlxdqOHrxXtTGgTHf0Ku1tPWVY0p7RqsfdU8cD52D1QlMyIdi4DxKcZ\n\tzijxeFu4OZrnjsKNwa3FafqmZsTwyHxL/DAbTB6V2pGkJuOC/9/tCU9uH/bWEQHNKQ7UnU\n\t9+GfL6pZirQuudoVUiFUNqfES2mJPJY=",
        "X-MC-Unique": "aEzC773UPjyl__zDUtdJyA-1",
        "From": "Milan Zamazal <mzamazal@redhat.com>",
        "To": "libcamera-devel@lists.libcamera.org",
        "Cc": "Milan Zamazal <mzamazal@redhat.com>,\n\tUmang Jain <umang.jain@ideasonboard.com>,\n\tLaurent Pinchart <laurent.pinchart@ideasonboard.com>,\n\tDaniel Scally <dan.scally@ideasonboard.com>",
        "Subject": "[PATCH v6 17/18] libcamera: software_isp: Move exposure+gain to an\n\talgorithm module",
        "Date": "Fri,  6 Sep 2024 14:09:26 +0200",
        "Message-ID": "<20240906120927.4071508-18-mzamazal@redhat.com>",
        "In-Reply-To": "<20240906120927.4071508-1-mzamazal@redhat.com>",
        "References": "<20240906120927.4071508-1-mzamazal@redhat.com>",
        "MIME-Version": "1.0",
        "X-Scanned-By": "MIMEDefang 3.0 on 10.30.177.15",
        "X-Mimecast-Spam-Score": "0",
        "X-Mimecast-Originator": "redhat.com",
        "Content-Transfer-Encoding": "8bit",
        "Content-Type": "text/plain; charset=\"US-ASCII\"; x-default=true",
        "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 is the last step to fully convert software ISP to Algorithm-based\nprocessing.\n\nThe newly introduced frameContext.sensor properties are set, and the\nupdated code moved, before calling Algorithm::process() to have the\nvalues up-to-date in stats processing.\n\nResolves software ISP TODO #10.\n\nSigned-off-by: Milan Zamazal <mzamazal@redhat.com>\n---\n src/ipa/simple/algorithms/agc.cpp     | 139 +++++++++++++++++++++++\n src/ipa/simple/algorithms/agc.h       |  33 ++++++\n src/ipa/simple/algorithms/meson.build |   1 +\n src/ipa/simple/data/uncalibrated.yaml |   1 +\n src/ipa/simple/ipa_context.cpp        |  11 ++\n src/ipa/simple/ipa_context.h          |  18 +++\n src/ipa/simple/soft_simple.cpp        | 157 +++++---------------------\n src/libcamera/software_isp/TODO       |  10 --\n 8 files changed, 233 insertions(+), 137 deletions(-)\n create mode 100644 src/ipa/simple/algorithms/agc.cpp\n create mode 100644 src/ipa/simple/algorithms/agc.h",
    "diff": "diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp\nnew file mode 100644\nindex 00000000..becfb99f\n--- /dev/null\n+++ b/src/ipa/simple/algorithms/agc.cpp\n@@ -0,0 +1,139 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2024, Red Hat Inc.\n+ *\n+ * Exposure and gain\n+ */\n+\n+#include \"agc.h\"\n+\n+#include <stdint.h>\n+\n+#include <libcamera/base/log.h>\n+\n+namespace libcamera {\n+\n+LOG_DEFINE_CATEGORY(IPASoftExposure)\n+\n+namespace ipa::soft::algorithms {\n+\n+/*\n+ * The number of bins to use for the optimal exposure calculations.\n+ */\n+static constexpr unsigned int kExposureBinsCount = 5;\n+\n+/*\n+ * The exposure is optimal when the mean sample value of the histogram is\n+ * in the middle of the range.\n+ */\n+static constexpr float kExposureOptimal = kExposureBinsCount / 2.0;\n+\n+/*\n+ * The below value implements the hysteresis for the exposure adjustment.\n+ * It is small enough to have the exposure close to the optimal, and is big\n+ * enough to prevent the exposure from wobbling around the optimal value.\n+ */\n+static constexpr float kExposureSatisfactory = 0.2;\n+\n+Agc::Agc()\n+{\n+}\n+\n+void Agc::updateExposure(IPAContext &context, double exposureMSV)\n+{\n+\t/*\n+\t * kExpDenominator of 10 gives ~10% increment/decrement;\n+\t * kExpDenominator of 5 - about ~20%\n+\t */\n+\tstatic constexpr uint8_t kExpDenominator = 10;\n+\tstatic constexpr uint8_t kExpNumeratorUp = kExpDenominator + 1;\n+\tstatic constexpr uint8_t kExpNumeratorDown = kExpDenominator - 1;\n+\n+\tdouble next;\n+\tint32_t &exposure = context.activeState.agc.exposure;\n+\tdouble &again = context.activeState.agc.again;\n+\n+\tif (exposureMSV < kExposureOptimal - kExposureSatisfactory) {\n+\t\tnext = exposure * kExpNumeratorUp / kExpDenominator;\n+\t\tif (next - exposure < 1)\n+\t\t\texposure += 1;\n+\t\telse\n+\t\t\texposure = next;\n+\t\tif (exposure >= context.configuration.agc.exposureMax) {\n+\t\t\tnext = again * kExpNumeratorUp / kExpDenominator;\n+\t\t\tif (next - again < context.configuration.agc.againMinStep)\n+\t\t\t\tagain += context.configuration.agc.againMinStep;\n+\t\t\telse\n+\t\t\t\tagain = next;\n+\t\t}\n+\t}\n+\n+\tif (exposureMSV > kExposureOptimal + kExposureSatisfactory) {\n+\t\tif (exposure == context.configuration.agc.exposureMax &&\n+\t\t    again > context.configuration.agc.againMin) {\n+\t\t\tnext = again * kExpNumeratorDown / kExpDenominator;\n+\t\t\tif (again - next < context.configuration.agc.againMinStep)\n+\t\t\t\tagain -= context.configuration.agc.againMinStep;\n+\t\t\telse\n+\t\t\t\tagain = next;\n+\t\t} else {\n+\t\t\tnext = exposure * kExpNumeratorDown / kExpDenominator;\n+\t\t\tif (exposure - next < 1)\n+\t\t\t\texposure -= 1;\n+\t\t\telse\n+\t\t\t\texposure = next;\n+\t\t}\n+\t}\n+\n+\texposure = std::clamp(exposure, context.configuration.agc.exposureMin,\n+\t\t\t      context.configuration.agc.exposureMax);\n+\tagain = std::clamp(again, context.configuration.agc.againMin,\n+\t\t\t   context.configuration.agc.againMax);\n+\n+\tLOG(IPASoftExposure, Debug)\n+\t\t<< \"exposureMSV \" << exposureMSV\n+\t\t<< \" exp \" << exposure << \" again \" << again;\n+}\n+\n+void Agc::process(IPAContext &context,\n+\t\t  [[maybe_unused]] const uint32_t frame,\n+\t\t  [[maybe_unused]] IPAFrameContext &frameContext,\n+\t\t  const SwIspStats *stats,\n+\t\t  [[maybe_unused]] ControlList &metadata)\n+{\n+\t/*\n+\t * Calculate Mean Sample Value (MSV) according to formula from:\n+\t * https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf\n+\t */\n+\tconst auto &histogram = stats->yHistogram;\n+\tconst unsigned int blackLevelHistIdx =\n+\t\tcontext.activeState.blc.level * SwIspStats::kYHistogramSize;\n+\tconst unsigned int histogramSize =\n+\t\tSwIspStats::kYHistogramSize - blackLevelHistIdx;\n+\tconst unsigned int yHistValsPerBin = histogramSize / kExposureBinsCount;\n+\tconst unsigned int yHistValsPerBinMod =\n+\t\thistogramSize / (histogramSize % kExposureBinsCount + 1);\n+\tint exposureBins[kExposureBinsCount] = {};\n+\tunsigned int denom = 0;\n+\tunsigned int num = 0;\n+\n+\tfor (unsigned int i = 0; i < histogramSize; i++) {\n+\t\tunsigned int idx = (i - (i / yHistValsPerBinMod)) / yHistValsPerBin;\n+\t\texposureBins[idx] += histogram[blackLevelHistIdx + i];\n+\t}\n+\n+\tfor (unsigned int i = 0; i < kExposureBinsCount; i++) {\n+\t\tLOG(IPASoftExposure, Debug) << i << \": \" << exposureBins[i];\n+\t\tdenom += exposureBins[i];\n+\t\tnum += exposureBins[i] * (i + 1);\n+\t}\n+\n+\tfloat exposureMSV = (denom == 0 ? 0 : static_cast<float>(num) / denom);\n+\tupdateExposure(context, exposureMSV);\n+}\n+\n+REGISTER_IPA_ALGORITHM(Agc, \"Agc\")\n+\n+} /* namespace ipa::soft::algorithms */\n+\n+} /* namespace libcamera */\ndiff --git a/src/ipa/simple/algorithms/agc.h b/src/ipa/simple/algorithms/agc.h\nnew file mode 100644\nindex 00000000..ad5fca9f\n--- /dev/null\n+++ b/src/ipa/simple/algorithms/agc.h\n@@ -0,0 +1,33 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2024, Red Hat Inc.\n+ *\n+ * Exposure and gain\n+ */\n+\n+#pragma once\n+\n+#include \"algorithm.h\"\n+\n+namespace libcamera {\n+\n+namespace ipa::soft::algorithms {\n+\n+class Agc : public Algorithm\n+{\n+public:\n+\tAgc();\n+\t~Agc() = default;\n+\n+\tvoid process(IPAContext &context, const uint32_t frame,\n+\t\t     IPAFrameContext &frameContext,\n+\t\t     const SwIspStats *stats,\n+\t\t     ControlList &metadata) override;\n+\n+private:\n+\tvoid updateExposure(IPAContext &context, double exposureMSV);\n+};\n+\n+} /* namespace ipa::soft::algorithms */\n+\n+} /* namespace libcamera */\ndiff --git a/src/ipa/simple/algorithms/meson.build b/src/ipa/simple/algorithms/meson.build\nindex f575611e..37a2eb53 100644\n--- a/src/ipa/simple/algorithms/meson.build\n+++ b/src/ipa/simple/algorithms/meson.build\n@@ -2,6 +2,7 @@\n \n soft_simple_ipa_algorithms = files([\n     'awb.cpp',\n+    'agc.cpp',\n     'blc.cpp',\n     'lut.cpp',\n ])\ndiff --git a/src/ipa/simple/data/uncalibrated.yaml b/src/ipa/simple/data/uncalibrated.yaml\nindex 893a0b92..3f147112 100644\n--- a/src/ipa/simple/data/uncalibrated.yaml\n+++ b/src/ipa/simple/data/uncalibrated.yaml\n@@ -6,4 +6,5 @@ algorithms:\n   - BlackLevel:\n   - Awb:\n   - Lut:\n+  - Agc:\n ...\ndiff --git a/src/ipa/simple/ipa_context.cpp b/src/ipa/simple/ipa_context.cpp\nindex 5fa492cb..3f94bbeb 100644\n--- a/src/ipa/simple/ipa_context.cpp\n+++ b/src/ipa/simple/ipa_context.cpp\n@@ -77,6 +77,17 @@ namespace libcamera::ipa::soft {\n  * \\brief Gain of blue color\n  */\n \n+/**\n+ * \\var IPAActiveState::agc\n+ * \\brief Context for the AGC algorithm\n+ *\n+ * \\var IPAActiveState::agc.exposure\n+ * \\brief Current exposure value\n+ *\n+ * \\var IPAActiveState::agc.again\n+ * \\brief Current analog gain value\n+ */\n+\n /**\n  * \\var IPAActiveState::gamma\n  * \\brief Context for gamma in the Colors algorithm\ndiff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h\nindex d09de9a9..ad869f64 100644\n--- a/src/ipa/simple/ipa_context.h\n+++ b/src/ipa/simple/ipa_context.h\n@@ -9,6 +9,8 @@\n \n #include <array>\n \n+#include <libcamera/controls.h>\n+\n #include <libipa/fc_queue.h>\n \n namespace libcamera {\n@@ -17,6 +19,13 @@ namespace ipa::soft {\n \n struct IPASessionConfiguration {\n \tfloat gamma;\n+\tstruct {\n+\t\tint32_t exposureMin, exposureMax;\n+\t\tdouble againMin, againMax, againMinStep;\n+\t} agc;\n+\tstruct {\n+\t\tdouble level;\n+\t} black;\n };\n \n struct IPAActiveState {\n@@ -30,6 +39,11 @@ struct IPAActiveState {\n \t\tdouble blue;\n \t} gains;\n \n+\tstruct {\n+\t\tint32_t exposure;\n+\t\tdouble again;\n+\t} agc;\n+\n \tstatic constexpr unsigned int kGammaLookupSize = 1024;\n \tstruct {\n \t\tstd::array<double, kGammaLookupSize> gammaTable;\n@@ -38,6 +52,10 @@ struct IPAActiveState {\n };\n \n struct IPAFrameContext : public FrameContext {\n+\tstruct {\n+\t\tuint32_t exposure;\n+\t\tdouble gain;\n+\t} sensor;\n };\n \n struct IPAContext {\ndiff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp\nindex 60310a8e..b28c7039 100644\n--- a/src/ipa/simple/soft_simple.cpp\n+++ b/src/ipa/simple/soft_simple.cpp\n@@ -34,23 +34,6 @@ LOG_DEFINE_CATEGORY(IPASoft)\n \n namespace ipa::soft {\n \n-/*\n- * The number of bins to use for the optimal exposure calculations.\n- */\n-static constexpr unsigned int kExposureBinsCount = 5;\n-\n-/*\n- * The exposure is optimal when the mean sample value of the histogram is\n- * in the middle of the range.\n- */\n-static constexpr float kExposureOptimal = kExposureBinsCount / 2.0;\n-\n-/*\n- * The below value implements the hysteresis for the exposure adjustment.\n- * It is small enough to have the exposure close to the optimal, and is big\n- * enough to prevent the exposure from wobbling around the optimal value.\n- */\n-static constexpr float kExposureSatisfactory = 0.2;\n /* Maximum number of frame contexts to be held */\n static constexpr uint32_t kMaxFrameContexts = 16;\n \n@@ -58,8 +41,7 @@ class IPASoftSimple : public ipa::soft::IPASoftInterface, public Module\n {\n public:\n \tIPASoftSimple()\n-\t\t: params_(nullptr), stats_(nullptr),\n-\t\t  context_({ {}, {}, { kMaxFrameContexts } })\n+\t\t: context_({ {}, {}, { kMaxFrameContexts } })\n \t{\n \t}\n \n@@ -92,11 +74,6 @@ private:\n \n \t/* Local parameter storage */\n \tstruct IPAContext context_;\n-\n-\tint32_t exposureMin_, exposureMax_;\n-\tint32_t exposure_;\n-\tdouble againMin_, againMax_, againMinStep_;\n-\tdouble again_;\n };\n \n IPASoftSimple::~IPASoftSimple()\n@@ -207,20 +184,23 @@ int IPASoftSimple::configure(const IPAConfigInfo &configInfo)\n \tconst ControlInfo &exposureInfo = sensorInfoMap_.find(V4L2_CID_EXPOSURE)->second;\n \tconst ControlInfo &gainInfo = sensorInfoMap_.find(V4L2_CID_ANALOGUE_GAIN)->second;\n \n-\texposureMin_ = exposureInfo.min().get<int32_t>();\n-\texposureMax_ = exposureInfo.max().get<int32_t>();\n-\tif (!exposureMin_) {\n+\tcontext_.configuration.agc.exposureMin = exposureInfo.min().get<int32_t>();\n+\tcontext_.configuration.agc.exposureMax = exposureInfo.max().get<int32_t>();\n+\tif (!context_.configuration.agc.exposureMin) {\n \t\tLOG(IPASoft, Warning) << \"Minimum exposure is zero, that can't be linear\";\n-\t\texposureMin_ = 1;\n+\t\tcontext_.configuration.agc.exposureMin = 1;\n \t}\n \n \tint32_t againMin = gainInfo.min().get<int32_t>();\n \tint32_t againMax = gainInfo.max().get<int32_t>();\n \n \tif (camHelper_) {\n-\t\tagainMin_ = camHelper_->gain(againMin);\n-\t\tagainMax_ = camHelper_->gain(againMax);\n-\t\tagainMinStep_ = (againMax_ - againMin_) / 100.0;\n+\t\tcontext_.configuration.agc.againMin = camHelper_->gain(againMin);\n+\t\tcontext_.configuration.agc.againMax = camHelper_->gain(againMax);\n+\t\tcontext_.configuration.agc.againMinStep =\n+\t\t\t(context_.configuration.agc.againMax -\n+\t\t\t context_.configuration.agc.againMin) /\n+\t\t\t100.0;\n \t} else {\n \t\t/*\n \t\t * The camera sensor gain (g) is usually not equal to the value written\n@@ -232,13 +212,14 @@ int IPASoftSimple::configure(const IPAConfigInfo &configInfo)\n \t\t * the AGC algorithm (abrupt near one edge, and very small near the\n \t\t * other) we limit the range of the gain values used.\n \t\t */\n-\t\tagainMax_ = againMax;\n+\t\tcontext_.configuration.agc.againMax = againMax;\n \t\tif (!againMin) {\n \t\t\tLOG(IPASoft, Warning)\n \t\t\t\t<< \"Minimum gain is zero, that can't be linear\";\n-\t\t\tagainMin_ = std::min(100, againMin / 2 + againMax / 2);\n+\t\t\tcontext_.configuration.agc.againMin =\n+\t\t\t\tstd::min(100, againMin / 2 + againMax / 2);\n \t\t}\n-\t\tagainMinStep_ = 1.0;\n+\t\tcontext_.configuration.agc.againMinStep = 1.0;\n \t}\n \n \tfor (auto const &algo : algorithms()) {\n@@ -247,9 +228,12 @@ int IPASoftSimple::configure(const IPAConfigInfo &configInfo)\n \t\t\treturn ret;\n \t}\n \n-\tLOG(IPASoft, Info) << \"Exposure \" << exposureMin_ << \"-\" << exposureMax_\n-\t\t\t   << \", gain \" << againMin_ << \"-\" << againMax_\n-\t\t\t   << \" (\" << againMinStep_ << \")\";\n+\tLOG(IPASoft, Info)\n+\t\t<< \"Exposure \" << context_.configuration.agc.exposureMin << \"-\"\n+\t\t<< context_.configuration.agc.exposureMax\n+\t\t<< \", gain \" << context_.configuration.agc.againMin << \"-\"\n+\t\t<< context_.configuration.agc.againMax\n+\t\t<< \" (\" << context_.configuration.agc.againMinStep << \")\";\n \n \treturn 0;\n }\n@@ -284,6 +268,12 @@ void IPASoftSimple::processStats(const uint32_t frame,\n \t\t\t\t const ControlList &sensorControls)\n {\n \tIPAFrameContext &frameContext = context_.frameContexts.get(frame);\n+\n+\tframeContext.sensor.exposure =\n+\t\tsensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();\n+\tint32_t again = sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>();\n+\tframeContext.sensor.gain = camHelper_ ? camHelper_->gain(again) : again;\n+\n \t/*\n \t * Software ISP currently does not produce any metadata. Use an empty\n \t * ControlList for now.\n@@ -294,37 +284,6 @@ void IPASoftSimple::processStats(const uint32_t frame,\n \tfor (auto const &algo : algorithms())\n \t\talgo->process(context_, frame, frameContext, stats_, metadata);\n \n-\t/* \\todo Switch to the libipa/algorithm.h API someday. */\n-\n-\t/*\n-\t * Calculate Mean Sample Value (MSV) according to formula from:\n-\t * https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf\n-\t */\n-\tconst uint8_t blackLevel = context_.activeState.blc.level;\n-\tconst unsigned int blackLevelHistIdx =\n-\t\tblackLevel / (256 / SwIspStats::kYHistogramSize);\n-\tconst unsigned int histogramSize =\n-\t\tSwIspStats::kYHistogramSize - blackLevelHistIdx;\n-\tconst unsigned int yHistValsPerBin = histogramSize / kExposureBinsCount;\n-\tconst unsigned int yHistValsPerBinMod =\n-\t\thistogramSize / (histogramSize % kExposureBinsCount + 1);\n-\tint exposureBins[kExposureBinsCount] = {};\n-\tunsigned int denom = 0;\n-\tunsigned int num = 0;\n-\n-\tfor (unsigned int i = 0; i < histogramSize; i++) {\n-\t\tunsigned int idx = (i - (i / yHistValsPerBinMod)) / yHistValsPerBin;\n-\t\texposureBins[idx] += stats_->yHistogram[blackLevelHistIdx + i];\n-\t}\n-\n-\tfor (unsigned int i = 0; i < kExposureBinsCount; i++) {\n-\t\tLOG(IPASoft, Debug) << i << \": \" << exposureBins[i];\n-\t\tdenom += exposureBins[i];\n-\t\tnum += exposureBins[i] * (i + 1);\n-\t}\n-\n-\tfloat exposureMSV = static_cast<float>(num) / denom;\n-\n \t/* Sanity check */\n \tif (!sensorControls.contains(V4L2_CID_EXPOSURE) ||\n \t    !sensorControls.contains(V4L2_CID_ANALOGUE_GAIN)) {\n@@ -332,70 +291,14 @@ void IPASoftSimple::processStats(const uint32_t frame,\n \t\treturn;\n \t}\n \n-\texposure_ = sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();\n-\tint32_t again = sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>();\n-\tagain_ = camHelper_ ? camHelper_->gain(again) : again;\n-\n-\tupdateExposure(exposureMSV);\n-\n \tControlList ctrls(sensorInfoMap_);\n \n-\tctrls.set(V4L2_CID_EXPOSURE, exposure_);\n+\tauto &againNew = context_.activeState.agc.again;\n+\tctrls.set(V4L2_CID_EXPOSURE, context_.activeState.agc.exposure);\n \tctrls.set(V4L2_CID_ANALOGUE_GAIN,\n-\t\t  static_cast<int32_t>(camHelper_ ? camHelper_->gainCode(again_) : again_));\n+\t\t  static_cast<int32_t>(camHelper_ ? camHelper_->gainCode(againNew) : againNew));\n \n \tsetSensorControls.emit(ctrls);\n-\n-\tLOG(IPASoft, Debug) << \"exposureMSV \" << exposureMSV\n-\t\t\t    << \" exp \" << exposure_ << \" again \" << again_\n-\t\t\t    << \" black level \" << static_cast<unsigned int>(blackLevel);\n-}\n-\n-void IPASoftSimple::updateExposure(double exposureMSV)\n-{\n-\t/*\n-\t * kExpDenominator of 10 gives ~10% increment/decrement;\n-\t * kExpDenominator of 5 - about ~20%\n-\t */\n-\tstatic constexpr uint8_t kExpDenominator = 10;\n-\tstatic constexpr uint8_t kExpNumeratorUp = kExpDenominator + 1;\n-\tstatic constexpr uint8_t kExpNumeratorDown = kExpDenominator - 1;\n-\n-\tdouble next;\n-\n-\tif (exposureMSV < kExposureOptimal - kExposureSatisfactory) {\n-\t\tnext = exposure_ * kExpNumeratorUp / kExpDenominator;\n-\t\tif (next - exposure_ < 1)\n-\t\t\texposure_ += 1;\n-\t\telse\n-\t\t\texposure_ = next;\n-\t\tif (exposure_ >= exposureMax_) {\n-\t\t\tnext = again_ * kExpNumeratorUp / kExpDenominator;\n-\t\t\tif (next - again_ < againMinStep_)\n-\t\t\t\tagain_ += againMinStep_;\n-\t\t\telse\n-\t\t\t\tagain_ = next;\n-\t\t}\n-\t}\n-\n-\tif (exposureMSV > kExposureOptimal + kExposureSatisfactory) {\n-\t\tif (exposure_ == exposureMax_ && again_ > againMin_) {\n-\t\t\tnext = again_ * kExpNumeratorDown / kExpDenominator;\n-\t\t\tif (again_ - next < againMinStep_)\n-\t\t\t\tagain_ -= againMinStep_;\n-\t\t\telse\n-\t\t\t\tagain_ = next;\n-\t\t} else {\n-\t\t\tnext = exposure_ * kExpNumeratorDown / kExpDenominator;\n-\t\t\tif (exposure_ - next < 1)\n-\t\t\t\texposure_ -= 1;\n-\t\t\telse\n-\t\t\t\texposure_ = next;\n-\t\t}\n-\t}\n-\n-\texposure_ = std::clamp(exposure_, exposureMin_, exposureMax_);\n-\tagain_ = std::clamp(again_, againMin_, againMax_);\n }\n \n std::string IPASoftSimple::logPrefix() const\ndiff --git a/src/libcamera/software_isp/TODO b/src/libcamera/software_isp/TODO\nindex 8eeede46..a50db668 100644\n--- a/src/libcamera/software_isp/TODO\n+++ b/src/libcamera/software_isp/TODO\n@@ -199,16 +199,6 @@ Yes, because, well... all the other IPAs were doing that...\n \n ---\n \n-10. Switch to libipa/algorithm.h API in processStats\n-\n->> void IPASoftSimple::processStats(const ControlList &sensorControls)\n->>\n-> Do you envision switching to the libipa/algorithm.h API at some point ?\n-\n-At some point, yes.\n-\n----\n-\n 13. Improve black level and colour gains application\n \n I think the black level should eventually be moved before debayering, and\n",
    "prefixes": [
        "v6",
        "17/18"
    ]
}