Show a patch.

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

{
    "id": 19789,
    "url": "https://patchwork.libcamera.org/api/1.1/patches/19789/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/19789/",
    "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": "<20240322131451.3092931-4-dan.scally@ideasonboard.com>",
    "date": "2024-03-22T13:14:44",
    "name": "[03/10] ipa: libipa: Add ExposureModeHelper",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": false,
    "hash": "492c98e99bde1179af7bb06ad00a81bf842e2a60",
    "submitter": {
        "id": 156,
        "url": "https://patchwork.libcamera.org/api/1.1/people/156/?format=api",
        "name": "Dan Scally",
        "email": "dan.scally@ideasonboard.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/19789/mbox/",
    "series": [
        {
            "id": 4236,
            "url": "https://patchwork.libcamera.org/api/1.1/series/4236/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=4236",
            "date": "2024-03-22T13:14:41",
            "name": "Centralise Agc into libipa",
            "version": 1,
            "mbox": "https://patchwork.libcamera.org/series/4236/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/19789/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/19789/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 62621C3272\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 22 Mar 2024 13:15:18 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 18D8163079;\n\tFri, 22 Mar 2024 14:15:13 +0100 (CET)",
            "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 9B82761C45\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 22 Mar 2024 14:15:08 +0100 (CET)",
            "from mail.ideasonboard.com\n\t(cpc141996-chfd3-2-0-cust928.12-3.cable.virginm.net [86.13.91.161])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 8ED6F8CC;\n\tFri, 22 Mar 2024 14:14:39 +0100 (CET)"
        ],
        "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"ak2J9ibh\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1711113279;\n\tbh=eo7TiyCG8+FJpjgxvEtbeh9ejggUlpeW4hnEZXI4KDk=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=ak2J9ibh8UvljWLh6giN8STz8rKZjJDR6L3Gv3lerfxrQRlCc7ESm6JOL0KqMuPC+\n\tjR0W4+fhhzp1GTU8dvySaw8Xe3h26pru7YZd/aPFaEWBECdBLepuysAgUUbuk5MdU1\n\thr5d+FHKsUUGCs9+8zCAwF9i8WrM9wQkcaH20n2o=",
        "From": "Daniel Scally <dan.scally@ideasonboard.com>",
        "To": "libcamera-devel@lists.libcamera.org",
        "Cc": "Paul Elder <paul.elder@ideasonboard.com>,\n\tDaniel Scally <dan.scally@ideasonboard.com>",
        "Subject": "[PATCH 03/10] ipa: libipa: Add ExposureModeHelper",
        "Date": "Fri, 22 Mar 2024 13:14:44 +0000",
        "Message-Id": "<20240322131451.3092931-4-dan.scally@ideasonboard.com>",
        "X-Mailer": "git-send-email 2.34.1",
        "In-Reply-To": "<20240322131451.3092931-1-dan.scally@ideasonboard.com>",
        "References": "<20240322131451.3092931-1-dan.scally@ideasonboard.com>",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit",
        "X-BeenThere": "libcamera-devel@lists.libcamera.org",
        "X-Mailman-Version": "2.1.29",
        "Precedence": "list",
        "List-Id": "<libcamera-devel.lists.libcamera.org>",
        "List-Unsubscribe": "<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>",
        "List-Archive": "<https://lists.libcamera.org/pipermail/libcamera-devel/>",
        "List-Post": "<mailto:libcamera-devel@lists.libcamera.org>",
        "List-Help": "<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>",
        "List-Subscribe": "<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>",
        "Errors-To": "libcamera-devel-bounces@lists.libcamera.org",
        "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"
    },
    "content": "From: Paul Elder <paul.elder@ideasonboard.com>\n\nAdd a helper for managing exposure modes and splitting exposure times\ninto shutter and gain values.\n\nSigned-off-by: Paul Elder <paul.elder@ideasonboard.com>\nSigned-off-by: Daniel Scally <dan.scally@ideasonboard.com>\n---\n src/ipa/libipa/exposure_mode_helper.cpp | 307 ++++++++++++++++++++++++\n src/ipa/libipa/exposure_mode_helper.h   |  61 +++++\n src/ipa/libipa/meson.build              |   2 +\n 3 files changed, 370 insertions(+)\n create mode 100644 src/ipa/libipa/exposure_mode_helper.cpp\n create mode 100644 src/ipa/libipa/exposure_mode_helper.h",
    "diff": "diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp\nnew file mode 100644\nindex 00000000..9e01f908\n--- /dev/null\n+++ b/src/ipa/libipa/exposure_mode_helper.cpp\n@@ -0,0 +1,307 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>\n+ *\n+ * exposure_mode_helper.h - Helper class that performs computations relating to exposure\n+ */\n+#include \"exposure_mode_helper.h\"\n+\n+#include <algorithm>\n+\n+#include <libcamera/base/log.h>\n+\n+/**\n+ * \\file exposure_mode_helper.h\n+ * \\brief Helper class that performs computations relating to exposure\n+ *\n+ * Exposure modes contain a list of shutter and gain values to determine how to\n+ * split the supplied exposure time into shutter and gain. As this function is\n+ * expected to be replicated in various IPAs, this helper class factors it\n+ * away.\n+ */\n+\n+namespace libcamera {\n+\n+LOG_DEFINE_CATEGORY(ExposureModeHelper)\n+\n+namespace ipa {\n+\n+/**\n+ * \\class ExposureModeHelper\n+ * \\brief Class for splitting exposure time into shutter and gain\n+ */\n+\n+/**\n+ * \\brief Initialize an ExposureModeHelper instance\n+ */\n+ExposureModeHelper::ExposureModeHelper()\n+\t: minShutter_(0), maxShutter_(0), minGain_(0), maxGain_(0)\n+{\n+}\n+\n+ExposureModeHelper::~ExposureModeHelper()\n+{\n+}\n+\n+/**\n+ * \\brief Initialize an ExposureModeHelper instance\n+ * \\param[in] shutter The list of shutter values\n+ * \\param[in] gain The list of gain values\n+ *\n+ * When splitting an exposure time into shutter and gain, the shutter will be\n+ * increased first before increasing the gain. This is done in stages, where\n+ * each stage is an index into both lists. Both lists consequently need to be\n+ * the same length.\n+ *\n+ * \\return Zero on success, negative error code otherwise\n+ */\n+int ExposureModeHelper::init(std::vector<utils::Duration> &shutter, std::vector<double> &gain)\n+{\n+\tif (shutter.size() != gain.size()) {\n+\t\tLOG(ExposureModeHelper, Error)\n+\t\t\t<< \"Invalid exposure mode:\"\n+\t\t\t<< \" expected size of 'shutter' and 'gain to be equal,\"\n+\t\t\t<< \" got \" << shutter.size() << \" and \" << gain.size()\n+\t\t\t<< \" respectively\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tstd::copy(shutter.begin(), shutter.end(), std::back_inserter(shutters_));\n+\tstd::copy(gain.begin(), gain.end(), std::back_inserter(gains_));\n+\n+\t/*\n+\t * Initialize the max shutter and gain if they aren't initialized yet.\n+\t * This is to protect against the event that configure() is not called\n+\t * before splitExposure().\n+\t */\n+\tif (!maxShutter_) {\n+\t\tif (shutters_.size() > 0)\n+\t\t\tmaxShutter_ = shutter.back();\n+\t}\n+\n+\tif (!maxGain_) {\n+\t\tif (gains_.size() > 0)\n+\t\t\tmaxGain_ = gain.back();\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * \\brief Configure the ExposureModeHelper\n+ * \\param[in] minShutter The minimum shutter time\n+ * \\param[in] maxShutter The maximum shutter time\n+ * \\param[in] minGain The minimum gain\n+ * \\param[in] maxGain The maximum gain\n+ *\n+ * Note that the ExposureModeHelper needs to be reconfigured when\n+ * FrameDurationLimits is passed, and not just at IPA configuration time.\n+ *\n+ * This function configures the maximum shutter and maximum gain.\n+ */\n+void ExposureModeHelper::configure(utils::Duration minShutter,\n+\t\t\t\t   utils::Duration maxShutter,\n+\t\t\t\t   double minGain,\n+\t\t\t\t   double maxGain)\n+{\n+\tminShutter_ = minShutter;\n+\tmaxShutter_ = maxShutter;\n+\tminGain_ = minGain;\n+\tmaxGain_ = maxGain;\n+}\n+\n+utils::Duration ExposureModeHelper::clampShutter(utils::Duration shutter)\n+{\n+\treturn std::clamp(shutter, minShutter_, maxShutter_);\n+}\n+\n+double ExposureModeHelper::clampGain(double gain)\n+{\n+\treturn std::clamp(gain, minGain_, maxGain_);\n+}\n+\n+std::tuple<utils::Duration, double, double>\n+ExposureModeHelper::splitExposure(utils::Duration exposure,\n+\t\t\t\t  utils::Duration shutter, bool shutterFixed,\n+\t\t\t\t  double gain, bool gainFixed)\n+{\n+\tshutter = clampShutter(shutter);\n+\tgain = clampGain(gain);\n+\n+\t/* Not sure why you'd want to do this... */\n+\tif (shutterFixed && gainFixed)\n+\t\treturn { shutter, gain, 1 };\n+\n+\t/* Initial shutter and gain settings are sufficient */\n+\tif (shutter * gain >= exposure) {\n+\t\t/* Both shutter and gain cannot go lower */\n+\t\tif (shutter == minShutter_ && gain == minGain_)\n+\t\t\treturn { shutter, gain, 1 };\n+\n+\t\t/* Shutter cannot go lower */\n+\t\tif (shutter == minShutter_ || shutterFixed)\n+\t\t\treturn { shutter,\n+\t\t\t\t gainFixed ? gain : clampGain(exposure / shutter),\n+\t\t\t\t 1 };\n+\n+\t\t/* Gain cannot go lower */\n+\t\tif (gain == minGain_ || gainFixed)\n+\t\t\treturn {\n+\t\t\t\tshutterFixed ? shutter : clampShutter(exposure / gain),\n+\t\t\t\tgain,\n+\t\t\t\t1\n+\t\t\t};\n+\n+\t\t/* Both can go lower */\n+\t\treturn { clampShutter(exposure / minGain_),\n+\t\t\t exposure / clampShutter(exposure / minGain_),\n+\t\t\t 1 };\n+\t}\n+\n+\tunsigned int stage;\n+\tutils::Duration stageShutter;\n+\tdouble stageGain;\n+\tdouble lastStageGain;\n+\n+\t/* We've already done stage 0 above so we start at 1 */\n+\tfor (stage = 1; stage < gains_.size(); stage++) {\n+\t\tstageShutter = shutterFixed ? shutter : clampShutter(shutters_[stage]);\n+\t\tstageGain = gainFixed ? gain : clampGain(gains_[stage]);\n+\t\tlastStageGain = gainFixed ? gain : clampGain(gains_[stage - 1]);\n+\n+\t\t/*\n+\t\t * If the product of the new stage shutter and the old stage\n+\t\t * gain is sufficient and we can change the shutter, reduce it.\n+\t\t */\n+\t\tif (!shutterFixed && stageShutter * lastStageGain >= exposure)\n+\t\t\treturn { clampShutter(exposure / lastStageGain), lastStageGain, 1 };\n+\n+\t\t/*\n+\t\t * The new stage shutter with old stage gain were insufficient,\n+\t\t * so try the new stage shutter with new stage gain. If it is\n+\t\t * sufficient and we can change the shutter, reduce it.\n+\t\t */\n+\t\tif (!shutterFixed && stageShutter * stageGain >= exposure)\n+\t\t\treturn { clampShutter(exposure / stageGain), stageGain, 1 };\n+\n+\t\t/*\n+\t\t * Same as above, but we can't change the shutter, so change\n+\t\t * the gain instead.\n+\t\t *\n+\t\t * Note that at least one of !shutterFixed and !gainFixed is\n+\t\t * guaranteed.\n+\t\t */\n+\t\tif (!gainFixed && stageShutter * stageGain >= exposure)\n+\t\t\treturn { stageShutter, clampGain(exposure / stageShutter), 1 };\n+\t}\n+\n+\t/* From here on we're going to try to max out shutter then gain */\n+\tshutter = shutterFixed ? shutter : maxShutter_;\n+\tgain = gainFixed ? gain : maxGain_;\n+\n+\t/*\n+\t * We probably don't want to use the actual maximum analogue gain (as\n+\t * it'll be unreasonably high), so we'll at least try to max out the\n+\t * shutter, which is expected to be a bit more reasonable, as it is\n+\t * limited by FrameDurationLimits and/or the sensor configuration.\n+\t */\n+\tif (!shutterFixed && shutter * stageGain >= exposure)\n+\t\treturn { clampShutter(exposure / stageGain), stageGain, 1 };\n+\n+\t/*\n+\t * If that's still not enough exposure, or if shutter is fixed, then\n+\t * we'll max out the analogue gain before using digital gain.\n+\t */\n+\tif (!gainFixed && shutter * gain >= exposure)\n+\t\treturn { shutter, clampGain(exposure / shutter), 1 };\n+\n+\t/*\n+\t * We're out of shutter time and analogue gain; send the rest of the\n+\t * exposure time to digital gain.\n+\t */\n+\treturn { shutter, gain, exposure / (shutter * gain) };\n+}\n+\n+/**\n+ * \\brief Split exposure time into shutter and gain\n+ * \\param[in] exposure Exposure time\n+ * \\return Tuple of shutter time, analogue gain, and digital gain\n+ */\n+std::tuple<utils::Duration, double, double>\n+ExposureModeHelper::splitExposure(utils::Duration exposure)\n+{\n+\tASSERT(maxShutter_);\n+\tASSERT(maxGain_);\n+\tutils::Duration shutter;\n+\tdouble gain;\n+\n+\tif (shutters_.size()) {\n+\t\tshutter = shutters_.at(0);\n+\t\tgain = gains_.at(0);\n+\t} else {\n+\t\tshutter = maxShutter_;\n+\t\tgain = maxGain_;\n+\t}\n+\n+\treturn splitExposure(exposure, shutter, false, gain, false);\n+}\n+\n+/**\n+ * \\brief Split exposure time into shutter and gain, with fixed shutter\n+ * \\param[in] exposure Exposure time\n+ * \\param[in] fixedShutter Fixed shutter time\n+ *\n+ * Same as the base splitExposure, but with a fixed shutter (aka \"shutter priority\").\n+ *\n+ * \\return Tuple of shutter time, analogue gain, and digital gain\n+ */\n+std::tuple<utils::Duration, double, double>\n+ExposureModeHelper::splitExposure(utils::Duration exposure, utils::Duration fixedShutter)\n+{\n+\tASSERT(maxGain_);\n+\tdouble gain = gains_.size() ? gains_.at(0) : maxGain_;\n+\n+\treturn splitExposure(exposure, fixedShutter, true, gain, false);\n+}\n+\n+/**\n+ * \\brief Split exposure time into shutter and gain, with fixed gain\n+ * \\param[in] exposure Exposure time\n+ * \\param[in] fixedGain Fixed gain\n+ *\n+ * Same as the base splitExposure, but with a fixed gain (aka \"gain priority\").\n+ *\n+ * \\return Tuple of shutter time, analogue gain, and digital gain\n+ */\n+std::tuple<utils::Duration, double, double>\n+ExposureModeHelper::splitExposure(utils::Duration exposure, double fixedGain)\n+{\n+\tASSERT(maxShutter_);\n+\tutils::Duration shutter = shutters_.size() ? shutters_.at(0) : maxShutter_;\n+\n+\treturn splitExposure(exposure, shutter, false, fixedGain, true);\n+}\n+\n+/**\n+ * \\fn ExposureModeHelper::minShutter()\n+ * \\brief Retrieve the configured minimum shutter time\n+ */\n+\n+/**\n+ * \\fn ExposureModeHelper::maxShutter()\n+ * \\brief Retrieve the configured maximum shutter time\n+ */\n+\n+/**\n+ * \\fn ExposureModeHelper::minGain()\n+ * \\brief Retrieve the configured minimum gain\n+ */\n+\n+/**\n+ * \\fn ExposureModeHelper::maxGain()\n+ * \\brief Retrieve the configured maximum gain\n+ */\n+\n+} /* namespace ipa */\n+\n+} /* namespace libcamera */\ndiff --git a/src/ipa/libipa/exposure_mode_helper.h b/src/ipa/libipa/exposure_mode_helper.h\nnew file mode 100644\nindex 00000000..d576c952\n--- /dev/null\n+++ b/src/ipa/libipa/exposure_mode_helper.h\n@@ -0,0 +1,61 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>\n+ *\n+ * exposure_mode_helper.h - Helper class that performs computations relating to exposure\n+ */\n+\n+#pragma once\n+\n+#include <algorithm>\n+#include <tuple>\n+#include <vector>\n+\n+#include <libcamera/base/utils.h>\n+\n+namespace libcamera {\n+\n+namespace ipa {\n+\n+class ExposureModeHelper\n+{\n+public:\n+\tExposureModeHelper();\n+\t~ExposureModeHelper();\n+\n+\tint init(std::vector<utils::Duration> &shutters, std::vector<double> &gains);\n+\tvoid configure(utils::Duration minShutter, utils::Duration maxShutter,\n+\t\t       double minGain, double maxGain);\n+\n+\tstd::tuple<utils::Duration, double, double> splitExposure(utils::Duration exposure);\n+\tstd::tuple<utils::Duration, double, double> splitExposure(utils::Duration exposure,\n+\t\t\t\t\t\t\t\t  utils::Duration fixedShutter);\n+\tstd::tuple<utils::Duration, double, double> splitExposure(utils::Duration exposure,\n+\t\t\t\t\t\t\t\t  double fixedGain);\n+\n+\tutils::Duration minShutter() { return minShutter_; };\n+\tutils::Duration maxShutter() { return maxShutter_; };\n+\tdouble minGain() { return minGain_; };\n+\tdouble maxGain() { return maxGain_; };\n+\n+private:\n+\tutils::Duration clampShutter(utils::Duration shutter);\n+\tdouble clampGain(double gain);\n+\n+\tstd::tuple<utils::Duration, double, double>\n+\tsplitExposure(utils::Duration exposure,\n+\t\t      utils::Duration shutter, bool shutterFixed,\n+\t\t      double gain, bool gainFixed);\n+\n+\tstd::vector<utils::Duration> shutters_;\n+\tstd::vector<double> gains_;\n+\n+\tutils::Duration minShutter_;\n+\tutils::Duration maxShutter_;\n+\tdouble minGain_;\n+\tdouble maxGain_;\n+};\n+\n+} /* namespace ipa */\n+\n+} /* namespace libcamera */\ndiff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build\nindex 016b8e0e..37fbd177 100644\n--- a/src/ipa/libipa/meson.build\n+++ b/src/ipa/libipa/meson.build\n@@ -3,6 +3,7 @@\n libipa_headers = files([\n     'algorithm.h',\n     'camera_sensor_helper.h',\n+    'exposure_mode_helper.h',\n     'fc_queue.h',\n     'histogram.h',\n     'module.h',\n@@ -11,6 +12,7 @@ libipa_headers = files([\n libipa_sources = files([\n     'algorithm.cpp',\n     'camera_sensor_helper.cpp',\n+    'exposure_mode_helper.cpp',\n     'fc_queue.cpp',\n     'histogram.cpp',\n     'module.cpp',\n",
    "prefixes": [
        "03/10"
    ]
}