Patch Detail
Show a patch.
GET /api/patches/27173/?format=api
{ "id": 27173, "url": "https://patchwork.libcamera.org/api/patches/27173/?format=api", "web_url": "https://patchwork.libcamera.org/patch/27173/", "project": { "id": 1, "url": "https://patchwork.libcamera.org/api/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": "<20260703122543.1991189-15-paul.elder@ideasonboard.com>", "date": "2026-07-03T12:25:20", "name": "[RFC,14/19] ipa: rkisp2: algo: lsc: Implement lens shading correction", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "82f0e5e34633c189de24bbe15dd3d8da66907db8", "submitter": { "id": 17, "url": "https://patchwork.libcamera.org/api/people/17/?format=api", "name": "Paul Elder", "email": "paul.elder@ideasonboard.com" }, "delegate": null, "mbox": "https://patchwork.libcamera.org/patch/27173/mbox/", "series": [ { "id": 6035, "url": "https://patchwork.libcamera.org/api/series/6035/?format=api", "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=6035", "date": "2026-07-03T12:25:06", "name": "Add support for rkisp2", "version": 1, "mbox": "https://patchwork.libcamera.org/series/6035/mbox/" } ], "comments": "https://patchwork.libcamera.org/api/patches/27173/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/27173/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 63083C328C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 3 Jul 2026 12:26:52 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id E5C4A65FED;\n\tFri, 3 Jul 2026 14:26: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 051D665FE2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 3 Jul 2026 14:26:50 +0200 (CEST)", "from neptunite.hamster-moth.ts.net (unknown\n\t[IPv6:2404:7a81:160:2100:a2cc:2f45:3bd7:2589])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 262261121;\n\tFri, 3 Jul 2026 14:26:00 +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=\"cJkcWQTG\"; dkim-atps=neutral", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1783081564;\n\tbh=F6zaFA2WMPuL8yNraGr/Kka8q5Lh7u+ysuifoo/7AxA=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=cJkcWQTGeOw0mbR6ztFR7ES/zg458iuQrNjM+Tp6ZJdpuG8YpO5ltkisFbTAwOy9D\n\tzUG0kFrkdzH4IKd6p1YzJNVRcC9cUlEZ2tO3uB3NC9EjNKAp8OCQoKTEdfuzX8pJkT\n\tI4kzZ3RsfXlJjE2qyjjoWw7grhvzkLxEdxhoM0qg=", "From": "Paul Elder <paul.elder@ideasonboard.com>", "To": "laurent.pinchart@ideasonboard.com", "Cc": "Paul Elder <paul.elder@ideasonboard.com>, michael.riesch@collabora.com, \n\txuhf@rock-chips.com, stefan.klug@ideasonboard.com,\n\tkieran.bingham@ideasonboard.com, dan.scally@ideasonboard.com,\n\tjacopo.mondi@ideasonboard.com, nicolas.dufresne@collabora.com,\n\tlibcamera-devel@lists.libcamera.org", "Subject": "[RFC PATCH 14/19] ipa: rkisp2: algo: lsc: Implement lens shading\n\tcorrection", "Date": "Fri, 3 Jul 2026 21:25:20 +0900", "Message-ID": "<20260703122543.1991189-15-paul.elder@ideasonboard.com>", "X-Mailer": "git-send-email 2.47.2", "In-Reply-To": "<20260703122543.1991189-1-paul.elder@ideasonboard.com>", "References": "<20260703122543.1991189-1-paul.elder@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": "Implement a lens shading correction algorithm for the rkisp2 IPA. It\nuses the libipa lens shading correction.\n\nSigned-off-by: Paul Elder <paul.elder@ideasonboard.com>\n---\n src/ipa/rkisp2/algorithms/lsc.cpp | 263 ++++++++++++++++++++++++++\n src/ipa/rkisp2/algorithms/lsc.h | 71 +++++++\n src/ipa/rkisp2/algorithms/meson.build | 1 +\n 3 files changed, 335 insertions(+)\n create mode 100644 src/ipa/rkisp2/algorithms/lsc.cpp\n create mode 100644 src/ipa/rkisp2/algorithms/lsc.h", "diff": "diff --git a/src/ipa/rkisp2/algorithms/lsc.cpp b/src/ipa/rkisp2/algorithms/lsc.cpp\nnew file mode 100644\nindex 000000000000..dd93583dffdc\n--- /dev/null\n+++ b/src/ipa/rkisp2/algorithms/lsc.cpp\n@@ -0,0 +1,263 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2026, Ideas On Board\n+ *\n+ * RkISP2 Lens Shading Correction control\n+ */\n+\n+#include \"lsc.h\"\n+\n+#include <algorithm>\n+#include <cmath>\n+#include <numeric>\n+\n+#include <libcamera/base/log.h>\n+#include <libcamera/base/utils.h>\n+\n+/**\n+ * \\file lsc.h\n+ */\n+\n+namespace libcamera {\n+\n+namespace ipa::rkisp2::algorithms {\n+\n+/**\n+ * \\class LensShadingCorrection\n+ * \\brief RkISP2 Lens Shading Correction control\n+ */\n+\n+LOG_DEFINE_CATEGORY(RkISP2Lsc)\n+\n+namespace {\n+\n+constexpr int kColourTemperatureQuantization = 10;\n+\n+unsigned int quantize(unsigned int value, unsigned int step)\n+{\n+\treturn std::lround(value / static_cast<double>(step)) * step;\n+}\n+\n+} /* namespace */\n+\n+LensShadingCorrection::LensShadingCorrection()\n+\t: lastAppliedCt_(0), lastAppliedQuantizedCt_(0)\n+{\n+}\n+\n+std::vector<double> LensShadingCorrection::parseSizes(const ValueNode &tuningData,\n+\t\t\t\t\t\t const char *prop)\n+{\n+\tstd::vector<double> sizes =\n+\t\ttuningData[prop].get<std::vector<double>>().value_or(utils::defopt);\n+\t/* Nobody cares about 8x8 mirrored mode; we'll just use 16x16 mode */\n+\tif (sizes.size() != RKISP2_ISP_LSC_SECTORS_TBL_SIZE_MAX) {\n+\t\tLOG(RkISP2Lsc, Error)\n+\t\t\t<< \"Invalid '\" << prop << \"' values: expected \"\n+\t\t\t<< RKISP2_ISP_LSC_SECTORS_TBL_SIZE_MAX\n+\t\t\t<< \" elements, got \" << sizes.size();\n+\t\treturn {};\n+\t}\n+\n+\t/*\n+\t * The sum of all elements must be 1 to satisfy hardware constraints.\n+\t * Validate it here, allowing a 1% tolerance as rounding errors may\n+\t * prevent an exact match (further adjustments will be performed in\n+\t * LensShadingCorrection::prepare()).\n+\t *\n+\t * If we were in 8x8 mode then we'd have to mirror the quadrants like\n+\t * in rkisp1, but in 16x16 mode we get to configure the entire table.\n+\t * Since 8x8 table support is a todo, we only need to handle the 16x16\n+\t * case here thus the sum should be 1.\n+\t *\n+\t * \\todo Support 8x8 mode?\n+\t */\n+\tdouble sum = std::accumulate(sizes.begin(), sizes.end(), 0.0);\n+\tif (sum < 0.95 || sum > 1.05) {\n+\t\tLOG(RkISP2Lsc, Error)\n+\t\t\t<< \"Invalid '\" << prop << \"' values: sum of the elements\"\n+\t\t\t<< \" should be 1.0, got \" << sum;\n+\t\treturn {};\n+\t}\n+\n+\treturn sizes;\n+}\n+\n+std::vector<double> LensShadingCorrection::sizesToPositions(Span<const double> sizes)\n+{\n+\tstd::vector<double> positions(sizes.size() + 1);\n+\n+\tpositions[0] = 0.0;\n+\tfor (size_t i = 1; i < positions.size(); i++)\n+\t\tpositions[i] = positions[i - 1] + sizes[i - 1];\n+\n+\treturn positions;\n+}\n+\n+/**\n+ * \\copydoc libcamera::ipa::Algorithm::init\n+ */\n+int LensShadingCorrection::init(IPAContext &context,\n+\t\t\t\tconst ValueNode &tuningData)\n+{\n+\txSize_ = parseSizes(tuningData, \"x-size\");\n+\tySize_ = parseSizes(tuningData, \"y-size\");\n+\n+\tif (xSize_.empty() || ySize_.empty())\n+\t\treturn -EINVAL;\n+\n+\txPos_ = sizesToPositions(xSize_);\n+\tyPos_ = sizesToPositions(ySize_);\n+\n+\treturn lscAlgo_.init(tuningData, context.ctrlMap, {\n+\t\t\t\t.keys = { \"r\", \"gr\", \"gb\", \"b\" },\n+\t\t\t\t.numHCells = RKISP2_ISP_LSC_SAMPLES_MAX,\n+\t\t\t\t.numVCells = RKISP2_ISP_LSC_SAMPLES_MAX,\n+\t\t\t\t.sensorSize = context.sensorInfo.activeAreaSize\n+\t\t\t });\n+}\n+\n+/**\n+ * \\copydoc libcamera::ipa::Algorithm::configure\n+ */\n+int LensShadingCorrection::configure(IPAContext &context,\n+\t\t\t\t const IPACameraSensorInfo &configInfo)\n+{\n+\tconst Size &size = context.configuration.sensor.size;\n+\tSize totalSize{};\n+\n+\t/* Calculate gradients. */\n+\tfor (unsigned int i = 0; i < RKISP2_ISP_LSC_SECTORS_TBL_SIZE_MAX; ++i) {\n+\t\txSizes_[i] = xSize_[i] * size.width;\n+\t\tySizes_[i] = ySize_[i] * size.height;\n+\n+\t\t/*\n+\t\t * To prevent unexpected behavior of the ISP, the sum of\n+\t\t * x_sizes and y_sizes items shall be equal to\n+\t\t * respectively size.width and size.height. Enforce it by\n+\t\t * computing the last tables value to avoid\n+\t\t * rounding-induced errors.\n+\t\t */\n+\t\tif (i == RKISP2_ISP_LSC_SECTORS_TBL_SIZE_MAX - 1) {\n+\t\t\txSizes_[i] = size.width - totalSize.width;\n+\t\t\tySizes_[i] = size.height - totalSize.height;\n+\t\t}\n+\n+\t\ttotalSize.width += xSizes_[i];\n+\t\ttotalSize.height += ySizes_[i];\n+\n+\t\txGrad_[i] = std::round(32768 / xSizes_[i]);\n+\t\tyGrad_[i] = std::round(32768 / ySizes_[i]);\n+\t}\n+\n+\treturn lscAlgo_.configure(context.activeState.lsc, configInfo.analogCrop,\n+\t\t\t\t xPos_, yPos_);\n+}\n+\n+void LensShadingCorrection::setParameters(rkisp2_params_lsc &config)\n+{\n+\tmemcpy(config.x_grads, xGrad_, sizeof(config.x_grads));\n+\tmemcpy(config.y_grads, yGrad_, sizeof(config.y_grads));\n+\tmemcpy(config.x_sizes, xSizes_, sizeof(config.x_sizes));\n+\tmemcpy(config.y_sizes, ySizes_, sizeof(config.y_sizes));\n+}\n+\n+void LensShadingCorrection::copyTable(rkisp2_params_lsc &config,\n+\t\t\t\t const ipa::lsc::Components<uint16_t> &set)\n+{\n+\tconst auto &r = set.at(\"r\");\n+\tstd::copy(r.begin(), r.end(), &config.r_data_tbl[0][0][0]);\n+\tconst auto &gr = set.at(\"gr\");\n+\tstd::copy(gr.begin(), gr.end(), &config.gr_data_tbl[0][0][0]);\n+\tconst auto &gb = set.at(\"gb\");\n+\tstd::copy(gb.begin(), gb.end(), &config.gb_data_tbl[0][0][0]);\n+\tconst auto &b = set.at(\"b\");\n+\tstd::copy(b.begin(), b.end(), &config.b_data_tbl[0][0][0]);\n+}\n+\n+/**\n+ * \\copydoc libcamera::ipa::Algorithm::queueRequest\n+ */\n+void LensShadingCorrection::queueRequest(IPAContext &context,\n+\t\t\t\t\t [[maybe_unused]] const uint32_t frame,\n+\t\t\t\t\t IPAFrameContext &frameContext,\n+\t\t\t\t\t const ControlList &controls)\n+{\n+\tlscAlgo_.queueRequest(context.activeState.lsc, frameContext.lsc,\n+\t\t\t controls);\n+}\n+\n+/**\n+ * \\copydoc libcamera::ipa::Algorithm::prepare\n+ */\n+void LensShadingCorrection::prepare([[maybe_unused]] IPAContext &context,\n+\t\t\t\t [[maybe_unused]] const uint32_t frame,\n+\t\t\t\t IPAFrameContext &frameContext,\n+\t\t\t\t RkISP2Params *params)\n+{\n+\tuint32_t ct = frameContext.awb.temperatureK;\n+\tunsigned int quantizedCt = quantize(ct, kColourTemperatureQuantization);\n+\n+\t/* Check if we can skip the update. */\n+\tif (!frameContext.lsc.update) {\n+\t\tif (!frameContext.lsc.enabled)\n+\t\t\treturn;\n+\n+\t\t/*\n+\t\t * Add a threshold so that oscillations around a quantization\n+\t\t * step don't lead to constant changes.\n+\t\t */\n+\t\tif (utils::abs_diff(ct, lastAppliedCt_) < kColourTemperatureQuantization / 2)\n+\t\t\treturn;\n+\n+\t\tif (quantizedCt == lastAppliedQuantizedCt_)\n+\t\t\treturn;\n+\t}\n+\n+\tauto config = params->block<RkISP2Blocks::Lsc>();\n+\tconfig.setEnabled(frameContext.lsc.enabled);\n+\n+\tif (!frameContext.lsc.enabled)\n+\t\treturn;\n+\n+\t/*\n+\t * \\todo Should add support for the lsc table swapping functionality?\n+\t * Or maybe we don't need it because the lsc doesn't change very\n+\t * frequently. Just use the 0th table for now.\n+\t */\n+\tconfig->window_mode = RKISP2_ISP_LSC_CONFIG_16X16;\n+\tconfig->write_table[0] = 1;\n+\tconfig->write_table[1] = 0;\n+\tconfig->active_table = 0;\n+\tconfig->set_active_table_when = RKISP2_ISP_LSC_SET_ACTIVE_TABLE_AFTER;\n+\n+\tsetParameters(*config);\n+\n+\tconst auto &set = lscAlgo_.interpolateComponents(quantizedCt);\n+\tcopyTable(*config, set);\n+\n+\tlastAppliedCt_ = ct;\n+\tlastAppliedQuantizedCt_ = quantizedCt;\n+\n+\tLOG(RkISP2Lsc, Debug)\n+\t\t<< \"ct is \" << ct << \", quantized to \"\n+\t\t<< quantizedCt;\n+}\n+\n+/**\n+ * \\copydoc libcamera::ipa::Algorithm::process\n+ */\n+void LensShadingCorrection::process([[maybe_unused]] IPAContext &context,\n+\t\t\t\t [[maybe_unused]] const uint32_t frame,\n+\t\t\t\t IPAFrameContext &frameContext,\n+\t\t\t\t [[maybe_unused]] const rkisp2_stats_buffer *stats,\n+\t\t\t\t ControlList &metadata)\n+{\n+\tlscAlgo_.process(frameContext.lsc, metadata);\n+}\n+\n+REGISTER_IPA_ALGORITHM(LensShadingCorrection, \"LensShadingCorrection\")\n+\n+} /* namespace ipa::rkisp2::algorithms */\n+\n+} /* namespace libcamera */\ndiff --git a/src/ipa/rkisp2/algorithms/lsc.h b/src/ipa/rkisp2/algorithms/lsc.h\nnew file mode 100644\nindex 000000000000..78cbb094ec64\n--- /dev/null\n+++ b/src/ipa/rkisp2/algorithms/lsc.h\n@@ -0,0 +1,71 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2026, Ideas On Board\n+ *\n+ * RkISP2 Lens Shading Correction algorithm\n+ */\n+\n+#pragma once\n+\n+#include <vector>\n+\n+#include <linux/rkisp2-config.h>\n+\n+#include \"libcamera/internal/value_node.h\"\n+\n+#include \"libipa/fixedpoint.h\"\n+#include \"libipa/lsc.h\"\n+\n+#include \"algorithm.h\"\n+#include \"ipa_context.h\"\n+#include \"params.h\"\n+\n+namespace libcamera {\n+\n+namespace ipa::rkisp2::algorithms {\n+\n+class LensShadingCorrection : public Algorithm\n+{\n+public:\n+\tLensShadingCorrection();\n+\t~LensShadingCorrection() = default;\n+\n+\tint init(IPAContext &context, const ValueNode &tuningData) override;\n+\tint configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override;\n+\tvoid queueRequest(IPAContext &context, const uint32_t frame,\n+\t\t\t IPAFrameContext &frameContext,\n+\t\t\t const ControlList &controls) override;\n+\tvoid prepare(IPAContext &context, const uint32_t frame,\n+\t\t IPAFrameContext &frameContext,\n+\t\t RkISP2Params *params) override;\n+\tvoid process(IPAContext &context, const uint32_t frame,\n+\t\t IPAFrameContext &frameContext,\n+\t\t const rkisp2_stats_buffer *stats,\n+\t\t ControlList &metadata) override;\n+\n+private:\n+\tstd::vector<double> parseSizes(const ValueNode &tuningData,\n+\t\t\t\t const char *prop);\n+\tstd::vector<double> sizesToPositions(Span<const double> sizes);\n+\n+\tvoid setParameters(rkisp2_params_lsc &config);\n+\tvoid copyTable(rkisp2_params_lsc &config,\n+\t\t const ipa::lsc::Components<uint16_t> &set0);\n+\n+\tstd::vector<double> xSize_;\n+\tstd::vector<double> ySize_;\n+\tuint16_t xGrad_[RKISP2_ISP_LSC_SECTORS_TBL_SIZE_MAX];\n+\tuint16_t yGrad_[RKISP2_ISP_LSC_SECTORS_TBL_SIZE_MAX];\n+\tuint16_t xSizes_[RKISP2_ISP_LSC_SECTORS_TBL_SIZE_MAX];\n+\tuint16_t ySizes_[RKISP2_ISP_LSC_SECTORS_TBL_SIZE_MAX];\n+\tstd::vector<double> xPos_;\n+\tstd::vector<double> yPos_;\n+\n+\tunsigned int lastAppliedCt_;\n+\tunsigned int lastAppliedQuantizedCt_;\n+\n+\tLscAlgorithm<uint16_t, UQ<3, 10>> lscAlgo_;\n+};\n+\n+} /* namespace ipa::rkisp2::algorithms */\n+} /* namespace libcamera */\ndiff --git a/src/ipa/rkisp2/algorithms/meson.build b/src/ipa/rkisp2/algorithms/meson.build\nindex 3d73b0a99320..bcc947fabdb4 100644\n--- a/src/ipa/rkisp2/algorithms/meson.build\n+++ b/src/ipa/rkisp2/algorithms/meson.build\n@@ -7,5 +7,6 @@ rkisp2_ipa_algorithms = files([\n 'ccm.cpp',\n 'csm.cpp',\n 'goc.cpp',\n+ 'lsc.cpp',\n ])\n \n", "prefixes": [ "RFC", "14/19" ] }