From patchwork Thu Oct 6 12:01:00 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 17535 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 D0709C0DA4 for ; Thu, 6 Oct 2022 12:01:23 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9219362CF8; Thu, 6 Oct 2022 14:01:23 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1665057683; bh=c+qS29ldGV7Aa1nD7UelnDk7nzDuEuxLLD/x4uniRP4=; 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=3cQrxvgClIDiSFcd6qdkzdlA6aBDNbJ03G0qZtE7BkmtRAO2BFWtlsEOCrmuBRXfm PO17VcWBUGSiFGEvmyX+15U4z2WSzGVEO3V+PhmG7V/VeyVnyInC2wfMToJplhwsGv UcVaxgsD/GiomyEJSo5DtXnXPdwZOJb641azS8ohpLtVzhMIkZ49NQO2xk2otDmrho SAi3U7A9EtR/n3vg3m1qWEeVwitlhU9zu1JJ/hWtuagPH0aqofjbdkTMbH1HrvFQpk T9MTf5i7K37K/oMTnbbkSbcmGU31B9gteo3A4Bu4W91vLRyg7mZQNLy3Ewmjk7RS94 ELkYTTwbHw3iA== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 7EAC962CF2 for ; Thu, 6 Oct 2022 14:01:22 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="B17kxgAK"; 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 DC7B94DD; Thu, 6 Oct 2022 14:01:20 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1665057682; bh=c+qS29ldGV7Aa1nD7UelnDk7nzDuEuxLLD/x4uniRP4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=B17kxgAKGzgMufbZ0+zJZUhbQBgLdaD3uLSEW1PE25ijcecbsbhaaZARQtiGO4odB 7zRURrGK98COOC3ap8+1dNJfvHrdddSv4dU4dL43Kn0JQg1CGH6WWvNeLi7ILe0H8E /x0H5fRTUH0+EH6VhjxnVPX1ZGXs2j7jlo/FTdNg= To: libcamera-devel@lists.libcamera.org Date: Thu, 6 Oct 2022 21:01:00 +0900 Message-Id: <20221006120105.3861831-3-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20221006120105.3861831-1-paul.elder@ideasonboard.com> References: <20221006120105.3861831-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH 2/7] 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 --- utils/tuning/libtuning/modules/__init__.py | 1 + utils/tuning/libtuning/modules/alsc.py | 321 +++++++++++++++++++++ 2 files changed, 322 insertions(+) create mode 100644 utils/tuning/libtuning/modules/alsc.py diff --git a/utils/tuning/libtuning/modules/__init__.py b/utils/tuning/libtuning/modules/__init__.py index e69de29b..f41ef550 100644 --- a/utils/tuning/libtuning/modules/__init__.py +++ b/utils/tuning/libtuning/modules/__init__.py @@ -0,0 +1 @@ +from libtuning.modules.alsc import ALSC diff --git a/utils/tuning/libtuning/modules/alsc.py b/utils/tuning/libtuning/modules/alsc.py new file mode 100644 index 00000000..788cf2a9 --- /dev/null +++ b/utils/tuning/libtuning/modules/alsc.py @@ -0,0 +1,321 @@ +from .module import Module + +import libtuning as lt +import libtuning.utils as utils + +import decimal +import numbers +import numpy as np +from typing import Callable + + +class ALSC(Module): + def __init__(self, *, + do_color: lt.param, + debug: list, + luminance_strength: lt.param, + sector_shape: tuple, + sector_x_gradient: lt.gradient.Gradient, + sector_y_gradient: lt.gradient.Gradient, + sector_x_remainder: lt.remainder = None, + sector_y_remainder: lt.remainder = None, + sector_average_function: Callable, + sector_average_function_parameters: list = [], + smoothing_function: Callable, + smoothing_parameters: list = [], + output_type: type, + output_color_channels: list, + output_range: tuple): + super().__init__() + self.hr_name = "ALSC" + self.name = "alsc" + self.options = {} + + 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.do_color = do_color + self.luminance_strength = luminance_strength + + self.debug = debug + + self.sector_shape = sector_shape + self.sector_x_gradient = sector_x_gradient(sector_x_remainder) + self.sector_y_gradient = sector_y_gradient(sector_y_remainder) + self.sector_average_function = sector_average_function(sector_average_function_parameters) + + self.smoothing_function = smoothing_function(smoothing_parameters) + + # todo This isn't actually used. Can we assume/force float? Is there a + # use case for non-float? Or maybe we should use numpy types like + # float64? I don't think it makes a difference in the output tuning + # anyway though. + self.output_type = output_type + + # This isn't used for now. Once we add support for two green channels + # we can. + self.output_color_channels = output_color_channels + + self.output_range = output_range + + def enumerate_alsc_images(images): + for image in images: + if image.alsc_only: + yield image + + def __validateConfig__(self, config: dict) -> bool: + if not super().__validateConfig__(config): + return False + + conf = config[self] + valid = True + + lum_key = self.luminance_strength.name + color_key = self.do_color.name + + if lum_key not in conf and self.luminance_strength.isRequired(): + 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.isRequired(): + utils.eprint(f'{color_key} is not in config') + valid = False + + return valid + + def _getGrid(self, channel, image): + # List of number of pixels in each sector + sectors_x = self.sector_x_gradient.distribute(image.w, self.sector_shape[0]) + sectors_y = self.sector_y_gradient.distribute(image.h, 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(channel[r:r + y, c:c + x])) + c += x + r += y + + return np.array(grid) + + # @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 _doSingleAlsc(self, image: lt.Image, do_alsc_colour: bool): + # Get channel in correct order: [R, GR, GB, B] + channels = [image.channels[i] for i in image.order] + + channels.append(np.mean((channels[color.GR:color.GB + 1]), axis=0)) + + g = self._getGrid(av_ch_g, image) - image.blacklevel_16 + cg = np.reshape(1 / g, self.sector_shape[::-1]) + cg = self.smoothing_function.smoothing(cg, 3) + cg = cg / np.min(cg) + + if not do_alsc_colour: + return image.color, None, None, cg.flatten(), cg.flatten() + + r = self._getGrid(channels[0], image) - image.blacklevel_16 + cr = np.reshape(g / r, self.sector_shape[::-1]) + cr = self.smoothing_function.smoothing(cr, 3) + + b = self._getGrid(channels[3], image) - image.blacklevel_16 + cb = np.reshape(g / b, self.sector_shape[::-1]) + cb = self.smoothing_function.smoothing(cb, 3) + + # todo implement debug + + return image.color, cr.flatten(), cb.flatten(), cg.flatten(), cg.flatten() + + # todo Figure out if we can do something cooler if we can keep the greens + # separate. For now, just average them and use the average, but duplicate + # them so that it's easier to handle them in the future, especially since + # some platforms have separate tables for GR and GB. + # + # todo Should we return a third shading table for the average of greens? + # For platforms that only want one green, as opposed to just using red's + # green shading table + # + # @return Red shading table, Blue shading table, (red's) Green shading + # table, (blue's) Green shading table, number of images processed + + def _doAllAlsc(self, images: list, do_alsc_colour: bool, general_conf: dict) -> (list, list, list, numbers.Number, int): + # List of colour temperatures + list_col = [] + # Associated calibration tables + list_cr = [] + list_cb = [] + list_cg1 = [] + list_cg2 = [] + count = 0 + for image in enumerate_alsc_images(images): + col, cr, cb, cg1, cg2 = self._doSingleAlsc(image, do_alsc_colour) + list_col.append(col) + list_cr.append(cr) + list_cb.append(cb) + list_cg1.append(cg1) + list_cg2.append(cg2) + 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_cg1 = np.array(list_cg1) + list_cg2 = np.array(list_cg2) + + 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 = roundWithSigfigs(np.mean(list_cg1, axis=0), self.output_range) + + if not do_alsc_colour: + return None, None, lum_lut, 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.roundWithSigfigs(np.mean(list_cr[indices], axis=0), + self.output_range) + t_b = utils.roundWithSigfigs(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, 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 _getSigma(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.getValue(conf) + + # todo I have no idea where this input parameter is used + luminance_strength = self.luminance_strength.getValue(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._doAllAlsc(images, do_alsc_colour, general_conf) + # todo Handle the second green lut + cal_cr_list, cal_cb_list, luminance_lut, _, count = alsc_out + + output['luminance_lut'] = luminance_lut + + if not do_alsc_colour: + output['n_iter'] = 0 + return output + + output['calibrations_Cr'] = cal_cr_list + output['calibrations_Cb'] = cal_cb_list + + # 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._getSigma(self, 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 Oct 6 12:01: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: 17536 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 5B5FEC0DA4 for ; Thu, 6 Oct 2022 12:01:26 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2153562CFD; Thu, 6 Oct 2022 14:01:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1665057686; bh=SSXu8Blp2anhjs1wkvhoOZvB0DTiqluNwgnzmvqUjQ0=; 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=tRUZ8tdHodCNqiHAKnGVxdJ8TAvZnOv70Duy9/A7kCTBgaqqzNZrG/tn5BvZdK4eO e4AK6sOj/5x2q5tXQmq/jnGV5/uoDAXGHt2aI6Xdiqn2z/nvE0vuobDNKzLaTEdSDf dvuEZxpkQ+7aymJRXXltpRDjHH0Hqx5J5Dm/n4dnZAqPOeJ4HZ2Y/cbEs0y6B1HKEx AelOIyvRbVrX4AP65x3BRQSzjLAglnfS9QwAp61nuDqdE9KuJ+F/O+yq50cMUM1DEb 9xiB1pnPqCi5pqof/8hhNNCjljWp3teLZJ0Kg9SO9oqHycBp+rfpYByOj4IuUHb2QY cFcFmFR6Cr9cQ== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 30CBC62CF7 for ; Thu, 6 Oct 2022 14:01:24 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="m148hrkF"; 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 BB24FA1C; Thu, 6 Oct 2022 14:01:22 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1665057683; bh=SSXu8Blp2anhjs1wkvhoOZvB0DTiqluNwgnzmvqUjQ0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=m148hrkFK6AKYM89dPpx9rJKlknMSXjXJaKt5mN4f7cNx6HjJmC0VAJSizmrDEGLm h64m72n0ndf5WxjgmylKE2JAfXdaup6fm/vn473IbkFghGB98kcSf/JVqvwd4VAhwB Qlbd5Wh3nGscIOEr27q4N12cMvMqHfTD2vqeq3IQ= To: libcamera-devel@lists.libcamera.org Date: Thu, 6 Oct 2022 21:01:01 +0900 Message-Id: <20221006120105.3861831-4-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20221006120105.3861831-1-paul.elder@ideasonboard.com> References: <20221006120105.3861831-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH 3/7] 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 --- utils/tuning/libtuning/parsers/__init__.py | 1 + .../libtuning/parsers/raspberrypi_parser.py | 82 +++++++++++++++++++ 2 files changed, 83 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..ba829161 100644 --- a/utils/tuning/libtuning/parsers/__init__.py +++ b/utils/tuning/libtuning/parsers/__init__.py @@ -0,0 +1 @@ +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..1a7d4c0a --- /dev/null +++ b/utils/tuning/libtuning/parsers/raspberrypi_parser.py @@ -0,0 +1,82 @@ +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(listt, dictt, modules): + for x in listt: + name = x.replace('rpi.', '') + if name not in dictt: + continue + module = utils.getModuleByName(modules, name) + if module is not None: + yield module + + def _validMacbethOption(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 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 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 + + for module_name in config: + module = utils.getModuleByName(modules, module_name) + if module is not None and \ + module.validateConfig(config[module_name]): + new_config[module] = config.pop(module_name) + + new_config['general'] = {} + + if 'blacklevel' in config: + if not isinstance(config['blacklevel'], numbers.Number): + raise TypeError('Config "blacklevel" must be a number') + new_config['general']['blacklevel'] = config['blacklevel'] + + if 'macbeth' in config: + if not _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 Thu Oct 6 12:01: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: 17537 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 E467BC0DA4 for ; Thu, 6 Oct 2022 12:01:27 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A004962CFE; Thu, 6 Oct 2022 14:01:27 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1665057687; bh=K74rGkxTIsp5mWnp6ujEQtDW4zStQUuyvk9gON1M/c4=; 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=FjLkX/M6Xcl5IlS02BeB14WkXs7/DgnbYgHMzw5lECuH+l0BwVj0P+RoBYtg4J9Lx xTy/YbFAgXiAqIH/DkplhYdz+dYINcxhL4lRHe40i7lZfi4qdFOAzdaPTBZp7gZ59H 08hnqzm53nGYRW7C6sxi8DDFAcwk4yR9q86K31pRaU7XoIe10M+HdEHqupAs9od7l6 8U7Dg/bvceJHWku6X0Z5IP11ySjm4eq0zEqUTHQx+pKI2fetH+IIuuWvhSWAXdRsTw qa4JEjBZnoDCujeEKZmA6FHgg8CDzE08JBsdZAHqj9O8HFwBO+epQKFPd91KoQV3x0 exBoKgijntpYQ== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 143A962CF7 for ; Thu, 6 Oct 2022 14:01:26 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="nvWu2JuL"; 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 8FDF5A42; Thu, 6 Oct 2022 14:01:24 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1665057685; bh=K74rGkxTIsp5mWnp6ujEQtDW4zStQUuyvk9gON1M/c4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=nvWu2JuLLDleu1q9MGBvazPbXVdShHfI9beOpdbHfk3S66h1nvuZ3JimW1xX5D2Ts ftlbOl8az5qWmCtmPaMjAb3J2tMOrqzBMvFYfVmw0Di+2EwI3L1OqWG72KVC+qqttK LWNL6RkDSLvMlpGlEf4cJZ5LKmS6XJY2ZjIYPv/k= To: libcamera-devel@lists.libcamera.org Date: Thu, 6 Oct 2022 21:01:02 +0900 Message-Id: <20221006120105.3861831-5-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20221006120105.3861831-1-paul.elder@ideasonboard.com> References: <20221006120105.3861831-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH 4/7] 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 --- utils/tuning/libtuning/generators/__init__.py | 1 + .../generators/raspberrypi_output.py | 115 ++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 utils/tuning/libtuning/generators/raspberrypi_output.py diff --git a/utils/tuning/libtuning/generators/__init__.py b/utils/tuning/libtuning/generators/__init__.py index e69de29b..50064196 100644 --- a/utils/tuning/libtuning/generators/__init__.py +++ b/utils/tuning/libtuning/generators/__init__.py @@ -0,0 +1 @@ +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..d5cc0c48 --- /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'rpi.{module.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 Thu Oct 6 12:01: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: 17538 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 53A7DC0DA4 for ; Thu, 6 Oct 2022 12:01:30 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 14BB062D01; Thu, 6 Oct 2022 14:01:30 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1665057690; bh=OD3pHQLn5o9+nGWkB9nOeKv2ZxjIZctNvJLiT2moTIM=; 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=hFNSHYuKIipMxNL9/A+d9TJAEMWuf8nlOf2nvMbCQIZ94hiRPbn+QvY4G3+f42erq vonYR9FCU+YzjR4wjLyLR+X+Jsv1bpez+m9ckuyl0NDTRARD57to4ZTbEwYSzinJU3 33PZV3h/+U1MN/QazQieaZsgnp1+KZzhqHUdSLJDJ+trEJCEqy7cCP4IewC8WVEKE+ TDF8tDNngyo2GQFzeKB+oSlIFQFwme8acc6thcyHRBnUTqjVisiogyvRr2P5oed1l0 AtB+ojIh4o1OeK23JZ6hOeiObv34zNuiJpXk+bTlAbs1c2UIjUGd+0O8mFxR/G0uYU I2G16kGGiq2bQ== 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 DF50F62D01 for ; Thu, 6 Oct 2022 14:01:27 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="UUN5ntTW"; 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 692B94DD; Thu, 6 Oct 2022 14:01:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1665057687; bh=OD3pHQLn5o9+nGWkB9nOeKv2ZxjIZctNvJLiT2moTIM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=UUN5ntTWH9lLVb30AVw9zKtREItN9PuwAF+IIrhlVj5UDdp2ttuMaDQN9WU23Z7Yd W1J4U3iipY3wgOlOn7iADnYlTGdRO1MMA0dNY8rl/+ZK/g/eJWWm/TKA4hpAivs0/d 7s5kIZ5yzKbG8ntvHeK2N1vzwVPxnVAE/P+AxTxQ= To: libcamera-devel@lists.libcamera.org Date: Thu, 6 Oct 2022 21:01:03 +0900 Message-Id: <20221006120105.3861831-6-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20221006120105.3861831-1-paul.elder@ideasonboard.com> References: <20221006120105.3861831-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH 5/7] 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 --- utils/tuning/raspberrypi/__init__.py | 0 utils/tuning/raspberrypi/alsc.py | 18 ++++++++++++++++++ utils/tuning/raspberrypi_alsc_only.py | 18 ++++++++++++++++++ 3 files changed, 36 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..dd92cb8b --- /dev/null +++ b/utils/tuning/raspberrypi/alsc.py @@ -0,0 +1,18 @@ +import libtuning as lt +from libtuning.modules import ALSC + +RaspberryPiALSC = \ + ALSC(do_color=lt.param('do_alsc_colour', lt.param.mode.Optional, True), + debug=[lt.debug.Plot], + luminance_strength=lt.param('luminance_strength', lt.param.mode.Optional, 0.5), + sector_shape=(16, 12), + sector_x_gradient=lt.gradient.Linear, + sector_y_gradient=lt.gradient.Linear, + sector_x_remainder=lt.remainder.DistributeFront, + sector_y_remainder=lt.remainder.DistributeFront, + sector_average_function=lt.average_functions.Mean, + smoothing_function=lt.smoothing.MedianBlur, + output_type=float, + output_color_channels=[lt.color.R, lt.color.G, lt.color.B], + output_range=(0, 3.999) + ) diff --git a/utils/tuning/raspberrypi_alsc_only.py b/utils/tuning/raspberrypi_alsc_only.py new file mode 100755 index 00000000..e1e7b6e3 --- /dev/null +++ b/utils/tuning/raspberrypi_alsc_only.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 + +import sys + +import libtuning as lt +from libtuning.modules import ALSC +from libtuning.parsers import RaspberryPiParser +from libtuning.generators import RaspberryPiOutput + +from raspberrypi.alsc import ALSC as RaspberryPiALSC + +tuner = lt.Camera('Raspberry Pi (ALSC only)') +tuner.add(RaspberryPiALSC) +tuner.setInputType(RaspberryPiParser) +tuner.setOutputType(RaspberryPiOutput) +tuner.setOutputOrder([ALSC]) + +tuner.run(sys.argv) From patchwork Thu Oct 6 12:01: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: 17539 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 E8E4AC0DA4 for ; Thu, 6 Oct 2022 12:01:31 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A972062D07; Thu, 6 Oct 2022 14:01:31 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1665057691; bh=ZWJzbNK9Sgp3i+vmT9zCJ0eHRVMs8PmjGI/+/E0ehHU=; 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=z4iItAg+vVREzO4QCmioN5HaFGp7s1mJLUv+Vb9jFnM1zIyrYxCSSSCUza+pZlBx9 A1q0ZSinqF03kZe3NLJ7n7Hitj/gdwdlCQjIllT0DR3qRpms30d+l0d41RZQwwQgRn 4YglDZDQETTAbHtzXciJ4GSiBUoXfxljfr0DQPvblvbrHN+39VCvBASc651+NkLETp 2KnLj4zJBzyE6h+N3Yp332iYeFrWoPZD/tuI0Wkro9itGAQaebuUML/uByZlMOBKd6 DM9tPSYXou2dJ8B2gCMIcOOmBfD52wCmG7BSz4ruS6fC87BHoI1Lhxf0qZCbGi9+2Z SGDKNi7M6Sr7g== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 972E162CFC for ; Thu, 6 Oct 2022 14:01:29 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="qeWc2JDW"; 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 429E2A1C; Thu, 6 Oct 2022 14:01:27 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1665057689; bh=ZWJzbNK9Sgp3i+vmT9zCJ0eHRVMs8PmjGI/+/E0ehHU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=qeWc2JDWRpzA1djJKmmsPHrwTidxGfs4h+K2M+KrKnE3mUEgGm8MAPZe9c7AuSW6m dYdsDv/WH5ESDIpK/AsjZfYxThXisugaIeJxdIqGC6YHcVOakHq4ZxPCLRvXfNOUj+ sExVm1TiYMgES2HpQH9PvfdkktUefplTYehMDKPE= To: libcamera-devel@lists.libcamera.org Date: Thu, 6 Oct 2022 21:01:04 +0900 Message-Id: <20221006120105.3861831-7-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20221006120105.3861831-1-paul.elder@ideasonboard.com> References: <20221006120105.3861831-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH 6/7] [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 --- This is incomplete, hence the DNI. --- utils/tuning/raspberrypi.py | 39 +++++++++++++++++++++++++++++++++++++ 1 file changed, 39 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..b1592f0f --- /dev/null +++ b/utils/tuning/raspberrypi.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 + +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 as RaspberryPiALSC + +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(RaspberryPiALSC) + +# 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 Thu Oct 6 12:01: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: 17540 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 748A3C0DA4 for ; Thu, 6 Oct 2022 12:01:33 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2798662D01; Thu, 6 Oct 2022 14:01:33 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1665057693; bh=MsJfSHZA+x99EnG652uupmIyLe/hzEKsaP1x+7Nlgfk=; 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=X4Ol/wxi7c0osJgxpuF3+Z/j0e3dk7UmzvXw4E3gEAfBa0u8IR2LtNxIPGGGAcEpq nBtRY6jOAnhA81zRqSMBADE7gdjTo+R/REoGXGiRXPEBVl/MkrrOmqPtrwJ6r2SJ8d 9W7l+wTtBjQQTKGUN4fd3VrZ9xTwzX0Hpy2K9dxQz47cMkIOneGFtfhSKisHVNfix4 zInV4KRcjvAfmYUG0GJchTcdlh7gG0DjbO64T9ZFrWgNwNdEqHGqKL/LBVd27+40el bqoI4O6KiV9aYQs79kmckh8wrX1en/8jWH+GoNDVIqU0dTYovdkqbhb/52y4Usdv2a JVG0htAWfP09w== 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 5970162CFE for ; Thu, 6 Oct 2022 14:01:31 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ok1GZZLr"; 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 EBA624DD; Thu, 6 Oct 2022 14:01:29 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1665057691; bh=MsJfSHZA+x99EnG652uupmIyLe/hzEKsaP1x+7Nlgfk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ok1GZZLrnGD9uGMrG/RH8d7/ftttz9lO5ddyPPL+rsikyIzm/j6bxpBFa8PItTtPK gKcDSVqC9HWn3kDPE7LLXZZVusjqICYqv5LDbkJtbgftvl0nYHto8yfQGYhA/78suN bp5DNlRa8lEMjYjQ22wStkGLOukobl3YYcxu9l70= To: libcamera-devel@lists.libcamera.org Date: Thu, 6 Oct 2022 21:01:05 +0900 Message-Id: <20221006120105.3861831-8-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20221006120105.3861831-1-paul.elder@ideasonboard.com> References: <20221006120105.3861831-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH 7/7] [DNI] 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 --- This won't run as we're missing the necessary parser and generator for yaml, parabolic gradient support, and multiple green support in the ALSC module, hence the DNI. As soon as those are added though, this *should* work. --- utils/tuning/rkisp1.py | 69 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 utils/tuning/rkisp1.py diff --git a/utils/tuning/rkisp1.py b/utils/tuning/rkisp1.py new file mode 100644 index 00000000..6ccd8ceb --- /dev/null +++ b/utils/tuning/rkisp1.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 + +import sys + +import libtuning as lt +from libtuning.modules import ALSC +from libtuning.parsers import YamlParser +from libtuning.generators import YamlOutput + +tuner = lt.Camera('RkISP1') +tuner.add(ALSC(do_color=lt.param('do_alsc_colour', lt.param.mode.Optional, True), + + # This can support other debug options (I can't think of any rn + # but for future-proofing) + debug=[lt.debug.Plot], + + # The name of IfUnSet can obviously be changed, but really I + # just want something that says "it must be specified in the + # configuration, and get the value from there" or "if the value + # is not in the configuration, use this" + luminance_strength=lt.param('luminance_strength', lt.param.mode.Optional, 0.5), + + sector_shape=(16, 16), + + # 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.Parabolic, + sector_y_gradient=lt.gradient.Parabolic, + + # 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_functions.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, + smoothing_parameters=[3], + + # Are there any platforms that use integer values for their lsc table? + output_type=lt.type.Float, + + # Required if and only if do_color can be or is True + output_color_channels=[lt.color.R, lt.color.GR, lt.color.GB, lt.color.B], + + # Required if and only if do_color can be or is False + output_luminance_channels=[lt.color.GR, lt.color.GB], + + # Automatically get the precision from this + output_range=(0, 3.999) + + # 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([ALSC]) + +# 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)