From patchwork Sat Oct 22 06:23:01 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 17653 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 059D0C0DA4 for ; Sat, 22 Oct 2022 06:23:29 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B571362ED0; Sat, 22 Oct 2022 08:23:28 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1666419808; bh=sjsrLUhf4WlqMjHRF76GfAgAS7z1irWu2Vo1Lwc5b0c=; 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=4Zs0X7MFECamDD10UpDBnKmzy0nvp8vQUCOaH8sAEpP0WIggPD5gjGn0BWAdoKaqi eoXYRVTboj1U/JI34k5YZYVmWdZk0H7/j0V1jwXeuo+zsHAqKX2i/hIiuln5auRvDB B1d1PvmDxXqsA1yO8HjiThBG3vxYY1aNOuD6AGKYpeoatVMa5SFTuQhYZCcaOb4x5n 7nRbTthqK+0TbYmTO4OmOcJ4n6atPznmAnvkGB/t+tWBLdynKx6Bsor1HeIx8whV7H BJ9bHFN9xwHIdI8dbYBVoAMnCEQAFzyo+eqHT98rl+eAiQn9MSTFegwMeiJGwRF2Nx Ij8Tg5l3XqeOQ== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id C2A6F62EC3 for ; Sat, 22 Oct 2022 08:23:24 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="hN1386eW"; dkim-atps=neutral Received: from pyrite.rasen.tech (h175-177-042-159.catv02.itscom.jp [175.177.42.159]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 67F25106; Sat, 22 Oct 2022 08:23:23 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1666419804; bh=sjsrLUhf4WlqMjHRF76GfAgAS7z1irWu2Vo1Lwc5b0c=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=hN1386eWkTea7lEWuBiSD9BaBIkzSBZMF7otHylqSXKTow1b2Gx2ArtRpiB16O2YY k3ifk6eglDu5awg6tmnX57ESfapS6WGmiBFf9z1FAmDt6ikBeS9h/dE8d/sQfgwrAm 3j1HA9j+ZY6fSk23qvNAiOgZscTo86k8R2Pyyhrs= To: libcamera-devel@lists.libcamera.org Date: Sat, 22 Oct 2022 15:23:01 +0900 Message-Id: <20221022062310.2545463-3-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20221022062310.2545463-1-paul.elder@ideasonboard.com> References: <20221022062310.2545463-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 02/11] utils: libtuning: modules: Add 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 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. Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart --- 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/alsc/__init__.py | 5 ++ utils/tuning/libtuning/modules/alsc/alsc.py | 78 +++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 utils/tuning/libtuning/modules/alsc/__init__.py create mode 100644 utils/tuning/libtuning/modules/alsc/alsc.py diff --git a/utils/tuning/libtuning/modules/alsc/__init__.py b/utils/tuning/libtuning/modules/alsc/__init__.py new file mode 100644 index 00000000..a8f28923 --- /dev/null +++ b/utils/tuning/libtuning/modules/alsc/__init__.py @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2022, Paul Elder + +from libtuning.modules.alsc.alsc import ALSC diff --git a/utils/tuning/libtuning/modules/alsc/alsc.py b/utils/tuning/libtuning/modules/alsc/alsc.py new file mode 100644 index 00000000..b708b28d --- /dev/null +++ b/utils/tuning/libtuning/modules/alsc/alsc.py @@ -0,0 +1,78 @@ +# 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 ALSC(Module): + type = 'alsc' + hr_name = 'ALSC (Base)' + out_name = 'GenericALSC' + + 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__() + + if (sector_x_gradient is lt.gradient.Linear and sector_x_remainder is None): + raise ValueError('sector_x_remainder must be specified if sector_x_gradient is Linear') + + if (sector_y_gradient is lt.gradient.Linear and sector_y_remainder is None): + raise ValueError('sector_y_remainder must be specified if sector_y_gradient is Linear') + + 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_alsc_images(self, images): + for image in images: + if image.alsc_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 Sat Oct 22 06:23:02 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 17654 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 3D2F4C327C for ; Sat, 22 Oct 2022 06:23:30 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A5D4F62ED1; Sat, 22 Oct 2022 08:23:29 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1666419809; bh=QwIIKLKs60P8RlVm7Uxw8cwAWj1D3bY4a7SjBVzL3yQ=; 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=Xa/QJBXl4sJdSKP87xcYyJOQ+D7kBcBFzCKJgknfAdLM0kZlBrIVb+CulPZ8RfFiD Joj7eEalGp6joNmQXIbsNuKnkdAMslh5wloYMl2SOkdpLelxMC7rrHzYzkvoTgYGGv SchE+KKQ4+eB8LLgECBs+wJ77npZz4b1x0HEBmZo+Af7stfdCOqNvNwcPVZeVfXrBA bBwRGcepQMYKgfghvXHqL4/wtDXqj7qBVjS+wds3IQI6cGSDZiikxnYln43xtcUGNX RHMcR8YC20KX+6Z+3jL+T20qHQ2jG4k07sGl3YZrmvByAyqCU0qWK0nBl9ib7vtgpk FBvOhArn7VikA== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6532B62EC5 for ; Sat, 22 Oct 2022 08:23:26 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="k10sPJsa"; dkim-atps=neutral Received: from pyrite.rasen.tech (h175-177-042-159.catv02.itscom.jp [175.177.42.159]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 286FE891; Sat, 22 Oct 2022 08:23:24 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1666419806; bh=QwIIKLKs60P8RlVm7Uxw8cwAWj1D3bY4a7SjBVzL3yQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=k10sPJsaQgZUMH1gj/Pg9jQ0NlbxQdgV58wk39K0024fhpV9Vm8cFyuGDbbI5mMiK 8YAGbPFkG9GjEHhsX4lNVIFy4QyF/iVVOwJvkXqr/EnIqCfhJpVOtNmoXUl55YREuO X/qPiWM5QORJw+95nSYrmX/fh3+pAuApCFQCjaXY= To: libcamera-devel@lists.libcamera.org Date: Sat, 22 Oct 2022 15:23:02 +0900 Message-Id: <20221022062310.2545463-4-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20221022062310.2545463-1-paul.elder@ideasonboard.com> References: <20221022062310.2545463-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 03/11] 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 --- New in v2 --- .../tuning/libtuning/modules/alsc/__init__.py | 1 + .../libtuning/modules/alsc/raspberrypi.py | 246 ++++++++++++++++++ 2 files changed, 247 insertions(+) create mode 100644 utils/tuning/libtuning/modules/alsc/raspberrypi.py diff --git a/utils/tuning/libtuning/modules/alsc/__init__.py b/utils/tuning/libtuning/modules/alsc/__init__.py index a8f28923..ef6300c2 100644 --- a/utils/tuning/libtuning/modules/alsc/__init__.py +++ b/utils/tuning/libtuning/modules/alsc/__init__.py @@ -3,3 +3,4 @@ # Copyright (C) 2022, Paul Elder from libtuning.modules.alsc.alsc import ALSC +from libtuning.modules.alsc.raspberrypi import ALSCRaspberryPi diff --git a/utils/tuning/libtuning/modules/alsc/raspberrypi.py b/utils/tuning/libtuning/modules/alsc/raspberrypi.py new file mode 100644 index 00000000..bea1a9a9 --- /dev/null +++ b/utils/tuning/libtuning/modules/alsc/raspberrypi.py @@ -0,0 +1,246 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (C) 2019, Raspberry Pi Ltd +# Copyright (C) 2022, Paul Elder + +from .alsc import ALSC + +import libtuning as lt +import libtuning.utils as utils + +from numbers import Number +import numpy as np + + +class ALSCRaspberryPi(ALSC): + hr_name = 'ALSC (Raspberry Pi)' + out_name = 'rpi.alsc' + # todo Do something with the compatible list + 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.is_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.is_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_alsc_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 Sat Oct 22 06:23:03 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 17655 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 E35F1C0DA4 for ; Sat, 22 Oct 2022 06:23:31 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9145D62ED6; Sat, 22 Oct 2022 08:23:31 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1666419811; bh=fm9gcyQT6tddQvpWlWmvET8AL/c3VhA4sd8JH59SfWg=; 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=2FEIKbgKu1opG0Xdt+sO+OlaXT7R+iyyQL2yzlB7EGSud+0EFCAfreZ6eRaXuqtXL nvPLvCXxSPopktdpILPmpch43fYEhKuSM/GOBC0K15+WCwQxmJvYeOf3jap5lMXceH yShmeN1j7IJfHF0FbKxkSjaunJZqbWmxV5F/TmPRzuond+ScVt/rqRE7AN0c/0xdjg 4KbkYzwK/vr2fzKVAovLhDNWiOhCNFSflXnEOx5jcuYpr0NPevWQRSkQzKezKXC3YG tjMZayrT86GlQC+aRMIS3hLjpyh0EgXGrXtj7dAfDQZvgXGsNlUxmePvNKnPf23Rpm vv7SgPolnwb8g== 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 2990B62EC8 for ; Sat, 22 Oct 2022 08:23:28 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="cfgjEjSu"; dkim-atps=neutral Received: from pyrite.rasen.tech (h175-177-042-159.catv02.itscom.jp [175.177.42.159]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id B8EC9891; Sat, 22 Oct 2022 08:23:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1666419807; bh=fm9gcyQT6tddQvpWlWmvET8AL/c3VhA4sd8JH59SfWg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=cfgjEjSuowzytPnHPecZZZrFahaFTARI41ne2/G6BvRP3gFgsmcxBcsjHnhaxxKjr yBB7GqKNlRWDpwqp9U2afI25QDYApIGA1MSAGWif0CSWOC1OI1r3qmIBHmgRM78e8F C+c0x7mYqgA12P+TXXaKp5djlTf4ZhpkFdVlcmj8= To: libcamera-devel@lists.libcamera.org Date: Sat, 22 Oct 2022 15:23:03 +0900 Message-Id: <20221022062310.2545463-5-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20221022062310.2545463-1-paul.elder@ideasonboard.com> References: <20221022062310.2545463-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 04/11] utils: libtuning: modules: alsc: Add rkisp1 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 RkISP1. Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart --- New in v2 --- .../tuning/libtuning/modules/alsc/__init__.py | 1 + utils/tuning/libtuning/modules/alsc/rkisp1.py | 112 ++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 utils/tuning/libtuning/modules/alsc/rkisp1.py diff --git a/utils/tuning/libtuning/modules/alsc/__init__.py b/utils/tuning/libtuning/modules/alsc/__init__.py index ef6300c2..932ce38e 100644 --- a/utils/tuning/libtuning/modules/alsc/__init__.py +++ b/utils/tuning/libtuning/modules/alsc/__init__.py @@ -4,3 +4,4 @@ from libtuning.modules.alsc.alsc import ALSC from libtuning.modules.alsc.raspberrypi import ALSCRaspberryPi +from libtuning.modules.alsc.rkisp1 import ALSCRkISP1 diff --git a/utils/tuning/libtuning/modules/alsc/rkisp1.py b/utils/tuning/libtuning/modules/alsc/rkisp1.py new file mode 100644 index 00000000..d8f253e6 --- /dev/null +++ b/utils/tuning/libtuning/modules/alsc/rkisp1.py @@ -0,0 +1,112 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (C) 2019, Raspberry Pi Ltd +# Copyright (C) 2022, Paul Elder + +from .alsc import ALSC + +import libtuning as lt +import libtuning.utils as utils + +from numbers import Number +import numpy as np + + +class ALSCRkISP1(ALSC): + hr_name = 'ALSC (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_alsc(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) + + # 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_alsc(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_alsc_images(images): + col, cr, cb, cgr, cgb = self._do_single_alsc(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_alsc(images) + + # todo Validate images from greyscale camera and force grescale mode + # todo Debug functionality + + return output From patchwork Sat Oct 22 06:23:04 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 17656 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 DC81DC327C for ; Sat, 22 Oct 2022 06:23:32 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9F1F462ED4; Sat, 22 Oct 2022 08:23:32 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1666419812; bh=oTz/amQ7ZjYtekRYlssX1ydgV9EwvTT8q6SSQzZqRh4=; 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=LeYrh0JY5jui2pJoHoLpdkDILi8dzO2T7He2GuVmSyoQWlG+s5DEs2on3CysGuwzY vJVOWMHP/zm3Eaaz7GbjUx1rNgUAYPHj/TcpSdIlAcVeyuaNvVv5hM9xuSBBJTWgGw mcicd/KER6OCN7UPcSClHS37CATQkxFRWUm0f/QlbR8PH24zj/hpuQC9SgvVe+ScIh bUUEfchFiI+BXt0Ks/4wM7e2JetS8015/Iz/JKe0z5kck7BV3U38I7ZMPg4L0v/Ixd WBY+IOP8afDUX+TcIJuDpSPneMbMKE3HRIUTQ1HFDDwISvoIazG1yPw41qRxCZAdI0 D+dNul3Q4Fs3Q== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0216E62ED3 for ; Sat, 22 Oct 2022 08:23:29 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="AgSsW9+E"; dkim-atps=neutral Received: from pyrite.rasen.tech (h175-177-042-159.catv02.itscom.jp [175.177.42.159]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 79900106; Sat, 22 Oct 2022 08:23:28 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1666419809; bh=oTz/amQ7ZjYtekRYlssX1ydgV9EwvTT8q6SSQzZqRh4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=AgSsW9+EmPJQCGLwgW54qUNlDIIMBPw1xnAXdWPJaqHwRJnsi4Ca4oZEYKc908UAr o9EVTxfJ+zCncDIHROUT5DtL0vSNkrEiN7d9lT8mmoJRtt7vsX75TtdOsOdDMxddY9 0IiW9MytUbpW1yXE5cI0MXdiezdq68lap+t6+kWI= To: libcamera-devel@lists.libcamera.org Date: Sat, 22 Oct 2022 15:23:04 +0900 Message-Id: <20221022062310.2545463-6-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20221022062310.2545463-1-paul.elder@ideasonboard.com> References: <20221022062310.2545463-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 05/11] 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 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 | 91 +++++++++++++++++++ 2 files changed, 96 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..196c5b36 --- /dev/null +++ b/utils/tuning/libtuning/parsers/raspberrypi_parser.py @@ -0,0 +1,91 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2022, Paul Elder + +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 _validMacbethOption(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, modules): + 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_typename(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._validMacbethOption(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 Sat Oct 22 06:23:05 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 17657 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 D7313C3285 for ; Sat, 22 Oct 2022 06:23:33 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 485EE62ED3; Sat, 22 Oct 2022 08:23:33 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1666419813; bh=3xIfSIABCUMi62Db0ja6vOFz0lsqnAZdPwKUMdqnYfE=; 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=nAJGRvRUgr4tJJ0TJKraiwC4CUs4GZ4sS8lZ1aG2Cys5CjVH20an/aL88rUFgORqn UN5I5UPlDuq487H4xz401kr02VPFKTJPemrpJQG8yEL8DZkUhljnU2/P/bVL+XwjCr 9a5BLQ/qUkE2fXk1torACEr9sXUszkQ8AcEAfMTurATMAgsrhm1JM7VCchJ3vaN0ip JnH0TAUCVBUysLH/Q7M0O57n+k0Bckfg2jvBFePmLbnKYF/cNnOx1bCQ40vL+as/it pQ35aSPXduzEci91MFeYTEyq4B31BDWGG5kQ/vt7IKW8KVEG/zbkJpIC7Z+BaEoHdl PC+D8bgSHAICA== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id C7FFF62ED8 for ; Sat, 22 Oct 2022 08:23:31 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="e0clPbUl"; dkim-atps=neutral Received: from pyrite.rasen.tech (h175-177-042-159.catv02.itscom.jp [175.177.42.159]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 5FCCA106; Sat, 22 Oct 2022 08:23:30 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1666419811; bh=3xIfSIABCUMi62Db0ja6vOFz0lsqnAZdPwKUMdqnYfE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=e0clPbUltD7JV91AXXOsYbx6gsDbC2zN8yWl6j2beMR8nbVEcVp7tEshTIH+Wmbh1 y0obWL/s1bwrv54GWkY9ht+gQqjwzDhPmAZCBaXWzgqqTuvwm4rZyt4X+tSdLbLVZF TniCiefzz58bLm0OVsiMUNNgacTkzobfAJkAwWsM= To: libcamera-devel@lists.libcamera.org Date: Sat, 22 Oct 2022 15:23:05 +0900 Message-Id: <20221022062310.2545463-7-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20221022062310.2545463-1-paul.elder@ideasonboard.com> References: <20221022062310.2545463-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 06/11] 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 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 | 115 ++++++++++++++++++ 2 files changed, 120 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..e06aeddf --- /dev/null +++ b/utils/tuning/libtuning/generators/raspberrypi_output.py @@ -0,0 +1,115 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright 2022 Raspberry Pi Ltd +# +# Script to pretty print a Raspberry Pi tuning config JSON structure in +# version 2.0 and later formats. +# (Copied from ctt_pretty_print_json.py) + +from .generator import Generator + +import json +from pathlib import Path +import textwrap + + +class RaspberryPiOutput(Generator): + def __init__(self): + super().__init__() + + 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(pretty_print(out_json)) + + +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) + + +def pretty_print(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) From patchwork Sat Oct 22 06:23:06 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 17658 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 B8813C0DA4 for ; Sat, 22 Oct 2022 06:23:35 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 465FB62EE0; Sat, 22 Oct 2022 08:23:35 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1666419815; bh=eUZ5iClDjXH4FfCv+fF4bKigaJ2ZOi/J+wjubQODn7k=; 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=i+5iEyTibKDWC8zvKrNrBGX74tHs5fCF9JTycOq9qHr369+dSJ7YMZzKm3Vomyis3 Tr56a5KmOPf4CK5DrtGWY1HTeLt6zcx0ZKEk6EBKgtDHvXluZfmKbiLVpaXnaqAjXv +huxfSh6PI90sWb2Q+w1hJSmem7UW+bM9s1w6EcHFJ9d+Ce8k5DH5Tfo62LBKq3TUE OcAIsikeITSNftWCB3ZKYgTwPxllqhvwgPU7UG9O/t0bh/OXa8L9hpVtomc4fjpiKL MdnrlnUr3Gfmc9+exWehn9E1ZVnDFpZt+Qb/+2MhzNMSV5X4yDRE3bTAvYlLeGnICK ypEP4+2L/I9cw== 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 A6D3362ECF for ; Sat, 22 Oct 2022 08:23:33 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Qrt91SA9"; dkim-atps=neutral Received: from pyrite.rasen.tech (h175-177-042-159.catv02.itscom.jp [175.177.42.159]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 2DE2A106; Sat, 22 Oct 2022 08:23:31 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1666419813; bh=eUZ5iClDjXH4FfCv+fF4bKigaJ2ZOi/J+wjubQODn7k=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Qrt91SA9SCAyTePXpIgmBneC/npMzF5c8t8/9Ycldk7HFnUOwWHpvo6HLMqwmVzWg kG+0sz9Skru053LoQTGNP5YzoVItSdS7Xc2SHOxDt1GqqI3R37s0pSQWzZLrYhrTLa hlHgpFX46oAN/sGPLrF+ozxZGwDQX4/GfivQ/2b4= To: libcamera-devel@lists.libcamera.org Date: Sat, 22 Oct 2022 15:23:06 +0900 Message-Id: <20221022062310.2545463-8-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20221022062310.2545463-1-paul.elder@ideasonboard.com> References: <20221022062310.2545463-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 07/11] [WIP] 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. Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart --- Obviously this doesn't work. Technically it's fine because the only user of the yaml parser at the moment works fine with no input configuration file. --- utils/tuning/libtuning/parsers/__init__.py | 1 + utils/tuning/libtuning/parsers/yaml_parser.py | 15 +++++++++++++++ 2 files changed, 16 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..e2ce6e20 --- /dev/null +++ b/utils/tuning/libtuning/parsers/yaml_parser.py @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2022, Paul Elder + +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) + def _parse(self, config_file, modules): + return {}, [] From patchwork Sat Oct 22 06:23:07 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 17659 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 11AF1C327C for ; Sat, 22 Oct 2022 06:23:38 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B2EFD62EE4; Sat, 22 Oct 2022 08:23:37 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1666419817; bh=effseQameShPlL9W9zmOafgFX33GzrOmP6x/o1B2Slg=; 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=TcEFgAyg3VuGm8+VVRzsDkeLyUnRaEEBVoQ7FdhhjyKub5am72QhHIZmrGxCNFiTS +urNZ24PyyBOv3T13vEI3XSibo/ReRUxE5AO4/AaqDXCjXOf5bzTnj05eKKkGgptcQ lMMUkjN4VGiBVfAJbBmg1xvstLzjNtuomY+6wkMmGHlv436Kcr/dntds4IUUO/GwRH cLP2TjTy4/uR0Kv0LubMkEV4s35SbHDfUdOreowv6nb815FvRzo9CGap9z+7aszqnX z9Ic1BfTMZi5z1e1NR+RQzTucNOXTNY2XjQX7WBe7yIdPm2283tV6QIzDn1mKbuPtT URnOvuj1CYb5g== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 4356E62EDF for ; Sat, 22 Oct 2022 08:23:35 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="mEKbVzyI"; dkim-atps=neutral Received: from pyrite.rasen.tech (h175-177-042-159.catv02.itscom.jp [175.177.42.159]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id EF63BABC; Sat, 22 Oct 2022 08:23:33 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1666419815; bh=effseQameShPlL9W9zmOafgFX33GzrOmP6x/o1B2Slg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=mEKbVzyIOOGMCv2ZOy6elmVWSbyT6tFWIaALDE86q1L1pvqpawdMY9U+oArrAlFtg ui6MlAET5sOwfJbyHiXjo6HRH+G7A0pVVoJ+rlU+sj5szyOZlnKP23cIM0gyEX+ctm /S9PmegSqNI/UX1YY6sXl3h4ocnuYKaPa/O4hNNU= To: libcamera-devel@lists.libcamera.org Date: Sat, 22 Oct 2022 15:23:07 +0900 Message-Id: <20221022062310.2545463-9-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20221022062310.2545463-1-paul.elder@ideasonboard.com> References: <20221022062310.2545463-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 08/11] 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 --- New in 2 --- utils/tuning/libtuning/generators/__init__.py | 1 + .../libtuning/generators/yaml_output.py | 121 ++++++++++++++++++ 2 files changed, 122 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..b5e600f1 --- /dev/null +++ b/utils/tuning/libtuning/generators/yaml_output.py @@ -0,0 +1,121 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright 2022 Paul Elder + +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 Sat Oct 22 06:23:08 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 17660 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 041C5C0DA4 for ; Sat, 22 Oct 2022 06:23:40 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8529962EEA; Sat, 22 Oct 2022 08:23:40 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1666419820; bh=GP95adX2fmAVd7GTXaE0rVDCGm3n/tfALf55IdMMSj0=; 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=XrQ447qh8Wgus6ZwbgczVgAqfI6EiUOuGgDne8HcSUkVu11TPWDhg89jkCYdeYzH3 yjfmp0Hyxctq/+nLjpOwkdxzUkcEyr+yYf3JA7rVAL+hsx0LOQYHzI5xBQfSD+RGcE hw3hfg376ucwnT9mF04GgpCEabe4Y1+W/TRYIbUyfDxgYIjNttgUJzDdCdKbQraJ0y HMgepw/gAfMIIAssIhpb5h1bj5PBwMvk9qfGJUB4IH68Ja6xkgL3Lg4/alkVsT/Sfs MPIYRwEjP0+DO76o50SaNlIJDxhPqe/A59qcEmlAT2nYFaVlwaOZdkoCbINYi3lrnh x55AiaF7IAhBg== 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 ED5D462EDC for ; Sat, 22 Oct 2022 08:23:36 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ep13Yd1e"; dkim-atps=neutral Received: from pyrite.rasen.tech (h175-177-042-159.catv02.itscom.jp [175.177.42.159]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 95571106; Sat, 22 Oct 2022 08:23:35 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1666419816; bh=GP95adX2fmAVd7GTXaE0rVDCGm3n/tfALf55IdMMSj0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ep13Yd1eaLPjgnRUaLY9ptynvjSAz+vjUZ5/pVx0Iw4AKlxHEyer1nx7yAYQWyWm9 VMrMKezxZ+QdT2bo8S50Yd4V5O8RNw2qw+b4IBUL5h0qY5J4nU4sv2b7vRb05mjtjl QzVKJTr+qbXUUXXOIO0voFClTXnZAJrScVGrt65M= To: libcamera-devel@lists.libcamera.org Date: Sat, 22 Oct 2022 15:23:08 +0900 Message-Id: <20221022062310.2545463-10-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20221022062310.2545463-1-paul.elder@ideasonboard.com> References: <20221022062310.2545463-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 09/11] 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 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 | 17 +++++++++++++++++ utils/tuning/raspberrypi_alsc_only.py | 22 ++++++++++++++++++++++ 3 files changed, 39 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..71ab3995 --- /dev/null +++ b/utils/tuning/raspberrypi/alsc.py @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2022, Paul Elder + +import libtuning as lt +from libtuning.modules.alsc 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..3cd7b074 --- /dev/null +++ b/utils/tuning/raspberrypi_alsc_only.py @@ -0,0 +1,22 @@ +#!/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.Camera('Raspberry Pi (ALSC only)') +tuner.add(ALSC) +tuner.setInputType(RaspberryPiParser) +tuner.setOutputType(RaspberryPiOutput) +tuner.setOutputOrder([ALSC]) + +tuner.run(sys.argv) From patchwork Sat Oct 22 06:23:09 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 17661 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 35D71C327C for ; Sat, 22 Oct 2022 06:23:42 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A89C262EEC; Sat, 22 Oct 2022 08:23:41 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1666419821; bh=okeMoeWqj1EblqU83DqjMjkYekp8+PSq3UnXEZT98fo=; 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=uo0fpgJOcRRrU+Zk9868go8qVAixnrutDOK1B1o/Tno/cpkzI40RR77hhHm7At0CN lySX2e2heQ9E4YPrJVw8R9vlPtTQGBzjK/t1NYDFhXmyw9WcfC1h/KATWXBntqulR5 A9fvNGYCxGDdV6vIL7Q0zPHuzKqi5joVC58ooaJyo0NNv4s96SyF/pCmeZ/A+MR/Eg 9PsKGeIRFn8oRjKxi4p0U8FEEYNM2adP3bnHEncYtlanciPLQ5xKJQWUo3DEXeVyHO sRR9CCQG7k1fU1pp5hezskrycoyhuwRzH1+mcIhJ5ivI4OuqREakLI4tnaqBBhHyb9 LI6d0SIrJcCsA== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id A68FE62EC6 for ; Sat, 22 Oct 2022 08:23:38 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="JkwzhaG8"; dkim-atps=neutral Received: from pyrite.rasen.tech (h175-177-042-159.catv02.itscom.jp [175.177.42.159]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 55E7A106; Sat, 22 Oct 2022 08:23:37 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1666419818; bh=okeMoeWqj1EblqU83DqjMjkYekp8+PSq3UnXEZT98fo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=JkwzhaG8xaD5Yptpv8OnJ6YTPRzQdyAhQ4Pizwb1F7XIvZCg6CFy3LQmG+TTcXDyB h0U/34oYodnKmBPHYUIUDsQ85wJ6xP90u7/0bJCn7f0oLi8puGvSNm6OrENQQCZ29Y nvpNF15YWcGJSR4Y6mJOQHQ7iLR5mOO57301VJUE= To: libcamera-devel@lists.libcamera.org Date: Sat, 22 Oct 2022 15:23:09 +0900 Message-Id: <20221022062310.2545463-11-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20221022062310.2545463-1-paul.elder@ideasonboard.com> References: <20221022062310.2545463-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 10/11] [DNI] utils: tuning: Add full 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 that uses libtuning. This reproduces what ctt does (albeit with less logging for now), but with common components factored out into libtuning so that tuning scripts for other platforms can reuse them. Signed-off-by: Paul Elder --- Changes in v2: - add SPDX and copyright This is incomplete, hence the DNI. --- utils/tuning/raspberrypi.py | 44 +++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 utils/tuning/raspberrypi.py diff --git a/utils/tuning/raspberrypi.py b/utils/tuning/raspberrypi.py new file mode 100644 index 00000000..8e963743 --- /dev/null +++ b/utils/tuning/raspberrypi.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2022, Paul Elder +# +# raspberrypi.py - Tuning script for raspberrypi + +import sys + +import libtuning as lt +from libtuning.modules import AWB +from libtuning.parsers import RaspberryPiParser +from libtuning.generators import RaspberryPiOutput + +from raspberrypi.alsc import ALSC + +tuner = lt.Camera('Raspberry Pi') + +# These modules can also be custom modules. libtuning will come with utilities +# for handling stuff like images, so there shouldn't be too much boilerplate +# involved in creating custom modules, though I don't yet have a concrete +# vision on how custom implementations of modules would look. +tuner.add(ALSC) + +# Other tuning modules can be added like so. +# The order that the tuning modules will be executed is determined by the order +# that they're added. +# This is kind of an implementation detail, but the "context" is saved +# internally in lt.Camera, so modules that are added (and therefore executed) +# later can use the output of the previous modules. I'm thinking that a module +# that depends on values from another module has two modes of execution, for +# when those values are available and another for when they're not. Not quite +# sure concretely how to handle this yet. +tuner.add(AWB( # module parameters +)) + +tuner.setInputType(RaspberryPiParser) +tuner.setOutputType(RaspberryPiOutput) + +# The order of the output doesn't necessarily have to be the same as the order +# of input, which is specified by the order of adding the modules above. +tuner.setOutputOrder([AWB, ALSC]) + +tuner.run(sys.argv) From patchwork Sat Oct 22 06:23:10 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 17662 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 8E5D2C3285 for ; Sat, 22 Oct 2022 06:23:42 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 35E2062EE7; Sat, 22 Oct 2022 08:23:42 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1666419822; bh=jgWGfEWaME0hSQK8FMruyWsBu04/S6Vs0ytIaFyiGoQ=; 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=GkdARBNx78qRL2GelUhLqmAlaqsuYDM625H86s08PBqkPGpy69Dcyu45Z1FUB+JZu C3AIuMNpsTHVLaFwX8L4vm++018Af/wVzMCCkMefYYQXtsd75aU/R5sCLg0CyX9/ic BUda93VVX07L9W0rWAwAXDR5vkK0+MMonlh9fzLIJs3H5OlapA1g75Juha9Mdl/kyK m8Fx78/Zdh6GGzrC+yHlVCMERRvRAXFmfhSf+K48Z9PLW52V1LFOg2VlD44lC89hxC IymewB7wX7eSm5QZ1ueSsxgaLi+raVSnUMBrtmXW0DXTfBu306dcPNIvony/eZ2mOq 9E1iYWSH5Pd6Q== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 47F2E62EDF for ; Sat, 22 Oct 2022 08:23:40 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="t4I00rWi"; dkim-atps=neutral Received: from pyrite.rasen.tech (h175-177-042-159.catv02.itscom.jp [175.177.42.159]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 1664D891; Sat, 22 Oct 2022 08:23:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1666419820; bh=jgWGfEWaME0hSQK8FMruyWsBu04/S6Vs0ytIaFyiGoQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=t4I00rWipHC8siTxpkPEQ1AgkUpF9nB7jfXl0GFlkp8IWjhQHnXoUazq47ZR6SJg4 0z3H5XeA/cjjQqdw+hgXVW4mpGrx6HWgMylxFde61H6F+mDkQ7sKQF/Q1X/Ye3AKCN 9+QFIB3r5RMwrI4FS//Tm6IAnxXz5gZ9B109bruY= To: libcamera-devel@lists.libcamera.org Date: Sat, 22 Oct 2022 15:23:10 +0900 Message-Id: <20221022062310.2545463-12-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20221022062310.2545463-1-paul.elder@ideasonboard.com> References: <20221022062310.2545463-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 11/11] [WIP] 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. Signed-off-by: Paul Elder --- Changes in v2: - add SPDX and copyright - s/average_functions/average/ - update the script to work with the new rkisp1 alsc module As of v2, this runs, but only with LSC. Parabolic gradient support is put on hold, and instead the sectors are divided linearly. Is this what we want? Technically this script is complete in the sense that it works, but it's still WIP because it only support LSC. What should we do about this? Should it be "complete" at this stage and we can add the other modules later, or should we keep it WIP until the other modules are added? 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 | 58 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 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..cb692dd1 --- /dev/null +++ b/utils/tuning/rkisp1.py @@ -0,0 +1,58 @@ +#!/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.alsc import ALSCRkISP1 + +tuner = lt.Camera('RkISP1') +tuner.add(ALSCRkISP1( + # This can support other debug options (I can't think of any rn + # 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 size (16x16 programmed as mirrored + # 8x8) is separate, and is hardcoded in its specific ALSC tuning + # module + sector_shape=(17, 17), + + # Other functions might include Circular, Hyperbolic, Gaussian, + # Linear, Exponential, Logarithmic, etc + # Of course, both need not be the same function + # Some functions would need a sector_x_parameter (eg. sigma for Gaussian) + # Alternatively: sector_x_sizes = [] ? I don't think it would work tho + 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), + + # Do we need a flag to enable/disable calculating sigmas? afaik + # the rkisp1 IPA doesn't use it? But we could output it anyway + # and algorithms can decide whether or not if they want to use + # it. Oh well, no flag for now, we can always add it later if + # there's a big demand, plus it's only two to three values and + # not an entire table. + )) +tuner.setInputType(YamlParser) +tuner.setOutputType(YamlOutput) +tuner.setOutputOrder([ALSCRkISP1]) + +# Maybe this should be wrapped in an if __main__ = '__main__' ? That way the +# developer can control which tuner they want to be executed based on another +# layer of arguments? But I was thinking that this would handle *all* arguments +# based on the modules' and the input/output configurations. +tuner.run(sys.argv)