[{"id":39814,"web_url":"https://patchwork.libcamera.org/comment/39814/","msgid":"<3a9ab525-876a-4d0f-9223-78755a5ebfb5@ideasonboard.com>","date":"2026-07-24T08:43:15","subject":"Re: [PATCH v6 23/31] ipa: libipa: lsc: Make Components a map<>","submitter":{"id":216,"url":"https://patchwork.libcamera.org/api/people/216/","name":"Barnabás Pőcze","email":"barnabas.pocze@ideasonboard.com"},"content":"2026. 07. 20. 16:59 keltezéssel, Jacopo Mondi írta:\n> The current definition of lsc::Components assumes 4 gain vectors named\n> after the colour components supported by the RkISP1 ISP (r, gr, gb and\n> b).\n> \n> As different ISP support different colour channels for their Lsc tables\n> make lsc::Components a map that associates the name of the colour\n> channel to the vector of gains.\n> \n> The use of strings as index for the map comes from the requirement of\n> parsing the tuning file using string identifiers. Pass the strings to\n> use for tuning file parsing to the LscAlgorithm::init() function as part\n> of a newly introduced LscDescriptor type and use the same strings for\n> indexing the gain arrays.\n> \n> The newly introduced LscDescriptor type also provides to the\n> LscAlgorithm class the expected size of the Lsc grid, allowing to remove\n> RkISP1 specific values from the libipa generic implementation.\n> \n> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n> Reviewed-by: Stefan Klug <stefan.klug@ideasonboard.com>\n> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n> \n> ---\n> v5->v6:\n> - Rename LscDescriptor::num*Cells to num*Samples\n> ---\n>   src/ipa/libipa/lsc.cpp            | 10 +++---\n>   src/ipa/libipa/lsc.h              |  4 +--\n>   src/ipa/libipa/lsc_base.cpp       | 49 ++++++++++++++++++++--------\n>   src/ipa/libipa/lsc_base.h         | 18 ++++++-----\n>   src/ipa/libipa/lsc_polynomial.cpp | 65 +++++++++++++++++++------------------\n>   src/ipa/libipa/lsc_polynomial.h   | 10 ++----\n>   src/ipa/libipa/lsc_table.cpp      | 68 +++++++++++++++++++++++----------------\n>   src/ipa/libipa/lsc_table.h        |  9 ++++--\n>   src/ipa/rkisp1/algorithms/lsc.cpp | 20 ++++++++----\n>   9 files changed, 150 insertions(+), 103 deletions(-)\n> \n> [...]\n> diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp\n> index b3671376a12a..01616d1c2e2a 100644\n> --- a/src/ipa/libipa/lsc_base.cpp\n> +++ b/src/ipa/libipa/lsc_base.cpp\n> @@ -19,23 +19,20 @@ namespace ipa {\n>   namespace lsc {\n>   \n>   /**\n> - * \\struct Components\n> + * \\typedef Components\n>    * \\brief Associate colour components with a list of gains\n>    *\n>    * LSC tables are defined as a list of gain values associated to a colour\n>    * component.\n>    *\n> - * \\var Components::r\n> - * \\brief The list of gains for the Red colour component\n> + * As different ISP support different colour components (usually 'r', 'gr',\n> + * 'gb', 'b' or just 'r', 'g', 'b') this class associates a string\n> + * identifier for the colour component to a list of gains.\n>    *\n> - * \\var Components::gr\n> - * \\brief The list of gains for the Green/Red colour component\n> + * Each key name shall match an entry in the tuning file.\n>    *\n> - * \\var Components::gb\n> - * \\brief The list of gains for the Green/Blue colour component\n> - *\n> - * \\var Components::b\n> - * \\brief The list of gains for the Blue colour component\n> + * The list of keys is provided to the LscAlgorithm class using \\a\n> + * LscDescriptor::keys.\n>    */\n>   \n>   /**\n> @@ -57,13 +54,32 @@ void Interpolator<lsc::Components>::\n>   \t\t    lsc::Components &dest,\n>   \t\t    double lambda)\n>   {\n> -\tinterpolateVector(a.r, b.r, dest.r, lambda);\n> -\tinterpolateVector(a.gr, b.gr, dest.gr, lambda);\n> -\tinterpolateVector(a.gb, b.gb, dest.gb, lambda);\n> -\tinterpolateVector(a.b, b.b, dest.b, lambda);\n> +\tfor (auto const &[k, v] : a)\n> +\t\tinterpolateVector(v, b.at(k), dest[k], lambda);\n>   }\n>   #endif\n>   \n> +/**\n> + * \\struct LscDescriptor\n> + * \\brief Describe the ISP LSC engine\n> + *\n> + * \\var LscDescriptor::keys\n> + * \\brief The list of colour components to which a list of gains is associated\n> + * with in the tuning file. Used for parsing the tuning file\n> + *\n> + * \\var LscDescriptor::numHSamples\n> + * \\brief Number of horizontal gain samples of the ISP LSC grid. Used for\n> + * validating the list of gains parsed from tuning file\n> + *\n> + * \\var LscDescriptor::numVSamples\n> + * \\brief Number of vertical gain samples of the ISP LSC grid. Used for\n> + * validating the list of gains parsed from tuning file\n> + *\n> + * \\var LscDescriptor::sensorSize\n> + * \\brief The physical sensor size. This is the largest frame size used to\n> + * generate the LSC table. Only used by the polynomial LSC algorithm\n> + */\n> +\n>   /**\n>    * \\class LscImplementation\n>    * \\brief Pure virtual base class for LSC algorithm implementations\n> @@ -80,6 +96,11 @@ void Interpolator<lsc::Components>::\n>    * \\fn LscImplementation::parseLscData\n>    * \\brief Parse \\a tuningData\n>    * \\param[in] tuningData The tuning data\n> + * \\param[in] descriptor The LSC engine descriptor\n> + *\n> + * Parse the tuning file using the \\a descriptor to identify the colour\n> + * components in the tuning data and validate the size of the loaded gains\n> + * tables.\n>    *\n>    * \\return 0 on success, a negative error number otherwise\n>    */\n> diff --git a/src/ipa/libipa/lsc_base.h b/src/ipa/libipa/lsc_base.h\n> index 1bdb7d5c8ed9..b0b9b3b0712d 100644\n> --- a/src/ipa/libipa/lsc_base.h\n> +++ b/src/ipa/libipa/lsc_base.h\n> @@ -26,13 +26,7 @@ namespace ipa {\n>   \n>   namespace lsc {\n>   \n> -struct Components {\n> -\tstd::vector<uint16_t> r;\n> -\tstd::vector<uint16_t> gr;\n> -\tstd::vector<uint16_t> gb;\n> -\tstd::vector<uint16_t> b;\n> -};\n> -\n> +using Components = std::map<std::string, std::vector<uint16_t>>;\n\nI suggest\n\n   using Components = std::map<std::string, std::vector<uint16_t>, std::less<>>\n\nbecause that allows some map operations (e.g. find()) without having to\nconstruct a value of the key type (std::string).\n\n\n>   using ComponentsMap = std::map<unsigned int, Components>;\n>   \n>   } /* namespace lsc */\n> @@ -56,12 +50,20 @@ void Interpolator<lsc::Components>::\n>   \t\t    double lambda);\n>   #endif /* __DOXYGEN__ */\n>   \n> +struct LscDescriptor {\n> +\tstd::vector<std::string> keys;\n> +\tunsigned int numHSamples;\n> +\tunsigned int numVSamples;\n> +\tSize sensorSize;\n> +};\n> +\n>   class LscImplementation\n>   {\n>   public:\n>   \tvirtual ~LscImplementation() {}\n>   \n> -\tvirtual int parseLscData(const ValueNode &tuningData) = 0;\n> +\tvirtual int parseLscData(const ValueNode &tuningData,\n> +\t\t\t\t const LscDescriptor &descriptor) = 0;\n>   \n>   \tvirtual lsc::ComponentsMap\n>   \tsampleForCrop(const Rectangle &cropRectangle,\n> diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp\n> index 682e2f14fe09..e17ba85448be 100644\n> --- a/src/ipa/libipa/lsc_polynomial.cpp\n> +++ b/src/ipa/libipa/lsc_polynomial.cpp\n> @@ -133,44 +133,43 @@ void Polynomial::setReferenceImageSize(const Size &size)\n>   /**\n>    * \\brief Parse polynomial LSC data\n>    * \\param[in] sets The tuning file content\n> + * \\param[in] descriptor The LSC engine descriptor\n>    *\n>    * Parse the LSC data in polyomial form from the \\a sets tuning data.\n>    *\n>    * \\return 0 on success or a negative error number otherwise\n>    */\n> -int LscPolynomial::parseLscData(const ValueNode &sets)\n> +int LscPolynomial::parseLscData(const ValueNode &sets,\n> +\t\t\t\tconst LscDescriptor &descriptor)\n>   {\n>   \tfor (const auto &set : sets.asList()) {\n> -\t\tstd::optional<lsc::Polynomial> pr, pgr, pgb, pb;\n>   \t\tuint32_t ct = set[\"ct\"].get<uint32_t>(0);\n>   \n> -\t\tif (lscData_.count(ct)) {\n> -\t\t\tLOG(LscPolynomial, Error)\n> -\t\t\t\t<< \"Multiple sets found for \"\n> -\t\t\t\t<< \"color temperature \" << ct;\n> -\t\t\treturn -EINVAL;\n> +\t\tPolynomialComponents components;\n> +\t\tfor (auto &k : descriptor.keys) {\n> +\t\t\tauto polynomial = set[k.c_str()].get<lsc::Polynomial>();\n\nI would drop the `.c_str()`, it shouldn't be needed.\n\n\n> +\t\t\tif (!polynomial) {\n> +\t\t\t\tLOG(LscPolynomial, Error)\n> +\t\t\t\t\t<< \"Missing polynomial for component \"\n> +\t\t\t\t\t<< k;\n> +\t\t\t\treturn -EINVAL;\n> +\t\t\t}\n> +\n> +\t\t\tauto [it, inserted] =\n> +\t\t\t\tcomponents.emplace(std::piecewise_construct,\n> +\t\t\t\t\t\t   std::forward_as_tuple(k.c_str()),\n> +\t\t\t\t\t\t   std::forward_as_tuple(*polynomial));\n\nI would suggest using `try_emplace` over `emplace()` in essentially all situations:\n\n   auto [it, inserted] = components.try_emplace(k, std::move(*polynomial));\n\nand even just an `ASSERT(inserted)` could be useful to notice if the same key\nhas been specified multiple times by accident.\n\n> +\n> +\t\t\tit->second.setReferenceImageSize(descriptor.sensorSize);\n>   \t\t}\n>   \n> -\t\tpr = set[\"r\"].get<lsc::Polynomial>();\n> -\t\tpgr = set[\"gr\"].get<lsc::Polynomial>();\n> -\t\tpgb = set[\"gb\"].get<lsc::Polynomial>();\n> -\t\tpb = set[\"b\"].get<lsc::Polynomial>();\n> -\n> -\t\tif (!(pr || pgr || pgb || pb)) {\n> +\t\tauto [it, inserted] = lscData_.emplace(ct, components);\n\nSame here. And `std::move(components)`.\n\n\n> +\t\tif (!inserted) {\n>   \t\t\tLOG(LscPolynomial, Error)\n> -\t\t\t\t<< \"Failed to parse polynomial for \"\n> -\t\t\t\t<< \"colour temperature \" << ct;\n> +\t\t\t\t<< \"Multiple sets found for \"\n> +\t\t\t\t<< \"color temperature \" << ct;\n>   \t\t\treturn -EINVAL;\n>   \t\t}\n> -\n> -\t\tpr->setReferenceImageSize(sensorSize_);\n> -\t\tpgr->setReferenceImageSize(sensorSize_);\n> -\t\tpgb->setReferenceImageSize(sensorSize_);\n> -\t\tpb->setReferenceImageSize(sensorSize_);\n> -\n> -\t\tlscData_.emplace(std::piecewise_construct,\n> -\t\t\t\t std::forward_as_tuple(ct),\n> -\t\t\t\t std::forward_as_tuple(PolynomialComponents{ *pr, *pgr, *pgb, *pb }));\n>   \t}\n>   \n>   \tif (lscData_.empty()) {\n> @@ -210,13 +209,17 @@ LscPolynomial::sampleForCrop(const Rectangle &cropRectangle,\n>   \n>   \tlsc::ComponentsMap components;\n>   \n> -\tfor (const auto &[k, p] : lscData_) {\n> -\t\tcomponents[k] = {\n> -\t\t\tsamplePolynomial(p.pr, xPos, yPos, cropRectangle),\n> -\t\t\tsamplePolynomial(p.pgr, xPos, yPos, cropRectangle),\n> -\t\t\tsamplePolynomial(p.pgb, xPos, yPos, cropRectangle),\n> -\t\t\tsamplePolynomial(p.pb, xPos, yPos, cropRectangle)\n> -\t\t};\n> +\tfor (const auto &[t, c] : lscData_) {\n> +\t\tlsc::Components comp;\n> +\n> +\t\tfor (const auto &[k, p] : c) {\n> +\t\t\tcomp.emplace(std::piecewise_construct,\n> +\t\t\t\t     std::forward_as_tuple(k),\n> +\t\t\t\t     std::forward_as_tuple(samplePolynomial(p, xPos, yPos,\n> +\t\t\t\t\t\t\t\t\t    cropRectangle)));\n\nSame here wrt. `try_emplace()`.\n\n\n> +\t\t}\n> +\n> +\t\tcomponents[t] = comp;\n>   \t}\n>   \n>   \treturn components;\n> diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h\n> index d2276ecc6f6b..2a26db499d68 100644\n> --- a/src/ipa/libipa/lsc_polynomial.h\n> +++ b/src/ipa/libipa/lsc_polynomial.h\n> @@ -53,12 +53,7 @@ private:\n>   class LscPolynomial : public LscImplementation\n>   {\n>   private:\n> -\tstruct PolynomialComponents {\n> -\t\tlsc::Polynomial pr;\n> -\t\tlsc::Polynomial pgr;\n> -\t\tlsc::Polynomial pgb;\n> -\t\tlsc::Polynomial pb;\n> -\t};\n> +\tusing PolynomialComponents = std::map<std::string, lsc::Polynomial>;\n\nSame here wrt. `std::less<>`.\n\n\n>   \tusing PolynomialComponentsMap = std::map<unsigned int, PolynomialComponents>;\n>   \n>   public:\n> @@ -67,7 +62,8 @@ public:\n>   \t{\n>   \t}\n>   \n> -\tint parseLscData(const ValueNode &sets) override;\n> +\tint parseLscData(const ValueNode &sets,\n> +\t\t\t const LscDescriptor &descriptor) override;\n>   \n>   \tlsc::ComponentsMap\n>   \tsampleForCrop(const Rectangle &cropRectangle,\n> diff --git a/src/ipa/libipa/lsc_table.cpp b/src/ipa/libipa/lsc_table.cpp\n> index 47dff156963a..92e24bc5160a 100644\n> --- a/src/ipa/libipa/lsc_table.cpp\n> +++ b/src/ipa/libipa/lsc_table.cpp\n> @@ -7,9 +7,6 @@\n>   \n>   #include \"lsc_table.h\"\n>   \n> -/* \\todo Remove RkISP1 from libipa. */\n> -#include \"linux/rkisp1-config.h\"\n> -\n>   namespace libcamera {\n>   \n>   LOG_DEFINE_CATEGORY(LscTable)\n> @@ -29,42 +26,56 @@ namespace ipa {\n>   /**\n>    * \\brief Parse tabular LSC data\n>    * \\param[in] sets The tuning file content\n> + * \\param[in] descriptor The LSC engine descriptor\n>    *\n>    * Parse the LSC data in tabular form from the \\a sets tuning data.\n>    *\n>    * \\return 0 on success or a negative error number otherwise\n>    */\n> -int LscTable::parseLscData(const ValueNode &sets)\n> +int LscTable::parseLscData(const ValueNode &sets,\n> +\t\t\t   const LscDescriptor &descriptor)\n>   {\n>   \tfor (const auto &set : sets.asList()) {\n>   \t\tuint32_t ct = set[\"ct\"].get<uint32_t>(0);\n>   \n> -\t\tif (lscData_.count(ct)) {\n> -\t\t\tLOG(LscTable, Error)\n> -\t\t\t\t<< \"Multiple sets found for color temperature \"\n> -\t\t\t\t<< ct;\n> -\t\t\treturn -EINVAL;\n> -\t\t}\n> +\t\tint ret = parseLscComponent(set, ct, descriptor);\n> +\t\tif (ret)\n> +\t\t\treturn ret;\n> +\t}\n>   \n> -\t\tlsc::Components components;\n> -\t\tcomponents.r = parseTable(set, \"r\");\n> -\t\tcomponents.gr = parseTable(set, \"gr\");\n> -\t\tcomponents.gb = parseTable(set, \"gb\");\n> -\t\tcomponents.b = parseTable(set, \"b\");\n> +\tif (lscData_.empty()) {\n> +\t\tLOG(LscTable, Error) << \"Failed to load any sets\";\n> +\t\treturn -EINVAL;\n> +\t}\n>   \n> -\t\tif (components.r.empty() || components.gr.empty() ||\n> -\t\t    components.gb.empty() || components.b.empty()) {\n> +\treturn 0;\n> +}\n> +\n> +int LscTable::parseLscComponent(const ValueNode &yamlSet,\n> +\t\t\t\tunsigned int ct, const LscDescriptor &descriptor)\n> +{\n> +\tlsc::Components component;\n> +\tfor (auto &k : descriptor.keys) {\n> +\t\tauto [it, inserted] = component.emplace(\n> +\t\t\tstd::piecewise_construct,\n> +\t\t\tstd::forward_as_tuple(k.c_str()),\n> +\t\t\tstd::forward_as_tuple(parseTable(yamlSet,\n> +\t\t\t\t\t\t\t k.c_str(),\n> +\t\t\t\t\t\t\t descriptor.numHSamples,\n> +\t\t\t\t\t\t\t descriptor.numVSamples)));\n\nSame here wrt. `try_emplace()` and dropping `.c_str()`.\n\n\n> +\t\tif (!inserted || it->second.empty()) {\n>   \t\t\tLOG(LscTable, Error)\n> -\t\t\t\t<< \"Set for color temperature \" << ct\n> -\t\t\t\t<< \" is missing tables\";\n> +\t\t\t\t<< \"Set \" << k << \" for color temperature \"\n> +\t\t\t\t<< ct << \" is missing\";\n\n`!inserted` means that `k` is already in `component`, meaning `descriptor.keys`\ncontains duplicates. I'm not sure the error message is appropriate for that case.\n\n\n>   \t\t\treturn -EINVAL;\n>   \t\t}\n> -\n> -\t\tlscData_.emplace(ct, std::move(components));\n>   \t}\n>   \n> -\tif (lscData_.empty()) {\n> -\t\tLOG(LscTable, Error) << \"Failed to load any sets\";\n> +\tauto [it, inserted] = lscData_.emplace(ct, component);\n\n   std::move(component)\n\n\n> +\tif (!inserted) {\n> +\t\tLOG(LscTable, Error)\n> +\t\t\t<< \"Multiple sets found for color temperature \"\n> +\t\t\t<< ct;\n>   \t\treturn -EINVAL;\n>   \t}\n>   \n> @@ -72,17 +83,18 @@ int LscTable::parseLscData(const ValueNode &sets)\n>   }\n>   \n>   std::vector<uint16_t> LscTable::parseTable(const ValueNode &tuningData,\n> -\t\t\t\t\t   const char *prop)\n> +\t\t\t\t\t   const char *prop,\n> +\t\t\t\t\t   unsigned int numHSamples,\n> +\t\t\t\t\t   unsigned int numVSamples)\n>   {\n> -\tstatic constexpr unsigned int kLscNumSamples =\n> -\t\tRKISP1_CIF_ISP_LSC_SAMPLES_MAX * RKISP1_CIF_ISP_LSC_SAMPLES_MAX;\n> +\tunsigned int lscNumSamples = numHSamples * numVSamples;\n>   \n>   \tstd::vector<uint16_t> table =\n>   \t\ttuningData[prop].get<std::vector<uint16_t>>().value_or(utils::defopt);\n> -\tif (table.size() != kLscNumSamples) {\n> +\tif (table.size() != lscNumSamples) {\n>   \t\tLOG(LscTable, Error)\n>   \t\t\t<< \"Invalid '\" << prop << \"' values: expected \"\n> -\t\t\t<< kLscNumSamples\n> +\t\t\t<< lscNumSamples\n>   \t\t\t<< \" elements, got \" << table.size();\n>   \t\treturn {};\n>   \t}\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 A0B38BDE17\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 24 Jul 2026 08:43:21 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 529D567EDD;\n\tFri, 24 Jul 2026 10:43:20 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 047AE67E8A\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 24 Jul 2026 10:43:18 +0200 (CEST)","from [192.168.33.42] (185.182.215.156.nat.pool.zt.hu\n\t[185.182.215.156])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 412B673B;\n\tFri, 24 Jul 2026 10:42:17 +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=\"fNu5xOSg\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1784882537;\n\tbh=6gYuobZsAMJFtKnr3kYTO7j+hlB9jvrJxg5bwjJd+BQ=;\n\th=Date:Subject:To:Cc:References:From:In-Reply-To:From;\n\tb=fNu5xOSgflda+GLe4SF7g15lE8xy7Bs5jw9aBlnjT2wOwAMIiUpnqERCO4Js9Zy30\n\tYsE7C9xDXQMXO5Tt1HU891Asu4Jgmh9cRIXsayhBvgXpiE6Bo4KZb6Ivq5QOlZetvs\n\t6Yhe7AvpsHpbYtY7TD4EixJaWnO8QKQd4duRQbos=","Message-ID":"<3a9ab525-876a-4d0f-9223-78755a5ebfb5@ideasonboard.com>","Date":"Fri, 24 Jul 2026 10:43:15 +0200","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH v6 23/31] ipa: libipa: lsc: Make Components a map<>","To":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org,\n\tStefan Klug <stefan.klug@ideasonboard.com>,\n\tMilan Zamazal <mzamazal@redhat.com>","Cc":"Kieran Bingham <kieran.bingham@ideasonboard.com>","References":"<20260720-libipa-algorithms-v6-0-ececb73f97cb@ideasonboard.com>\n\t<20260720-libipa-algorithms-v6-23-ececb73f97cb@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":"<20260720-libipa-algorithms-v6-23-ececb73f97cb@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>"}}]