From patchwork Thu Nov 24 11:35:40 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 17868 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 4CCF9BDE6B for ; Thu, 24 Nov 2022 11:36:08 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D482F63322; Thu, 24 Nov 2022 12:36:07 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1669289767; bh=OR7jA3ja3gZEENbFWFk1feLfnKtXrfWePxElS6rcrGg=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=1/0N6aIZmMX41x+JUUx0YwLjZ+IiiKb66o2oBSr5XFcSmDQW0ZB9w1PYjwcTzmqzL WT0VIVVlQI6wVjgL2STx9QNRd+fr7O5IK6O9+eoHbJ4UPM962/eR+EMK/9S0fS04W9 50U1AFh/DU8T7miegqNw+oWFX9I4tKsRLp9okhMrDQifxPVeJEpqnZzv9mcVIwSC6e STdS0NSQCf09bZArsgK+nzb/aw3oKaJgvrVgpi2jUjscL3u82fKVr9Z27OqU9zh46B m1StblRboFmAr0S5kjDQydzduZ7Yrq4ngQzjBmuxNqbvE23GMpDoSe8E1L5RoCYM+M +1FRkMFdKOJGw== 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 7F34263319 for ; Thu, 24 Nov 2022 12:36:05 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="l1ti99zn"; dkim-atps=neutral Received: from pyrite.tail37cf.ts.net (h175-177-042-159.catv02.itscom.jp [175.177.42.159]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 0815C496; Thu, 24 Nov 2022 12:36:03 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1669289765; bh=OR7jA3ja3gZEENbFWFk1feLfnKtXrfWePxElS6rcrGg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=l1ti99znjcbb+40GYJ1reu+HLlMBGi4mwaJaDV2F8zDIHa3gm8J1W9b2UNBk00VzG 4TPV1tXJkrNbAfWYI19vDxiX79LXanh8BbtZ/SK4GtP3R80YTlNURulHXzKIhXhXrM 460790BPeRmwCj0yHgGXavpzqOr2uOUBzDNKiwNU= To: libcamera-devel@lists.libcamera.org Date: Thu, 24 Nov 2022 20:35:40 +0900 Message-Id: <20221124113550.2182342-3-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20221124113550.2182342-1-paul.elder@ideasonboard.com> References: <20221124113550.2182342-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 02/12] utils: tuning: libtuning: Implement math helpers 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: , X-Patchwork-Original-From: Paul Elder via libcamera-devel From: Paul Elder Reply-To: Paul Elder Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Implement math helpers for libtuning. This includes: - Average, a wrapper class for numpy averaging functions - Gradient, a class that represents gradients, for distributing and mapping - Smoothing, a wrapper class for cv2 smoothing functions Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart --- Changes in v4: - fix typo (leftover comma) Changes in v3: - Newly split from the first patch "utils: tuning: libtuning: Implement the core of libtuning" - See changelog from that patch --- utils/tuning/libtuning/average.py | 21 ++++++++ utils/tuning/libtuning/gradient.py | 75 +++++++++++++++++++++++++++++ utils/tuning/libtuning/smoothing.py | 24 +++++++++ 3 files changed, 120 insertions(+) create mode 100644 utils/tuning/libtuning/average.py create mode 100644 utils/tuning/libtuning/gradient.py create mode 100644 utils/tuning/libtuning/smoothing.py diff --git a/utils/tuning/libtuning/average.py b/utils/tuning/libtuning/average.py new file mode 100644 index 00000000..e28770d7 --- /dev/null +++ b/utils/tuning/libtuning/average.py @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2022, Paul Elder +# +# average.py - Wrapper for numpy averaging functions to enable duck-typing + +import numpy as np + + +# @brief Wrapper for np averaging functions so that they can be duck-typed +class Average(object): + def __init__(self): + pass + + def average(self, np_array): + raise NotImplementedError + + +class Mean(Average): + def average(self, np_array): + return np.mean(np_array) diff --git a/utils/tuning/libtuning/gradient.py b/utils/tuning/libtuning/gradient.py new file mode 100644 index 00000000..5106f821 --- /dev/null +++ b/utils/tuning/libtuning/gradient.py @@ -0,0 +1,75 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2022, Paul Elder +# +# gradient.py - Gradients that can be used to distribute or map numbers + +import libtuning as lt + +import math +from numbers import Number + + +# @brief Gradient for how to allocate pixels to sectors +# @description There are no parameters for the gradients as the domain is the +# number of pixels and the range is the number of sectors, and +# there is only one curve that has a startpoint and endpoint at +# (0, 0) and at (#pixels, #sectors). The exception is for curves +# that *do* have multiple solutions for only two points, such as +# gaussian, and curves of higher polynomial orders if we had them. +# +# \todo There will probably be a helper in the Gradient class, as I have a +# feeling that all the other curves (besides Linear and Gaussian) can be +# implemented in the same way. +class Gradient(object): + def __init__(self): + pass + + # @brief Distribute pixels into sectors (only in one dimension) + # @param domain Number of pixels + # @param sectors Number of sectors + # @return A list of number of pixels in each sector + def distribute(self, domain: list, sectors: list) -> list: + raise NotImplementedError + + # @brief Map a number on a curve + # @param domain Domain of the curve + # @param rang Range of the curve + # @param x Input on the domain of the curve + # @return y from the range of the curve + def map(self, domain: tuple, rang: tuple, x: Number) -> Number: + raise NotImplementedError + + +class Linear(Gradient): + # @param remainder Mode of handling remainder + def __init__(self, remainder: lt.Remainder = lt.Remainder.Float): + self.remainder = remainder + + def distribute(self, domain: list, sectors: list) -> list: + size = domain / sectors + rem = domain % sectors + + if rem == 0: + return [int(size)] * sectors + + size = math.ceil(size) + rem = domain % size + output_sectors = [int(size)] * (sectors - 1) + + if self.remainder == lt.Remainder.Float: + size = domain / sectors + output_sectors = [size] * sectors + elif self.remainder == lt.Remainder.DistributeFront: + output_sectors.append(int(rem)) + elif self.remainder == lt.Remainder.DistributeBack: + output_sectors.insert(0, int(rem)) + else: + raise ValueError + + return output_sectors + + def map(self, domain: tuple, rang: tuple, x: Number) -> Number: + m = (rang[1] - rang[0]) / (domain[1] - domain[0]) + b = rang[0] - m * domain[0] + return m * x + b diff --git a/utils/tuning/libtuning/smoothing.py b/utils/tuning/libtuning/smoothing.py new file mode 100644 index 00000000..b8a5a242 --- /dev/null +++ b/utils/tuning/libtuning/smoothing.py @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2022, Paul Elder +# +# smoothing.py - Wrapper for cv2 smoothing functions to enable duck-typing + +import cv2 + + +# @brief Wrapper for cv2 smoothing functions so that they can be duck-typed +class Smoothing(object): + def __init__(self): + pass + + def smoothing(self, src): + raise NotImplementedError + + +class MedianBlur(Smoothing): + def __init__(self, ksize): + self.ksize = ksize + + def smoothing(self, src): + return cv2.medianBlur(src.astype('float32'), self.ksize).astype('float64') From patchwork Thu Nov 24 11:35:41 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 17869 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 CC7D9BDE6B for ; Thu, 24 Nov 2022 11:36:09 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8549C6331E; Thu, 24 Nov 2022 12:36:09 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1669289769; bh=uMF4yPAsf6dMznZ6bfLD1kk7MDTUzoZIP5Re5PNa1xw=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=R67CEY7RgbPPXMjczkhXJEmdshYsdGllzq7FIaQbZBx74x2zyk6xyAHwSsNJiSWLC gCjzQoveusZZ8sjxT2YTf/E3wnxNQsWlIWNKi4zk+atia8XlWkxUNjl1nzezq0XcNS uWwX2wM2tRry2JeqBJ28RWxWl4MUsTPM5Q7MWSaT91QOeIEOwKGrpDKaUe+ne/0uXn sCzpm3FSgynyCROHfLVgzsQBTX4qx9tEVYfbwIzjOcF2X7e2XVh+U4fOGLG/Nyrk+B jNuLd+QkNnjbkZSMRjpF6I9gwxpEooUp6KV8N27nwO3RTcMekdsu6xO94AmPXVfS+e x+bxdu9O6sEdg== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5D08963313 for ; Thu, 24 Nov 2022 12:36:07 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="K99SPmfR"; dkim-atps=neutral Received: from pyrite.tail37cf.ts.net (h175-177-042-159.catv02.itscom.jp [175.177.42.159]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id D9E83978; Thu, 24 Nov 2022 12:36:05 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1669289767; bh=uMF4yPAsf6dMznZ6bfLD1kk7MDTUzoZIP5Re5PNa1xw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=K99SPmfRdx1HLom9SJbpCL5KjRSR013BQWHIGZaWFqyDWeoy3RXdz/392KSyEvhOT RakDa3s0itR9XQ1j4y5ifbYyWvVu+U8d2a/BeHPmzRjDWDcg/mNaxjlZ6mYPAXaCgu iAn8JLsdEUPEQS0NgZj1CMxCG63U4fe6JmqDnb7k= To: libcamera-devel@lists.libcamera.org Date: Thu, 24 Nov 2022 20:35:41 +0900 Message-Id: <20221124113550.2182342-4-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20221124113550.2182342-1-paul.elder@ideasonboard.com> References: <20221124113550.2182342-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 03/12] utils: tuning: libtuning: Implement extensible components of libtuning 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: , X-Patchwork-Original-From: Paul Elder via libcamera-devel From: Paul Elder Reply-To: Paul Elder Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Implement the extensible components of libtuning. This includes: - Parsers, for supporting different types of input config file formats - Generators, for supporting different types of output tuning file formats - Modules, for supporting different tuning modules for different algorithms and platforms No parsers, generators, or modules are actually implemented. Only the base classes are implemented. Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart --- Changes in v4: - Add license to generator __init__.py (it was around, just in a later patch) - remove cli args from module.process parameters Changes in v3: - Newly split from the first patch "utils: tuning: libtuning: Implement the core of libtuning" - See changelog from that patch --- utils/tuning/libtuning/generators/__init__.py | 3 ++ .../tuning/libtuning/generators/generator.py | 15 +++++++++ utils/tuning/libtuning/modules/__init__.py | 0 utils/tuning/libtuning/modules/module.py | 32 +++++++++++++++++++ utils/tuning/libtuning/parsers/__init__.py | 0 utils/tuning/libtuning/parsers/parser.py | 21 ++++++++++++ 6 files changed, 71 insertions(+) create mode 100644 utils/tuning/libtuning/generators/__init__.py create mode 100644 utils/tuning/libtuning/generators/generator.py create mode 100644 utils/tuning/libtuning/modules/__init__.py create mode 100644 utils/tuning/libtuning/modules/module.py create mode 100644 utils/tuning/libtuning/parsers/__init__.py create mode 100644 utils/tuning/libtuning/parsers/parser.py diff --git a/utils/tuning/libtuning/generators/__init__.py b/utils/tuning/libtuning/generators/__init__.py new file mode 100644 index 00000000..9ccabb0e --- /dev/null +++ b/utils/tuning/libtuning/generators/__init__.py @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2022, Paul Elder diff --git a/utils/tuning/libtuning/generators/generator.py b/utils/tuning/libtuning/generators/generator.py new file mode 100644 index 00000000..7c8c9b99 --- /dev/null +++ b/utils/tuning/libtuning/generators/generator.py @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2022, Paul Elder +# +# generator.py - Base class for a generator to convert dict to tuning file + +from pathlib import Path + + +class Generator(object): + def __init__(self): + pass + + def write(self, output_path: Path, output_dict: dict, output_order: list): + raise NotImplementedError diff --git a/utils/tuning/libtuning/modules/__init__.py b/utils/tuning/libtuning/modules/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/utils/tuning/libtuning/modules/module.py b/utils/tuning/libtuning/modules/module.py new file mode 100644 index 00000000..12e2fc7c --- /dev/null +++ b/utils/tuning/libtuning/modules/module.py @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2022, Paul Elder +# +# module.py - Base class for algorithm-specific tuning modules + + +# @var type Type of the module. Defined in the base module. +# @var out_name The key that will be used for the algorithm in the algorithms +# dictionary in the tuning output file +# @var hr_name Human-readable module name, mostly for debugging +class Module(object): + type = 'base' + hr_name = 'Base Module' + out_name = 'GenericAlgorithm' + + def __init__(self): + pass + + def validate_config(self, config: dict) -> bool: + raise NotImplementedError + + # @brief Do the module's processing + # @param config Full configuration from the input configuration file + # @param images List of images to process + # @param outputs The outputs of all modules that were executed before this + # module. Note that this is an input parameter, and the + # output of this module should be returned directly + # @return Result of the module's processing. It may be empty. None + # indicates failure and that the result should not be used. + def process(self, config: dict, images: list, outputs: dict) -> dict: + raise NotImplementedError diff --git a/utils/tuning/libtuning/parsers/__init__.py b/utils/tuning/libtuning/parsers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/utils/tuning/libtuning/parsers/parser.py b/utils/tuning/libtuning/parsers/parser.py new file mode 100644 index 00000000..a17d8d71 --- /dev/null +++ b/utils/tuning/libtuning/parsers/parser.py @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2022, Paul Elder +# +# parser.py - Base class for a parser for a specific format of config file + +class Parser(object): + def __init__(self): + pass + + # @brief Parse a config file into a config dict + # @details The config dict shall have one key 'general' with a dict value + # for general configuration options, and all other entries shall + # have the module as the key with its configuration options (as a + # dict) as the value. The config dict shall prune entries that are + # for modules that are not in @a modules. + # @param config (str) Path to config file + # @param modules (list) List of modules + # @return (dict, list) Configuration and list of modules to disable + def parse(self, config_file: str, modules: list) -> (dict, list): + raise NotImplementedError From patchwork Thu Nov 24 11:35:42 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 17870 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 2C103BDE6B for ; Thu, 24 Nov 2022 11:36:11 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id EDE166331B; Thu, 24 Nov 2022 12:36:10 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1669289770; bh=3cdWwHM8Ia0AWP/tCkVhKltnyHW7TVzU9UusVKHhod0=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=2SSRQP+iTc3ZCvEHOr14WyTjJflUNosCb9g2lFxzRCMlALpl6DlRm9GRY4wBP8otX EMvBJUlvAutgb5/qlTwBeJWmA+4fCJveNidKalL/HNXSdzAFpy2z0mZ+3K1Ear0VZx yDzTHgPnXq/iozMtxu/iARipV91IUCeP4U5yxeV6jpcQC6S3o5KOmEOo8MWYJW2En8 IJopttRvWzpqggVjqpcZmgPg7/W0LOm2bLiMTAFxnmINlH5x1ByGqJlb1ZySDbwyae 16PDI1TOCN7cfYqQxEsSObG112VQ4MGXj0p0qGHeDjr8FXgxVD8w/6ClWP/+7JgBbD ULRZQDVLcL8ZA== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 4373F63321 for ; Thu, 24 Nov 2022 12:36:09 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="TMMGqd3l"; dkim-atps=neutral Received: from pyrite.tail37cf.ts.net (h175-177-042-159.catv02.itscom.jp [175.177.42.159]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id B8EFD496; Thu, 24 Nov 2022 12:36:07 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1669289769; bh=3cdWwHM8Ia0AWP/tCkVhKltnyHW7TVzU9UusVKHhod0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=TMMGqd3lfJK8tvgoVbAR4XGONNKdGq2bP8getL3UR+BMshtxpQ/Bjii0HPlNdNdvJ X1jWl48JdLSq1NJ2if6vEb0cowarb4/cMRegzh9VQLFCw232wW3PuEke46V5Awzx/R sTj4vuv1L7e9f0SDc73NMNMkmjb9eo1gkaZJNffc= To: libcamera-devel@lists.libcamera.org Date: Thu, 24 Nov 2022 20:35:42 +0900 Message-Id: <20221124113550.2182342-5-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20221124113550.2182342-1-paul.elder@ideasonboard.com> References: <20221124113550.2182342-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 04/12] utils: libtuning: modules: Add base LSC module 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: , X-Patchwork-Original-From: Paul Elder via libcamera-devel From: Paul Elder Reply-To: Paul Elder Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a base LSC module to libtuning's collection of modules. It is based on raspberrypi's ctt's ALSC, but customizable for different lens shading table sizes, among other things. It alone is insufficient as a module, but it provides utilities that are useful for and which will simplify implementing LSC modules. Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart --- Changes in v3: - rename ALSC to LSC - update changelog to more accurately reflect the redesign (splitting core LSC from Raspberry Pi ALSC and rkisp1 LSC) Changes in v2: - fix python errors - fix style - add SPDX and copyright - don't call super().validateConfig() - fix sector splitting - i forgot that we have to half the image size for each color channel - make the base ALSC module into an abstract module, which can be easily used by other platforms to derive their own ALSC modules (see rkisp1's module later in this series; i think it's quite nice) --- .../tuning/libtuning/modules/lsc/__init__.py | 5 ++ utils/tuning/libtuning/modules/lsc/lsc.py | 72 +++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 utils/tuning/libtuning/modules/lsc/__init__.py create mode 100644 utils/tuning/libtuning/modules/lsc/lsc.py diff --git a/utils/tuning/libtuning/modules/lsc/__init__.py b/utils/tuning/libtuning/modules/lsc/__init__.py new file mode 100644 index 00000000..47d9b846 --- /dev/null +++ b/utils/tuning/libtuning/modules/lsc/__init__.py @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2022, Paul Elder + +from libtuning.modules.lsc.lsc import LSC diff --git a/utils/tuning/libtuning/modules/lsc/lsc.py b/utils/tuning/libtuning/modules/lsc/lsc.py new file mode 100644 index 00000000..344a07a3 --- /dev/null +++ b/utils/tuning/libtuning/modules/lsc/lsc.py @@ -0,0 +1,72 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (C) 2019, Raspberry Pi Ltd +# Copyright (C) 2022, Paul Elder + +from ..module import Module + +import libtuning as lt +import libtuning.utils as utils + +import numpy as np + + +class LSC(Module): + type = 'lsc' + hr_name = 'LSC (Base)' + out_name = 'GenericLSC' + + def __init__(self, *, + debug: list, + sector_shape: tuple, + sector_x_gradient: lt.Gradient, + sector_y_gradient: lt.Gradient, + sector_average_function: lt.Average, + smoothing_function: lt.Smoothing): + super().__init__() + + self.debug = debug + + self.sector_shape = sector_shape + self.sector_x_gradient = sector_x_gradient + self.sector_y_gradient = sector_y_gradient + self.sector_average_function = sector_average_function + + self.smoothing_function = smoothing_function + + def _enumerate_lsc_images(self, images): + for image in images: + if image.lsc_only: + yield image + + def _get_grid(self, channel, img_w, img_h): + # List of number of pixels in each sector + sectors_x = self.sector_x_gradient.distribute(img_w / 2, self.sector_shape[0]) + sectors_y = self.sector_y_gradient.distribute(img_h / 2, self.sector_shape[1]) + + grid = [] + + r = 0 + for y in sectors_y: + c = 0 + for x in sectors_x: + grid.append(self.sector_average_function.average(channel[r:r + y, c:c + x])) + c += x + r += y + + return np.array(grid) + + def _lsc_single_channel(self, channel: np.array, + image: lt.Image, green_grid: np.array = None): + grid = self._get_grid(channel, image.w, image.h) + grid -= image.blacklevel_16 + if green_grid is None: + table = np.reshape(1 / grid, self.sector_shape[::-1]) + else: + table = np.reshape(green_grid / grid, self.sector_shape[::-1]) + table = self.smoothing_function.smoothing(table) + + if green_grid is None: + table = table / np.min(table) + + return table, grid From patchwork Thu Nov 24 11:35:43 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 17871 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 B3553BDE6B for ; Thu, 24 Nov 2022 11:36:12 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 70F1963321; Thu, 24 Nov 2022 12:36:12 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1669289772; bh=cDCq7JJNR2UWkIvSoj8eSeAuK3jO7tRfJNCRiiTdAog=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=Ldpd+DPEsQeOYNJzfsflTdbdG9jcIKfd4GK0iOvQNRvLwmXhruOcTlHdB//gwXa34 BOUpDKU+JLi/dynja53kZGMdJVxlr3B6+wB7tGb2aheDHh3H5tSLLZu07v79/9NsRn kgckAVMzABt9WwzEAzBmewsV3sGeioW+oI4B/lxdW7irZmRLrrBJftfMY8HrFLy8KY 8LHSTgBK8QOMYemwuriBuwqBJTftFlooye9jzgG18ZwVyF99Chz91s3OiT8PgzrphY Alt6UtTcBqD6EMrxYU/lZpT72cjWU9VV2+wGU4/KxKMOOGQEzMPffECd9+aFHtMW+m OwDdrhr0FCAzQ== 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 28BD563321 for ; Thu, 24 Nov 2022 12:36:11 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="qyVO3hxn"; dkim-atps=neutral Received: from pyrite.tail37cf.ts.net (h175-177-042-159.catv02.itscom.jp [175.177.42.159]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 962107FA; Thu, 24 Nov 2022 12:36:09 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1669289770; bh=cDCq7JJNR2UWkIvSoj8eSeAuK3jO7tRfJNCRiiTdAog=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=qyVO3hxnGpO09hHszj6r+4m6TT4ouhqRAYR1htyFPfCcOQbkmoghHY8gSj6cl4lZd SGkvSmjnGr6Rvviqf5NCCIvoHAlxkdFOXEF817gvm5m7fLxMVpxawsOb2XWHxF9u3J IuJ2TO3/4kaw7qGR+lqI1JQ6+Efr59zOuNyxczKg= To: libcamera-devel@lists.libcamera.org Date: Thu, 24 Nov 2022 20:35:43 +0900 Message-Id: <20221124113550.2182342-6-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20221124113550.2182342-1-paul.elder@ideasonboard.com> References: <20221124113550.2182342-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 05/12] utils: libtuning: modules: alsc: Add raspberrypi ALSC module 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: , X-Patchwork-Original-From: Paul Elder via libcamera-devel From: Paul Elder Reply-To: Paul Elder Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add an ALSC module for Raspberry Pi. Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart --- Changes in v4: - minor style fix - remove cli args from module.process parameters - replace special rounding with standard rounding as it has higher accuracy Changes in v3: - add file description - add comment about type name change - override the type name to alsc now that the base type name is lsc - use Param's getter New in v2 --- .../tuning/libtuning/modules/lsc/__init__.py | 1 + .../libtuning/modules/lsc/raspberrypi.py | 246 ++++++++++++++++++ 2 files changed, 247 insertions(+) create mode 100644 utils/tuning/libtuning/modules/lsc/raspberrypi.py diff --git a/utils/tuning/libtuning/modules/lsc/__init__.py b/utils/tuning/libtuning/modules/lsc/__init__.py index 47d9b846..7cdecdb8 100644 --- a/utils/tuning/libtuning/modules/lsc/__init__.py +++ b/utils/tuning/libtuning/modules/lsc/__init__.py @@ -3,3 +3,4 @@ # Copyright (C) 2022, Paul Elder from libtuning.modules.lsc.lsc import LSC +from libtuning.modules.lsc.raspberrypi import ALSCRaspberryPi diff --git a/utils/tuning/libtuning/modules/lsc/raspberrypi.py b/utils/tuning/libtuning/modules/lsc/raspberrypi.py new file mode 100644 index 00000000..58f5000d --- /dev/null +++ b/utils/tuning/libtuning/modules/lsc/raspberrypi.py @@ -0,0 +1,246 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (C) 2019, Raspberry Pi Ltd +# Copyright (C) 2022, Paul Elder +# +# raspberrypi.py - ALSC module for tuning Raspberry Pi + +from .lsc import LSC + +import libtuning as lt +import libtuning.utils as utils + +from numbers import Number +import numpy as np + + +class ALSCRaspberryPi(LSC): + # Override the type name so that the parser can match the entry in the + # config file. + type = 'alsc' + hr_name = 'ALSC (Raspberry Pi)' + out_name = 'rpi.alsc' + compatible = ['raspberrypi'] + + def __init__(self, *, + do_color: lt.Param, + luminance_strength: lt.Param, + **kwargs): + super().__init__(**kwargs) + + self.do_color = do_color + self.luminance_strength = luminance_strength + + self.output_range = (0, 3.999) + + def validate_config(self, config: dict) -> bool: + if self not in config: + utils.eprint(f'{self.type} not in config') + return False + + valid = True + + conf = config[self] + + lum_key = self.luminance_strength.name + color_key = self.do_color.name + + if lum_key not in conf and self.luminance_strength.required: + utils.eprint(f'{lum_key} is not in config') + valid = False + + if lum_key in conf and (conf[lum_key] < 0 or conf[lum_key] > 1): + utils.eprint(f'Warning: {lum_key} is not in range [0, 1]; defaulting to 0.5') + + if color_key not in conf and self.do_color.required: + utils.eprint(f'{color_key} is not in config') + valid = False + + return valid + + # @return Image color temperature, flattened array of red calibration table + # (containing {sector size} elements), flattened array of blue + # calibration table, flattened array of green calibration + # table + + def _do_single_alsc(self, image: lt.Image, do_alsc_colour: bool): + average_green = np.mean((image.channels[lt.Color.GR:lt.Color.GB + 1]), axis=0) + + cg, g = self._lsc_single_channel(average_green, image) + + if not do_alsc_colour: + return image.color, None, None, cg.flatten() + + cr, _ = self._lsc_single_channel(image.channels[lt.Color.R], image, g) + cb, _ = self._lsc_single_channel(image.channels[lt.Color.B], image, g) + + # \todo implement debug + + return image.color, cr.flatten(), cb.flatten(), cg.flatten() + + # @return Red shading table, Blue shading table, Green shading table, + # number of images processed + + def _do_all_alsc(self, images: list, do_alsc_colour: bool, general_conf: dict) -> (list, list, list, Number, int): + # List of colour temperatures + list_col = [] + # Associated calibration tables + list_cr = [] + list_cb = [] + list_cg = [] + count = 0 + for image in self._enumerate_lsc_images(images): + col, cr, cb, cg = self._do_single_alsc(image, do_alsc_colour) + list_col.append(col) + list_cr.append(cr) + list_cb.append(cb) + list_cg.append(cg) + count += 1 + + # Convert to numpy array for data manipulation + list_col = np.array(list_col) + list_cr = np.array(list_cr) + list_cb = np.array(list_cb) + list_cg = np.array(list_cg) + + cal_cr_list = [] + cal_cb_list = [] + + # Note: Calculation of average corners and center of the shading tables + # has been removed (which ctt had, as it was unused) + + # Average all values for luminance shading and return one table for all temperatures + lum_lut = list(np.round(np.mean(list_cg, axis=0), 3)) + + if not do_alsc_colour: + return None, None, lum_lut, count + + for ct in sorted(set(list_col)): + # Average tables for the same colour temperature + indices = np.where(list_col == ct) + ct = int(ct) + t_r = np.round(np.mean(list_cr[indices], axis=0), 3) + t_b = np.round(np.mean(list_cb[indices], axis=0), 3) + + cr_dict = { + 'ct': ct, + 'table': list(t_r) + } + cb_dict = { + 'ct': ct, + 'table': list(t_b) + } + cal_cr_list.append(cr_dict) + cal_cb_list.append(cb_dict) + + return cal_cr_list, cal_cb_list, lum_lut, count + + # @brief Calculate sigma from two adjacent gain tables + def _calcSigma(self, g1, g2): + g1 = np.reshape(g1, self.sector_shape[::-1]) + g2 = np.reshape(g2, self.sector_shape[::-1]) + + # Apply gains to gain table + gg = g1 / g2 + if np.mean(gg) < 1: + gg = 1 / gg + + # For each internal patch, compute average difference between it and + # its 4 neighbours, then append to list + diffs = [] + for i in range(self.sector_shape[1] - 2): + for j in range(self.sector_shape[0] - 2): + # Indexing is incremented by 1 since all patches on borders are + # not counted + diff = np.abs(gg[i + 1][j + 1] - gg[i][j + 1]) + diff += np.abs(gg[i + 1][j + 1] - gg[i + 2][j + 1]) + diff += np.abs(gg[i + 1][j + 1] - gg[i + 1][j]) + diff += np.abs(gg[i + 1][j + 1] - gg[i + 1][j + 2]) + diffs.append(diff / 4) + + mean_diff = np.mean(diffs) + return np.round(mean_diff, 5) + + # @brief Obtains sigmas for red and blue, effectively a measure of the + # 'error' + def _get_sigma(self, cal_cr_list, cal_cb_list): + # Provided colour alsc tables were generated for two different colour + # temperatures sigma is calculated by comparing two calibration temperatures + # adjacent in colour space + + color_temps = [cal['ct'] for cal in cal_cr_list] + + # Calculate sigmas for each adjacent color_temps and return worst one + sigma_rs = [] + sigma_bs = [] + for i in range(len(color_temps) - 1): + sigma_rs.append(self._calcSigma(cal_cr_list[i]['table'], cal_cr_list[i + 1]['table'])) + sigma_bs.append(self._calcSigma(cal_cb_list[i]['table'], cal_cb_list[i + 1]['table'])) + + # Return maximum sigmas, not necessarily from the same colour + # temperature interval + sigma_r = max(sigma_rs) if sigma_rs else 0.005 + sigma_b = max(sigma_bs) if sigma_bs else 0.005 + + return sigma_r, sigma_b + + def process(self, config: dict, images: list, outputs: dict) -> dict: + output = { + 'omega': 1.3, + 'n_iter': 100, + 'luminance_strength': 0.7 + } + + conf = config[self] + general_conf = config['general'] + + do_alsc_colour = self.do_color.get_value(conf) + + # \todo I have no idea where this input parameter is used + luminance_strength = self.luminance_strength.get_value(conf) + if luminance_strength < 0 or luminance_strength > 1: + luminance_strength = 0.5 + + output['luminance_strength'] = luminance_strength + + # \todo Validate images from greyscale camera and force grescale mode + # \todo Debug functionality + + alsc_out = self._do_all_alsc(images, do_alsc_colour, general_conf) + # \todo Handle the second green lut + cal_cr_list, cal_cb_list, luminance_lut, count = alsc_out + + if not do_alsc_colour: + output['luminance_lut'] = luminance_lut + output['n_iter'] = 0 + return output + + output['calibrations_Cr'] = cal_cr_list + output['calibrations_Cb'] = cal_cb_list + output['luminance_lut'] = luminance_lut + + # The sigmas determine the strength of the adaptive algorithm, that + # cleans up any lens shading that has slipped through the alsc. These + # are determined by measuring a 'worst-case' difference between two + # alsc tables that are adjacent in colour space. If, however, only one + # colour temperature has been provided, then this difference can not be + # computed as only one table is available. + # To determine the sigmas you would have to estimate the error of an + # alsc table with only the image it was taken on as a check. To avoid + # circularity, dfault exaggerated sigmas are used, which can result in + # too much alsc and is therefore not advised. + # In general, just take another alsc picture at another colour + # temperature! + + if count == 1: + output['sigma'] = 0.005 + output['sigma_Cb'] = 0.005 + utils.eprint('Warning: Only one alsc calibration found; standard sigmas used for adaptive algorithm.') + return output + + # Obtain worst-case scenario residual sigmas + sigma_r, sigma_b = self._get_sigma(cal_cr_list, cal_cb_list) + output['sigma'] = np.round(sigma_r, 5) + output['sigma_Cb'] = np.round(sigma_b, 5) + + return output From patchwork Thu Nov 24 11:35:44 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 17872 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 1E5BCBDE6B for ; Thu, 24 Nov 2022 11:36:15 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id DCDE26331A; Thu, 24 Nov 2022 12:36:14 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1669289774; bh=68JvHufpiKs5rWAWg3nqZTiEXti5vbOJntMnvfKr6qA=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=qlP1odPN1YmF0Oi/ql+PwQJGSYgmLaY8iDUrz6zsgK8Ndhxyy9l2iYF0HWTyM1/RN FlIJbl2g0fKU8RYYKPoK5Sx8PYhQOfohghZXgtl38YGikOBqGoaF6tw/xtQhJ9C1gJ CkR9g1fSBL1iTOJzWGBjGR1P8tlF1jdGqvLF39VlX+R5vueK7Kp6GGxyHD7+iRs8XT OhRsMbBW/FqleYM2PeuVP/4ts6lCsLOQ2NeVY+97bBc9JfqlWD6EmkQ8U0QeJ9xuO8 QXTlrlSPCE6evEQMQUVzuGPzOVAUt78Vbyfh8mP9amWvmKHR0hZIqfcMImP7lrOf2T l0c10FgIt7hrg== 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 0575663319 for ; Thu, 24 Nov 2022 12:36:13 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="qsqLaKRS"; dkim-atps=neutral Received: from pyrite.tail37cf.ts.net (h175-177-042-159.catv02.itscom.jp [175.177.42.159]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7C2987FA; Thu, 24 Nov 2022 12:36:11 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1669289772; bh=68JvHufpiKs5rWAWg3nqZTiEXti5vbOJntMnvfKr6qA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=qsqLaKRSaFAKWlFyySelOFkIkjQ4y/SPAP15aa/6d3raQJy+NbNBTlhrFKey4ViOD QFbBgrvK9Q52wp2CWxqnNC55hvSVhSgDIwFdo5Q0GI0gFhdHROOTORHzmgI280NROx 5EdbutxrOVzHm+va66Ha2gCBMojSUyhKUipUQJNc= To: libcamera-devel@lists.libcamera.org Date: Thu, 24 Nov 2022 20:35:44 +0900 Message-Id: <20221124113550.2182342-7-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20221124113550.2182342-1-paul.elder@ideasonboard.com> References: <20221124113550.2182342-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 06/12] utils: libtuning: modules: alsc: Add rkisp1 LSC module 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: , X-Patchwork-Original-From: Paul Elder via libcamera-devel From: Paul Elder Reply-To: Paul Elder Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add an LSC module for RkISP1. Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart --- Changes in v4: - remove cli args from module.process parameters - inline output_map_func parameters as they are only used once Changes in v3: - rename ALSC to LSC - add file description New in v2 --- .../tuning/libtuning/modules/lsc/__init__.py | 1 + utils/tuning/libtuning/modules/lsc/rkisp1.py | 112 ++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 utils/tuning/libtuning/modules/lsc/rkisp1.py diff --git a/utils/tuning/libtuning/modules/lsc/__init__.py b/utils/tuning/libtuning/modules/lsc/__init__.py index 7cdecdb8..0ba4411b 100644 --- a/utils/tuning/libtuning/modules/lsc/__init__.py +++ b/utils/tuning/libtuning/modules/lsc/__init__.py @@ -4,3 +4,4 @@ from libtuning.modules.lsc.lsc import LSC from libtuning.modules.lsc.raspberrypi import ALSCRaspberryPi +from libtuning.modules.lsc.rkisp1 import LSCRkISP1 diff --git a/utils/tuning/libtuning/modules/lsc/rkisp1.py b/utils/tuning/libtuning/modules/lsc/rkisp1.py new file mode 100644 index 00000000..5701ae0a --- /dev/null +++ b/utils/tuning/libtuning/modules/lsc/rkisp1.py @@ -0,0 +1,112 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (C) 2019, Raspberry Pi Ltd +# Copyright (C) 2022, Paul Elder +# +# rkisp1.py - LSC module for tuning rkisp1 + +from .lsc import LSC + +import libtuning as lt +import libtuning.utils as utils + +from numbers import Number +import numpy as np + + +class LSCRkISP1(LSC): + hr_name = 'LSC (RkISP1)' + out_name = 'LensShadingCorrection' + # \todo Not sure if this is useful. Probably will remove later. + compatible = ['rkisp1'] + + def __init__(self, *args, **kwargs): + super().__init__(**kwargs) + + # We don't actually need anything from the config file + def validate_config(self, config: dict) -> bool: + return True + + # @return Image color temperature, flattened array of red calibration table + # (containing {sector size} elements), flattened array of blue + # calibration table, flattened array of (red's) green calibration + # table, flattened array of (blue's) green calibration table + + def _do_single_lsc(self, image: lt.Image): + cgr, gr = self._lsc_single_channel(image.channels[lt.Color.GR], image) + cgb, gb = self._lsc_single_channel(image.channels[lt.Color.GB], image) + + # \todo Should these ratio against the average of both greens or just + # each green like we've done here? + cr, _ = self._lsc_single_channel(image.channels[lt.Color.R], image, gr) + cb, _ = self._lsc_single_channel(image.channels[lt.Color.B], image, gb) + + return image.color, cr.flatten(), cb.flatten(), cgr.flatten(), cgb.flatten() + + # @return List of dictionaries of color temperature, red table, red's green + # table, blue's green table, and blue table + + def _do_all_lsc(self, images: list) -> list: + output_list = [] + output_map_func = lt.gradient.Linear().map + + # List of colour temperatures + list_col = [] + # Associated calibration tables + list_cr = [] + list_cb = [] + list_cgr = [] + list_cgb = [] + for image in self._enumerate_lsc_images(images): + col, cr, cb, cgr, cgb = self._do_single_lsc(image) + list_col.append(col) + list_cr.append(cr) + list_cb.append(cb) + list_cgr.append(cgr) + list_cgb.append(cgb) + + # Convert to numpy array for data manipulation + list_col = np.array(list_col) + list_cr = np.array(list_cr) + list_cb = np.array(list_cb) + list_cgr = np.array(list_cgr) + list_cgb = np.array(list_cgb) + + for color_temperature in sorted(set(list_col)): + # Average tables for the same colour temperature + indices = np.where(list_col == color_temperature) + color_temperature = int(color_temperature) + + tables = [] + for lis in [list_cr, list_cgr, list_cgb, list_cb]: + table = np.mean(lis[indices], axis=0) + table = output_map_func((1, 3.999), (1024, 4095), table) + table = np.round(table).astype('int32').tolist() + tables.append(table) + + entry = { + 'ct': color_temperature, + 'r': tables[0], + 'gr': tables[1], + 'gb': tables[2], + 'b': tables[3], + } + + output_list.append(entry) + + return output_list + + def process(self, config: dict, images: list, outputs: dict) -> dict: + output = {} + + # \todo This should actually come from self.sector_{x,y}_gradient + size_gradient = lt.gradient.Linear(lt.Remainder.Float) + output['x-size'] = size_gradient.distribute(0.5, 8) + output['y-size'] = size_gradient.distribute(0.5, 8) + + output['sets'] = self._do_all_lsc(images) + + # \todo Validate images from greyscale camera and force grescale mode + # \todo Debug functionality + + return output From patchwork Thu Nov 24 11:35:45 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 17873 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 9A9A0BDE6B for ; Thu, 24 Nov 2022 11:36:17 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5CB106331A; Thu, 24 Nov 2022 12:36:17 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1669289777; bh=9o/V3AcCFQQCjFd7DyXHh3v+yATuP0jJdvr8p2re2wE=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=EpWfrbgLRJIs3tXqcZKzhUkA//hULHWLO7A+wEK0RCiJaVw8Aj8G9GynzFLO3WTRg 0emnsBzvzPySZsr4EOQRyq1Q2ZomIxBptL7kePLA/7ZUqprvnu40aYbpd9AggRFwS/ oFRNLEFcuDmnHVLZV5tLUoX7FeLxWLBqhQm4g5IsYexVwtgM3ETarVpJvMu2wGuxhs BS6FhN0Y1eGZJyrRXH1aMzraRXOhwVX0Fqq1/U7oBESBokXbKm18vHiFRu+9h6Nmd3 fmyl1fqclruwb6ZVWHy8cPFaA12RsE4ro556xkvNbAE92p3/RY5h/Zwj8aGDHka9Dg 5HjcoTGbdCghA== 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 DDEA06331C for ; Thu, 24 Nov 2022 12:36:14 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="bnYXQ8NC"; dkim-atps=neutral Received: from pyrite.tail37cf.ts.net (h175-177-042-159.catv02.itscom.jp [175.177.42.159]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 5AFE1BC0; Thu, 24 Nov 2022 12:36:13 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1669289774; bh=9o/V3AcCFQQCjFd7DyXHh3v+yATuP0jJdvr8p2re2wE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=bnYXQ8NC9joMcHGrXKlNkHKDunjmwz1qIeY8I5llwsMvWmxxjxGCqNYHNzmwSOLKP bxc63AEmd9a00TGXe+AGzeOK9E4Ly1LLHAmdrs8+pSR6h/qUuwrO3enqCyikjW9bRw 4yDAaCZ4No2wTooYK0ao/EGYdH7CzW8kxHyXKbo8= To: libcamera-devel@lists.libcamera.org Date: Thu, 24 Nov 2022 20:35:45 +0900 Message-Id: <20221124113550.2182342-8-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20221124113550.2182342-1-paul.elder@ideasonboard.com> References: <20221124113550.2182342-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 07/12] utils: libtuning: parsers: Add raspberrypi parser 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: , X-Patchwork-Original-From: Paul Elder via libcamera-devel From: Paul Elder Reply-To: Paul Elder Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a parser to libtuning for parsing configuration files that are the same format as raspberrypi's ctt's configuration files. Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart --- Changes in v3: - style fixes - add file description - remove indirection from fake polymorphism Changes in v2: - fix python errors - fix style - add SPDX and copyright - don't put black level in the processed config, as raspberrypi ctt's config format uses -1 as a magical value to tell ctt to use the value from the image, but in our Image loading function it already does, and uses this config value to override it if its present - Don't validate module config in parser, instead libtuning will do it; parser just converts the key from string to module instance --- utils/tuning/libtuning/parsers/__init__.py | 5 + .../libtuning/parsers/raspberrypi_parser.py | 93 +++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 utils/tuning/libtuning/parsers/raspberrypi_parser.py diff --git a/utils/tuning/libtuning/parsers/__init__.py b/utils/tuning/libtuning/parsers/__init__.py index e69de29b..9d20d2fc 100644 --- a/utils/tuning/libtuning/parsers/__init__.py +++ b/utils/tuning/libtuning/parsers/__init__.py @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2022, Paul Elder + +from libtuning.parsers.raspberrypi_parser import RaspberryPiParser diff --git a/utils/tuning/libtuning/parsers/raspberrypi_parser.py b/utils/tuning/libtuning/parsers/raspberrypi_parser.py new file mode 100644 index 00000000..d26586ba --- /dev/null +++ b/utils/tuning/libtuning/parsers/raspberrypi_parser.py @@ -0,0 +1,93 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2022, Paul Elder +# +# raspberrypi_parser.py - Parser for Raspberry Pi config file format + +from .parser import Parser + +import json +import numbers + +import libtuning.utils as utils + + +class RaspberryPiParser(Parser): + def __init__(self): + super().__init__() + + # The string in the 'disable' and 'plot' lists are formatted as + # 'rpi.{module_name}'. + # @brief Enumerate, as a module, @a listt if its value exists in @a dictt + # and it is the name of a valid module in @a modules + def _enumerate_rpi_modules(self, listt, dictt, modules): + for x in listt: + name = x.replace('rpi.', '') + if name not in dictt: + continue + module = utils.get_module_by_typename(modules, name) + if module is not None: + yield module + + def _valid_macbeth_option(self, value): + if not isinstance(value, dict): + return False + + if list(value.keys()) != ['small', 'show']: + return False + + for val in value.values(): + if not isinstance(val, numbers.Number): + return False + + return True + + def parse(self, config_file: str, modules: list) -> (dict, list): + with open(config_file, 'r') as config_json: + config = json.load(config_json) + + disable = [] + for module in self._enumerate_rpi_modules(config['disable'], config, modules): + disable.append(module) + # Remove the disabled module's config too + config.pop(module.name) + config.pop('disable') + + # The raspberrypi config format has 'plot' map to a list of module + # names which should be plotted. libtuning has each module contain the + # plot information in itself so do this conversion. + + for module in self._enumerate_rpi_modules(config['plot'], config, modules): + # It's fine to set the value of a potentially disabled module, as + # the object still exists at this point + module.appendValue('debug', 'plot') + config.pop('plot') + + # Convert the keys from module name to module instance + + new_config = {} + + for module_name in config: + module = utils.get_module_by_type_name(modules, module_name) + if module is not None: + new_config[module] = config[module_name] + + new_config['general'] = {} + + if 'blacklevel' in config: + if not isinstance(config['blacklevel'], numbers.Number): + raise TypeError('Config "blacklevel" must be a number') + # Raspberry Pi's ctt config has magic blacklevel value -1 to mean + # "get it from the image metadata". Since we already do that in + # Image, don't save it to the config here. + if config['blacklevel'] >= 0: + new_config['general']['blacklevel'] = config['blacklevel'] + + if 'macbeth' in config: + if not self._valid_macbeth_option(config['macbeth']): + raise TypeError('Config "macbeth" must be a dict: {"small": number, "show": number}') + new_config['general']['macbeth'] = config['macbeth'] + else: + new_config['general']['macbeth'] = {'small': 0, 'show': 0} + + return new_config, disable From patchwork Thu Nov 24 11:35:46 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 17874 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 0F4CFC3286 for ; Thu, 24 Nov 2022 11:36:18 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B888C63329; Thu, 24 Nov 2022 12:36:17 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1669289777; bh=Y7nEBUYH5b4W9avHl2TdJIL1TOaNPAQgLWaKihtoTmY=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=ab5gvRuu45GvV68r4vHXw/bYUJM184WeH6DYkbctkdqRFeNfmuqiUlSsNucl8peyc KqQTlZfB/DgqGOpMC6+W5G1VliZfVaEyJfMT9F1iigRDJdx9NstA376JJqGwBV3My7 V8BaMCmNN2pI658krDBLzYJiy0mAMHvQoK9tzDHhUNs6h6qV6m8UkeScW+pFDwqAuh mNLwIRePegeyPnZuP7jANzMz3/tFoCrFBX03CHEERcBX/H3GyRiKrPQq3Z647wQI/G 2GRWYi03qSUTJl323CmAIYo+SATwE8shb8h+0Xf7IG54Yw4ci/gpPaYQiG/Qw2AWPL Z8RDU/mrANbBg== 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 AE01863319 for ; Thu, 24 Nov 2022 12:36:16 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="iVeUAOGY"; dkim-atps=neutral Received: from pyrite.tail37cf.ts.net (h175-177-042-159.catv02.itscom.jp [175.177.42.159]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 398A0496; Thu, 24 Nov 2022 12:36:14 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1669289776; bh=Y7nEBUYH5b4W9avHl2TdJIL1TOaNPAQgLWaKihtoTmY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=iVeUAOGYqEzve9MYV9jz+FEfzNRTaphfKy7x476Ustsv9jgOvruQHry4Cnamo5FwS LC+Xu9mr8gnLpVDqjs8pYTyYeg1Rd+0qb5jrQxS5nEZml2a0q5akqvDEAJcJkv6I+y K1PjgUzNoIh9nG9IwDz3jZWnXguankTPSSC5s0tw= To: libcamera-devel@lists.libcamera.org Date: Thu, 24 Nov 2022 20:35:46 +0900 Message-Id: <20221124113550.2182342-9-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20221124113550.2182342-1-paul.elder@ideasonboard.com> References: <20221124113550.2182342-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 08/12] utils: libtuning: generators: Add raspberrypi output 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: , X-Patchwork-Original-From: Paul Elder via libcamera-devel From: Paul Elder Reply-To: Paul Elder Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a generator to libtuning for writing tuning output to a json file formatted the same way that raspberrypi's ctt formats them. Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart --- Changes in v4: - move adding the license to generators __init__.py to the appropriate patch earlier in the series Changes in v3: - move pretty_print into a private class member - move RaspberryPiOutput to end of file - add file descriptions - remove indirection from fake polymorphism Changes in v2: - add SPDX and copyright - fix style - move the 'rpi.' prefix of module names in the tuning output from here to into the Modules' out_name field --- utils/tuning/libtuning/generators/__init__.py | 2 + .../generators/raspberrypi_output.py | 114 ++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 utils/tuning/libtuning/generators/raspberrypi_output.py diff --git a/utils/tuning/libtuning/generators/__init__.py b/utils/tuning/libtuning/generators/__init__.py index 9ccabb0e..937aff30 100644 --- a/utils/tuning/libtuning/generators/__init__.py +++ b/utils/tuning/libtuning/generators/__init__.py @@ -1,3 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-or-later # # Copyright (C) 2022, Paul Elder + +from libtuning.generators.raspberrypi_output import RaspberryPiOutput diff --git a/utils/tuning/libtuning/generators/raspberrypi_output.py b/utils/tuning/libtuning/generators/raspberrypi_output.py new file mode 100644 index 00000000..813491cd --- /dev/null +++ b/utils/tuning/libtuning/generators/raspberrypi_output.py @@ -0,0 +1,114 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright 2022 Raspberry Pi Ltd +# +# raspberrypi_output.py - Generate tuning file in Raspberry Pi's json format +# +# (Copied from ctt_pretty_print_json.py) + +from .generator import Generator + +import json +from pathlib import Path +import textwrap + + +class Encoder(json.JSONEncoder): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.indentation_level = 0 + self.hard_break = 120 + self.custom_elems = { + 'table': 16, + 'luminance_lut': 16, + 'ct_curve': 3, + 'ccm': 3, + 'gamma_curve': 2, + 'y_target': 2, + 'prior': 2 + } + + def encode(self, o, node_key=None): + if isinstance(o, (list, tuple)): + # Check if we are a flat list of numbers. + if not any(isinstance(el, (list, tuple, dict)) for el in o): + s = ', '.join(json.dumps(el) for el in o) + if node_key in self.custom_elems.keys(): + # Special case handling to specify number of elements in a row for tables, ccm, etc. + self.indentation_level += 1 + sl = s.split(', ') + num = self.custom_elems[node_key] + chunk = [self.indent_str + ', '.join(sl[x:x + num]) for x in range(0, len(sl), num)] + t = ',\n'.join(chunk) + self.indentation_level -= 1 + output = f'\n{self.indent_str}[\n{t}\n{self.indent_str}]' + elif len(s) > self.hard_break - len(self.indent_str): + # Break a long list with wraps. + self.indentation_level += 1 + t = textwrap.fill(s, self.hard_break, break_long_words=False, + initial_indent=self.indent_str, subsequent_indent=self.indent_str) + self.indentation_level -= 1 + output = f'\n{self.indent_str}[\n{t}\n{self.indent_str}]' + else: + # Smaller lists can remain on a single line. + output = f' [ {s} ]' + return output + else: + # Sub-structures in the list case. + self.indentation_level += 1 + output = [self.indent_str + self.encode(el) for el in o] + self.indentation_level -= 1 + output = ',\n'.join(output) + return f' [\n{output}\n{self.indent_str}]' + + elif isinstance(o, dict): + self.indentation_level += 1 + output = [] + for k, v in o.items(): + if isinstance(v, dict) and len(v) == 0: + # Empty config block special case. + output.append(self.indent_str + f'{json.dumps(k)}: {{ }}') + else: + # Only linebreak if the next node is a config block. + sep = f'\n{self.indent_str}' if isinstance(v, dict) else '' + output.append(self.indent_str + f'{json.dumps(k)}:{sep}{self.encode(v, k)}') + output = ',\n'.join(output) + self.indentation_level -= 1 + return f'{{\n{output}\n{self.indent_str}}}' + + else: + return ' ' + json.dumps(o) + + @property + def indent_str(self) -> str: + return ' ' * self.indentation_level * self.indent + + def iterencode(self, o, **kwargs): + return self.encode(o) + + +class RaspberryPiOutput(Generator): + def __init__(self): + super().__init__() + + def _pretty_print(self, in_json: dict) -> str: + + if 'version' not in in_json or \ + 'target' not in in_json or \ + 'algorithms' not in in_json or \ + in_json['version'] < 2.0: + raise RuntimeError('Incompatible JSON dictionary has been provided') + + return json.dumps(in_json, cls=Encoder, indent=4, sort_keys=False) + + def write(self, output_file: Path, output_dict: dict, output_order: list): + # Write json dictionary to file using ctt's version 2 format + out_json = { + "version": 2.0, + 'target': 'bcm2835', + "algorithms": [{f'{module.out_name}': output_dict[module]} for module in output_order] + } + + with open(output_file, 'w') as f: + f.write(self._pretty_print(out_json)) From patchwork Thu Nov 24 11:35:47 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 17875 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 68957BDE6B for ; Thu, 24 Nov 2022 11:36:20 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3749D6331C; Thu, 24 Nov 2022 12:36:20 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1669289780; bh=G750lojvZ6bd8ltrUOeCxvAIGwzQyDxBta4MuReVT6w=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=cH0eMQtG0o9jlp3/tSiDWrs/wwwFpkuIhKFN4mIG5reHtOQTu8Q3p6h+BIJT1UFKs 3/7LSx8Ai4SgI1Fr8kvsdTnodIMpkoGsmcmJLfcvdOFVowibMaqi3HOZ0UJW3WQj4j JgP5cBfNjUcBIrFVDsCpmuNLTYS6bdhlNdSPXA3XnvKg/DA/EFpuPgj2u7iBOm/v4b BqN1jAuTfLy62WxmGfcrgnwkDkzF9HO0IV4oyNySynijN0kxXpBjfHAXWprBSKYxyA w/+XLQCFxlVlijiyFHLvEDvFLRLzpo7iToPe0Lqp/KnxzHGGJsGoCbs2lOF6/usbG8 G3V8tXEJporZw== 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 9B61D6331B for ; Thu, 24 Nov 2022 12:36:18 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="KXUIfB53"; dkim-atps=neutral Received: from pyrite.tail37cf.ts.net (h175-177-042-159.catv02.itscom.jp [175.177.42.159]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 1765D496; Thu, 24 Nov 2022 12:36:16 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1669289778; bh=G750lojvZ6bd8ltrUOeCxvAIGwzQyDxBta4MuReVT6w=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=KXUIfB53RfrsLWgkFKpFZEohaZEyfICK1s4MitgCoI3cXyqL7bumRbHnpoMLTcSRr AzgsnLOOgl6Mervo9N4yH1FwMWFqxsibYDak0JhM0hD3IdbiIWRSaHFppvRHCyw7j7 mI0wfMKddJBs8Wlw2dnNf/XPpmQWBg0j/RiImVuU= To: libcamera-devel@lists.libcamera.org Date: Thu, 24 Nov 2022 20:35:47 +0900 Message-Id: <20221124113550.2182342-10-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20221124113550.2182342-1-paul.elder@ideasonboard.com> References: <20221124113550.2182342-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 09/12] utils: libtuning: parsers: Add yaml parser 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: , X-Patchwork-Original-From: Paul Elder via libcamera-devel From: Paul Elder Reply-To: Paul Elder Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a parser to libtuning for parsing configuration files in yaml format. At the moment it doesn't parse anything and simply returns an empty config. This is fine for the time being, as the only user of it is the rkisp1 tuning script, which only has an LSC module which doesn't consume anything from the configuration file. When a module comes around that requires the yaml parser, it can be implemented then. Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart --- Changes in v3: - add file description - remove indirection from fake polymorphism - update commit message to reflect the fact that it doesn't actually do anything at the moment --- utils/tuning/libtuning/parsers/__init__.py | 1 + utils/tuning/libtuning/parsers/yaml_parser.py | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 utils/tuning/libtuning/parsers/yaml_parser.py diff --git a/utils/tuning/libtuning/parsers/__init__.py b/utils/tuning/libtuning/parsers/__init__.py index 9d20d2fc..022c1e5d 100644 --- a/utils/tuning/libtuning/parsers/__init__.py +++ b/utils/tuning/libtuning/parsers/__init__.py @@ -3,3 +3,4 @@ # Copyright (C) 2022, Paul Elder from libtuning.parsers.raspberrypi_parser import RaspberryPiParser +from libtuning.parsers.yaml_parser import YamlParser diff --git a/utils/tuning/libtuning/parsers/yaml_parser.py b/utils/tuning/libtuning/parsers/yaml_parser.py new file mode 100644 index 00000000..5c1673a5 --- /dev/null +++ b/utils/tuning/libtuning/parsers/yaml_parser.py @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2022, Paul Elder +# +# yaml_parser.py - Parser for YAML format config file + +from .parser import Parser + + +class YamlParser(Parser): + def __init__(self): + super().__init__() + + # \todo Implement this (it's fine for now as we don't need a config for + # rkisp1 LSC, which is the only user of this so far) + def parse(self, config_file: str, modules: list) -> (dict, list): + return {}, [] From patchwork Thu Nov 24 11:35:48 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 17876 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 CD639BDE6B for ; Thu, 24 Nov 2022 11:36:21 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8DF1363325; Thu, 24 Nov 2022 12:36:21 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1669289781; bh=53DyiSS3srg0cI0RR99TOTG49RcdzgKi64fV+ms/8Yw=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=z++kp9WjXnHWjYiaHDKBrKnftmuv6XiBUzrDX/LI0CyEJfOWuVuepvMvs2bqKp3Bz X4RtEBf+3DOzYtZGgr3VIgyUOkDkUtL4F8U/rCSu+GeKwkiT/AwOadApy7+b4xvWai 7wfbqi8lvT3jRlomrra2R1fED8GrArTk4qJPcv2lpo0pyd7p+2zyoYW2OCoYBtCc/A 0H3HjBhJp64zD6qCoGVqr8Dcr+y4i0c9B+SzsuVAwBHq6KBNWEEHEWR/C0NP7571Hs HCKhJOVq6dJoaFLDyf9oei+R0TRtwol5sRs83tj1S3ATsAADyi1BkJEXcjUuogHLG2 dPOu19Tuo8OHQ== 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 81D0B6331B for ; Thu, 24 Nov 2022 12:36:20 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="AyMrvKf7"; dkim-atps=neutral Received: from pyrite.tail37cf.ts.net (h175-177-042-159.catv02.itscom.jp [175.177.42.159]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id F3A76BC0; Thu, 24 Nov 2022 12:36:18 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1669289780; bh=53DyiSS3srg0cI0RR99TOTG49RcdzgKi64fV+ms/8Yw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=AyMrvKf7bP0wFiAdnk4+HK/tOSQvT7NwspvyavoIP9fHlR+W+s7LJZexaT+hdIN30 3/iHoEQbcE5YkQwaqC3FRW0s/kuo2N5dNIu54bVFFX1clSk1iMOE4MaoWmcKu/MsKe oU4j//N1RZSmQqNMaHkfgo5eom5CxbGEhm7wnj1w= To: libcamera-devel@lists.libcamera.org Date: Thu, 24 Nov 2022 20:35:48 +0900 Message-Id: <20221124113550.2182342-11-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20221124113550.2182342-1-paul.elder@ideasonboard.com> References: <20221124113550.2182342-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 10/12] utils: libtuning: generators: Add yaml output 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: , X-Patchwork-Original-From: Paul Elder via libcamera-devel From: Paul Elder Reply-To: Paul Elder Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a generator to libtuning for writing tuning output to a yaml file. Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart --- Changes in v4: - explicitly set utf-8 encoding Changes in v3: - add file description - remove indirection from fake polymorphism New in 2 --- utils/tuning/libtuning/generators/__init__.py | 1 + .../libtuning/generators/yaml_output.py | 123 ++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 utils/tuning/libtuning/generators/yaml_output.py diff --git a/utils/tuning/libtuning/generators/__init__.py b/utils/tuning/libtuning/generators/__init__.py index 937aff30..f28b6149 100644 --- a/utils/tuning/libtuning/generators/__init__.py +++ b/utils/tuning/libtuning/generators/__init__.py @@ -3,3 +3,4 @@ # Copyright (C) 2022, Paul Elder from libtuning.generators.raspberrypi_output import RaspberryPiOutput +from libtuning.generators.yaml_output import YamlOutput diff --git a/utils/tuning/libtuning/generators/yaml_output.py b/utils/tuning/libtuning/generators/yaml_output.py new file mode 100644 index 00000000..2449a093 --- /dev/null +++ b/utils/tuning/libtuning/generators/yaml_output.py @@ -0,0 +1,123 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright 2022 Paul Elder +# +# yaml_output.py - Generate tuning file in YAML format + +from .generator import Generator + +from numbers import Number +from pathlib import Path + +import libtuning.utils as utils + + +class YamlOutput(Generator): + def __init__(self): + super().__init__() + + def _stringify_number_list(self, listt: list): + line_wrap = 80 + + line = '[ ' + ', '.join([str(x) for x in listt]) + ' ]' + if len(line) <= line_wrap: + return [line] + + out_lines = ['['] + line = ' ' + for x in listt: + x_str = str(x) + # If the first number is longer than line_wrap, it'll add an extra line + if len(line) + len(x_str) > line_wrap: + out_lines.append(line) + line = f' {x_str},' + continue + line += f' {x_str},' + out_lines.append(line) + out_lines.append(']') + + return out_lines + + # @return Array of lines, and boolean of if all elements were numbers + def _stringify_list(self, listt: list): + out_lines = [] + + all_numbers = set([isinstance(x, Number) for x in listt]).issubset({True}) + + if all_numbers: + return self._stringify_number_list(listt), True + + for value in listt: + if isinstance(value, Number): + out_lines.append(f'- {str(value)}') + elif isinstance(value, str): + out_lines.append(f'- "{value}"') + elif isinstance(value, list): + lines, all_numbers = self._stringify_list(value) + + if all_numbers: + out_lines.append( f'- {lines[0]}') + out_lines += [f' {line}' for line in lines[1:]] + else: + out_lines.append( f'-') + out_lines += [f' {line}' for line in lines] + elif isinstance(value, dict): + lines = self._stringify_dict(value) + out_lines.append( f'- {lines[0]}') + out_lines += [f' {line}' for line in lines[1:]] + + return out_lines, False + + def _stringify_dict(self, dictt: dict): + out_lines = [] + + for key in dictt: + value = dictt[key] + + if isinstance(value, Number): + out_lines.append(f'{key}: {str(value)}') + elif isinstance(value, str): + out_lines.append(f'{key}: "{value}"') + elif isinstance(value, list): + lines, all_numbers = self._stringify_list(value) + + if all_numbers: + out_lines.append( f'{key}: {lines[0]}') + out_lines += [f'{" " * (len(key) + 2)}{line}' for line in lines[1:]] + else: + out_lines.append( f'{key}:') + out_lines += [f' {line}' for line in lines] + elif isinstance(value, dict): + lines = self._stringify_dict(value) + out_lines.append( f'{key}:') + out_lines += [f' {line}' for line in lines] + + return out_lines + + def write(self, output_file: Path, output_dict: dict, output_order: list): + out_lines = [ + '%YAML 1.1', + '---', + 'version: 1', + # No need to condition this, as libtuning already guarantees that + # we have at least one module. Even if the module has no output, + # its prescence is sufficient. + 'algorithms:' + ] + + for module in output_order: + out_lines.append(f' - {module.out_name}:') + + if len(output_dict[module]) == 0: + continue + + if not isinstance(output_dict[module], dict): + utils.eprint(f'Error: Output of {module.type} is not a dictionary') + continue + + lines = self._stringify_dict(output_dict[module]) + out_lines += [f' {line}' for line in lines] + + with open(output_file, 'w', encoding='utf_8') as f: + for line in out_lines: + f.write(f'{line}\n') From patchwork Thu Nov 24 11:35:49 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 17877 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 554F6BDE6B for ; Thu, 24 Nov 2022 11:36:24 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 21C2F63322; Thu, 24 Nov 2022 12:36:24 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1669289784; bh=Fgf46HEmOv2at5EcVS9YyASZedI8PKg6nfBtederpYg=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=oPCmKmcPtwXGAg035VokkKruTzfvtpzRErRNsQ7w6uwTxoRyYj+EyfKXQpOYLEQRW MMLgOGBEhiDyT29RfkqFnMULSYmiggSPDgYvkA6Ve4mE4gQ4B6D3j/NsXnV1qlQ8Bu fiGU1lD3jBAGTiqTX7EzTbw7r6zFUwgJhPswMjzR/EBCz5bVHuOKLvYD012LM3yhfC U2ufYjB50ruNlv/Fx46Kv2qocP06179lEHBXedDTrr/VO3g20JSRHQgyJmFNQN3tax 9sU5JIO3xbWoDEVR32hKGpX1L48DWbghCGkzZLBnPclv0UhJfTvhZN7udvOXwBfTdT UDudkkO6lv54Q== 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 595886331A for ; Thu, 24 Nov 2022 12:36:22 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="VnF5HJQ1"; dkim-atps=neutral Received: from pyrite.tail37cf.ts.net (h175-177-042-159.catv02.itscom.jp [175.177.42.159]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id D2B0D7FA; Thu, 24 Nov 2022 12:36:20 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1669289782; bh=Fgf46HEmOv2at5EcVS9YyASZedI8PKg6nfBtederpYg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=VnF5HJQ1WnfPL3aTT08hOQAHBsTV32H7mAJtwmOe+oCLJtW9yKCUX+vBboV8On3ks 4KHYPK4BycRBc0lkcvSQaL0xW4becrcSPhOexZsIPe12nPzm78b4TPTvOOyEwRPgHt e2ekuC7/vh3bppzlXeSF+LACgU0rmn9zyr7pcFGc= To: libcamera-devel@lists.libcamera.org Date: Thu, 24 Nov 2022 20:35:49 +0900 Message-Id: <20221124113550.2182342-12-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20221124113550.2182342-1-paul.elder@ideasonboard.com> References: <20221124113550.2182342-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 11/12] utils: tuning: Add alsc-only libtuning raspberrypi tuning script 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: , X-Patchwork-Original-From: Paul Elder via libcamera-devel From: Paul Elder Reply-To: Paul Elder Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a tuning script for raspberrypi for alsc only, that uses libtuning. Since there will also be a tuning script for raspberrypi that has more modules, put the libtuning alsc module definition in a separate file so that it can be reused later. Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart --- Changes in v3: - add __main__ guard - add file description - fix style Changes in v2: - fix python errors - fix style - add SPDX and copyright - s/average_functions/average/ - update script to work with new raspberrypi alsc module --- utils/tuning/raspberrypi/__init__.py | 0 utils/tuning/raspberrypi/alsc.py | 19 +++++++++++++++++++ utils/tuning/raspberrypi_alsc_only.py | 23 +++++++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 utils/tuning/raspberrypi/__init__.py create mode 100644 utils/tuning/raspberrypi/alsc.py create mode 100755 utils/tuning/raspberrypi_alsc_only.py diff --git a/utils/tuning/raspberrypi/__init__.py b/utils/tuning/raspberrypi/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/utils/tuning/raspberrypi/alsc.py b/utils/tuning/raspberrypi/alsc.py new file mode 100644 index 00000000..024eb5a3 --- /dev/null +++ b/utils/tuning/raspberrypi/alsc.py @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2022, Paul Elder +# +# alsc.py - ALSC module instance for Raspberry Pi tuning scripts + +import libtuning as lt +from libtuning.modules.lsc import ALSCRaspberryPi + +ALSC = \ + ALSCRaspberryPi(do_color=lt.Param('do_alsc_colour', lt.Param.Mode.Optional, True), + luminance_strength=lt.Param('luminance_strength', lt.Param.Mode.Optional, 0.5), + debug=[lt.Debug.Plot], + sector_shape=(16, 12), + sector_x_gradient=lt.gradient.Linear(lt.Remainder.DistributeFront), + sector_y_gradient=lt.gradient.Linear(lt.Remainder.DistributeFront), + sector_average_function=lt.average.Mean(), + smoothing_function=lt.smoothing.MedianBlur(3), + ) diff --git a/utils/tuning/raspberrypi_alsc_only.py b/utils/tuning/raspberrypi_alsc_only.py new file mode 100755 index 00000000..af04e6a8 --- /dev/null +++ b/utils/tuning/raspberrypi_alsc_only.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2022, Paul Elder +# +# raspberrypi_alsc_only.py - Tuning script for raspberrypi, ALSC only + +import sys + +import libtuning as lt +from libtuning.parsers import RaspberryPiParser +from libtuning.generators import RaspberryPiOutput + +from raspberrypi.alsc import ALSC + +tuner = lt.Tuner('Raspberry Pi (ALSC only)') +tuner.add(ALSC) +tuner.set_input_parser(RaspberryPiParser()) +tuner.set_output_formatter(RaspberryPiOutput()) +tuner.set_output_order([ALSC]) + +if __name__ == '__main__': + sys.exit(tuner.run(sys.argv)) From patchwork Thu Nov 24 11:35:50 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 17878 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 F29F0BDE6B for ; Thu, 24 Nov 2022 11:36:25 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id AD4ED63322; Thu, 24 Nov 2022 12:36:25 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1669289785; bh=sX8mqhqZGt5ApFAlfUHXTapjnnHZN8j4u59BaOF0gqc=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=4fYApYPWhZYfXkUUStRWDsnECQUMVtzTCLKRVi6CWdG7VvqEE9JuUsnoScccwvC5g osot28hCWuyRQk43FVZ8DepXSfnyoxdNmJbCSedDd/6qE/FyR4WxCOK0d4GnA88QlR Xqy/XhWL5RkwHTftutUXMGJBtvTniMfDy1jKcEq/hw9pmi5J96HYQGHYHLsoRqNbGM Sdus54vMrtyxI9UOAzORKCNfJ5ZqVQpuyjcKl9fpuEA5/HCNPma5m7+1X1kEclx7bk Eqs6i+xj0vymTmnGe4PcIilE/ZNia6tyhuqowQWbq2aVyFS6mS8BoD8tvTTco+7rhQ Y7Ety1Ncy+jvg== 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 378BA63325 for ; Thu, 24 Nov 2022 12:36:24 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="WZVMuhtt"; dkim-atps=neutral Received: from pyrite.tail37cf.ts.net (h175-177-042-159.catv02.itscom.jp [175.177.42.159]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id B1AF4496; Thu, 24 Nov 2022 12:36:22 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1669289784; bh=sX8mqhqZGt5ApFAlfUHXTapjnnHZN8j4u59BaOF0gqc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=WZVMuhttapETcOjeOOg1Z5CZY+UzjB9Pij6ad7WrPbSnQRmh28FHPN/ZXWxb/ANWP /UCsZAXFd4mRKPNgiKQJD7qg5khJpTQO3Qz6wbde8jE7nrunnK5HzQ9+d37Q7K29b8 GYVgdB59lJRBWSwUIoYU4vNsXFFld5n71ohcaSbo= To: libcamera-devel@lists.libcamera.org Date: Thu, 24 Nov 2022 20:35:50 +0900 Message-Id: <20221124113550.2182342-13-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20221124113550.2182342-1-paul.elder@ideasonboard.com> References: <20221124113550.2182342-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 12/12] utils: tuning: Add tuning script for rkisp1 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: , X-Patchwork-Original-From: Paul Elder via libcamera-devel From: Paul Elder Reply-To: Paul Elder Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a tuning script for rkisp1 that uses libtuning. So far it only supports LSC. Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart --- Changes in v4: - s/ALSC/LSC/ - Remove the debug parameter from the tuning script, as it will be moved to cli argument Changes in v3: - cosmetic changes / fix style - add __main__ guard - rename ALSC to LSC Changes in v2: - add SPDX and copyright - s/average_functions/average/ - update the script to work with the new rkisp1 alsc module v1: This won't run as we're missing the necessary parser and generator for yaml, parabolic gradient support, and multiple green support in the LSC module, hence the DNI. As soon as those are added though, this *should* work. --- utils/tuning/rkisp1.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100755 utils/tuning/rkisp1.py diff --git a/utils/tuning/rkisp1.py b/utils/tuning/rkisp1.py new file mode 100755 index 00000000..1cea6ddb --- /dev/null +++ b/utils/tuning/rkisp1.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2022, Paul Elder +# +# rkisp1.py - Tuning script for rkisp1 + +import sys + +import libtuning as lt +from libtuning.parsers import YamlParser +from libtuning.generators import YamlOutput +from libtuning.modules.lsc import LSCRkISP1 + +tuner = lt.Tuner('RkISP1') +tuner.add(LSCRkISP1( + 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=(17, 17), + + 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([LSCRkISP1]) + +if __name__ == '__main__': + sys.exit(tuner.run(sys.argv))