[{"id":24390,"web_url":"https://patchwork.libcamera.org/comment/24390/","msgid":"<Yu19srTC86UiVhUm@pendragon.ideasonboard.com>","date":"2022-08-05T20:29:38","subject":"Re: [libcamera-devel] [PATCH v2 3/3] ipa: rkisp1: Add support of\n\tDenoise Pre-Filter control","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Florian,\n\nThank you for the patch.\n\nOn Fri, Aug 05, 2022 at 02:40:27PM +0200, Florian Sylvestre via libcamera-devel wrote:\n> The denoise pre-filter algorithm is a bilateral filter which combines a range\n> filter and a domain filter. The denoise pre-filter is applied before\n> demosaicing.\n> \n> Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>\n> ---\n>  src/ipa/rkisp1/algorithms/dpf.cpp     | 253 ++++++++++++++++++++++++++\n>  src/ipa/rkisp1/algorithms/dpf.h       |  36 ++++\n>  src/ipa/rkisp1/algorithms/meson.build |   1 +\n>  src/ipa/rkisp1/data/ov5640.yaml       |  11 ++\n>  src/ipa/rkisp1/ipa_context.cpp        |  11 ++\n>  src/ipa/rkisp1/ipa_context.h          |   5 +\n>  6 files changed, 317 insertions(+)\n>  create mode 100644 src/ipa/rkisp1/algorithms/dpf.cpp\n>  create mode 100644 src/ipa/rkisp1/algorithms/dpf.h\n> \n> diff --git a/src/ipa/rkisp1/algorithms/dpf.cpp b/src/ipa/rkisp1/algorithms/dpf.cpp\n> new file mode 100644\n> index 00000000..07dc20d3\n> --- /dev/null\n> +++ b/src/ipa/rkisp1/algorithms/dpf.cpp\n> @@ -0,0 +1,253 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2021-2022, Ideas On Board\n> + *\n> + * dpf.cpp - RkISP1 Denoise Pre-Filter control\n> + */\n> +\n> +#include \"dpf.h\"\n> +\n> +#include <cmath>\n> +\n> +#include <libcamera/base/log.h>\n> +\n> +#include <libcamera/control_ids.h>\n> +#include \"linux/rkisp1-config.h\"\n> +\n> +/**\n> + * \\file dpf.h\n> + */\n> +\n> +namespace libcamera {\n> +\n> +namespace ipa::rkisp1::algorithms {\n> +\n> +/**\n> + * \\class Dpf\n> + * \\brief RkISP1 Denoise Pre-Filter control\n> + *\n> + * The denoise pre-filter algorithm is a bilateral filter which combines a\n> + * range filter and a domain filter. The denoise pre-filter is applied before\n> + * demosaicing.\n> + */\n> +\n> +LOG_DEFINE_CATEGORY(RkISP1Dpf)\n> +\n> +Dpf::Dpf()\n> +\t: initialized_(false), config_({}), strengthConfig_({})\n> +{\n> +}\n> +\n> +/**\n> + * \\copydoc libcamera::ipa::Algorithm::init\n> + */\n> +int Dpf::init([[maybe_unused]] IPAContext &context,\n> +\t      const YamlObject &tuningData)\n> +{\n> +\tstd::vector<uint16_t> values;\n> +\n> +\t/*\n> +\t * The domain kernel is configured with a 9x9 kernel for the green\n> +\t * pixels, and a 13x9 or 9x9 kernel for red and blue pixels.\n> +\t */\n> +\tconst YamlObject &dFObject = tuningData[\"DomainFilter\"];\n> +\n> +\t/*\n> +\t * For the green component, we have the 9x9 kernel specified\n> +\t * as 6 coefficients:\n> +\t *    Y\n> +\t *    ^\n> +\t *  4 | 6   5   4   5   6\n> +\t *  3 |   5   3   3   5\n> +\t *  2 | 5   3   2   3   5\n> +\t *  1 |   3   1   1   3\n> +\t *  0 - 4   2   0   2   4\n> +\t * -1 |   3   1   1   3\n> +\t * -2 | 5   3   2   3   5\n> +\t * -3 |   5   3   3   5\n> +\t * -4 | 6   5   4   5   6\n> +\t *    +---------|--------> X\n> +\t *     -4....-1 0 1 2 3 4\n> +\t */\n> +\tvalues = dFObject[\"g\"].getList<uint16_t>().value_or(utils::defopt);\n> +\tif (values.size() != RKISP1_CIF_ISP_DPF_MAX_SPATIAL_COEFFS) {\n> +\t\tLOG(RkISP1Dpf, Error)\n> +\t\t\t<< \"Invalid 'DomainFilter:g': expected \"\n> +\t\t\t<< RKISP1_CIF_ISP_DPF_MAX_SPATIAL_COEFFS\n> +\t\t\t<< \" elements, got \" << values.size();\n> +\t\treturn -EINVAL;\n> +\t}\n> +\n> +\tstd::copy_n(values.begin(), values.size(),\n> +\t\t    std::begin(config_.g_flt.spatial_coeff));\n> +\n> +\tconfig_.g_flt.gr_enable = true;\n> +\tconfig_.g_flt.gb_enable = true;\n> +\n> +\t/*\n> +\t * For the red and blue components, we have the 13x9 kernel specified\n> +\t * as 6 coefficients:\n> +\t *\n> +\t *    Y\n> +\t *    ^\n> +\t *  4 | 6   5   4   3   4   5   6\n> +\t *    |\n> +\t *  2 | 5   4   2   1   2   4   5\n> +\t *    |\n> +\t *  0 - 5   3   1   0   1   3   5\n> +\t *    |\n> +\t * -2 | 5   4   2   1   2   4   5\n> +\t *    |\n> +\t * -4 | 6   5   4   3   4   5   6\n> +\t *    +-------------|------------> X\n> +\t *     -6  -4  -2   0   2   4   6\n> +\t *\n> +\t * For a 9x9 kernel, columns -6 and 6 are dropped, so coefficient\n> +\t * number 6 is not used.\n> +\t */\n> +\tvalues = dFObject[\"rb\"].getList<uint16_t>().value_or(utils::defopt);\n> +\tif (values.size() != RKISP1_CIF_ISP_DPF_MAX_SPATIAL_COEFFS &&\n> +\t    values.size() != RKISP1_CIF_ISP_DPF_MAX_SPATIAL_COEFFS - 1) {\n> +\t\tLOG(RkISP1Dpf, Error)\n> +\t\t\t<< \"Invalid 'DomainFilter:rb': expected \"\n> +\t\t\t<< RKISP1_CIF_ISP_DPF_MAX_SPATIAL_COEFFS - 1\n> +\t\t\t<< \" or \" << RKISP1_CIF_ISP_DPF_MAX_SPATIAL_COEFFS\n> +\t\t\t<< \" elements, got \" << values.size();\n> +\t\treturn -EINVAL;\n> +\t}\n> +\n> +\tconfig_.rb_flt.fltsize = values.size() == RKISP1_CIF_ISP_DPF_MAX_SPATIAL_COEFFS\n> +\t\t\t       ? RKISP1_CIF_ISP_DPF_RB_FILTERSIZE_13x9\n> +\t\t\t       : RKISP1_CIF_ISP_DPF_RB_FILTERSIZE_9x9;\n> +\n> +\tstd::copy_n(values.begin(), values.size(),\n> +\t\t    std::begin(config_.rb_flt.spatial_coeff));\n> +\n> +\tconfig_.rb_flt.r_enable = true;\n> +\tconfig_.rb_flt.b_enable = true;\n> +\n> +\t/*\n> +\t * The range kernel is configured with a noise level lookup table (NLL)\n> +\t * which stores a piecewise linear function that characterizes the\n> +\t * sensor noise profile as a noise level function curve (NLF).\n> +\t */\n> +\tconst YamlObject &rFObject = tuningData[\"NoiseLevelFunction\"];\n> +\n> +\tvalues = rFObject[\"coeff\"].getList<uint16_t>().value_or(utils::defopt);\n> +\tif (values.size() != RKISP1_CIF_ISP_DPF_MAX_NLF_COEFFS) {\n> +\t\tLOG(RkISP1Dpf, Error)\n> +\t\t\t<< \"Invalid 'RangeFilter:coeff': expected \"\n> +\t\t\t<< RKISP1_CIF_ISP_DPF_MAX_NLF_COEFFS\n> +\t\t\t<< \" elements, got \" << values.size();\n> +\t\treturn -EINVAL;\n> +\t}\n> +\n> +\tstd::copy_n(values.begin(), values.size(),\n> +\t\t    std::begin(config_.nll.coeff));\n> +\n> +\tstd::string scaleMode = rFObject[\"scale-mode\"].get<std::string>(\"\");\n> +\tif (scaleMode == \"linear\") {\n> +\t\tconfig_.nll.scale_mode = RKISP1_CIF_ISP_NLL_SCALE_LINEAR;\n> +\t} else if (scaleMode == \"logarithmic\") {\n> +\t\tconfig_.nll.scale_mode = RKISP1_CIF_ISP_NLL_SCALE_LOGARITHMIC;\n> +\t} else {\n> +\t\tLOG(RkISP1Dpf, Error)\n> +\t\t\t<< \"Invalid 'RangeFilter:scale-mode': expected \"\n> +\t\t\t<< \"'linear' or 'logarithmic' value, got \"\n> +\t\t\t<< scaleMode;\n> +\t\treturn -EINVAL;\n> +\t}\n> +\n> +\tconst YamlObject &fSObject = tuningData[\"FilterStrength\"];\n> +\n> +\tstrengthConfig_.r = fSObject[\"r\"].get<uint16_t>(64);\n> +\tstrengthConfig_.g = fSObject[\"g\"].get<uint16_t>(64);\n> +\tstrengthConfig_.b = fSObject[\"b\"].get<uint16_t>(64);\n> +\n> +\tinitialized_ = true;\n> +\n> +\treturn 0;\n> +}\n> +\n> +/**\n> + * \\copydoc libcamera::ipa::Algorithm::queueRequest\n> + */\n> +void Dpf::queueRequest(IPAContext &context,\n> +\t\t       [[maybe_unused]] const uint32_t frame,\n> +\t\t       const ControlList &controls)\n> +{\n> +\tauto &dpf = context.frameContext.dpf;\n> +\n> +\tconst auto &denoise = controls.get(controls::draft::NoiseReductionMode);\n> +\tif (denoise) {\n> +\t\tLOG(RkISP1Dpf, Debug) << \"Set denoise to \" << *denoise;\n> +\n> +\t\tswitch (*denoise) {\n> +\t\tcase controls::draft::NoiseReductionModeOff:\n> +\t\t\tdpf.denoise = false;\n> +\t\t\tdpf.updateParams = true;\n> +\t\t\tbreak;\n> +\t\tcase controls::draft::NoiseReductionModeMinimal:\n> +\t\tcase controls::draft::NoiseReductionModeHighQuality:\n> +\t\tcase controls::draft::NoiseReductionModeFast:\n> +\t\t\tdpf.denoise = true;\n> +\t\t\tdpf.updateParams = true;\n> +\t\t\tbreak;\n> +\t\tdefault:\n> +\t\t\tLOG(RkISP1Dpf, Error)\n> +\t\t\t\t<< \"Unsupported denoise value \"\n> +\t\t\t\t<< *denoise;\n> +\t\t}\n> +\t}\n> +}\n> +\n> +/**\n> + * \\copydoc libcamera::ipa::Algorithm::prepare\n> + */\n> +void Dpf::prepare(IPAContext &context, rkisp1_params_cfg *params)\n> +{\n> +\tauto &dpf = context.frameContext.dpf;\n> +\n> +\tif (context.frameContext.frameCount > 0 && !dpf.updateParams)\n> +\t\treturn;\n> +\n> +\tif (!initialized_)\n> +\t\treturn;\n\nThis can go to the top of the function.\n\n> +\n> +\tauto &awb = context.configuration.awb;\n> +\tauto &lsc = context.configuration.lsc;\n> +\tauto &dpf_config = params->others.dpf_config;\n> +\tauto &dpf_strength_config = params->others.dpf_strength_config;\n> +\tauto &mode = dpf_config.gain.mode;\n\nAnd this can go inside the if below as it's only used there. Some\nvariables are used once only and are possibly a bit overkill.\n\n> +\n> +\tif (context.frameContext.frameCount == 0) {\n> +\t\tdpf_config = config_;\n> +\t\tdpf_strength_config = strengthConfig_;\n> +\n> +\t\tif (awb.enabled && lsc.enabled)\n> +\t\t\tmode = RKISP1_CIF_ISP_DPF_GAIN_USAGE_AWB_LSC_GAINS;\n> +\t\telse if (awb.enabled)\n> +\t\t\tmode = RKISP1_CIF_ISP_DPF_GAIN_USAGE_AWB_GAINS;\n> +\t\telse if (lsc.enabled)\n> +\t\t\tmode = RKISP1_CIF_ISP_DPF_GAIN_USAGE_LSC_GAINS;\n> +\t\telse\n> +\t\t\tmode = RKISP1_CIF_ISP_DPF_GAIN_USAGE_DISABLED;\n> +\n> +\t\tparams->module_cfg_update |= RKISP1_CIF_ISP_MODULE_DPF |\n> +\t\t\t\t\t     RKISP1_CIF_ISP_MODULE_DPF_STRENGTH;\n> +\t}\n> +\n> +\tif (dpf.updateParams) {\n> +\t\tparams->module_en_update |= RKISP1_CIF_ISP_MODULE_DPF;\n> +\t\tif (dpf.denoise)\n> +\t\t\tparams->module_ens |= RKISP1_CIF_ISP_MODULE_DPF;\n> +\n> +\t\tdpf.updateParams = false;\n> +\t}\n> +}\n> +\n> +REGISTER_IPA_ALGORITHM(Dpf, \"Dpf\")\n> +\n> +} /* namespace ipa::rkisp1::algorithms */\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/ipa/rkisp1/algorithms/dpf.h b/src/ipa/rkisp1/algorithms/dpf.h\n> new file mode 100644\n> index 00000000..128ebd5e\n> --- /dev/null\n> +++ b/src/ipa/rkisp1/algorithms/dpf.h\n> @@ -0,0 +1,36 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2021-2022, Ideas On Board\n> + *\n> + * dpf.h - RkISP1 Denoise Pre-Filter control\n> + */\n> +\n> +#pragma once\n> +\n> +#include <sys/types.h>\n> +\n> +#include \"algorithm.h\"\n> +\n> +namespace libcamera {\n> +\n> +namespace ipa::rkisp1::algorithms {\n> +\n> +class Dpf : public Algorithm\n> +{\n> +public:\n> +\tDpf();\n> +\t~Dpf() = default;\n> +\n> +\tint init(IPAContext &context, const YamlObject &tuningData) override;\n> +\tvoid queueRequest(IPAContext &context, const uint32_t frame,\n> +\t\t\t  const ControlList &controls) override;\n> +\tvoid prepare(IPAContext &context, rkisp1_params_cfg *params) override;\n> +\n> +private:\n> +\tbool initialized_;\n> +\tstruct rkisp1_cif_isp_dpf_config config_;\n> +\tstruct rkisp1_cif_isp_dpf_strength_config strengthConfig_;\n> +};\n> +\n> +} /* namespace ipa::rkisp1::algorithms */\n> +} /* namespace libcamera */\n> diff --git a/src/ipa/rkisp1/algorithms/meson.build b/src/ipa/rkisp1/algorithms/meson.build\n> index e48974b4..93a48329 100644\n> --- a/src/ipa/rkisp1/algorithms/meson.build\n> +++ b/src/ipa/rkisp1/algorithms/meson.build\n> @@ -6,6 +6,7 @@ rkisp1_ipa_algorithms = files([\n>      'blc.cpp',\n>      'cproc.cpp',\n>      'dpcc.cpp',\n> +    'dpf.cpp',\n>      'filter.cpp',\n>      'gsl.cpp',\n>      'lsc.cpp',\n> diff --git a/src/ipa/rkisp1/data/ov5640.yaml b/src/ipa/rkisp1/data/ov5640.yaml\n> index 93d7d1e7..e251abd4 100644\n> --- a/src/ipa/rkisp1/data/ov5640.yaml\n> +++ b/src/ipa/rkisp1/data/ov5640.yaml\n> @@ -156,5 +156,16 @@ algorithms:\n>            rnd-offsets:\n>              green: 2\n>              red-blue: 2\n> +  - Dpf:\n> +      DomainFilter:\n> +        g: [ 16, 16, 16, 16, 16, 16 ]\n> +        rb: [ 16, 16, 16, 16, 16, 16 ]\n> +      NoiseLevelFunction:\n> +        coeff: [ 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023 ]\n\nThat's a long line, let's wrap it.\n\nReviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n\nI can fix those small issues when applying.\n\n> +        scale-mode: \"linear\"\n> +      FilterStrength:\n> +        r: 64\n> +        g: 64\n> +        b: 64\n>    - Filter:\n>  ...\n> diff --git a/src/ipa/rkisp1/ipa_context.cpp b/src/ipa/rkisp1/ipa_context.cpp\n> index 1338ae40..70001d32 100644\n> --- a/src/ipa/rkisp1/ipa_context.cpp\n> +++ b/src/ipa/rkisp1/ipa_context.cpp\n> @@ -164,6 +164,17 @@ namespace libcamera::ipa::rkisp1 {\n>   * \\brief Indicates if ISP parameters need to be updated\n>   */\n>  \n> +/**\n> + * \\var IPAFrameContext::dpf\n> + * \\brief Context for the Denoise Pre-Filter algorithm\n> + *\n> + * \\var IPAFrameContext::dpf.denoise\n> + * \\brief Indicates if denoise is activated\n> + *\n> + * \\var IPAFrameContext::dpf.updateParams\n> + * \\brief Indicates if ISP parameters need to be updated\n> + */\n> +\n>  /**\n>   * \\var IPAFrameContext::filter\n>   * \\brief Context for the Filter algorithm\n> diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h\n> index 0cd6aadb..3a743ac3 100644\n> --- a/src/ipa/rkisp1/ipa_context.h\n> +++ b/src/ipa/rkisp1/ipa_context.h\n> @@ -69,6 +69,11 @@ struct IPAFrameContext {\n>  \t\tbool updateParams;\n>  \t} cproc;\n>  \n> +\tstruct {\n> +\t\tbool denoise;\n> +\t\tbool updateParams;\n> +\t} dpf;\n> +\n>  \tstruct {\n>  \t\tuint8_t denoise;\n>  \t\tuint8_t sharpness;","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 374D2BE173\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri,  5 Aug 2022 20:29:50 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 5C9AC603F0;\n\tFri,  5 Aug 2022 22:29:49 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 81DDE603E4\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri,  5 Aug 2022 22:29:47 +0200 (CEST)","from pendragon.ideasonboard.com (62-78-145-57.bb.dnainternet.fi\n\t[62.78.145.57])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id E21B1480;\n\tFri,  5 Aug 2022 22:29:46 +0200 (CEST)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1659731389;\n\tbh=3U7xSwG2AOb4jquk7xyr0u2pG2P5xA9vmK7QSmIeL3E=;\n\th=Date:To:References:In-Reply-To:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc:\n\tFrom;\n\tb=CRR+ZTFRCBEji4tRZzpOiM7d3tJmQzo+OCndezg8J0A3gWEa8mcLLiIkuW5MqBDK+\n\t2ugFG9FoVrJQIfxMsbG6RyeoLjvqUAZzMW9IrvmHi7lpdM15ZDWYHuJXTorBf/t9SV\n\tqw2Oe515mIFOkih21qDCyA5T+dJhRgpH0bCuE5OuaXfwaYgKoVtxUGf1nWYwVg/J1I\n\tW0lQUPUv0QuLbynkNoO59mJzOSmv07UH+cTzSOrF8Zae2DIZfnrtIXQpZ/UTFPudT4\n\tGz9ma9LN2jMkNx+Yz9a0W81Ui3rvfsi49CjSffbyQxq1jDlyCWT/T5joSzL6RQpTBZ\n\tGH/gQWbRCHLkQ==","v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1659731387;\n\tbh=3U7xSwG2AOb4jquk7xyr0u2pG2P5xA9vmK7QSmIeL3E=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=Lx4XY1DW6CTqTjSb28y1bZ4hM0qkK9wKHNnnnJlqH1ur5qrXSHXYYPZhMUF3pdZVu\n\tnHyR1paQw0aavqtG6qkurcVepNPqm9vbAfHcXIuPIRyq6+cPBOtNywf7K7XQ6NIG3z\n\tFz3wv41vLFJ968t+Rw4xTLwEhutJz86yKMY7vNTo="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"Lx4XY1DW\"; dkim-atps=neutral","Date":"Fri, 5 Aug 2022 23:29:38 +0300","To":"Florian Sylvestre <fsylvestre@baylibre.com>","Message-ID":"<Yu19srTC86UiVhUm@pendragon.ideasonboard.com>","References":"<20220805124027.530212-1-fsylvestre@baylibre.com>\n\t<20220805124027.530212-4-fsylvestre@baylibre.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20220805124027.530212-4-fsylvestre@baylibre.com>","Subject":"Re: [libcamera-devel] [PATCH v2 3/3] ipa: rkisp1: Add support of\n\tDenoise Pre-Filter control","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>","From":"Laurent Pinchart via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>","Reply-To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]