Patch Detail
Show a patch.
GET /api/patches/27072/?format=api
{ "id": 27072, "url": "https://patchwork.libcamera.org/api/patches/27072/?format=api", "web_url": "https://patchwork.libcamera.org/patch/27072/", "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": "<20260626-ipu3-libipa-rework-v2-12-41546e23de3e@ideasonboard.com>", "date": "2026-06-26T13:05:59", "name": "[v2,12/12] ipa: ipu3: Add Lens Shading Correction algorithm", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "f5225e7efa090bc6f8fd988983a4b2c2b60a3e45", "submitter": { "id": 156, "url": "https://patchwork.libcamera.org/api/people/156/?format=api", "name": "Dan Scally", "email": "dan.scally@ideasonboard.com" }, "delegate": null, "mbox": "https://patchwork.libcamera.org/patch/27072/mbox/", "series": [ { "id": 6022, "url": "https://patchwork.libcamera.org/api/series/6022/?format=api", "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=6022", "date": "2026-06-26T13:05:47", "name": "libipa: Re-work IPU3 IPA to use libipa algorithms", "version": 2, "mbox": "https://patchwork.libcamera.org/series/6022/mbox/" } ], "comments": "https://patchwork.libcamera.org/api/patches/27072/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/27072/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 D2041C3318\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 26 Jun 2026 13:06:25 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id AD17965F19;\n\tFri, 26 Jun 2026 15:06:23 +0200 (CEST)", "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 32D2565EE1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 26 Jun 2026 15:06:08 +0200 (CEST)", "from [127.0.1.1]\n\t(chfd-03-b2-v4wan-176392-cust229.vm15.cable.virginm.net\n\t[82.19.20.230])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 3FD561E7;\n\tFri, 26 Jun 2026 15:05:27 +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=\"Wd749cbz\"; dkim-atps=neutral", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1782479127;\n\tbh=FIBgslO/k1aow8xgwgmZBi8HwhIqX3xLhKNM4OUPFgY=;\n\th=From:Date:Subject:References:In-Reply-To:To:Cc:From;\n\tb=Wd749cbzBOuQWBojgwuu2NovyKIT2IwMtS6FyRI6qrKDFeCDH6/V/US0K3sYVI2ib\n\tnLP4+GZJPA6dBPM8AzWcvjuBrsbC0n/ribQoHlBbB1SMZOkEQ+IVYT13P6Az4xpAl+\n\t0yMpuusagRFSmYXYBW2MO5KWl59eF9hv1mbz/AVc=", "From": "Daniel Scally <dan.scally@ideasonboard.com>", "Date": "Fri, 26 Jun 2026 14:05:59 +0100", "Subject": "[PATCH v2 12/12] ipa: ipu3: Add Lens Shading Correction algorithm", "MIME-Version": "1.0", "Content-Type": "text/plain; charset=\"utf-8\"", "Content-Transfer-Encoding": "7bit", "Message-Id": "<20260626-ipu3-libipa-rework-v2-12-41546e23de3e@ideasonboard.com>", "References": "<20260626-ipu3-libipa-rework-v2-0-41546e23de3e@ideasonboard.com>", "In-Reply-To": "<20260626-ipu3-libipa-rework-v2-0-41546e23de3e@ideasonboard.com>", "To": "libcamera-devel@lists.libcamera.org", "Cc": "Daniel Scally <dan.scally@ideasonboard.com>, \n\tJacopo Mondi <jacopo.mondi@ideasonboard.com>", "X-Mailer": "b4 0.14.2", "X-Developer-Signature": "v=1; a=openpgp-sha256; l=15230;\n\ti=dan.scally@ideasonboard.com; h=from:subject:message-id;\n\tbh=FIBgslO/k1aow8xgwgmZBi8HwhIqX3xLhKNM4OUPFgY=;\n\tb=owEBbQKS/ZANAwAKAchJV3psRXUyAcsmYgBqPnk760DlwdVPMyl90d5Px7YI6hV2AJOk55IJl\n\t5YZrN56TeiJAjMEAAEKAB0WIQQqyuwyDnZdb+mxmm/ISVd6bEV1MgUCaj55OwAKCRDISVd6bEV1\n\tMlDlEACmNEom8sWYOyDUfkTwV/UiMxEpIJ95xQUhNNRS8hX+tpr7BViRCf3ZlbucC3LMN5Rn4E+\n\tDccld6KAjn/oWuLOCnWyIiASm5u3OGFDxFZkF6dpFxd9zFbCyoAIG+WBZSUvgQ6OFsF1rJgkbfJ\n\tlcxFGeyKASnYDjB/YBVpYuiVsE+V275l5TH8cIBR5s8HjHnTmL+gQZBhaVT4o5uuYQqIi1a/vr/\n\t5G95vMHhF2y85ExOZQ12B3CLfN85FA5QkrkskCAvV91yo6osUZkx8AmmV3fM/Og8HcBdla+qnki\n\tXfaOCymIJqZOtvEuXkroCdpHMap6wQ5GJ7Irh9U0XPdjOhanIrvmJ/U94k7U56pPEtCbG1OzDEE\n\t7B0NmdMtoVaDY7qs3C9Vhsm6vCOSlm0280XKCCkiZLFGBWaJE4c5gk1Rw73LRKdPNNZuLMjhtEA\n\tQAqXBSJ434yilHyh56eQ8pXogG/Z1ZnNq59Tj6L07q8BqQ9Ipg/yvPDNwnDiSasq8/uAq8FVv38\n\tuWUmZK6q5wg3YnR0djY+0x7TDEd2VhNPsZVJ+4C8NDfGkTakC2S3jzz998SHMNqP5saueSfg0o2\n\tUkZQko4i5CQ7lc458lWEnhoAvJLnZ9fYYCMF5bg9SfuXod1TXuv1u+qR37ZAPQtU9OkJ5ydKubU\n\tHJjkuwrtIqg6yig==", "X-Developer-Key": "i=dan.scally@ideasonboard.com; a=openpgp;\n\tfpr=EEC699ACA1B7CB5D31330C0BBD501C2A3546CCF6", "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": "Add a lens shading correction algorithm for the IPU3 IPA, using the\nrecently implemented libipa base algorithm class.\n\nReviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\nSigned-off-by: Daniel Scally <dan.scally@ideasonboard.com>\n---\nChanges in v2:\n\n - Fixed includes\n\t- Updated some documentation comments\n\t- Check tuning file for \"type\" key, not \"polynomial\"\n---\n src/ipa/ipu3/algorithms/lsc.cpp | 296 ++++++++++++++++++++++++++++++++++++\n src/ipa/ipu3/algorithms/lsc.h | 60 ++++++++\n src/ipa/ipu3/algorithms/meson.build | 1 +\n src/ipa/ipu3/ipa_context.cpp | 10 ++\n src/ipa/ipu3/ipa_context.h | 3 +\n 5 files changed, 370 insertions(+)", "diff": "diff --git a/src/ipa/ipu3/algorithms/lsc.cpp b/src/ipa/ipu3/algorithms/lsc.cpp\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..ab9775e6a64ed3d32e7ae3dd90f55f4128da40be\n--- /dev/null\n+++ b/src/ipa/ipu3/algorithms/lsc.cpp\n@@ -0,0 +1,296 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2026, Ideas on Board, Oy\n+ *\n+ * IPU3 Lens Shading Correction algorithm\n+ */\n+\n+#include \"lsc.h\"\n+\n+#include <bit>\n+#include <cmath>\n+\n+#include <libcamera/control_ids.h>\n+\n+/**\n+ * \\file lsc.h\n+ * \\brief IPU3 Lens Shading Correction\n+ */\n+\n+namespace libcamera {\n+\n+namespace ipa::ipu3::algorithms {\n+\n+/**\n+ * \\class Lsc\n+ * \\brief IPU3 Lens Shading Correction algorithm\n+ */\n+\n+LOG_DEFINE_CATEGORY(IPU3Lsc)\n+\n+static constexpr unsigned int kMaxNumHCells = 73;\n+static constexpr unsigned int kMaxNumVCells = 56;\n+static constexpr int kColourTemperatureQuantization = 10;\n+\n+/**\n+ * \\copydoc libcamera::ipa::Algorithm::init\n+ */\n+int Lsc::init(IPAContext &context, const ValueNode &tuningData)\n+{\n+\t/*\n+\t * The IPU3 lens shading block expects a table of data that isn't of a\n+\t * fixed size, but rather is configurable based on 4 parameters:\n+\t *\n+\t * block_width_log2:\tThe log2 of the horizontal pixel count per cell\n+\t * block_height_log2:\tThe log2 of the vertical pixel count per cell\n+\t * width:\t\tThe number of horizontal cells\n+\t * height:\t\tThe number of vertical cells\n+\t *\n+\t * The constructed grid should be capable of covering the image, but\n+\t * ideally won't extend past the edges of the image. Fixing either set\n+\t * of parameters for the algorithm as a whole is likely to result in\n+\t * suboptimal situations for some sensors, so let's determine them\n+\t * programmatically instead.\n+\t *\n+\t * What we want is the densest possible grid that ideally doesn't extend\n+\t * past the edges of the image at all. The maximum grid size is 73x56,\n+\t * which gives us the lower bounds on cell size. Unfortunately we can\n+\t * only specify sizes in powers of two, which can have the effect of\n+\t * making the grid much more coarse. For example for a 2592x1944 input\n+\t * image, 2592 / 73 = 35.5...which means we need to set blockWidthLog2\n+\t * to 6 (I.E. 64) and have just 40.5 (or rather 41) cells horizontally.\n+\t */\n+\tsensorWidth_ = context.sensorInfo.activeAreaSize.width;\n+\tsensorHeight_ = context.sensorInfo.activeAreaSize.height;\n+\n+\tunsigned int cellWidth = (sensorWidth_ + kMaxNumHCells - 1) / kMaxNumHCells;\n+\tunsigned int cellHeight = (sensorHeight_ + kMaxNumVCells - 1) / kMaxNumVCells;\n+\n+\tunsigned int minCellWidth = std::bit_ceil(cellWidth);\n+\tunsigned int minCellHeight = std::bit_ceil(cellHeight);\n+\n+\tnumHCells_ = (sensorWidth_ + minCellWidth - 1) / minCellWidth;\n+\tnumVCells_ = (sensorHeight_ + minCellHeight - 1) / minCellHeight;\n+\n+\tblockWidthLog2_ = std::bit_width(minCellWidth) - 1;\n+\tblockHeightLog2_ = std::bit_width(minCellHeight) - 1;\n+\n+\tLOG(IPU3Lsc, Debug) << \"Calculated Grid configuration: \"\n+\t\t\t << numHCells_ << \"x\" << numVCells_ << \" cells of \"\n+\t\t\t << minCellWidth << \"x\" << minCellHeight << \" pixels\";\n+\n+\t/*\n+\t * We need to know if we're running the polynomial algorithm or not as\n+\t * things will behave slightly differently.\n+\t */\n+\tpolynomial_ = tuningData[\"type\"].get<bool>(false);\n+\n+\treturn lscAlgo_.init(tuningData, context.ctrlMap, {\n+\t\t\t .keys = { \"r\", \"gr\", \"gb\", \"b\" },\n+\t\t\t .numHCells = numHCells_,\n+\t\t\t .numVCells = numVCells_,\n+\t\t\t .sensorSize = context.sensorInfo.activeAreaSize\n+\t});\n+}\n+\n+std::vector<double> Lsc::calculatePositions(unsigned int dimension)\n+{\n+\tstd::vector<double> positions(dimension);\n+\tfor (double i = 0.0; i < dimension; i++)\n+\t\tpositions[i] = i / (dimension - 1);\n+\n+\treturn positions;\n+}\n+\n+/**\n+ * \\copydoc libcamera::ipa::Algorithm::configure\n+ */\n+int Lsc::configure(IPAContext &context, const IPAConfigInfo &configInfo)\n+{\n+\tcropWidth_ = configInfo.sensorInfo.analogCrop.width;\n+\tcropHeight_ = configInfo.sensorInfo.analogCrop.height;\n+\tstd::vector<double> xPos = calculatePositions(numHCells_);\n+\tstd::vector<double> yPos = calculatePositions(numVCells_);\n+\n+\treturn lscAlgo_.configure(context.activeState.lsc,\n+\t\t\t\t configInfo.sensorInfo.analogCrop, xPos, yPos);\n+}\n+\n+/**\n+ * \\copydoc libcamera::ipa::Algorithm::queueRequest\n+ */\n+void Lsc::queueRequest(IPAContext &context, const uint32_t frame,\n+\t\t IPAFrameContext &frameContext,\n+\t\t const ControlList &controls)\n+{\n+\t/*\n+\t * The base algorithm defines the LensShadingCorrectionEnable control\n+\t * with a default value of true, but actually the IPU3 driver defaults\n+\t * it to off. If this is the first frame, check for the control, but if\n+\t * there isn't one, force it on to fulfil the advertised default.\n+\t */\n+\tif (frame == 0) {\n+\t\tconst auto &lscEnable = controls.get(controls::LensShadingCorrectionEnable);\n+\t\tif (!lscEnable) {\n+\t\t\tframeContext.lsc.enabled = true;\n+\t\t\tframeContext.lsc.update = true;\n+\t\t}\n+\t}\n+\n+\tlscAlgo_.queueRequest(context.activeState.lsc, frameContext.lsc, controls);\n+}\n+\n+static unsigned int quantize(unsigned int value, unsigned int step)\n+{\n+\treturn std::lround(value / static_cast<double>(step)) * step;\n+}\n+\n+/**\n+ * \\copydoc libcamera::ipa::Algorithm::prepare\n+ */\n+void Lsc::prepare([[maybe_unused]] IPAContext &context, [[maybe_unused]] const uint32_t frame,\n+\t\t IPAFrameContext &frameContext, ipu3_uapi_params *params)\n+{\n+\tuint32_t ct = frameContext.awb.temperatureK;\n+\tunsigned int quantizedCt = quantize(ct, kColourTemperatureQuantization);\n+\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+\t/*\n+\t * This flag tells the kernel driver that it should read the LSC params\n+\t * passed from userspace instead of using its cached copy.\n+\t */\n+\tparams->use.acc_shd = 1;\n+\n+\t/*\n+\t * Pass the enabled flag. If we're not enabled, we can then just bail\n+\t * out.\n+\t */\n+\tipu3_uapi_shd_config_static *config = ¶ms->acc_param.shd.shd;\n+\tconfig->general.shd_enable = frameContext.lsc.enabled;\n+\n+\tif (!frameContext.lsc.enabled)\n+\t\treturn;\n+\n+\tconfig->grid.width = numHCells_;\n+\tconfig->grid.height = numVCells_;\n+\tconfig->grid.block_width_log2 = blockWidthLog2_;\n+\tconfig->grid.block_height_log2 = blockHeightLog2_;\n+\tconfig->grid.grid_height_per_slice = IPU3_UAPI_SHD_MAX_CELLS_PER_SET / numHCells_;\n+\n+\t/*\n+\t * The IPU3's documentation describes the x_start and y_start members\n+\t * as follows:\n+\t *\n+\t * \"[X/Y] value of top left corner of sensor relative to ROI\n+\t * s13, [-4096, 0], default 0, only negative values.\"\n+\t *\n+\t * I interpret that as allowing us to configure the cropped rectangle\n+\t * relative to the full grid. That's useful if we're running the tabular\n+\t * algorithm, which would otherwise apply the full grid inappropriately.\n+\t * If we're running the polynomial one though the calculated grid is\n+\t * probably more appropriate than a coarse application of the full grid\n+\t * so let's tell the hardware not to bother correcting in that case.\n+\t */\n+\tif (polynomial_) {\n+\t\tconfig->grid.x_start = 0;\n+\t\tconfig->grid.y_start = 0;\n+\t} else {\n+\t\tconfig->grid.x_start = (cropWidth_ - sensorWidth_) / 2;\n+\t\tconfig->grid.y_start = (cropHeight_ - sensorHeight_) / 2;\n+\t}\n+\n+\t/* No idea what this is, but the docs say it should be set as so */\n+\tconfig->general.init_set_vrt_offst_ul =\n+\t\tconfig->grid.y_start >> (config->grid.block_height_log2 %\n+\t\t\t\t\t config->grid.grid_height_per_slice);\n+\n+\t/*\n+\t * Values in the LUT cease taking effect at 4096, and a value of 0.0 is\n+\t * \"no correction\" rather than black. The gain factor is described by\n+\t * the documentation like so:\n+\t *\n+\t * \"Shift calculated anti shading value. Precision u2. 0x0 - gain factor\n+\t * [1, 5], means no shift interpolated value. 0x1 - gain factor [1, 9],\n+\t * means shift interpolated by 1. 0x2 - gain factor [1, 17], means shift\n+\t * interpolated by 2.\"\n+\t *\n+\t * The simplest interpretation for those pieces of information is I\n+\t * think that the LUT stores 12-bit Q numbers who's represented values\n+\t * depend on the gain_factor setting like so:\n+\t *\n+\t * 0: UQ<2, 10> representing values in range [0, 4)\n+\t * 1: UQ<3, 9> representing values in range [0, 8)\n+\t * 2: UQ<4, 8> representing values in range [0, 16)\n+\t *\n+\t * And that a base gain of 1.0 is added to those configured values. As a\n+\t * gain of more than 5.0 is fairly unlikely, let's fix gain_factor to 0\n+\t * for now and revisit if needed.\n+\t */\n+\tconfig->general.gain_factor = 0;\n+\n+\t/*\n+\t * Disable the black level settings here - we do that through another\n+\t * parameters block.\n+\t */\n+\tconfig->black_level.bl_r = 0;\n+\tconfig->black_level.bl_gr = 0;\n+\tconfig->black_level.bl_gb = 0;\n+\tconfig->black_level.bl_b = 0;\n+\n+\tipu3_uapi_shd_lut *lut = ¶ms->acc_param.shd.shd_lut;\n+\n+\tconst auto &set = lscAlgo_.interpolateComponents(quantizedCt);\n+\n+\tunsigned int totalCells = numHCells_ * numVCells_;\n+\tunsigned int cellsPerSet = numHCells_ * config->grid.grid_height_per_slice;\n+\tunsigned int numSets = (numHCells_ + config->grid.grid_height_per_slice - 1) /\n+\t\t\t config->grid.grid_height_per_slice;\n+\tunsigned int i = 0;\n+\n+\tfor (unsigned int s = 0; s < numSets; s++) {\n+\t\tfor (unsigned int c = 0; c < cellsPerSet && i < totalCells; c++, i++) {\n+\t\t\tlut->sets[s].r_and_gr[c].r = set.at(\"r\")[i];\n+\t\t\tlut->sets[s].r_and_gr[c].gr = set.at(\"gr\")[i];\n+\t\t\tlut->sets[s].gb_and_b[c].gb = set.at(\"gb\")[i];\n+\t\t\tlut->sets[s].gb_and_b[c].b = set.at(\"b\")[i];\n+\t\t}\n+\t}\n+\n+\tlastAppliedCt_ = ct;\n+\tlastAppliedQuantizedCt_ = quantizedCt;\n+\tLOG(IPU3Lsc, 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 Lsc::process([[maybe_unused]] IPAContext &context,\n+\t\t [[maybe_unused]] const uint32_t frame,\n+\t\t IPAFrameContext &frameContext,\n+\t\t [[maybe_unused]] const ipu3_uapi_stats_3a *stats,\n+\t\t ControlList &metadata)\n+{\n+\tlscAlgo_.process(frameContext.lsc, metadata);\n+}\n+\n+REGISTER_IPA_ALGORITHM(Lsc, \"Lsc\")\n+\n+} /* ipa::ipu3::algorithms */\n+\n+} /* namespace libcamera */\ndiff --git a/src/ipa/ipu3/algorithms/lsc.h b/src/ipa/ipu3/algorithms/lsc.h\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..050ce91ad115a93224efb161ae0a3536fe32cc7d\n--- /dev/null\n+++ b/src/ipa/ipu3/algorithms/lsc.h\n@@ -0,0 +1,60 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2026, Ideas on Board, Oy\n+ *\n+ * IPU3 Lens Shading Correction algorithm\n+ */\n+\n+#pragma once\n+\n+#include <vector>\n+\n+#include \"libipa/fixedpoint.h\"\n+#include \"libipa/lsc.h\"\n+\n+#include \"algorithm.h\"\n+\n+namespace libcamera {\n+\n+namespace ipa::ipu3::algorithms {\n+\n+class Lsc : public Algorithm\n+{\n+public:\n+\tint init(IPAContext &context, const ValueNode &tuningData) 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+\tint configure(IPAContext &context, const IPAConfigInfo &configInfo) override;\n+\tvoid prepare(IPAContext &context, const uint32_t frame,\n+\t\t IPAFrameContext &frameContext,\n+\t\t ipu3_uapi_params *params) override;\n+\tvoid process(IPAContext &context, const uint32_t frame,\n+\t\t IPAFrameContext &frameContext,\n+\t\t const ipu3_uapi_stats_3a *stats,\n+\t\t ControlList &metadata) override;\n+\n+private:\n+\tstd::vector<double> calculatePositions(unsigned int dimension);\n+\n+\tunsigned int numHCells_;\n+\tunsigned int numVCells_;\n+\tunsigned int blockWidthLog2_;\n+\tunsigned int blockHeightLog2_;\n+\n+\tunsigned int lastAppliedCt_;\n+\tunsigned int lastAppliedQuantizedCt_;\n+\n+\tunsigned int sensorWidth_;\n+\tunsigned int sensorHeight_;\n+\tunsigned int cropWidth_;\n+\tunsigned int cropHeight_;\n+\n+\tbool polynomial_;\n+\n+\tLscAlgorithm<uint16_t, UQ<2, 10>> lscAlgo_;\n+};\n+\n+} /* ipa::ipu3::algorithms */\n+\n+} /* namespace libcamera */\ndiff --git a/src/ipa/ipu3/algorithms/meson.build b/src/ipa/ipu3/algorithms/meson.build\nindex 3dafd2fda9897942cf87d9640665c4fcff383859..70177f50415259e0c2bd207205c385dde07d6624 100644\n--- a/src/ipa/ipu3/algorithms/meson.build\n+++ b/src/ipa/ipu3/algorithms/meson.build\n@@ -6,5 +6,6 @@ ipu3_ipa_algorithms = files([\n 'awb.cpp',\n 'blc.cpp',\n 'ccm.cpp',\n+ 'lsc.cpp',\n 'tone_mapping.cpp',\n ])\ndiff --git a/src/ipa/ipu3/ipa_context.cpp b/src/ipa/ipu3/ipa_context.cpp\nindex 9537802ceca5018118bdf4c71ef361c20fc44bfb..935264279a8a6346cb3e58ddb5577811b46163e8 100644\n--- a/src/ipa/ipu3/ipa_context.cpp\n+++ b/src/ipa/ipu3/ipa_context.cpp\n@@ -127,6 +127,11 @@ namespace libcamera::ipa::ipu3 {\n * \\brief Active gamma correction parameters for the IPA\n */\n \n+/**\n+ * \\var IPAActiveState::lsc\n+ * \\brief Active lens shading correction parameters for the IPA\n+ */\n+\n /**\n * \\var IPASessionConfiguration::sensor\n * \\brief Sensor-specific configuration of the IPA\n@@ -186,4 +191,9 @@ namespace libcamera::ipa::ipu3 {\n * \\brief Per-frame gamma correction parameters for the IPA\n */\n \n+/**\n+ * \\var IPAFrameContext::lsc\n+ * \\brief Per-frame lens shading correction parameters for the IPA\n+ */\n+\n } /* namespace libcamera::ipa::ipu3 */\ndiff --git a/src/ipa/ipu3/ipa_context.h b/src/ipa/ipu3/ipa_context.h\nindex d650f2fe1ad8eab91b7128a47c9690e42b1595f1..d4bf2091a64fb1aad5c715a2d9d265ae3aecacb4 100644\n--- a/src/ipa/ipu3/ipa_context.h\n+++ b/src/ipa/ipu3/ipa_context.h\n@@ -21,6 +21,7 @@\n #include <libipa/ccm.h>\n #include <libipa/fc_queue.h>\n #include <libipa/gamma.h>\n+#include <libipa/lsc.h>\n \n namespace libcamera {\n \n@@ -68,6 +69,7 @@ struct IPAActiveState {\n \tipa::awb::ActiveState awb;\n \tipa::ccm::ActiveState ccm;\n \tipa::gamma::ActiveState gamma;\n+\tipa::lsc::ActiveState lsc;\n };\n \n struct IPAFrameContext : public FrameContext {\n@@ -79,6 +81,7 @@ struct IPAFrameContext : public FrameContext {\n \tipa::awb::FrameContext awb;\n \tipa::ccm::FrameContext ccm;\n \tipa::gamma::FrameContext gamma;\n+\tipa::lsc::FrameContext lsc;\n };\n \n struct IPAContext {\n", "prefixes": [ "v2", "12/12" ] }