{"id":22922,"url":"https://patchwork.libcamera.org/api/1.1/patches/22922/?format=json","web_url":"https://patchwork.libcamera.org/patch/22922/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/1.1/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20250304231254.10588-2-dan.scally@ideasonboard.com>","date":"2025-03-04T23:12:52","name":"[1/3] utils: tuning: Add tuning scripts for mali-c55","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"1875f736238f058b0ab2e223c36d2a23782d8c0d","submitter":{"id":156,"url":"https://patchwork.libcamera.org/api/1.1/people/156/?format=json","name":"Dan Scally","email":"dan.scally@ideasonboard.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/22922/mbox/","series":[{"id":5045,"url":"https://patchwork.libcamera.org/api/1.1/series/5045/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=5045","date":"2025-03-04T23:12:51","name":"Add Mali-C55 Tuning Script","version":1,"mbox":"https://patchwork.libcamera.org/series/5045/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/22922/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/22922/checks/","tags":{},"headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 12EB6C3257\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue,  4 Mar 2025 23:13:33 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 78B6868823;\n\tWed,  5 Mar 2025 00:13:29 +0100 (CET)","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 708CD68777\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed,  5 Mar 2025 00:13:26 +0100 (CET)","from mail.ideasonboard.com\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 DD423352;\n\tWed,  5 Mar 2025 00:11:53 +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=\"wg02JsXh\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1741129914;\n\tbh=t1p53joINf2QMEOBIMMMy3TjxPipXNBLGG2RbAVI2cI=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=wg02JsXh3e3cYW4IPT0bjns6Jv5nDF9xg1thoTwcQWB6DyBjr6L1FILiKMJGI45v6\n\tRwX6TXGGe6QDG2fleZZY6KSLuXBD6NWkC4cHZ+8e5SbASszHZE9M7cjLlpRG2vYmze\n\tiSmt2JXcTG3oCHVDXQ0DabYkmlGSkY7vlk7dwl5o=","From":"Daniel Scally <dan.scally@ideasonboard.com>","To":"libcamera-devel@lists.libcamera.org","Cc":"Daniel Scally <dan.scally@ideasonboard.com>","Subject":"[PATCH 1/3] utils: tuning: Add tuning scripts for mali-c55","Date":"Tue,  4 Mar 2025 23:12:52 +0000","Message-Id":"<20250304231254.10588-2-dan.scally@ideasonboard.com>","X-Mailer":"git-send-email 2.34.1","In-Reply-To":"<20250304231254.10588-1-dan.scally@ideasonboard.com>","References":"<20250304231254.10588-1-dan.scally@ideasonboard.com>","MIME-Version":"1.0","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>"},"content":"Add tuning scripts for the mali-c55 IPA. At present the only module\nthat is available is for lens shading correction.\n\nSigned-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","diff":"diff --git a/utils/tuning/libtuning/modules/lsc/__init__.py b/utils/tuning/libtuning/modules/lsc/__init__.py\nindex 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\ndiff --git a/utils/tuning/libtuning/modules/lsc/mali_c55.py b/utils/tuning/libtuning/modules/lsc/mali_c55.py\nnew file mode 100644\nindex 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+\tout_name = 'Lsc'\n+\tname = \"LSCMaliC55\"\n+\n+\tdef __init__(self, *args, **kwargs):\n+\t\tsuper().__init__(**kwargs)\n+\n+\tdef validate_config(self, config: dict) -> bool:\n+\t\t# There's nothing we need in config\n+\t\treturn True\n+\n+\tdef _do_single_lsc(self, image: lt.Image):\n+\t\t\"\"\"\n+\t\tGenerate lens shading tables of gain values for a single image\n+\n+\t\tThis function generates a set of lens shading tables - one for\n+\t\teach colour channel. The table values are gain values, expressed\n+\t\tgenerically (I.E. not in the IPA module's format)\n+\n+\t\tReturns a tuple of colour temperature, array of red gain values,\n+\t\tarray of blue gain values, array of red's green gain values and\n+\t\tarray of blue's green gain values.\n+\t\t\"\"\"\n+\t\tcgr, _ = self._lsc_single_channel(image.channels[lt.Color.GR],\n+\t\t\t\t\t\t   image)\n+\t\tcgb, _ = self._lsc_single_channel(image.channels[lt.Color.GB],\n+\t\t\t\t\t\t   image)\n+\t\tcr, _ = self._lsc_single_channel(image.channels[lt.Color.R],\n+\t\t\t\t\t\t image)\n+\t\tcb, _ = self._lsc_single_channel(image.channels[lt.Color.B],\n+\t\t\t\t\t\t image)\n+\n+\t\treturn (\n+\t\t\timage.color,\n+\t\t\tcr.flatten(),\n+\t\t\tcb.flatten(),\n+\t\t\tcgr.flatten(),\n+\t\t\tcgb.flatten()\n+\t\t)\n+\n+\tdef _do_all_lsc(self, images: list) -> dict:\n+\t\t\"\"\"\n+\t\tGenerate lens shading tables in the IPA's format\n+\n+\t\tThis function generates lens shading tables from the list of\n+\t\timages. A set (one per colour channel) of tables is generated\n+\t\tfor each image, and then the sets from all images which were\n+\t\tgenerated from the same colour temperature are averaged together\n+\t\tand transformed into the format required by the IPA module.\n+\n+\t\tReturns a list of dictionaries containing colour temperature,\n+\t\tred lens shading table, green lens shading table and blue lens\n+\t\tshading table.\n+\t\t\"\"\"\n+\n+\t\toutput_map_func = lt.gradient.Linear().map\n+\n+\t\tlist_ct = []\n+\n+\t\tlist_cr = []\n+\t\tlist_cb = []\n+\t\tlist_cgr = []\n+\t\tlist_cgb = []\n+\n+\t\tfor image in images:\n+\t\t\tct, cr, cg, cgr, cgb = self._do_single_lsc(image)\n+\n+\t\t\tlist_ct.append(ct)\n+\t\t\tlist_cr.append(cr)\n+\t\t\tlist_cb.append(cg)\n+\t\t\tlist_cgr.append(cgr)\n+\t\t\tlist_cgb.append(cgb)\n+\n+\t\tlist_ct = np.array(list_ct)\n+\t\tlist_cr = np.array(list_cr)\n+\t\tlist_cb = np.array(list_cb)\n+\t\tlist_cgr = np.array(list_cgr)\n+\t\tlist_cgb = np.array(list_cgb)\n+\t\tlist_cg = (list_cgr + list_cgb) / 2\n+\n+\t\t# We need to map the gains into the IPA-specific values to pass\n+\t\t# to the ISP. For the mali-c55 the values are always in the\n+\t\t# range [0..255] but the min/max that those values represent\n+\t\t# depend on the mesh scale parameter, so we'll need to choose\n+\t\t# what that should be and use the gain-range it represents as\n+\t\t# the domain for output_map_func().\n+\t\t#\n+\t\t# For convenient reference, the possible mesh scale values are\n+\t\t# as follows (taken from include/linux/mali-c55-config.h)\n+\t\t#\n+\t\t#\t- 0 = 0-2x gain\n+\t\t#\t- 1 = 0-4x gain\n+\t\t#\t- 2 = 0-8x gain\n+\t\t#\t- 3 = 0-16x gain\n+\t\t#\t- 4 = 1-2x gain\n+\t\t#\t- 5 = 1-3x gain\n+\t\t#\t- 6 = 1-5x gain\n+\t\t#\t- 7 = 1-9x gain\n+\t\t#\n+\t\t# We want to use the scale with the smallest range that still\n+\t\t# covers the minimum and maximum value we want to set...but this\n+\t\t# process at present hard-codes a minimum gain of 1.0, so the\n+\t\t# first 4 scales are out right away. We'll just consider the\n+\t\t# minimum as 1.0 for now and if we ever need more than 9.0 gain\n+\t\t# we'll have to fix this - shout about that if so.\n+\n+\t\tmax_gain = np.max([list_cr, list_cgr, list_cgb, list_cb])\n+\t\tif (max_gain > 9.0):\n+\t\t\tprint(\"WARNING: Maximum gain restricted artificially to 9.0\")\n+\n+\t\tmesh_scales = {\n+\t\t\t4: (1.0, 2.0),\n+\t\t\t5: (1.0, 3.0),\n+\t\t\t6: (1.0, 5.0),\n+\t\t\t7: (1.0, 9.0)\n+\t\t}\n+\n+\t\tfor i in mesh_scales.keys():\n+\t\t\tif max_gain <= mesh_scales[i][1]:\n+\t\t\t\tbreak\n+\n+\t\tmesh_scale = i\n+\n+\t\toutput_list = []\n+\t\tfor ct in sorted(set(list_ct)):\n+\t\t\tindices = np.where(list_ct == ct)\n+\t\t\tct = int(ct)\n+\n+\t\t\ttables = []\n+\t\t\tfor lists in [list_cr, list_cg, list_cb]:\n+\t\t\t\ttable = np.mean(lists[indices], axis=0)\n+\n+\t\t\t\ttable = output_map_func(\n+\t\t\t\t\t(\n+\t\t\t\t\t\tmesh_scales[mesh_scale][0],\n+\t\t\t\t\t\tmesh_scales[mesh_scale][1] - 0.001\n+\t\t\t\t\t),\n+\t\t\t\t\t(0, 255),\n+\t\t\t\t\ttable\n+\t\t\t\t)\n+\t\t\t\ttable = np.round(table).astype('uint8').tolist()\n+\t\t\t\ttables.append(table)\n+\n+\t\t\tentry = {\n+\t\t\t\t'ct': ct,\n+\t\t\t\t'r': tables[0],\n+\t\t\t\t'g': tables[1],\n+\t\t\t\t'b': tables[2],\n+\t\t\t}\n+\n+\t\t\toutput_list.append(entry)\n+\n+\t\treturn {\n+\t\t\t'meshScale': mesh_scale,\n+\t\t\t'sets': output_list\n+\t\t}\n+\n+\tdef process(self, config: dict, images: list, outputs: dict) -> dict:\n+\t\treturn self._do_all_lsc(images)\n+\ndiff --git a/utils/tuning/mali-c55.py b/utils/tuning/mali-c55.py\nnew file mode 100755\nindex 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","prefixes":["1/3"]}