From patchwork Thu Nov 10 17:31: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: 17773 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 D921FBD16B for ; Thu, 10 Nov 2022 17:32:21 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3074A63086; Thu, 10 Nov 2022 18:32:21 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1668101541; bh=cDeERBi0lVPKemJBXXs6ncnPI/AH+uP5QuGku0ZDk+c=; 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=0wjm8iBYVvoBgedZVi0YtjGRnm4VSNotkV3/QryqOexMm/uqVWHV3SpKKfN0V6+du POz2V8M5gbugLyf+mlSG2FWHXrtvFW7dfzjLGJq4Wmth8/ogGG5bqCS3ADPDvURT/X XMnxwFfwY1naqZJX2i3F/7zVAn5At/pBLxQnDdaF8nqcwJk3G1HFrFZQTQgqGA2IeP +VGrWhoNAs+uVME0VMdfmjMidFz5d9undLBlAnDcUDrw77VqLni/CSQXU9cNGu9LxX mqPDx0o02DuQkGQP68ky5/1c24RqL/vC5in0VJpnSCU9V77f1tzYPGuKa1gD3NGMib bmWETIcDBIoHQ== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 3C66463075 for ; Thu, 10 Nov 2022 18:32:20 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="FuPrsHOF"; 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 C792B32A; Thu, 10 Nov 2022 18:32:18 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1668101540; bh=cDeERBi0lVPKemJBXXs6ncnPI/AH+uP5QuGku0ZDk+c=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=FuPrsHOFGL3Svq6f5VWflILEIjmbbWdEMROtYdIuCRIWlmftC0Ja43d+rIkAjRwc9 WXaMj8Ou4q/a5+YoIdTVIF1VGLUVlXBgscWW4tO5RrDxC25cs0NRmeSsAStQdOBDNJ p+JooCiDdQ1ZLXAfnOqi8axErEZvqnAEgt3JfkTk= To: libcamera-devel@lists.libcamera.org Date: Fri, 11 Nov 2022 02:31:44 +0900 Message-Id: <20221110173154.488445-3-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20221110173154.488445-1-paul.elder@ideasonboard.com> References: <20221110173154.488445-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 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 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..64a96369 --- /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 10 17:31: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: 17774 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 E3CBEBD16B for ; Thu, 10 Nov 2022 17:32:23 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A7AFC6308D; Thu, 10 Nov 2022 18:32:23 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1668101543; bh=Gzh1OxX/X31k/RYZusu/64//6peFJxOggB+bstxYUfo=; 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=gSziz5QMhEEMPm3Y/ci6YVsFBS1vFGTNKFR+IuDQ01T8iwdelvJeKgpIV192dhV1L cNB5m9lpjoKLtNFBy/lTktB37Kz0xCcxe2u0HskMkXSrCugau+P6ElDpoy79HPHf9x 7py6En20UM2R5UJQdp4B9d7LMQeNhgCrYrWltuRsfBNP3abGmrgvILfQG8G66/JPky C1ZXcycqSCDvDpxmHVnL5HXrZd88qb0MKbr9qqUScyNEVnpkrfHkPZFMDH3V+Z/glk ACJYnsRlwtgMjlOHnir8TozDIBib+Di3U1J6TGZX0GFovsTp0oPsKOL2jjwsE9KAxw ypT2VAVjnvAoQ== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0A6F163085 for ; Thu, 10 Nov 2022 18:32:22 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="V3IWWGv6"; 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 A37D032A; Thu, 10 Nov 2022 18:32:20 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1668101541; bh=Gzh1OxX/X31k/RYZusu/64//6peFJxOggB+bstxYUfo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=V3IWWGv6ZQPlsaNXozN+SXStdvSIcVFgUMY6bnmZ1wRlD/6v/8fw2G9aAeOKUpnkq 0gG9eGfKfckrKl6qlo1SGvp2D9HDsSwnwUHpqspjyFh1Mpfse6uo3/rnACPD9aCKzo 6BvrNS9McNvmkvfjxYfv1qeWc75YgGrHPo/zV83k= To: libcamera-devel@lists.libcamera.org Date: Fri, 11 Nov 2022 02:31:45 +0900 Message-Id: <20221110173154.488445-4-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20221110173154.488445-1-paul.elder@ideasonboard.com> References: <20221110173154.488445-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 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 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 | 0 .../tuning/libtuning/generators/generator.py | 15 +++++++++ utils/tuning/libtuning/modules/__init__.py | 0 utils/tuning/libtuning/modules/module.py | 33 +++++++++++++++++++ utils/tuning/libtuning/parsers/__init__.py | 0 utils/tuning/libtuning/parsers/parser.py | 21 ++++++++++++ 6 files changed, 69 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..e69de29b 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..cb213154 --- /dev/null +++ b/utils/tuning/libtuning/modules/module.py @@ -0,0 +1,33 @@ +# 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 args argparse arguments + # @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, args, 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 10 17:31: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: 17775 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 4EF33BD16B for ; Thu, 10 Nov 2022 17:32:25 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 12A5C63092; Thu, 10 Nov 2022 18:32:25 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1668101545; 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=IWGbo4PI90/VUmAD/tWqPs8u9PkJGWG3lBbsLOaplqfU1Rr0jPFXugHmds1dgvw33 r4gk+CVJsMKy7FTj3Jl0BOAZcpT+8OT8yOT/E5ZxY+XKvHvgTpvC6Y7+8pbXN0FwgV t9MXhXbJXhn6hslxbZJCu8GqAlieuaf9qZpwy8E3Sw7kKf9WRB0SCrjcdqMXqBxWJK Vjz5oPc5xkJZdnLNfjHKWNKNj07iyVMnk3h3t4GJej/knnx5X5N7CdXmmOLpP+j2iQ n3ZrSuCJBXXOGhILYBA2VAZPCTwjDLPBon0locAmy5gPaQw8fat8+/KsoLq8Nn5jzq krZYxZFsoq/EQ== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 3F58D63085 for ; Thu, 10 Nov 2022 18:32:24 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="oMEhoPib"; 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 7B5884D4; Thu, 10 Nov 2022 18:32:22 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1668101544; bh=3cdWwHM8Ia0AWP/tCkVhKltnyHW7TVzU9UusVKHhod0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=oMEhoPibxoYOx/Yk2gA1MXqBoWneDxg/ioWHg9aRtvnzYwbufElg/2Yn8H9MDVsIf 9iv6HZQYM4+LuPGUXZlThX+4qrrmeTJn6fDrzh5NopSncisAGNyYcLiX+CgpYh2OId DH+OBgOFVzXsKy2UhIocrTx0iIXRP1s0K1V+B0Vo= To: libcamera-devel@lists.libcamera.org Date: Fri, 11 Nov 2022 02:31:46 +0900 Message-Id: <20221110173154.488445-5-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20221110173154.488445-1-paul.elder@ideasonboard.com> References: <20221110173154.488445-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 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 10 17:31: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: 17776 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 BB20EBD16B for ; Thu, 10 Nov 2022 17:32:28 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7875963086; Thu, 10 Nov 2022 18:32:28 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1668101548; bh=XOZ4FzxWDtAZopF2YiTKMZciZ6uy9jNTKtKrc52gKeg=; 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=fyAUOx1jqU2WheYJPp23oFcquCp41JhVFzt1UnI8TDU8+oeeEkMGrrqHUCfoVIMyg Ob93C0Y8BHDfD4VzVixoBQcLB8TAUIj5DSyTh5XzMJZwT1LOFK7TKB+pMFFaK9iqpk XWgz6/zoVPfcpTBycRZwijOw2PWoGc08lXBmP0PImzsuBL1tb724icbRAxenHMQQet W5ZWDnm31UP+e4tdxy3Eij5el6V+oBFGCvlmaTuqZz+KcZTj/Ta+EJdtF343tqJ/vU /ZvvU/o7ur9F8lumLjs+TAad5MPzZf6y8C5RnBwr4PYbDbmKgivZUjJ4klLCa4EDt+ 0s7WOTj8geLrA== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 7B43363091 for ; Thu, 10 Nov 2022 18:32:26 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="BVnQvMgs"; 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 953BC499; Thu, 10 Nov 2022 18:32:24 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1668101546; bh=XOZ4FzxWDtAZopF2YiTKMZciZ6uy9jNTKtKrc52gKeg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=BVnQvMgsed9bKBSD3wXaxFKalJ38KuzOD1q440npd/1Zl8ySf+XrVqVL1RsgNsY8r /9dqvMQ2oXlir8WfFtqVF8Dd0xpuZ+jHzSvpe8RF9QGCpCYChO4Y8SabmTAW7DBgUD NL6ohbDvQLnBfOhvfwayRpsn/jG+ThjoM3c+wG/k= To: libcamera-devel@lists.libcamera.org Date: Fri, 11 Nov 2022 02:31:47 +0900 Message-Id: <20221110173154.488445-6-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20221110173154.488445-1-paul.elder@ideasonboard.com> References: <20221110173154.488445-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 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 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 | 250 ++++++++++++++++++ 2 files changed, 251 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..7fd346cc --- /dev/null +++ b/utils/tuning/libtuning/modules/lsc/raspberrypi.py @@ -0,0 +1,250 @@ +# 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 being unused) + + # \todo Handle the second green table + + # Average all values for luminance shading and return one table for all temperatures + lum_lut = list(utils.round_with_sigfigs(np.mean(list_cg, axis=0), self.output_range)) + + 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 = utils.round_with_sigfigs(np.mean(list_cr[indices], axis=0), + self.output_range) + t_b = utils.round_with_sigfigs(np.mean(list_cb[indices], axis=0), + self.output_range) + + 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, args, 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 10 17:31: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: 17777 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 19A3EBD16B for ; Thu, 10 Nov 2022 17:32:31 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id CE3C56308B; Thu, 10 Nov 2022 18:32:30 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1668101550; bh=BjCaB0QldZmxZ0Fh5H/UuRQuK71C907T+uet7dBiI9Q=; 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=n68QArsVOqV99NNvh6Q2LqugMwulTp5A52qctGITWRY8wawrmDINUH5jopda2aBrN bcOUNZuIIeb4+aM5YHJdoD4+8F8PARgSYj/9QMUZWbelxTbnci/Yxa4EKiGpaIeCBb puaDijvN1OTqMc08IkG2R00gvBjZ1rAx2lM/n5oPKeBK+xEJNXirtuG/oxtECb2+A8 erPBrz7WVD1P3+lfdTP3XJ5PVv16A2GjPCtk679XYwp37vvDSuQ/QLcuhi/ziBC3jz lIm8KYFlp2klGcXnplFYjkLyMsAIEw2OilVcBtNXR2zOQGft9RisEpzjsEOQ+LTXu7 FTeYTNpOG673A== 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 933C463095 for ; Thu, 10 Nov 2022 18:32:28 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Jxl3adbd"; 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 DE0DD32A; Thu, 10 Nov 2022 18:32:26 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1668101548; bh=BjCaB0QldZmxZ0Fh5H/UuRQuK71C907T+uet7dBiI9Q=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Jxl3adbd8NZ9ojOATATL22tGSrpjzQqDcmd5ErQbVOjVPlkXbJ26CB4dPhhLzvzRO EHyo7ioU1DMHC/e7Rp9m9gAXbZyJSJ66b7ex0cIe3A6WsRz1kDBJuOBUpGPmIHUrTY MXKODKh96TbS9cjUxFh4pW/PIcET0sH56cx6Tpso= To: libcamera-devel@lists.libcamera.org Date: Fri, 11 Nov 2022 02:31:48 +0900 Message-Id: <20221110173154.488445-7-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20221110173154.488445-1-paul.elder@ideasonboard.com> References: <20221110173154.488445-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 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 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 | 114 ++++++++++++++++++ 2 files changed, 115 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..150d6001 --- /dev/null +++ b/utils/tuning/libtuning/modules/lsc/rkisp1.py @@ -0,0 +1,114 @@ +# 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 + output_map_domain = (1, 3.999) + output_map_range = (1024, 4095) + + # 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(output_map_domain, output_map_range, 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, args, 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 10 17:31: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: 17778 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 7D442BD16B for ; Thu, 10 Nov 2022 17:32:32 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3860663088; Thu, 10 Nov 2022 18:32:32 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1668101552; 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=AKHp+QePjsN95PO/yJjeE2pDfaKSBaU9tqEZOhZETZlbFdAbdnojNxOooA/F67QNG u4XJ/W+FjgyynRkugzaqZ0yTpWBiELJRi8Z/I3OOS4E4wAqWOHWi39h4KX3VgsznpX jWNYXsQPEk/4Gz2qjqhLf35soT3trIQxrvsMr2sw3Vi3mYpSMV73zjL/io6+9FMxEO UFEc/5q8b1GBA87Jr6GCspST8sOzf8JNdgQNRjhtTHw7WlvEi/vqSWn6oF1VNQxTE6 c9JIaJbKrjxAepvuXaY7JjQKDVrOPAfbA5wUUsgxe5xPEdjGNgocnJoM5tOQpuGoUG dRiM59sFGTjFQ== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id A51D663085 for ; Thu, 10 Nov 2022 18:32:30 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="rxJXiUlO"; 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 E9E724D4; Thu, 10 Nov 2022 18:32:28 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1668101550; bh=9o/V3AcCFQQCjFd7DyXHh3v+yATuP0jJdvr8p2re2wE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=rxJXiUlOHqt4SkH70cZCdthxOkYqVAaVF0TNbxneVadvVJaBs1fMa2JS1hL4+Qqrn XvUg2EU3xcz9BlnC/JPfmjYeoSjCPx/qmq0v14R0XvZ2y4RtGrOI61iLC14Q6sGhv3 RrHLR0+LRSy9RiulGWmCVbhxKx/LA9AMd9677vnU= To: libcamera-devel@lists.libcamera.org Date: Fri, 11 Nov 2022 02:31:49 +0900 Message-Id: <20221110173154.488445-8-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20221110173154.488445-1-paul.elder@ideasonboard.com> References: <20221110173154.488445-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 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 10 17:31: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: 17779 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 E3851BD16B for ; Thu, 10 Nov 2022 17:32:34 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9B77E63098; Thu, 10 Nov 2022 18:32:34 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1668101554; bh=CuU1K1m8mrxqBjkaDa8ED9wVDqe4niKSyF1qiHslvto=; 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=Q7As18XXhU/3VqOWRP4TwN5KsqCSt7C3zZZ9YbK8SzQ0pO7PbcuuSsFLPvJbluY64 kRbQppJJTVmorEdDK6xvBEUIgKXJ53UiD0vzFYDCn6r0asLGo0qSQ9uudL3A5UWBdx A6EUdtcUrUmm/G0r12sMdaDgqE8FC78wM3nTXl/9lep1DUElIbBqxA8uBSg7Lv6Bwx jbLN8KFGKUG2mhfxkE/O0Kn2E7CWnAPAMFLh6e5RzroLCbonbhByEUlfwKX8Wt/2wy sDCTm68M7s2K6hQXd2WVzoZCIUuC/bPsNf/IuZRVih494Ha9temtQZMQm1A1BkM++P JOxDL4zbgEizg== 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 C8DA66307B for ; Thu, 10 Nov 2022 18:32:32 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="PONOyjPv"; 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 1AAA132A; Thu, 10 Nov 2022 18:32:30 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1668101552; bh=CuU1K1m8mrxqBjkaDa8ED9wVDqe4niKSyF1qiHslvto=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=PONOyjPvW+hNVxcAR1jKXfiCSCceYAMAzSm/hZfLAW5kyfKGdQeMByDNPZT7JE26x 9tS3+dRp2NA8v+IQ5iNcb+l4c7quGwsuIJ4JBF/+TUU0cdLiMT/rRVp0ES6oW4VRAp FLbx6O38sJEBRdzK1WiwyICk3gXY1/IiObxgweKY= To: libcamera-devel@lists.libcamera.org Date: Fri, 11 Nov 2022 02:31:50 +0900 Message-Id: <20221110173154.488445-9-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20221110173154.488445-1-paul.elder@ideasonboard.com> References: <20221110173154.488445-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 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 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 | 5 + .../generators/raspberrypi_output.py | 114 ++++++++++++++++++ 2 files changed, 119 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 e69de29b..937aff30 100644 --- a/utils/tuning/libtuning/generators/__init__.py +++ b/utils/tuning/libtuning/generators/__init__.py @@ -0,0 +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 10 17:31:51 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 17780 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 6CC0CBD16B for ; Thu, 10 Nov 2022 17:32:36 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2D2A56307B; Thu, 10 Nov 2022 18:32:36 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1668101556; 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=dQJ3/UDR5BFa3k1sZgxrvWQYxDP7t0R1hh1eYfwnaynE+a+MweoXNEW42tOueRW7c vJ7liFvJ97hWZXLeACHFiNNExgRgOQBQcKlNayA9e/oOfxKCi83qdl1XpdI+7R0OwO AZIq8uLgY5ToPPKhZl+uYrKlqdXlmaE3Wru1mXN2fcgIovZ2rPA0HEsBrW7qf0wASu PCHgVh+CZdf96iNpSNeL5rzi8yfjitwpfigByPCcPBH5RRyGrS3+BzlmJvgVZSZoAb VbYR/NtCpQr3mrYkbNJ5DeYX6aNpgK4OrDUIT/oBPmWiGu2arFauSP1jdUeYNEkwB7 HpwiitfbhnjKg== 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 DB1A36309A for ; Thu, 10 Nov 2022 18:32:34 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="QdphwgmC"; 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 3ECBC499; Thu, 10 Nov 2022 18:32:32 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1668101554; bh=G750lojvZ6bd8ltrUOeCxvAIGwzQyDxBta4MuReVT6w=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=QdphwgmCUu2IgGqaAnVtP9MIkwqKuV/EWSZfet95PtwIfbI9ML1u63KYrUKlTqvI3 K16gcfHNsPnCnPvP3NgZ7kKfKtB5S9LV40xf1Kn8mMnZB6O0kBiUTa4LoUj3ASckz+ uxuXK3SW/CW1vJXI9pzrLbrwNYjRMBGAwV4+y2uc= To: libcamera-devel@lists.libcamera.org Date: Fri, 11 Nov 2022 02:31:51 +0900 Message-Id: <20221110173154.488445-10-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20221110173154.488445-1-paul.elder@ideasonboard.com> References: <20221110173154.488445-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 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 10 17:31:52 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 17781 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 EBEFCBD16B for ; Thu, 10 Nov 2022 17:32:38 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id AAE8763085; Thu, 10 Nov 2022 18:32:38 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1668101558; bh=AqJwKyQZM0aJZ6Jh7VOQFZO9HCXxBT1jbI9j/OH8T+Q=; 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=DwPYcfnD8jbctl0Ud3ZEW0bnmeszgc1rBmz7t0MF487cy8z37iDLFLngx3sQucPsf x47JT0f+i24l5ibHVX0h/yoD6uroGbAPMBtJ7EiIpSOjMJ+TCTlFg5jsqiNw+6tYyv Q/GxHeSKYEHu3bxT5BxIN8zD8DOGxoIDkkbs3QjAKOaNqPv5WxjTD4dXlu5dZ8pVqt U3o/jpTFDCc+GuZBhieCYx8PcyNvaDETwBU7RMbMQVSMPADYVT0uSdwqxir0MHpmzx mmdnhPdeCWXoAzVMfrVt3lJwH5r3KXu0JufiYqpIZszOXPXRtHSP2ds3VfdrFluJ6R qhP4vInYUohGw== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id B9B8663085 for ; Thu, 10 Nov 2022 18:32:36 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Yy9Id8K9"; 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 4A7F1499; Thu, 10 Nov 2022 18:32:35 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1668101556; bh=AqJwKyQZM0aJZ6Jh7VOQFZO9HCXxBT1jbI9j/OH8T+Q=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Yy9Id8K9zHQXv9i5Jk9pYcmpCNcdHvDSGKrI/6Tbvbiq08sMJv2XAhyba3GL/Zx9w KfEgHp6cboQ57N6m+UGt3OPoCNYXvOvYSrcWS5cN2W2pGa3X1pcGK34ojg/aDRjbSd W/FfW0Nw/TbZoYUTJb7DQnzBgATdTgqMVhoo8lqo= To: libcamera-devel@lists.libcamera.org Date: Fri, 11 Nov 2022 02:31:52 +0900 Message-Id: <20221110173154.488445-11-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20221110173154.488445-1-paul.elder@ideasonboard.com> References: <20221110173154.488445-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 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 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..6398fc59 --- /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') as f: + for line in out_lines: + f.write(f'{line}\n') From patchwork Thu Nov 10 17:31:53 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 17782 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 52D17BD16B for ; Thu, 10 Nov 2022 17:32:41 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 17AFB6307B; Thu, 10 Nov 2022 18:32:41 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1668101561; 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=CFpD0xC7szhQvtjrNDbIcbptFg9+oEYNoNR88JQ2VhKRCXtBTQ4FCY2rFMkjpE59/ AwEz1ewRyQPBk5m7jNHzT2xB36RLZbWoLnQJ3KIHAcvZkTeZ23IHRWdD979Au7WDU/ DzNMvNBMErVIy1HrH1YFUrtfz45UofwmqvVzoyE4zxTRROB1x8VK/dCnDNEBd/K4lp TXHRsg2o0mKyeEmPETFizofr+gKI0OlRSXh9EBnol6MFsCpLzKXmssBAyLEOVk/eVB g6oFCprGwBitf15JK8FCnd40K0X8+7KCNGtAeYgdqhdr+pd8pV9kBn9Z1KBdHgc/jK MPxi+8J87+TLQ== 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 C210863098 for ; Thu, 10 Nov 2022 18:32:38 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="P8GrLISA"; 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 1644F499; Thu, 10 Nov 2022 18:32:36 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1668101558; bh=Fgf46HEmOv2at5EcVS9YyASZedI8PKg6nfBtederpYg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=P8GrLISAuBso/Ci9sWKcJesGPkdnHFyfblILrLyfqVC7AT5V0IoF6Sg9UOB03P0nl IAVh+RGfgV4vrIM/9Vk12b9nolpBTFHWDS4zOfFUlVe5Ag61mj3vNDZYBH/iI2DKc1 v3JE1b+SMs8gj32QjW2d2Wywklh54ME1997zXjeI= To: libcamera-devel@lists.libcamera.org Date: Fri, 11 Nov 2022 02:31:53 +0900 Message-Id: <20221110173154.488445-12-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20221110173154.488445-1-paul.elder@ideasonboard.com> References: <20221110173154.488445-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 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 10 17:31:54 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 17783 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 BF77DC3285 for ; Thu, 10 Nov 2022 17:32:41 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8479963083; Thu, 10 Nov 2022 18:32:41 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1668101561; bh=9ZGQ9hgbXREqVUXDt8Mjsmo3EE5ObzpWFtrLYNUwviM=; 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=B+gFWLAykFe1zu6aR07BGrs+ABTd4QTyidq2C+pAKh/kOTyAz2yjvQvGfmwhXDdt/ 34cEB/n4dD7aYXoH5DhJ7kr8e6UdcErMNf35+ubhoZH/kV6lIXmLN1Flub/QxZ/i9B n88SDMJC+h4Zquu3HUyCIotUT/vSv+ji3EF6A2uMJDng/RdH8tIUY/OREAPVylXbJg cTc6IE30mblxLGdvojOMgEdUHoayNEbsq1R8jl5zqgnx4YYLsb9Fvi4qzDXomqHRCb eYeJS7ERfCkc8W7wWrpBTOW4o/FKxh5d9kQyIuXaH2FeBd7gzZd+LA2ZPfGLWiAKgz dqu7nD3L6GY4Q== 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 8271063083 for ; Thu, 10 Nov 2022 18:32:40 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Ah/cE8nO"; 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 31789499; Thu, 10 Nov 2022 18:32:38 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1668101560; bh=9ZGQ9hgbXREqVUXDt8Mjsmo3EE5ObzpWFtrLYNUwviM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Ah/cE8nOMfllbaXDGUMRzw4EpvGJtXR8Ocug5TUWt1+TkH7PT1upmReLzNlshM5iZ rQak4TiuTnepbQeKmWbkFMT1Wo4zCLJczEIZelfmAxoaVFT49IVE15mWRETWHkwr4c 0oiBw8SXysWME30f2J83XnG5EBxmiAvGbuPNsjxw= To: libcamera-devel@lists.libcamera.org Date: Fri, 11 Nov 2022 02:31:54 +0900 Message-Id: <20221110173154.488445-13-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20221110173154.488445-1-paul.elder@ideasonboard.com> References: <20221110173154.488445-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 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 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 | 43 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 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..a8cb0c39 --- /dev/null +++ b/utils/tuning/rkisp1.py @@ -0,0 +1,43 @@ +#!/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( + # This can support other debug options (I can't think of any right + # now, but for future-proofing). + debug=[lt.Debug.Plot], + + # This is for the actual LSC tuning, and is part of the base ALSC + # module. rkisp1's table sector sizes (16x16 programmed as mirrored + # 8x8) are separate, and is hardcoded in its specific ALSC 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))