From patchwork Tue Mar 4 23:12:52 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dan Scally X-Patchwork-Id: 22922 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 12EB6C3257 for ; Tue, 4 Mar 2025 23:13:33 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 78B6868823; Wed, 5 Mar 2025 00:13:29 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="wg02JsXh"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 708CD68777 for ; Wed, 5 Mar 2025 00:13:26 +0100 (CET) Received: from mail.ideasonboard.com (cpc141996-chfd3-2-0-cust928.12-3.cable.virginm.net [86.13.91.161]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id DD423352; Wed, 5 Mar 2025 00:11:53 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1741129914; bh=t1p53joINf2QMEOBIMMMy3TjxPipXNBLGG2RbAVI2cI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=wg02JsXh3e3cYW4IPT0bjns6Jv5nDF9xg1thoTwcQWB6DyBjr6L1FILiKMJGI45v6 RwX6TXGGe6QDG2fleZZY6KSLuXBD6NWkC4cHZ+8e5SbASszHZE9M7cjLlpRG2vYmze iSmt2JXcTG3oCHVDXQ0DabYkmlGSkY7vlk7dwl5o= From: Daniel Scally To: libcamera-devel@lists.libcamera.org Cc: Daniel Scally 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 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add tuning scripts for the mali-c55 IPA. At present the only module that is available is for lens shading correction. Signed-off-by: Daniel Scally --- .../tuning/libtuning/modules/lsc/__init__.py | 1 + .../tuning/libtuning/modules/lsc/mali_c55.py | 173 ++++++++++++++++++ utils/tuning/mali-c55.py | 40 ++++ 3 files changed, 214 insertions(+) create mode 100644 utils/tuning/libtuning/modules/lsc/mali_c55.py create mode 100755 utils/tuning/mali-c55.py diff --git a/utils/tuning/libtuning/modules/lsc/__init__.py b/utils/tuning/libtuning/modules/lsc/__init__.py index 0ba4411b..edd5ce7f 100644 --- a/utils/tuning/libtuning/modules/lsc/__init__.py +++ b/utils/tuning/libtuning/modules/lsc/__init__.py @@ -3,5 +3,6 @@ # Copyright (C) 2022, Paul Elder from libtuning.modules.lsc.lsc import LSC +from libtuning.modules.lsc.mali_c55 import LSCMaliC55 from libtuning.modules.lsc.raspberrypi import ALSCRaspberryPi from libtuning.modules.lsc.rkisp1 import LSCRkISP1 diff --git a/utils/tuning/libtuning/modules/lsc/mali_c55.py b/utils/tuning/libtuning/modules/lsc/mali_c55.py new file mode 100644 index 00000000..7d85aee9 --- /dev/null +++ b/utils/tuning/libtuning/modules/lsc/mali_c55.py @@ -0,0 +1,173 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (C) 2019, Raspberry Pi Ltd +# Copyright (C) 2024, Ideas on Board oy +# +# mali-c55.py - LSC module for tuning mali-c55 + +from .lsc import LSC + +import libtuning as lt + +import numpy as np + +class LSCMaliC55(LSC): + out_name = 'Lsc' + name = "LSCMaliC55" + + def __init__(self, *args, **kwargs): + super().__init__(**kwargs) + + def validate_config(self, config: dict) -> bool: + # There's nothing we need in config + return True + + def _do_single_lsc(self, image: lt.Image): + """ + Generate lens shading tables of gain values for a single image + + This function generates a set of lens shading tables - one for + each colour channel. The table values are gain values, expressed + generically (I.E. not in the IPA module's format) + + Returns a tuple of colour temperature, array of red gain values, + array of blue gain values, array of red's green gain values and + array of blue's green gain values. + """ + cgr, _ = self._lsc_single_channel(image.channels[lt.Color.GR], + image) + cgb, _ = self._lsc_single_channel(image.channels[lt.Color.GB], + image) + cr, _ = self._lsc_single_channel(image.channels[lt.Color.R], + image) + cb, _ = self._lsc_single_channel(image.channels[lt.Color.B], + image) + + return ( + image.color, + cr.flatten(), + cb.flatten(), + cgr.flatten(), + cgb.flatten() + ) + + def _do_all_lsc(self, images: list) -> dict: + """ + Generate lens shading tables in the IPA's format + + This function generates lens shading tables from the list of + images. A set (one per colour channel) of tables is generated + for each image, and then the sets from all images which were + generated from the same colour temperature are averaged together + and transformed into the format required by the IPA module. + + Returns a list of dictionaries containing colour temperature, + red lens shading table, green lens shading table and blue lens + shading table. + """ + + output_map_func = lt.gradient.Linear().map + + list_ct = [] + + list_cr = [] + list_cb = [] + list_cgr = [] + list_cgb = [] + + for image in images: + ct, cr, cg, cgr, cgb = self._do_single_lsc(image) + + list_ct.append(ct) + list_cr.append(cr) + list_cb.append(cg) + list_cgr.append(cgr) + list_cgb.append(cgb) + + list_ct = np.array(list_ct) + list_cr = np.array(list_cr) + list_cb = np.array(list_cb) + list_cgr = np.array(list_cgr) + list_cgb = np.array(list_cgb) + list_cg = (list_cgr + list_cgb) / 2 + + # We need to map the gains into the IPA-specific values to pass + # to the ISP. For the mali-c55 the values are always in the + # range [0..255] but the min/max that those values represent + # depend on the mesh scale parameter, so we'll need to choose + # what that should be and use the gain-range it represents as + # the domain for output_map_func(). + # + # For convenient reference, the possible mesh scale values are + # as follows (taken from include/linux/mali-c55-config.h) + # + # - 0 = 0-2x gain + # - 1 = 0-4x gain + # - 2 = 0-8x gain + # - 3 = 0-16x gain + # - 4 = 1-2x gain + # - 5 = 1-3x gain + # - 6 = 1-5x gain + # - 7 = 1-9x gain + # + # We want to use the scale with the smallest range that still + # covers the minimum and maximum value we want to set...but this + # process at present hard-codes a minimum gain of 1.0, so the + # first 4 scales are out right away. We'll just consider the + # minimum as 1.0 for now and if we ever need more than 9.0 gain + # we'll have to fix this - shout about that if so. + + max_gain = np.max([list_cr, list_cgr, list_cgb, list_cb]) + if (max_gain > 9.0): + print("WARNING: Maximum gain restricted artificially to 9.0") + + mesh_scales = { + 4: (1.0, 2.0), + 5: (1.0, 3.0), + 6: (1.0, 5.0), + 7: (1.0, 9.0) + } + + for i in mesh_scales.keys(): + if max_gain <= mesh_scales[i][1]: + break + + mesh_scale = i + + output_list = [] + for ct in sorted(set(list_ct)): + indices = np.where(list_ct == ct) + ct = int(ct) + + tables = [] + for lists in [list_cr, list_cg, list_cb]: + table = np.mean(lists[indices], axis=0) + + table = output_map_func( + ( + mesh_scales[mesh_scale][0], + mesh_scales[mesh_scale][1] - 0.001 + ), + (0, 255), + table + ) + table = np.round(table).astype('uint8').tolist() + tables.append(table) + + entry = { + 'ct': ct, + 'r': tables[0], + 'g': tables[1], + 'b': tables[2], + } + + output_list.append(entry) + + return { + 'meshScale': mesh_scale, + 'sets': output_list + } + + def process(self, config: dict, images: list, outputs: dict) -> dict: + return self._do_all_lsc(images) + diff --git a/utils/tuning/mali-c55.py b/utils/tuning/mali-c55.py new file mode 100755 index 00000000..01535366 --- /dev/null +++ b/utils/tuning/mali-c55.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2024, Ideas on Board oy +# +# mali-c55.py - Tuning script for mali-c55 + +import sys + +import libtuning as lt +from libtuning.parsers import YamlParser +from libtuning.generators import YamlOutput +from libtuning.modules.lsc import LSCMaliC55 + +tuner = lt.Tuner('MaliC55') +tuner.add(LSCMaliC55( + debug=[lt.Debug.Plot], + # This is for the actual LSC tuning, and is part of the base LSC + # module. rkisp1's table sector sizes (16x16 programmed as mirrored + # 8x8) are separate, and is hardcoded in its specific LSC tuning + # module. + sector_shape=(32, 32), + + sector_x_gradient=lt.gradient.Linear(lt.Remainder.DistributeFront), + sector_y_gradient=lt.gradient.Linear(lt.Remainder.DistributeFront), + + # This is the function that will be used to average the pixels in + # each sector. This can also be a custom function. + sector_average_function=lt.average.Mean(), + + # This is the function that will be used to smooth the color ratio + # values. This can also be a custom function. + smoothing_function=lt.smoothing.MedianBlur(3), + )) +tuner.set_input_parser(YamlParser()) +tuner.set_output_formatter(YamlOutput()) +tuner.set_output_order([LSCMaliC55]) + +if __name__ == '__main__': + sys.exit(tuner.run(sys.argv))