[{"id":34989,"web_url":"https://patchwork.libcamera.org/comment/34989/","msgid":"<175312365806.50296.3161751053352547156@ping.linuxembedded.co.uk>","date":"2025-07-21T18:47:38","subject":"Re: [PATCH 1/3] utils: tuning: Add tuning scripts for mali-c55","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"Quoting Daniel Scally (2025-03-04 23:12:52)\n> Add tuning scripts for the mali-c55 IPA. At present the only module\n> that is available is for lens shading correction.\n> \n\nAs a new module - I don't think this can cause regressions - so as long\nas it runs ...\n\nAcked-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n\n> Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>\n> ---\n>  .../tuning/libtuning/modules/lsc/__init__.py  |   1 +\n>  .../tuning/libtuning/modules/lsc/mali_c55.py  | 173 ++++++++++++++++++\n>  utils/tuning/mali-c55.py                      |  40 ++++\n>  3 files changed, 214 insertions(+)\n>  create mode 100644 utils/tuning/libtuning/modules/lsc/mali_c55.py\n>  create mode 100755 utils/tuning/mali-c55.py\n> \n> diff --git a/utils/tuning/libtuning/modules/lsc/__init__.py b/utils/tuning/libtuning/modules/lsc/__init__.py\n> index 0ba4411b..edd5ce7f 100644\n> --- a/utils/tuning/libtuning/modules/lsc/__init__.py\n> +++ b/utils/tuning/libtuning/modules/lsc/__init__.py\n> @@ -3,5 +3,6 @@\n>  # Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>\n>  \n>  from libtuning.modules.lsc.lsc import LSC\n> +from libtuning.modules.lsc.mali_c55 import LSCMaliC55\n>  from libtuning.modules.lsc.raspberrypi import ALSCRaspberryPi\n>  from libtuning.modules.lsc.rkisp1 import LSCRkISP1\n> diff --git a/utils/tuning/libtuning/modules/lsc/mali_c55.py b/utils/tuning/libtuning/modules/lsc/mali_c55.py\n> new file mode 100644\n> index 00000000..7d85aee9\n> --- /dev/null\n> +++ b/utils/tuning/libtuning/modules/lsc/mali_c55.py\n> @@ -0,0 +1,173 @@\n> +# SPDX-License-Identifier: BSD-2-Clause\n> +#\n> +# Copyright (C) 2019, Raspberry Pi Ltd\n> +# Copyright (C) 2024, Ideas on Board oy\n> +#\n> +# mali-c55.py - LSC module for tuning mali-c55\n> +\n> +from .lsc import LSC\n> +\n> +import libtuning as lt\n> +\n> +import numpy as np\n> +\n> +class LSCMaliC55(LSC):\n> +       out_name = 'Lsc'\n> +       name = \"LSCMaliC55\"\n> +\n> +       def __init__(self, *args, **kwargs):\n> +               super().__init__(**kwargs)\n> +\n> +       def validate_config(self, config: dict) -> bool:\n> +               # There's nothing we need in config\n> +               return True\n> +\n> +       def _do_single_lsc(self, image: lt.Image):\n> +               \"\"\"\n> +               Generate lens shading tables of gain values for a single image\n> +\n> +               This function generates a set of lens shading tables - one for\n> +               each colour channel. The table values are gain values, expressed\n> +               generically (I.E. not in the IPA module's format)\n> +\n> +               Returns a tuple of colour temperature, array of red gain values,\n> +               array of blue gain values, array of red's green gain values and\n> +               array of blue's green gain values.\n> +               \"\"\"\n> +               cgr, _ = self._lsc_single_channel(image.channels[lt.Color.GR],\n> +                                                  image)\n> +               cgb, _ = self._lsc_single_channel(image.channels[lt.Color.GB],\n> +                                                  image)\n> +               cr, _ = self._lsc_single_channel(image.channels[lt.Color.R],\n> +                                                image)\n> +               cb, _ = self._lsc_single_channel(image.channels[lt.Color.B],\n> +                                                image)\n> +\n> +               return (\n> +                       image.color,\n> +                       cr.flatten(),\n> +                       cb.flatten(),\n> +                       cgr.flatten(),\n> +                       cgb.flatten()\n> +               )\n> +\n> +       def _do_all_lsc(self, images: list) -> dict:\n> +               \"\"\"\n> +               Generate lens shading tables in the IPA's format\n> +\n> +               This function generates lens shading tables from the list of\n> +               images. A set (one per colour channel) of tables is generated\n> +               for each image, and then the sets from all images which were\n> +               generated from the same colour temperature are averaged together\n> +               and transformed into the format required by the IPA module.\n> +\n> +               Returns a list of dictionaries containing colour temperature,\n> +               red lens shading table, green lens shading table and blue lens\n> +               shading table.\n> +               \"\"\"\n> +\n> +               output_map_func = lt.gradient.Linear().map\n> +\n> +               list_ct = []\n> +\n> +               list_cr = []\n> +               list_cb = []\n> +               list_cgr = []\n> +               list_cgb = []\n> +\n> +               for image in images:\n> +                       ct, cr, cg, cgr, cgb = self._do_single_lsc(image)\n> +\n> +                       list_ct.append(ct)\n> +                       list_cr.append(cr)\n> +                       list_cb.append(cg)\n> +                       list_cgr.append(cgr)\n> +                       list_cgb.append(cgb)\n> +\n> +               list_ct = np.array(list_ct)\n> +               list_cr = np.array(list_cr)\n> +               list_cb = np.array(list_cb)\n> +               list_cgr = np.array(list_cgr)\n> +               list_cgb = np.array(list_cgb)\n> +               list_cg = (list_cgr + list_cgb) / 2\n> +\n> +               # We need to map the gains into the IPA-specific values to pass\n> +               # to the ISP. For the mali-c55 the values are always in the\n> +               # range [0..255] but the min/max that those values represent\n> +               # depend on the mesh scale parameter, so we'll need to choose\n> +               # what that should be and use the gain-range it represents as\n> +               # the domain for output_map_func().\n> +               #\n> +               # For convenient reference, the possible mesh scale values are\n> +               # as follows (taken from include/linux/mali-c55-config.h)\n> +               #\n> +               #       - 0 = 0-2x gain\n> +               #       - 1 = 0-4x gain\n> +               #       - 2 = 0-8x gain\n> +               #       - 3 = 0-16x gain\n> +               #       - 4 = 1-2x gain\n> +               #       - 5 = 1-3x gain\n> +               #       - 6 = 1-5x gain\n> +               #       - 7 = 1-9x gain\n> +               #\n> +               # We want to use the scale with the smallest range that still\n> +               # covers the minimum and maximum value we want to set...but this\n> +               # process at present hard-codes a minimum gain of 1.0, so the\n> +               # first 4 scales are out right away. We'll just consider the\n> +               # minimum as 1.0 for now and if we ever need more than 9.0 gain\n> +               # we'll have to fix this - shout about that if so.\n> +\n> +               max_gain = np.max([list_cr, list_cgr, list_cgb, list_cb])\n> +               if (max_gain > 9.0):\n> +                       print(\"WARNING: Maximum gain restricted artificially to 9.0\")\n> +\n> +               mesh_scales = {\n> +                       4: (1.0, 2.0),\n> +                       5: (1.0, 3.0),\n> +                       6: (1.0, 5.0),\n> +                       7: (1.0, 9.0)\n> +               }\n> +\n> +               for i in mesh_scales.keys():\n> +                       if max_gain <= mesh_scales[i][1]:\n> +                               break\n> +\n> +               mesh_scale = i\n> +\n> +               output_list = []\n> +               for ct in sorted(set(list_ct)):\n> +                       indices = np.where(list_ct == ct)\n> +                       ct = int(ct)\n> +\n> +                       tables = []\n> +                       for lists in [list_cr, list_cg, list_cb]:\n> +                               table = np.mean(lists[indices], axis=0)\n> +\n> +                               table = output_map_func(\n> +                                       (\n> +                                               mesh_scales[mesh_scale][0],\n> +                                               mesh_scales[mesh_scale][1] - 0.001\n> +                                       ),\n> +                                       (0, 255),\n> +                                       table\n> +                               )\n> +                               table = np.round(table).astype('uint8').tolist()\n> +                               tables.append(table)\n> +\n> +                       entry = {\n> +                               'ct': ct,\n> +                               'r': tables[0],\n> +                               'g': tables[1],\n> +                               'b': tables[2],\n> +                       }\n> +\n> +                       output_list.append(entry)\n> +\n> +               return {\n> +                       'meshScale': mesh_scale,\n> +                       'sets': output_list\n> +               }\n> +\n> +       def process(self, config: dict, images: list, outputs: dict) -> dict:\n> +               return self._do_all_lsc(images)\n> +\n> diff --git a/utils/tuning/mali-c55.py b/utils/tuning/mali-c55.py\n> new file mode 100755\n> index 00000000..01535366\n> --- /dev/null\n> +++ b/utils/tuning/mali-c55.py\n> @@ -0,0 +1,40 @@\n> +#!/usr/bin/env python3\n> +# SPDX-License-Identifier: GPL-2.0-or-later\n> +#\n> +# Copyright (C) 2024, Ideas on Board oy\n> +#\n> +# mali-c55.py - Tuning script for mali-c55\n> +\n> +import sys\n> +\n> +import libtuning as lt\n> +from libtuning.parsers import YamlParser\n> +from libtuning.generators import YamlOutput\n> +from libtuning.modules.lsc import LSCMaliC55\n> +\n> +tuner = lt.Tuner('MaliC55')\n> +tuner.add(LSCMaliC55(\n> +          debug=[lt.Debug.Plot],\n> +          # This is for the actual LSC tuning, and is part of the base LSC\n> +          # module. rkisp1's table sector sizes (16x16 programmed as mirrored\n> +          # 8x8) are separate, and is hardcoded in its specific LSC tuning\n> +          # module.\n> +          sector_shape=(32, 32),\n> +\n> +          sector_x_gradient=lt.gradient.Linear(lt.Remainder.DistributeFront),\n> +          sector_y_gradient=lt.gradient.Linear(lt.Remainder.DistributeFront),\n> +\n> +          # This is the function that will be used to average the pixels in\n> +          # each sector. This can also be a custom function.\n> +          sector_average_function=lt.average.Mean(),\n> +\n> +          # This is the function that will be used to smooth the color ratio\n> +          # values.  This can also be a custom function.\n> +          smoothing_function=lt.smoothing.MedianBlur(3),\n> +          ))\n> +tuner.set_input_parser(YamlParser())\n> +tuner.set_output_formatter(YamlOutput())\n> +tuner.set_output_order([LSCMaliC55])\n> +\n> +if __name__ == '__main__':\n> +    sys.exit(tuner.run(sys.argv))\n> -- \n> 2.34.1\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 2F8C1C3237\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 21 Jul 2025 18:47:42 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id CE4C269009;\n\tMon, 21 Jul 2025 20:47:41 +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 BD02468FB1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 21 Jul 2025 20:47:40 +0200 (CEST)","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 BCC8C7967;\n\tMon, 21 Jul 2025 20:47:03 +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=\"b0LfnUeu\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1753123623;\n\tbh=us5LgeNB3WKMI3UWVchQWHhjJXx37F7EwXkC4wTx0qw=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=b0LfnUeu1I8a259EBmk4qacppSV5+nCFCfSofAxjnnAAXfkaFcGIipER2QsVeakYh\n\t9mIqBOdpHE5f9BxpVuLC2Z+yF9jINdlV7j22jD8EhWEY0rsKHLMw7I2WBuhtLjetWR\n\t0H4OPzTJZPpvEB7kUbklDfpSWFelbk3JBAjkD1Ko=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<20250304231254.10588-2-dan.scally@ideasonboard.com>","References":"<20250304231254.10588-1-dan.scally@ideasonboard.com>\n\t<20250304231254.10588-2-dan.scally@ideasonboard.com>","Subject":"Re: [PATCH 1/3] utils: tuning: Add tuning scripts for mali-c55","From":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Cc":"Daniel Scally <dan.scally@ideasonboard.com>","To":"Daniel Scally <dan.scally@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","Date":"Mon, 21 Jul 2025 19:47:38 +0100","Message-ID":"<175312365806.50296.3161751053352547156@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":35012,"web_url":"https://patchwork.libcamera.org/comment/35012/","msgid":"<175317939858.3923365.867127487583187537@neptunite.rasen.tech>","date":"2025-07-22T10:16:38","subject":"Re: [PATCH 1/3] utils: tuning: Add tuning scripts for mali-c55","submitter":{"id":17,"url":"https://patchwork.libcamera.org/api/people/17/","name":"Paul Elder","email":"paul.elder@ideasonboard.com"},"content":"Quoting Daniel Scally (2025-03-05 08:12:52)\n> Add tuning scripts for the mali-c55 IPA. At present the only module\n> that is available is for lens shading correction.\n> \n> Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>\n> ---\n>  .../tuning/libtuning/modules/lsc/__init__.py  |   1 +\n>  .../tuning/libtuning/modules/lsc/mali_c55.py  | 173 ++++++++++++++++++\n>  utils/tuning/mali-c55.py                      |  40 ++++\n>  3 files changed, 214 insertions(+)\n>  create mode 100644 utils/tuning/libtuning/modules/lsc/mali_c55.py\n>  create mode 100755 utils/tuning/mali-c55.py\n> \n> diff --git a/utils/tuning/libtuning/modules/lsc/__init__.py b/utils/tuning/libtuning/modules/lsc/__init__.py\n> index 0ba4411b..edd5ce7f 100644\n> --- a/utils/tuning/libtuning/modules/lsc/__init__.py\n> +++ b/utils/tuning/libtuning/modules/lsc/__init__.py\n> @@ -3,5 +3,6 @@\n>  # Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>\n>  \n>  from libtuning.modules.lsc.lsc import LSC\n> +from libtuning.modules.lsc.mali_c55 import LSCMaliC55\n>  from libtuning.modules.lsc.raspberrypi import ALSCRaspberryPi\n>  from libtuning.modules.lsc.rkisp1 import LSCRkISP1\n> diff --git a/utils/tuning/libtuning/modules/lsc/mali_c55.py b/utils/tuning/libtuning/modules/lsc/mali_c55.py\n> new file mode 100644\n> index 00000000..7d85aee9\n> --- /dev/null\n> +++ b/utils/tuning/libtuning/modules/lsc/mali_c55.py\n> @@ -0,0 +1,173 @@\n> +# SPDX-License-Identifier: BSD-2-Clause\n> +#\n> +# Copyright (C) 2019, Raspberry Pi Ltd\n> +# Copyright (C) 2024, Ideas on Board oy\n> +#\n> +# mali-c55.py - LSC module for tuning mali-c55\n> +\n> +from .lsc import LSC\n> +\n> +import libtuning as lt\n> +\n> +import numpy as np\n> +\n> +class LSCMaliC55(LSC):\n> +       out_name = 'Lsc'\n> +       name = \"LSCMaliC55\"\n> +\n> +       def __init__(self, *args, **kwargs):\n> +               super().__init__(**kwargs)\n> +\n> +       def validate_config(self, config: dict) -> bool:\n> +               # There's nothing we need in config\n> +               return True\n> +\n> +       def _do_single_lsc(self, image: lt.Image):\n> +               \"\"\"\n> +               Generate lens shading tables of gain values for a single image\n> +\n> +               This function generates a set of lens shading tables - one for\n> +               each colour channel. The table values are gain values, expressed\n> +               generically (I.E. not in the IPA module's format)\n> +\n> +               Returns a tuple of colour temperature, array of red gain values,\n> +               array of blue gain values, array of red's green gain values and\n> +               array of blue's green gain values.\n> +               \"\"\"\n> +               cgr, _ = self._lsc_single_channel(image.channels[lt.Color.GR],\n> +                                                  image)\n> +               cgb, _ = self._lsc_single_channel(image.channels[lt.Color.GB],\n> +                                                  image)\n> +               cr, _ = self._lsc_single_channel(image.channels[lt.Color.R],\n> +                                                image)\n> +               cb, _ = self._lsc_single_channel(image.channels[lt.Color.B],\n> +                                                image)\n> +\n> +               return (\n> +                       image.color,\n> +                       cr.flatten(),\n> +                       cb.flatten(),\n> +                       cgr.flatten(),\n> +                       cgb.flatten()\n> +               )\n\nThis looks exactly the same as rkisp1's... I wonder if it's worth pulling this\nout into the LSC class. At least _lsc_single_channel is deduplicated :)\n\nIn any case, out of scope for this series; just me mumbling to myself.\n\n> +\n> +       def _do_all_lsc(self, images: list) -> dict:\n> +               \"\"\"\n> +               Generate lens shading tables in the IPA's format\n> +\n> +               This function generates lens shading tables from the list of\n> +               images. A set (one per colour channel) of tables is generated\n> +               for each image, and then the sets from all images which were\n> +               generated from the same colour temperature are averaged together\n> +               and transformed into the format required by the IPA module.\n> +\n> +               Returns a list of dictionaries containing colour temperature,\n> +               red lens shading table, green lens shading table and blue lens\n> +               shading table.\n> +               \"\"\"\n> +\n> +               output_map_func = lt.gradient.Linear().map\n> +\n> +               list_ct = []\n> +\n> +               list_cr = []\n> +               list_cb = []\n> +               list_cgr = []\n> +               list_cgb = []\n> +\n> +               for image in images:\n> +                       ct, cr, cg, cgr, cgb = self._do_single_lsc(image)\n> +\n> +                       list_ct.append(ct)\n> +                       list_cr.append(cr)\n> +                       list_cb.append(cg)\n> +                       list_cgr.append(cgr)\n> +                       list_cgb.append(cgb)\n> +\n> +               list_ct = np.array(list_ct)\n> +               list_cr = np.array(list_cr)\n> +               list_cb = np.array(list_cb)\n> +               list_cgr = np.array(list_cgr)\n> +               list_cgb = np.array(list_cgb)\n> +               list_cg = (list_cgr + list_cgb) / 2\n> +\n> +               # We need to map the gains into the IPA-specific values to pass\n> +               # to the ISP. For the mali-c55 the values are always in the\n> +               # range [0..255] but the min/max that those values represent\n> +               # depend on the mesh scale parameter, so we'll need to choose\n> +               # what that should be and use the gain-range it represents as\n> +               # the domain for output_map_func().\n> +               #\n> +               # For convenient reference, the possible mesh scale values are\n> +               # as follows (taken from include/linux/mali-c55-config.h)\n> +               #\n> +               #       - 0 = 0-2x gain\n> +               #       - 1 = 0-4x gain\n> +               #       - 2 = 0-8x gain\n> +               #       - 3 = 0-16x gain\n> +               #       - 4 = 1-2x gain\n> +               #       - 5 = 1-3x gain\n> +               #       - 6 = 1-5x gain\n> +               #       - 7 = 1-9x gain\n> +               #\n> +               # We want to use the scale with the smallest range that still\n> +               # covers the minimum and maximum value we want to set...but this\n> +               # process at present hard-codes a minimum gain of 1.0, so the\n> +               # first 4 scales are out right away. We'll just consider the\n> +               # minimum as 1.0 for now and if we ever need more than 9.0 gain\n> +               # we'll have to fix this - shout about that if so.\n> +\n> +               max_gain = np.max([list_cr, list_cgr, list_cgb, list_cb])\n> +               if (max_gain > 9.0):\n> +                       print(\"WARNING: Maximum gain restricted artificially to 9.0\")\n> +\n> +               mesh_scales = {\n> +                       4: (1.0, 2.0),\n> +                       5: (1.0, 3.0),\n> +                       6: (1.0, 5.0),\n> +                       7: (1.0, 9.0)\n> +               }\n> +\n> +               for i in mesh_scales.keys():\n> +                       if max_gain <= mesh_scales[i][1]:\n> +                               break\n> +\n> +               mesh_scale = i\n> +\n> +               output_list = []\n> +               for ct in sorted(set(list_ct)):\n> +                       indices = np.where(list_ct == ct)\n> +                       ct = int(ct)\n> +\n> +                       tables = []\n> +                       for lists in [list_cr, list_cg, list_cb]:\n> +                               table = np.mean(lists[indices], axis=0)\n> +\n> +                               table = output_map_func(\n> +                                       (\n> +                                               mesh_scales[mesh_scale][0],\n> +                                               mesh_scales[mesh_scale][1] - 0.001\n> +                                       ),\n> +                                       (0, 255),\n> +                                       table\n> +                               )\n> +                               table = np.round(table).astype('uint8').tolist()\n> +                               tables.append(table)\n> +\n> +                       entry = {\n> +                               'ct': ct,\n> +                               'r': tables[0],\n> +                               'g': tables[1],\n> +                               'b': tables[2],\n> +                       }\n> +\n> +                       output_list.append(entry)\n> +\n> +               return {\n> +                       'meshScale': mesh_scale,\n> +                       'sets': output_list\n> +               }\n> +\n> +       def process(self, config: dict, images: list, outputs: dict) -> dict:\n> +               return self._do_all_lsc(images)\n> +\n> diff --git a/utils/tuning/mali-c55.py b/utils/tuning/mali-c55.py\n> new file mode 100755\n> index 00000000..01535366\n> --- /dev/null\n> +++ b/utils/tuning/mali-c55.py\n> @@ -0,0 +1,40 @@\n> +#!/usr/bin/env python3\n> +# SPDX-License-Identifier: GPL-2.0-or-later\n> +#\n> +# Copyright (C) 2024, Ideas on Board oy\n> +#\n> +# mali-c55.py - Tuning script for mali-c55\n> +\n> +import sys\n> +\n> +import libtuning as lt\n> +from libtuning.parsers import YamlParser\n> +from libtuning.generators import YamlOutput\n> +from libtuning.modules.lsc import LSCMaliC55\n> +\n> +tuner = lt.Tuner('MaliC55')\n> +tuner.add(LSCMaliC55(\n> +          debug=[lt.Debug.Plot],\n> +          # This is for the actual LSC tuning, and is part of the base LSC\n> +          # module. rkisp1's table sector sizes (16x16 programmed as mirrored\n> +          # 8x8) are separate, and is hardcoded in its specific LSC tuning\n> +          # module.\n\nrkisp1 doesn't look relevant to mali-c55 :)\n\n\nWith that removed (or changed to fit), looks good to me.\n\nReviewed-by: Paul Elder <paul.elder@ideasonboard.com>\n\n> +          sector_shape=(32, 32),\n> +\n> +          sector_x_gradient=lt.gradient.Linear(lt.Remainder.DistributeFront),\n> +          sector_y_gradient=lt.gradient.Linear(lt.Remainder.DistributeFront),\n> +\n> +          # This is the function that will be used to average the pixels in\n> +          # each sector. This can also be a custom function.\n> +          sector_average_function=lt.average.Mean(),\n> +\n> +          # This is the function that will be used to smooth the color ratio\n> +          # values.  This can also be a custom function.\n> +          smoothing_function=lt.smoothing.MedianBlur(3),\n> +          ))\n> +tuner.set_input_parser(YamlParser())\n> +tuner.set_output_formatter(YamlOutput())\n> +tuner.set_output_order([LSCMaliC55])\n> +\n> +if __name__ == '__main__':\n> +    sys.exit(tuner.run(sys.argv))\n> -- \n> 2.34.1\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 CC2F3BDCC1\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 22 Jul 2025 10:16:47 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id A074769021;\n\tTue, 22 Jul 2025 12:16:46 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id EAB3368F93\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 22 Jul 2025 12:16:44 +0200 (CEST)","from neptunite.rasen.tech (unknown\n\t[IPv6:2404:7a81:160:2100:5fe2:6df7:ad99:5884])\n\tby perceval.ideasonboard.com (Postfix) with UTF8SMTPSA id E6FE679CC; \n\tTue, 22 Jul 2025 12:16:06 +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=\"szMO7JwX\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1753179367;\n\tbh=qz8/ab4ilaMlU7PHc4XxAMKLsTL70BW+kEadpdvsA8g=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=szMO7JwXhKWxAX2/NVO0Cfr3t/8Oy1VLNA8t9JZ8IcwGCTNc60dS1oV0zpIiERLY8\n\t7w7QOVMLN7q2d9dQkBleFTTtEl9zthKb257YMOvNMIDf3V5OmFHtM+v9OoNX6GnxBp\n\tGzxkgci256IcVur1HbCaoNmlbX77FgzX+QjkCGIA=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<20250304231254.10588-2-dan.scally@ideasonboard.com>","References":"<20250304231254.10588-1-dan.scally@ideasonboard.com>\n\t<20250304231254.10588-2-dan.scally@ideasonboard.com>","Subject":"Re: [PATCH 1/3] utils: tuning: Add tuning scripts for mali-c55","From":"Paul Elder <paul.elder@ideasonboard.com>","Cc":"Daniel Scally <dan.scally@ideasonboard.com>","To":"Daniel Scally <dan.scally@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","Date":"Tue, 22 Jul 2025 19:16:38 +0900","Message-ID":"<175317939858.3923365.867127487583187537@neptunite.rasen.tech>","User-Agent":"alot/0.0.0","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":35131,"web_url":"https://patchwork.libcamera.org/comment/35131/","msgid":"<c9fbfdbf-f765-4875-b853-d576f0eedb37@ideasonboard.com>","date":"2025-07-25T12:01:25","subject":"Re: [PATCH 1/3] utils: tuning: Add tuning scripts for mali-c55","submitter":{"id":156,"url":"https://patchwork.libcamera.org/api/people/156/","name":"Dan Scally","email":"dan.scally@ideasonboard.com"},"content":"Hi Paul\n\nOn 22/07/2025 11:16, Paul Elder wrote:\n> Quoting Daniel Scally (2025-03-05 08:12:52)\n>> Add tuning scripts for the mali-c55 IPA. At present the only module\n>> that is available is for lens shading correction.\n>>\n>> Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>\n>> ---\n>>   .../tuning/libtuning/modules/lsc/__init__.py  |   1 +\n>>   .../tuning/libtuning/modules/lsc/mali_c55.py  | 173 ++++++++++++++++++\n>>   utils/tuning/mali-c55.py                      |  40 ++++\n>>   3 files changed, 214 insertions(+)\n>>   create mode 100644 utils/tuning/libtuning/modules/lsc/mali_c55.py\n>>   create mode 100755 utils/tuning/mali-c55.py\n>>\n>> diff --git a/utils/tuning/libtuning/modules/lsc/__init__.py b/utils/tuning/libtuning/modules/lsc/__init__.py\n>> index 0ba4411b..edd5ce7f 100644\n>> --- a/utils/tuning/libtuning/modules/lsc/__init__.py\n>> +++ b/utils/tuning/libtuning/modules/lsc/__init__.py\n>> @@ -3,5 +3,6 @@\n>>   # Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>\n>>   \n>>   from libtuning.modules.lsc.lsc import LSC\n>> +from libtuning.modules.lsc.mali_c55 import LSCMaliC55\n>>   from libtuning.modules.lsc.raspberrypi import ALSCRaspberryPi\n>>   from libtuning.modules.lsc.rkisp1 import LSCRkISP1\n>> diff --git a/utils/tuning/libtuning/modules/lsc/mali_c55.py b/utils/tuning/libtuning/modules/lsc/mali_c55.py\n>> new file mode 100644\n>> index 00000000..7d85aee9\n>> --- /dev/null\n>> +++ b/utils/tuning/libtuning/modules/lsc/mali_c55.py\n>> @@ -0,0 +1,173 @@\n>> +# SPDX-License-Identifier: BSD-2-Clause\n>> +#\n>> +# Copyright (C) 2019, Raspberry Pi Ltd\n>> +# Copyright (C) 2024, Ideas on Board oy\n>> +#\n>> +# mali-c55.py - LSC module for tuning mali-c55\n>> +\n>> +from .lsc import LSC\n>> +\n>> +import libtuning as lt\n>> +\n>> +import numpy as np\n>> +\n>> +class LSCMaliC55(LSC):\n>> +       out_name = 'Lsc'\n>> +       name = \"LSCMaliC55\"\n>> +\n>> +       def __init__(self, *args, **kwargs):\n>> +               super().__init__(**kwargs)\n>> +\n>> +       def validate_config(self, config: dict) -> bool:\n>> +               # There's nothing we need in config\n>> +               return True\n>> +\n>> +       def _do_single_lsc(self, image: lt.Image):\n>> +               \"\"\"\n>> +               Generate lens shading tables of gain values for a single image\n>> +\n>> +               This function generates a set of lens shading tables - one for\n>> +               each colour channel. The table values are gain values, expressed\n>> +               generically (I.E. not in the IPA module's format)\n>> +\n>> +               Returns a tuple of colour temperature, array of red gain values,\n>> +               array of blue gain values, array of red's green gain values and\n>> +               array of blue's green gain values.\n>> +               \"\"\"\n>> +               cgr, _ = self._lsc_single_channel(image.channels[lt.Color.GR],\n>> +                                                  image)\n>> +               cgb, _ = self._lsc_single_channel(image.channels[lt.Color.GB],\n>> +                                                  image)\n>> +               cr, _ = self._lsc_single_channel(image.channels[lt.Color.R],\n>> +                                                image)\n>> +               cb, _ = self._lsc_single_channel(image.channels[lt.Color.B],\n>> +                                                image)\n>> +\n>> +               return (\n>> +                       image.color,\n>> +                       cr.flatten(),\n>> +                       cb.flatten(),\n>> +                       cgr.flatten(),\n>> +                       cgb.flatten()\n>> +               )\n> This looks exactly the same as rkisp1's... I wonder if it's worth pulling this\n> out into the LSC class. At least _lsc_single_channel is deduplicated :)\n>\n> In any case, out of scope for this series; just me mumbling to myself.\n>\n>> +\n>> +       def _do_all_lsc(self, images: list) -> dict:\n>> +               \"\"\"\n>> +               Generate lens shading tables in the IPA's format\n>> +\n>> +               This function generates lens shading tables from the list of\n>> +               images. A set (one per colour channel) of tables is generated\n>> +               for each image, and then the sets from all images which were\n>> +               generated from the same colour temperature are averaged together\n>> +               and transformed into the format required by the IPA module.\n>> +\n>> +               Returns a list of dictionaries containing colour temperature,\n>> +               red lens shading table, green lens shading table and blue lens\n>> +               shading table.\n>> +               \"\"\"\n>> +\n>> +               output_map_func = lt.gradient.Linear().map\n>> +\n>> +               list_ct = []\n>> +\n>> +               list_cr = []\n>> +               list_cb = []\n>> +               list_cgr = []\n>> +               list_cgb = []\n>> +\n>> +               for image in images:\n>> +                       ct, cr, cg, cgr, cgb = self._do_single_lsc(image)\n>> +\n>> +                       list_ct.append(ct)\n>> +                       list_cr.append(cr)\n>> +                       list_cb.append(cg)\n>> +                       list_cgr.append(cgr)\n>> +                       list_cgb.append(cgb)\n>> +\n>> +               list_ct = np.array(list_ct)\n>> +               list_cr = np.array(list_cr)\n>> +               list_cb = np.array(list_cb)\n>> +               list_cgr = np.array(list_cgr)\n>> +               list_cgb = np.array(list_cgb)\n>> +               list_cg = (list_cgr + list_cgb) / 2\n>> +\n>> +               # We need to map the gains into the IPA-specific values to pass\n>> +               # to the ISP. For the mali-c55 the values are always in the\n>> +               # range [0..255] but the min/max that those values represent\n>> +               # depend on the mesh scale parameter, so we'll need to choose\n>> +               # what that should be and use the gain-range it represents as\n>> +               # the domain for output_map_func().\n>> +               #\n>> +               # For convenient reference, the possible mesh scale values are\n>> +               # as follows (taken from include/linux/mali-c55-config.h)\n>> +               #\n>> +               #       - 0 = 0-2x gain\n>> +               #       - 1 = 0-4x gain\n>> +               #       - 2 = 0-8x gain\n>> +               #       - 3 = 0-16x gain\n>> +               #       - 4 = 1-2x gain\n>> +               #       - 5 = 1-3x gain\n>> +               #       - 6 = 1-5x gain\n>> +               #       - 7 = 1-9x gain\n>> +               #\n>> +               # We want to use the scale with the smallest range that still\n>> +               # covers the minimum and maximum value we want to set...but this\n>> +               # process at present hard-codes a minimum gain of 1.0, so the\n>> +               # first 4 scales are out right away. We'll just consider the\n>> +               # minimum as 1.0 for now and if we ever need more than 9.0 gain\n>> +               # we'll have to fix this - shout about that if so.\n>> +\n>> +               max_gain = np.max([list_cr, list_cgr, list_cgb, list_cb])\n>> +               if (max_gain > 9.0):\n>> +                       print(\"WARNING: Maximum gain restricted artificially to 9.0\")\n>> +\n>> +               mesh_scales = {\n>> +                       4: (1.0, 2.0),\n>> +                       5: (1.0, 3.0),\n>> +                       6: (1.0, 5.0),\n>> +                       7: (1.0, 9.0)\n>> +               }\n>> +\n>> +               for i in mesh_scales.keys():\n>> +                       if max_gain <= mesh_scales[i][1]:\n>> +                               break\n>> +\n>> +               mesh_scale = i\n>> +\n>> +               output_list = []\n>> +               for ct in sorted(set(list_ct)):\n>> +                       indices = np.where(list_ct == ct)\n>> +                       ct = int(ct)\n>> +\n>> +                       tables = []\n>> +                       for lists in [list_cr, list_cg, list_cb]:\n>> +                               table = np.mean(lists[indices], axis=0)\n>> +\n>> +                               table = output_map_func(\n>> +                                       (\n>> +                                               mesh_scales[mesh_scale][0],\n>> +                                               mesh_scales[mesh_scale][1] - 0.001\n>> +                                       ),\n>> +                                       (0, 255),\n>> +                                       table\n>> +                               )\n>> +                               table = np.round(table).astype('uint8').tolist()\n>> +                               tables.append(table)\n>> +\n>> +                       entry = {\n>> +                               'ct': ct,\n>> +                               'r': tables[0],\n>> +                               'g': tables[1],\n>> +                               'b': tables[2],\n>> +                       }\n>> +\n>> +                       output_list.append(entry)\n>> +\n>> +               return {\n>> +                       'meshScale': mesh_scale,\n>> +                       'sets': output_list\n>> +               }\n>> +\n>> +       def process(self, config: dict, images: list, outputs: dict) -> dict:\n>> +               return self._do_all_lsc(images)\n>> +\n>> diff --git a/utils/tuning/mali-c55.py b/utils/tuning/mali-c55.py\n>> new file mode 100755\n>> index 00000000..01535366\n>> --- /dev/null\n>> +++ b/utils/tuning/mali-c55.py\n>> @@ -0,0 +1,40 @@\n>> +#!/usr/bin/env python3\n>> +# SPDX-License-Identifier: GPL-2.0-or-later\n>> +#\n>> +# Copyright (C) 2024, Ideas on Board oy\n>> +#\n>> +# mali-c55.py - Tuning script for mali-c55\n>> +\n>> +import sys\n>> +\n>> +import libtuning as lt\n>> +from libtuning.parsers import YamlParser\n>> +from libtuning.generators import YamlOutput\n>> +from libtuning.modules.lsc import LSCMaliC55\n>> +\n>> +tuner = lt.Tuner('MaliC55')\n>> +tuner.add(LSCMaliC55(\n>> +          debug=[lt.Debug.Plot],\n>> +          # This is for the actual LSC tuning, and is part of the base LSC\n>> +          # module. rkisp1's table sector sizes (16x16 programmed as mirrored\n>> +          # 8x8) are separate, and is hardcoded in its specific LSC tuning\n>> +          # module.\n> rkisp1 doesn't look relevant to mali-c55 :)\n\nOops, I'll fix that\n>\n>\n> With that removed (or changed to fit), looks good to me.\n>\n> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>\n\n\nThanks!\n\n>\n>> +          sector_shape=(32, 32),\n>> +\n>> +          sector_x_gradient=lt.gradient.Linear(lt.Remainder.DistributeFront),\n>> +          sector_y_gradient=lt.gradient.Linear(lt.Remainder.DistributeFront),\n>> +\n>> +          # This is the function that will be used to average the pixels in\n>> +          # each sector. This can also be a custom function.\n>> +          sector_average_function=lt.average.Mean(),\n>> +\n>> +          # This is the function that will be used to smooth the color ratio\n>> +          # values.  This can also be a custom function.\n>> +          smoothing_function=lt.smoothing.MedianBlur(3),\n>> +          ))\n>> +tuner.set_input_parser(YamlParser())\n>> +tuner.set_output_formatter(YamlOutput())\n>> +tuner.set_output_order([LSCMaliC55])\n>> +\n>> +if __name__ == '__main__':\n>> +    sys.exit(tuner.run(sys.argv))\n>> -- \n>> 2.34.1\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 A05D8C3237\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 25 Jul 2025 12:01:31 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 5FD8B690CC;\n\tFri, 25 Jul 2025 14:01:30 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id DEF42690A6\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 25 Jul 2025 14:01:28 +0200 (CEST)","from [192.168.0.43]\n\t(cpc141996-chfd3-2-0-cust928.12-3.cable.virginm.net [86.13.91.161])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id E1888C66;\n\tFri, 25 Jul 2025 14:00:48 +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=\"SYI0V1sV\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1753444849;\n\tbh=MZQAbphDQgML/CbS76NCXL3jrEIdBKJ+HU65uSwES+8=;\n\th=Date:Subject:To:References:From:In-Reply-To:From;\n\tb=SYI0V1sVvxVFZPdsfrS+0tC4cIOS3b4Xv64WoMHeKDNMytJPKFSY1hNCqOhr7R6FA\n\thRMDcPTtNKHSxtlV71BO2J3fQ4YQA7hC90fiqe4HCiz/e+81xyED5WDXuyu/iGHiJd\n\tgfcJizAjf+sh2ZlshZfM4aD+DI7kKtU9xHbLEf0w=","Message-ID":"<c9fbfdbf-f765-4875-b853-d576f0eedb37@ideasonboard.com>","Date":"Fri, 25 Jul 2025 13:01:25 +0100","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH 1/3] utils: tuning: Add tuning scripts for mali-c55","To":"Paul Elder <paul.elder@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","References":"<20250304231254.10588-1-dan.scally@ideasonboard.com>\n\t<20250304231254.10588-2-dan.scally@ideasonboard.com>\n\t<175317939858.3923365.867127487583187537@neptunite.rasen.tech>","Content-Language":"en-US","From":"Dan Scally <dan.scally@ideasonboard.com>","Autocrypt":"addr=dan.scally@ideasonboard.com; keydata=\n\txsFNBGLydlEBEADa5O2s0AbUguprfvXOQun/0a8y2Vk6BqkQALgeD6KnXSWwaoCULp18etYW\n\tB31bfgrdphXQ5kUQibB0ADK8DERB4wrzrUb5CMxLBFE7mQty+v5NsP0OFNK9XTaAOcmD+Ove\n\teIjYvqurAaro91jrRVrS1gBRxIFqyPgNvwwL+alMZhn3/2jU2uvBmuRrgnc/e9cHKiuT3Dtq\n\tMHGPKL2m+plk+7tjMoQFfexoQ1JKugHAjxAhJfrkXh6uS6rc01bYCyo7ybzg53m1HLFJdNGX\n\tsUKR+dQpBs3SY4s66tc1sREJqdYyTsSZf80HjIeJjU/hRunRo4NjRIJwhvnK1GyjOvvuCKVU\n\tRWpY8dNjNu5OeAfdrlvFJOxIE9M8JuYCQTMULqd1NuzbpFMjc9524U3Cngs589T7qUMPb1H1\n\tNTA81LmtJ6Y+IV5/kiTUANflpzBwhu18Ok7kGyCq2a2jsOcVmk8gZNs04gyjuj8JziYwwLbf\n\tvzABwpFVcS8aR+nHIZV1HtOzyw8CsL8OySc3K9y+Y0NRpziMRvutrppzgyMb9V+N31mK9Mxl\n\t1YkgaTl4ciNWpdfUe0yxH03OCuHi3922qhPLF4XX5LN+NaVw5Xz2o3eeWklXdouxwV7QlN33\n\tu4+u2FWzKxDqO6WLQGjxPE0mVB4Gh5Pa1Vb0ct9Ctg0qElvtGQARAQABzShEYW4gU2NhbGx5\n\tIDxkYW4uc2NhbGx5QGlkZWFzb25ib2FyZC5jb20+wsGNBBMBCAA3FiEEsdtt8OWP7+8SNfQe\n\tkiQuh/L+GMQFAmLydlIFCQWjmoACGwMECwkIBwUVCAkKCwUWAgMBAAAKCRCSJC6H8v4YxDI2\n\tEAC2Gz0iyaXJkPInyshrREEWbo0CA6v5KKf3I/HlMPqkZ48bmGoYm4mEQGFWZJAT3K4ir8bg\n\tcEfs9V54gpbrZvdwS4abXbUK4WjKwEs8HK3XJv1WXUN2bsz5oEJWZUImh9gD3naiLLI9QMMm\n\tw/aZkT+NbN5/2KvChRWhdcha7+2Te4foOY66nIM+pw2FZM6zIkInLLUik2zXOhaZtqdeJZQi\n\tHSPU9xu7TRYN4cvdZAnSpG7gQqmLm5/uGZN1/sB3kHTustQtSXKMaIcD/DMNI3JN/t+RJVS7\n\tc0Jh/ThzTmhHyhxx3DRnDIy7kwMI4CFvmhkVC2uNs9kWsj1DuX5kt8513mvfw2OcX9UnNKmZ\n\tnhNCuF6DxVrL8wjOPuIpiEj3V+K7DFF1Cxw1/yrLs8dYdYh8T8vCY2CHBMsqpESROnTazboh\n\tAiQ2xMN1cyXtX11Qwqm5U3sykpLbx2BcmUUUEAKNsM//Zn81QXKG8vOx0ZdMfnzsCaCzt8f6\n\t9dcDBBI3tJ0BI9ByiocqUoL6759LM8qm18x3FYlxvuOs4wSGPfRVaA4yh0pgI+ModVC2Pu3y\n\tejE/IxeatGqJHh6Y+iJzskdi27uFkRixl7YJZvPJAbEn7kzSi98u/5ReEA8Qhc8KO/B7wprj\n\txjNMZNYd0Eth8+WkixHYj752NT5qshKJXcyUU87BTQRi8nZSARAAx0BJayh1Fhwbf4zoY56x\n\txHEpT6DwdTAYAetd3yiKClLVJadYxOpuqyWa1bdfQWPb+h4MeXbWw/53PBgn7gI2EA7ebIRC\n\tPJJhAIkeym7hHZoxqDQTGDJjxFEL11qF+U3rhWiL2Zt0Pl+zFq0eWYYVNiXjsIS4FI2+4m16\n\ttPbDWZFJnSZ828VGtRDQdhXfx3zyVX21lVx1bX4/OZvIET7sVUufkE4hrbqrrufre7wsjD1t\n\t8MQKSapVrr1RltpzPpScdoxknOSBRwOvpp57pJJe5A0L7+WxJ+vQoQXj0j+5tmIWOAV1qBQp\n\thyoyUk9JpPfntk2EKnZHWaApFp5TcL6c5LhUvV7F6XwOjGPuGlZQCWXee9dr7zym8iR3irWT\n\t+49bIh5PMlqSLXJDYbuyFQHFxoiNdVvvf7etvGfqFYVMPVjipqfEQ38ST2nkzx+KBICz7uwj\n\tJwLBdTXzGFKHQNckGMl7F5QdO/35An/QcxBnHVMXqaSd12tkJmoRVWduwuuoFfkTY5mUV3uX\n\txGj3iVCK4V+ezOYA7c2YolfRCNMTza6vcK/P4tDjjsyBBZrCCzhBvd4VVsnnlZhVaIxoky4K\n\taL+AP+zcQrUZmXmgZjXOLryGnsaeoVrIFyrU6ly90s1y3KLoPsDaTBMtnOdwxPmo1xisH8oL\n\ta/VRgpFBfojLPxMAEQEAAcLBfAQYAQgAJhYhBLHbbfDlj+/vEjX0HpIkLofy/hjEBQJi8nZT\n\tBQkFo5qAAhsMAAoJEJIkLofy/hjEXPcQAMIPNqiWiz/HKu9W4QIf1OMUpKn3YkVIj3p3gvfM\n\tRes4fGX94Ji599uLNrPoxKyaytC4R6BTxVriTJjWK8mbo9jZIRM4vkwkZZ2bu98EweSucxbp\n\tvjESsvMXGgxniqV/RQ/3T7LABYRoIUutARYq58p5HwSP0frF0fdFHYdTa2g7MYZl1ur2JzOC\n\tFHRpGadlNzKDE3fEdoMobxHB3Lm6FDml5GyBAA8+dQYVI0oDwJ3gpZPZ0J5Vx9RbqXe8RDuR\n\tdu90hvCJkq7/tzSQ0GeD3BwXb9/R/A4dVXhaDd91Q1qQXidI+2jwhx8iqiYxbT+DoAUkQRQy\n\txBtoCM1CxH7u45URUgD//fxYr3D4B1SlonA6vdaEdHZOGwECnDpTxecENMbz/Bx7qfrmd901\n\tD+N9SjIwrbVhhSyUXYnSUb8F+9g2RDY42Sk7GcYxIeON4VzKqWM7hpkXZ47pkK0YodO+dRKM\n\tyMcoUWrTK0Uz6UzUGKoJVbxmSW/EJLEGoI5p3NWxWtScEVv8mO49gqQdrRIOheZycDmHnItt\n\t9Qjv00uFhEwv2YfiyGk6iGF2W40s2pH2t6oeuGgmiZ7g6d0MEK8Ql/4zPItvr1c1rpwpXUC1\n\tu1kQWgtnNjFHX3KiYdqjcZeRBiry1X0zY+4Y24wUU0KsEewJwjhmCKAsju1RpdlPg2kC","In-Reply-To":"<175317939858.3923365.867127487583187537@neptunite.rasen.tech>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"7bit","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>"}}]