[{"id":37691,"web_url":"https://patchwork.libcamera.org/comment/37691/","msgid":"<176856719884.3486172.7070695396131936829@ping.linuxembedded.co.uk>","date":"2026-01-16T12:39:58","subject":"Re: [PATCH v3 13/15] ipa: rkisp1: lsc: Resample polynomial lens\n\tshading tables at configure time","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"Quoting Stefan Klug (2026-01-14 11:58:06)\n> The lens shading correction is always applied based on the sensor crop\n> bounds. This leads to incorrect lens shading correction for analog crops\n> that do not cover the whole sensor.\n> \n> To fix that, we need to adapt the lens shading table for the selected\n> analog crop at configure time. Introduce an abstract ShadingDescriptor\n> class that holds the lens shading information that can then be sampled\n> at configure time for a specific crop rectangle.\n> \n> Resampling for a specific crop is only implemented for polynomial lsc\n> data. For tabular data, a warning is logged and the unmodified table is\n> returned. This matches the current functionality for tabular data and is\n> a huge improvement for polynomial data.\n> \n> Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>\n> \n> ---\n> \n> Changes in v2:\n> - Replaced \"auto const\" by \"const auto\"\n> - Replaces some vector parameters by Spans\n> - Changed sampleForCrop to return the components\n> - Replaced min/max by clamp\n> - Replaced map.swap() by move assignment\n> ---\n>  src/ipa/rkisp1/algorithms/lsc.cpp | 233 ++++++++++++++++++++++++--------------\n>  src/ipa/rkisp1/algorithms/lsc.h   |  13 +++\n>  2 files changed, 159 insertions(+), 87 deletions(-)\n> \n> diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp\n> index 5808381fd84dc49fd8548e4d00b2fb4b7015f509..8427c48f65b2e6e200f834db506939c6c85fd2a3 100644\n> --- a/src/ipa/rkisp1/algorithms/lsc.cpp\n> +++ b/src/ipa/rkisp1/algorithms/lsc.cpp\n> @@ -72,38 +72,127 @@ namespace {\n>  \n>  constexpr int kColourTemperatureQuantization = 10;\n>  \n> -class LscPolynomialLoader\n> +class LscPolynomialShadingDescriptor : public LensShadingCorrection::ShadingDescriptor\n>  {\n>  public:\n> -       LscPolynomialLoader(const Size &sensorSize,\n> -                           const Rectangle &cropRectangle,\n> -                           const std::vector<double> &xSizes,\n> -                           const std::vector<double> &ySizes)\n> -               : sensorSize_(sensorSize),\n> -                 cropRectangle_(cropRectangle),\n> -                 xSizes_(xSizes),\n> -                 ySizes_(ySizes)\n> +       LscPolynomialShadingDescriptor(const LscPolynomial &pr, const LscPolynomial &pgr,\n> +                                      const LscPolynomial &pgb, const LscPolynomial &pb)\n> +               : pr_(pr), pgr_(pgr), pgb_(pgb), pb_(pb)\n>         {\n>         }\n>  \n> -       int parseLscData(const YamlObject &yamlSets,\n> -                        std::map<unsigned int, LensShadingCorrection::Components> &lscData);\n> +       LensShadingCorrection::Components sampleForCrop(const Rectangle &cropRectangle,\n> +                                                       Span<const double> xSizes,\n> +                                                       Span<const double> ySizes) override;\n>  \n>  private:\n> -       std::vector<double> sizesListToPositions(const std::vector<double> &sizes);\n>         std::vector<uint16_t> samplePolynomial(const LscPolynomial &poly,\n>                                                Span<const double> xPositions,\n>                                                Span<const double> yPositions,\n>                                                const Rectangle &cropRectangle);\n>  \n> +       std::vector<double> sizesListToPositions(Span<const double> sizes);\n> +\n> +       LscPolynomial pr_;\n> +       LscPolynomial pgr_;\n> +       LscPolynomial pgb_;\n> +       LscPolynomial pb_;\n> +};\n> +\n> +LensShadingCorrection::Components\n> +LscPolynomialShadingDescriptor::sampleForCrop(const Rectangle &cropRectangle,\n> +                                             Span<const double> xSizes,\n> +                                             Span<const double> ySizes)\n> +{\n> +       std::vector<double> xPos = sizesListToPositions(xSizes);\n> +       std::vector<double> yPos = sizesListToPositions(ySizes);\n> +\n> +       return {\n> +               .r = samplePolynomial(pr_, xPos, yPos, cropRectangle),\n> +               .gr = samplePolynomial(pgr_, xPos, yPos, cropRectangle),\n> +               .gb = samplePolynomial(pgb_, xPos, yPos, cropRectangle),\n> +               .b = samplePolynomial(pb_, xPos, yPos, cropRectangle)\n> +       };\n> +}\n> +\n> +std::vector<uint16_t>\n> +LscPolynomialShadingDescriptor::samplePolynomial(const LscPolynomial &poly,\n> +                                                Span<const double> xPositions,\n> +                                                Span<const double> yPositions,\n> +                                                const Rectangle &cropRectangle)\n> +{\n> +       double m = poly.getM();\n> +       double x0 = cropRectangle.x / m;\n> +       double y0 = cropRectangle.y / m;\n> +       double w = cropRectangle.width / m;\n> +       double h = cropRectangle.height / m;\n> +       std::vector<uint16_t> samples;\n> +\n> +       samples.reserve(xPositions.size() * yPositions.size());\n> +\n> +       for (double y : yPositions) {\n> +               for (double x : xPositions) {\n> +                       double xp = x0 + x * w;\n> +                       double yp = y0 + y * h;\n> +                       /*\n> +                        * The hardware uses 2.10 fixed point format and limits\n> +                        * the legal values to [1..3.999]. Scale and clamp the\n> +                        * sampled value accordingly.\n> +                        */\n\nLooks like a good candidate for UQ<2,10> which would already clamp to 3.99902\n\n\t\t\tfloat s = poly.sampleAtNormalizedPixelPos(xp, yp);\n\t\t\ts = std::max(s, 1);\n\t\t\tsamples.push_back(UQ<2, 10>(s).quantized());\n\nWould that make the code more expressive or readable ?\n\nverifying ...\n\n+++ b/test/ipa/libipa/fixedpoint.cpp\n@@ -136,6 +136,9 @@ protected:\n                fails += quantizedCheck<UQ<1, 7>>(-100.0f, 0b0'0000000, 0.0f);\n                fails += quantizedCheck<UQ<1, 7>>(+100.0f, 0b1'1111111, 1.99219f);\n \n+               introduce<UQ<2, 10>>(\"UQ2.10\");\n+               fails += quantizedCheck<UQ<2, 10>>(4, 4095, 3.99902f);\n\n...\n\nUQ2.10(0 .. 3.99902)  Min: [0x0000:0] -- Max: [0x0fff:3.99902] Step:0.000976562\n  Checking 4 == [0x0fff:3.99902]\n\n\nBut this is just a code move of existing code - so anything like that\ncould be on top. (Although I see the clamp is already an update to the\nmoving code).\n\nAnyway, there's nothing I object to in this so:\n\n\nReviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n\n\n> +                       int v = static_cast<int>(\n> +                               poly.sampleAtNormalizedPixelPos(xp, yp) *\n> +                               1024);\n> +                       v = std::clamp(v, 1024, 4095);\n> +                       samples.push_back(v);\n> +               }\n> +       }\n> +       return samples;\n> +}\n> +\n> +/*\n> + * The rkisp1 LSC grid spacing is defined by the cell sizes on the first half of\n> + * the grid. This is then mirrored in hardware to the other half. See\n> + * parseSizes() for further details. For easier handling, this function converts\n> + * the cell sizes of half the grid to a list of position of the whole grid (on\n> + * one axis). Example:\n> + *\n> + * input:   | 0.2 | 0.3 |\n> + * output: 0.0   0.2   0.5   0.8   1.0\n> + */\n> +std::vector<double>\n> +LscPolynomialShadingDescriptor::sizesListToPositions(Span<const double> sizes)\n> +{\n> +       const int half = sizes.size();\n> +       std::vector<double> positions(half * 2 + 1);\n> +       double x = 0.0;\n> +\n> +       positions[half] = 0.5;\n> +       for (int i = 1; i <= half; i++) {\n> +               x += sizes[half - i];\n> +               positions[half - i] = 0.5 - x;\n> +               positions[half + i] = 0.5 + x;\n> +       }\n> +\n> +       return positions;\n> +}\n> +\n> +class LscPolynomialLoader\n> +{\n> +public:\n> +       LscPolynomialLoader(const Size &sensorSize)\n> +               : sensorSize_(sensorSize)\n> +       {\n> +       }\n> +\n> +       int parseLscData(const YamlObject &yamlSets,\n> +                        LensShadingCorrection::ShadingDescriptorMap &lscData);\n> +\n> +private:\n>         Size sensorSize_;\n> -       Rectangle cropRectangle_;\n> -       const std::vector<double> &xSizes_;\n> -       const std::vector<double> &ySizes_;\n>  };\n>  \n>  int LscPolynomialLoader::parseLscData(const YamlObject &yamlSets,\n> -                                     std::map<unsigned int, LensShadingCorrection::Components> &lscData)\n> +                                     LensShadingCorrection::ShadingDescriptorMap &lscData)\n>  {\n>         const auto &sets = yamlSets.asList();\n>         for (const auto &yamlSet : sets) {\n> @@ -117,7 +206,6 @@ int LscPolynomialLoader::parseLscData(const YamlObject &yamlSets,\n>                         return -EINVAL;\n>                 }\n>  \n> -               LensShadingCorrection::Components &set = lscData[ct];\n>                 pr = yamlSet[\"r\"].get<LscPolynomial>();\n>                 pgr = yamlSet[\"gr\"].get<LscPolynomial>();\n>                 pgb = yamlSet[\"gb\"].get<LscPolynomial>();\n> @@ -135,12 +223,9 @@ int LscPolynomialLoader::parseLscData(const YamlObject &yamlSets,\n>                 pgb->setReferenceImageSize(sensorSize_);\n>                 pb->setReferenceImageSize(sensorSize_);\n>  \n> -               std::vector<double> xPos = sizesListToPositions(xSizes_);\n> -               std::vector<double> yPos = sizesListToPositions(ySizes_);\n> -               set.r = samplePolynomial(*pr, xPos, yPos, cropRectangle_);\n> -               set.gr = samplePolynomial(*pgr, xPos, yPos, cropRectangle_);\n> -               set.gb = samplePolynomial(*pgb, xPos, yPos, cropRectangle_);\n> -               set.b = samplePolynomial(*pb, xPos, yPos, cropRectangle_);\n> +               lscData.emplace(\n> +                       ct, std::make_unique<LscPolynomialShadingDescriptor>(\n> +                                   *pr, *pgr, *pgb, *pb));\n>         }\n>  \n>         if (lscData.empty()) {\n> @@ -151,70 +236,33 @@ int LscPolynomialLoader::parseLscData(const YamlObject &yamlSets,\n>         return 0;\n>  }\n>  \n> -/*\n> - * The rkisp1 LSC grid spacing is defined by the cell sizes on the first half of\n> - * the grid. This is then mirrored in hardware to the other half. See\n> - * parseSizes() for further details. For easier handling, this function converts\n> - * the cell sizes of half the grid to a list of position of the whole grid (on\n> - * one axis). Example:\n> - *\n> - * input:   | 0.2 | 0.3 |\n> - * output: 0.0   0.2   0.5   0.8   1.0\n> - */\n> -std::vector<double> LscPolynomialLoader::sizesListToPositions(const std::vector<double> &sizes)\n> +class LscTableShadingDescriptor : public LensShadingCorrection::ShadingDescriptor\n>  {\n> -       const int half = sizes.size();\n> -       std::vector<double> positions(half * 2 + 1);\n> -       double x = 0.0;\n> -\n> -       positions[half] = 0.5;\n> -       for (int i = 1; i <= half; i++) {\n> -               x += sizes[half - i];\n> -               positions[half - i] = 0.5 - x;\n> -               positions[half + i] = 0.5 + x;\n> +public:\n> +       LscTableShadingDescriptor(LensShadingCorrection::Components components)\n> +               : lscData_(std::move(components))\n> +       {\n>         }\n>  \n> -       return positions;\n> -}\n> -\n> -std::vector<uint16_t> LscPolynomialLoader::samplePolynomial(const LscPolynomial &poly,\n> -                                                           Span<const double> xPositions,\n> -                                                           Span<const double> yPositions,\n> -                                                           const Rectangle &cropRectangle)\n> -{\n> -       double m = poly.getM();\n> -       double x0 = cropRectangle.x / m;\n> -       double y0 = cropRectangle.y / m;\n> -       double w = cropRectangle.width / m;\n> -       double h = cropRectangle.height / m;\n> -       std::vector<uint16_t> samples;\n> -\n> -       samples.reserve(xPositions.size() * yPositions.size());\n> -\n> -       for (double y : yPositions) {\n> -               for (double x : xPositions) {\n> -                       double xp = x0 + x * w;\n> -                       double yp = y0 + y * h;\n> -                       /*\n> -                        * The hardware uses 2.10 fixed point format and limits\n> -                        * the legal values to [1..3.999]. Scale and clamp the\n> -                        * sampled value accordingly.\n> -                        */\n> -                       int v = static_cast<int>(\n> -                               poly.sampleAtNormalizedPixelPos(xp, yp) *\n> -                               1024);\n> -                       v = std::min(std::max(v, 1024), 4095);\n> -                       samples.push_back(v);\n> -               }\n> +       LensShadingCorrection::Components\n> +       sampleForCrop([[maybe_unused]] const Rectangle &cropRectangle,\n> +                     [[maybe_unused]] Span<const double> xSizes,\n> +                     [[maybe_unused]] Span<const double> ySizes)\n> +       {\n> +               LOG(RkISP1Lsc, Warning)\n> +                       << \"Tabular LSC data doesn't support resampling.\";\n> +               return lscData_;\n>         }\n> -       return samples;\n> -}\n> +\n> +private:\n> +       LensShadingCorrection::Components lscData_;\n> +};\n>  \n>  class LscTableLoader\n>  {\n>  public:\n>         int parseLscData(const YamlObject &yamlSets,\n> -                        std::map<unsigned int, LensShadingCorrection::Components> &lscData);\n> +                        LensShadingCorrection::ShadingDescriptorMap &lscData);\n>  \n>  private:\n>         std::vector<uint16_t> parseTable(const YamlObject &tuningData,\n> @@ -222,7 +270,7 @@ private:\n>  };\n>  \n>  int LscTableLoader::parseLscData(const YamlObject &yamlSets,\n> -                                std::map<unsigned int, LensShadingCorrection::Components> &lscData)\n> +                                LensShadingCorrection::ShadingDescriptorMap &lscData)\n>  {\n>         const auto &sets = yamlSets.asList();\n>  \n> @@ -236,8 +284,7 @@ int LscTableLoader::parseLscData(const YamlObject &yamlSets,\n>                         return -EINVAL;\n>                 }\n>  \n> -               LensShadingCorrection::Components &set = lscData[ct];\n> -\n> +               LensShadingCorrection::Components set;\n>                 set.r = parseTable(yamlSet, \"r\");\n>                 set.gr = parseTable(yamlSet, \"gr\");\n>                 set.gb = parseTable(yamlSet, \"gb\");\n> @@ -250,6 +297,9 @@ int LscTableLoader::parseLscData(const YamlObject &yamlSets,\n>                                 << \" is missing tables\";\n>                         return -EINVAL;\n>                 }\n> +\n> +               lscData.emplace(\n> +                       ct, std::make_unique<LscTableShadingDescriptor>(std::move(set)));\n>         }\n>  \n>         if (lscData.empty()) {\n> @@ -341,7 +391,7 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context,\n>                 return -EINVAL;\n>         }\n>  \n> -       std::map<unsigned int, Components> lscData;\n> +       ShadingDescriptorMap lscData;\n>         int ret = 0;\n>  \n>         std::string type = tuningData[\"type\"].get<std::string>(\"table\");\n> @@ -351,10 +401,11 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context,\n>                 ret = loader.parseLscData(yamlSets, lscData);\n>         } else if (type == \"polynomial\") {\n>                 LOG(RkISP1Lsc, Debug) << \"Loading polynomial LSC data.\";\n> -               auto loader = LscPolynomialLoader(context.sensorInfo.activeAreaSize,\n> -                                                 context.sensorInfo.analogCrop,\n> -                                                 xSize_,\n> -                                                 ySize_);\n> +               /*\n> +                * \\todo: Most likely the reference frame should be native_size.\n> +                * Let's wait how the internal discussions progress.\n> +                */\n> +               auto loader = LscPolynomialLoader(context.sensorInfo.activeAreaSize);\n>                 ret = loader.parseLscData(yamlSets, lscData);\n>         } else {\n>                 LOG(RkISP1Lsc, Error) << \"Unsupported LSC data type '\"\n> @@ -365,7 +416,7 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context,\n>         if (ret)\n>                 return ret;\n>  \n> -       sets_.setData(std::move(lscData));\n> +       shadingDescriptors_ = std::move(lscData);\n>  \n>         return 0;\n>  }\n> @@ -401,6 +452,14 @@ int LensShadingCorrection::configure(IPAContext &context,\n>                 yGrad_[i] = std::round(32768 / ySizes_[i]);\n>         }\n>  \n> +       LOG(RkISP1Lsc, Debug) << \"Sample LSC data for \" << configInfo.analogCrop;\n> +       std::map<unsigned int, LensShadingCorrection::Components> shadingData;\n> +       for (const auto &[t, descriptor] : shadingDescriptors_)\n> +               shadingData[t] = descriptor->sampleForCrop(configInfo.analogCrop,\n> +                                                          xSize_, ySize_);\n> +\n> +       sets_.setData(std::move(shadingData));\n> +\n>         context.configuration.lsc.enabled = true;\n>         return 0;\n>  }\n> diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h\n> index 7b68dda1a0d4257c345e0f1b77aa498f8183c92f..3097740a6cb2ce9063a4ba087856987a489b0ab6 100644\n> --- a/src/ipa/rkisp1/algorithms/lsc.h\n> +++ b/src/ipa/rkisp1/algorithms/lsc.h\n> @@ -8,6 +8,7 @@\n>  #pragma once\n>  \n>  #include <map>\n> +#include <memory>\n>  \n>  #include \"libipa/interpolator.h\"\n>  \n> @@ -36,10 +37,22 @@ public:\n>                 std::vector<uint16_t> b;\n>         };\n>  \n> +       class ShadingDescriptor\n> +       {\n> +       public:\n> +               virtual ~ShadingDescriptor() = default;\n> +               virtual Components sampleForCrop(const Rectangle &cropRectangle,\n> +                                                Span<const double> xSizes,\n> +                                                Span<const double> ySizes) = 0;\n> +       };\n> +\n> +       using ShadingDescriptorMap = std::map<unsigned int, std::unique_ptr<ShadingDescriptor>>;\n> +\n>  private:\n>         void setParameters(rkisp1_cif_isp_lsc_config &config);\n>         void copyTable(rkisp1_cif_isp_lsc_config &config, const Components &set0);\n>  \n> +       ShadingDescriptorMap shadingDescriptors_;\n>         ipa::Interpolator<Components> sets_;\n>         std::vector<double> xSize_;\n>         std::vector<double> ySize_;\n> \n> -- \n> 2.51.0\n>","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 57E2ABDCBF\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 16 Jan 2026 12:40:05 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 2DC30615B2;\n\tFri, 16 Jan 2026 13:40:04 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 9EA6B615B2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 16 Jan 2026 13:40:02 +0100 (CET)","from pendragon.ideasonboard.com\n\t(cpc89244-aztw30-2-0-cust6594.18-1.cable.virginm.net [86.31.185.195])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 3B1904B3;\n\tFri, 16 Jan 2026 13:39:34 +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=\"YBdX0hXK\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1768567174;\n\tbh=JaUc0l7JGoPNMcGhb74oUM4bMXsqMlnKSApX0cDJC4w=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=YBdX0hXK9LQN1fU4/gHL2Yt1fF6d6ikfwz9S9T8mmd/ai2It0V27JTevaSNQ0ytQT\n\tbnU1qykvBMLoZToOswpfcwStzTcXC0ebyx2h1ZWhLBiYGjgNsPaHohZ89BG7KOB3+f\n\tFVo5JJ3OyAhhY5TDqblktgONDPf4/770GW2j58eY=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<20260114-sklug-lsc-resampling-v2-dev-v3-13-80cd24f4dd61@ideasonboard.com>","References":"<20260114-sklug-lsc-resampling-v2-dev-v3-0-80cd24f4dd61@ideasonboard.com>\n\t<20260114-sklug-lsc-resampling-v2-dev-v3-13-80cd24f4dd61@ideasonboard.com>","Subject":"Re: [PATCH v3 13/15] ipa: rkisp1: lsc: Resample polynomial lens\n\tshading tables at configure time","From":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Cc":"Stefan Klug <stefan.klug@ideasonboard.com>","To":"Stefan Klug <stefan.klug@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","Date":"Fri, 16 Jan 2026 12:39:58 +0000","Message-ID":"<176856719884.3486172.7070695396131936829@ping.linuxembedded.co.uk>","User-Agent":"alot/0.9.1","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>"}},{"id":37698,"web_url":"https://patchwork.libcamera.org/comment/37698/","msgid":"<cfda9781-17ef-4376-87a4-9e358d953a75@ideasonboard.com>","date":"2026-01-16T14:51:27","subject":"Re: [PATCH v3 13/15] ipa: rkisp1: lsc: Resample polynomial lens\n\tshading tables at configure time","submitter":{"id":216,"url":"https://patchwork.libcamera.org/api/people/216/","name":"Barnabás Pőcze","email":"barnabas.pocze@ideasonboard.com"},"content":"2026. 01. 14. 12:58 keltezéssel, Stefan Klug írta:\n> The lens shading correction is always applied based on the sensor crop\n> bounds. This leads to incorrect lens shading correction for analog crops\n> that do not cover the whole sensor.\n> \n> To fix that, we need to adapt the lens shading table for the selected\n> analog crop at configure time. Introduce an abstract ShadingDescriptor\n> class that holds the lens shading information that can then be sampled\n> at configure time for a specific crop rectangle.\n> \n> Resampling for a specific crop is only implemented for polynomial lsc\n> data. For tabular data, a warning is logged and the unmodified table is\n> returned. This matches the current functionality for tabular data and is\n> a huge improvement for polynomial data.\n> \n> Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>\n> \n> ---\n> \n> Changes in v2:\n> - Replaced \"auto const\" by \"const auto\"\n> - Replaces some vector parameters by Spans\n> - Changed sampleForCrop to return the components\n> - Replaced min/max by clamp\n> - Replaced map.swap() by move assignment\n> ---\n>   src/ipa/rkisp1/algorithms/lsc.cpp | 233 ++++++++++++++++++++++++--------------\n>   src/ipa/rkisp1/algorithms/lsc.h   |  13 +++\n>   2 files changed, 159 insertions(+), 87 deletions(-)\n> \n> diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp\n> index 5808381fd84dc49fd8548e4d00b2fb4b7015f509..8427c48f65b2e6e200f834db506939c6c85fd2a3 100644\n> --- a/src/ipa/rkisp1/algorithms/lsc.cpp\n> +++ b/src/ipa/rkisp1/algorithms/lsc.cpp\n> @@ -72,38 +72,127 @@ namespace {\n> [...]\n> -std::vector<double> LscPolynomialLoader::sizesListToPositions(const std::vector<double> &sizes)\n> +class LscTableShadingDescriptor : public LensShadingCorrection::ShadingDescriptor\n>   {\n> -\tconst int half = sizes.size();\n> -\tstd::vector<double> positions(half * 2 + 1);\n> -\tdouble x = 0.0;\n> -\n> -\tpositions[half] = 0.5;\n> -\tfor (int i = 1; i <= half; i++) {\n> -\t\tx += sizes[half - i];\n> -\t\tpositions[half - i] = 0.5 - x;\n> -\t\tpositions[half + i] = 0.5 + x;\n> +public:\n> +\tLscTableShadingDescriptor(LensShadingCorrection::Components components)\n> +\t\t: lscData_(std::move(components))\n> +\t{\n>   \t}\n>   \n> -\treturn positions;\n> -}\n> -\n> -std::vector<uint16_t> LscPolynomialLoader::samplePolynomial(const LscPolynomial &poly,\n> -\t\t\t\t\t\t\t    Span<const double> xPositions,\n> -\t\t\t\t\t\t\t    Span<const double> yPositions,\n> -\t\t\t\t\t\t\t    const Rectangle &cropRectangle)\n> -{\n> -\tdouble m = poly.getM();\n> -\tdouble x0 = cropRectangle.x / m;\n> -\tdouble y0 = cropRectangle.y / m;\n> -\tdouble w = cropRectangle.width / m;\n> -\tdouble h = cropRectangle.height / m;\n> -\tstd::vector<uint16_t> samples;\n> -\n> -\tsamples.reserve(xPositions.size() * yPositions.size());\n> -\n> -\tfor (double y : yPositions) {\n> -\t\tfor (double x : xPositions) {\n> -\t\t\tdouble xp = x0 + x * w;\n> -\t\t\tdouble yp = y0 + y * h;\n> -\t\t\t/*\n> -\t\t\t * The hardware uses 2.10 fixed point format and limits\n> -\t\t\t * the legal values to [1..3.999]. Scale and clamp the\n> -\t\t\t * sampled value accordingly.\n> -\t\t\t */\n> -\t\t\tint v = static_cast<int>(\n> -\t\t\t\tpoly.sampleAtNormalizedPixelPos(xp, yp) *\n> -\t\t\t\t1024);\n> -\t\t\tv = std::min(std::max(v, 1024), 4095);\n> -\t\t\tsamples.push_back(v);\n> -\t\t}\n> +\tLensShadingCorrection::Components\n> +\tsampleForCrop([[maybe_unused]] const Rectangle &cropRectangle,\n> +\t\t      [[maybe_unused]] Span<const double> xSizes,\n> +\t\t      [[maybe_unused]] Span<const double> ySizes)\n\nMissing `override`.\n\n\n> +\t{\n> +\t\tLOG(RkISP1Lsc, Warning)\n> +\t\t\t<< \"Tabular LSC data doesn't support resampling.\";\n\nSmall thing, but I noticed it while testing: most log message don't have punctuation at the end.\n\n\n\n> +\t\treturn lscData_;\n>   \t}\n> -\treturn samples;\n> -}\n> +\n> +private:\n> +\tLensShadingCorrection::Components lscData_;\n> +};\n>   \n>   class LscTableLoader\n>   {\n>   public:\n>   \tint parseLscData(const YamlObject &yamlSets,\n> -\t\t\t std::map<unsigned int, LensShadingCorrection::Components> &lscData);\n> +\t\t\t LensShadingCorrection::ShadingDescriptorMap &lscData);\n>   \n>   private:\n>   \tstd::vector<uint16_t> parseTable(const YamlObject &tuningData,\n> @@ -222,7 +270,7 @@ private:\n>   };\n>   \n>   int LscTableLoader::parseLscData(const YamlObject &yamlSets,\n> -\t\t\t\t std::map<unsigned int, LensShadingCorrection::Components> &lscData)\n> +\t\t\t\t LensShadingCorrection::ShadingDescriptorMap &lscData)\n>   {\n>   \tconst auto &sets = yamlSets.asList();\n>   \n> @@ -236,8 +284,7 @@ int LscTableLoader::parseLscData(const YamlObject &yamlSets,\n>   \t\t\treturn -EINVAL;\n>   \t\t}\n>   \n> -\t\tLensShadingCorrection::Components &set = lscData[ct];\n> -\n> +\t\tLensShadingCorrection::Components set;\n>   \t\tset.r = parseTable(yamlSet, \"r\");\n>   \t\tset.gr = parseTable(yamlSet, \"gr\");\n>   \t\tset.gb = parseTable(yamlSet, \"gb\");\n> @@ -250,6 +297,9 @@ int LscTableLoader::parseLscData(const YamlObject &yamlSets,\n>   \t\t\t\t<< \" is missing tables\";\n>   \t\t\treturn -EINVAL;\n>   \t\t}\n> +\n> +\t\tlscData.emplace(\n> +\t\t\tct, std::make_unique<LscTableShadingDescriptor>(std::move(set)));\n>   \t}\n>   \n>   \tif (lscData.empty()) {\n> @@ -341,7 +391,7 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context,\n>   \t\treturn -EINVAL;\n>   \t}\n>   \n> -\tstd::map<unsigned int, Components> lscData;\n> +\tShadingDescriptorMap lscData;\n>   \tint ret = 0;\n>   \n>   \tstd::string type = tuningData[\"type\"].get<std::string>(\"table\");\n> @@ -351,10 +401,11 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context,\n>   \t\tret = loader.parseLscData(yamlSets, lscData);\n>   \t} else if (type == \"polynomial\") {\n>   \t\tLOG(RkISP1Lsc, Debug) << \"Loading polynomial LSC data.\";\n> -\t\tauto loader = LscPolynomialLoader(context.sensorInfo.activeAreaSize,\n> -\t\t\t\t\t\t  context.sensorInfo.analogCrop,\n> -\t\t\t\t\t\t  xSize_,\n> -\t\t\t\t\t\t  ySize_);\n> +\t\t/*\n> +\t\t * \\todo: Most likely the reference frame should be native_size.\n> +\t\t * Let's wait how the internal discussions progress.\n> +\t\t */\n> +\t\tauto loader = LscPolynomialLoader(context.sensorInfo.activeAreaSize);\n>   \t\tret = loader.parseLscData(yamlSets, lscData);\n>   \t} else {\n>   \t\tLOG(RkISP1Lsc, Error) << \"Unsupported LSC data type '\"\n> @@ -365,7 +416,7 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context,\n>   \tif (ret)\n>   \t\treturn ret;\n>   \n> -\tsets_.setData(std::move(lscData));\n> +\tshadingDescriptors_ = std::move(lscData);\n>   \n>   \treturn 0;\n>   }\n> @@ -401,6 +452,14 @@ int LensShadingCorrection::configure(IPAContext &context,\n>   \t\tyGrad_[i] = std::round(32768 / ySizes_[i]);\n>   \t}\n>   \n> +\tLOG(RkISP1Lsc, Debug) << \"Sample LSC data for \" << configInfo.analogCrop;\n> +\tstd::map<unsigned int, LensShadingCorrection::Components> shadingData;\n> +\tfor (const auto &[t, descriptor] : shadingDescriptors_)\n> +\t\tshadingData[t] = descriptor->sampleForCrop(configInfo.analogCrop,\n> +\t\t\t\t\t\t\t   xSize_, ySize_);\n> +\n> +\tsets_.setData(std::move(shadingData));\n> +\n\nAs far as I understand, these previous lines are the important parts, the rest\nis just to make this possible, mostly moving things. I also compared the current\nand proposed source files themselves, and things look ok to me.\n\n\nReviewed-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>\n\n\n>   \tcontext.configuration.lsc.enabled = true;\n>   \treturn 0;\n>   }\n> diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h\n> index 7b68dda1a0d4257c345e0f1b77aa498f8183c92f..3097740a6cb2ce9063a4ba087856987a489b0ab6 100644\n> --- a/src/ipa/rkisp1/algorithms/lsc.h\n> +++ b/src/ipa/rkisp1/algorithms/lsc.h\n> @@ -8,6 +8,7 @@\n>   #pragma once\n>   \n>   #include <map>\n> +#include <memory>\n>   \n>   #include \"libipa/interpolator.h\"\n>   \n> @@ -36,10 +37,22 @@ public:\n>   \t\tstd::vector<uint16_t> b;\n>   \t};\n>   \n> +\tclass ShadingDescriptor\n> +\t{\n> +\tpublic:\n> +\t\tvirtual ~ShadingDescriptor() = default;\n> +\t\tvirtual Components sampleForCrop(const Rectangle &cropRectangle,\n> +\t\t\t\t\t\t Span<const double> xSizes,\n> +\t\t\t\t\t\t Span<const double> ySizes) = 0;\n> +\t};\n> +\n> +\tusing ShadingDescriptorMap = std::map<unsigned int, std::unique_ptr<ShadingDescriptor>>;\n> +\n>   private:\n>   \tvoid setParameters(rkisp1_cif_isp_lsc_config &config);\n>   \tvoid copyTable(rkisp1_cif_isp_lsc_config &config, const Components &set0);\n>   \n> +\tShadingDescriptorMap shadingDescriptors_;\n>   \tipa::Interpolator<Components> sets_;\n>   \tstd::vector<double> xSize_;\n>   \tstd::vector<double> ySize_;\n>","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 A1AD2C3226\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 16 Jan 2026 14:51:33 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id D255A61FBC;\n\tFri, 16 Jan 2026 15:51:32 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id DE052615B2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 16 Jan 2026 15:51:31 +0100 (CET)","from [192.168.33.22] (185.221.143.114.nat.pool.zt.hu\n\t[185.221.143.114])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 60ED7A30;\n\tFri, 16 Jan 2026 15:51:03 +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=\"fEZFXb0A\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1768575063;\n\tbh=CIhFaejvmYbvH/uOW1YM/TCTzD6AYBBKiwvfYvAgIwc=;\n\th=Date:Subject:To:References:From:In-Reply-To:From;\n\tb=fEZFXb0A7mfv0SxAlAiQg9cRAToPV8aOk654akhncx7hfrNb5P33ZpQITspxWRE8j\n\tzsNN0oMCtG2C/dypz+k8pIrJbqETdhS3S6gmHie9xfYcePqxeuNClJZ8Jj4DVIGgtu\n\tIyF4KWE45Utd6DENKhyllp8caW9295SsTd00BK0s=","Message-ID":"<cfda9781-17ef-4376-87a4-9e358d953a75@ideasonboard.com>","Date":"Fri, 16 Jan 2026 15:51:27 +0100","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH v3 13/15] ipa: rkisp1: lsc: Resample polynomial lens\n\tshading tables at configure time","To":"Stefan Klug <stefan.klug@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","References":"<20260114-sklug-lsc-resampling-v2-dev-v3-0-80cd24f4dd61@ideasonboard.com>\n\t<20260114-sklug-lsc-resampling-v2-dev-v3-13-80cd24f4dd61@ideasonboard.com>","From":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","Content-Language":"en-US, hu-HU","In-Reply-To":"<20260114-sklug-lsc-resampling-v2-dev-v3-13-80cd24f4dd61@ideasonboard.com>","Content-Type":"text/plain; charset=UTF-8; format=flowed","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>"}}]