From patchwork Mon Mar 6 17:24:38 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 18343 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 B798FBE080 for ; Mon, 6 Mar 2023 17:25:01 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 74072626AD; Mon, 6 Mar 2023 18:25:01 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1678123501; bh=Enkq3uLJzgGbDjLuX/ihmn3ahHN3l7Z0WgIs54fC3Kg=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc: From; b=HyQrqeH16QpKBWZd0GzWGe+AQhSxPOyptkukT1StIbbiTHrnaf6Wjd38AmZZ7rX/Z HPjkwyi4A0KxaBKLGn8rnLo/iMnHl6Z37nj6BeTK336WggIcjn8+ERIwDOq1Le1xm9 SiZ58kGmGE+F/sZ1jOe/qyU3Rf9dsD9h/PX1AwbWH4CIouNOCQFljVLAdFQSiAg/yx 3SnE4YQJr6SkySloQCOFs3QD/9gnySG+Ov3om+PpRa1t4jGYXtkni9ejSYiCXJic6g I2yVeLVpVCsEL63dzwPzVRavPcSfQ/i6CMXSGBGjSJQCLMYZtmimx5tGNZe6xLcz1B i6nG2dMp5h12A== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 325D8603B1 for ; Mon, 6 Mar 2023 18:24:57 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Dd+L8rew"; dkim-atps=neutral Received: from uno.homenet.telecomitalia.it (host-79-47-54-87.retail.telecomitalia.it [79.47.54.87]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 693897EC; Mon, 6 Mar 2023 18:24:56 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1678123496; bh=Enkq3uLJzgGbDjLuX/ihmn3ahHN3l7Z0WgIs54fC3Kg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Dd+L8rewJ/xs3UOdHr4X6n+F00J69vHEXSWZ1O76ngInpsg2W1bjuHZpBeJrDiLeb od+pZjzRIfa0O+w6M+oHyLdN1HBoZ7w2L+nlF+1siVerG3DrOXMKv5c9s/gIuOdMy7 eSdVZsj0oUOu6MDBlYclKuTreJNyNRzCTuwUAIyQ= To: libcamera-devel@lists.libcamera.org Date: Mon, 6 Mar 2023 18:24:38 +0100 Message-Id: <20230306172440.57764-2-jacopo.mondi@ideasonboard.com> X-Mailer: git-send-email 2.39.0 In-Reply-To: <20230306172440.57764-1-jacopo.mondi@ideasonboard.com> References: <20230306172440.57764-1-jacopo.mondi@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH 1/3] utils: rkisp1: Add script to extract LSC tables from Android 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: Jacopo Mondi via libcamera-devel From: Jacopo Mondi Reply-To: Jacopo Mondi Cc: Jacopo Mondi Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Android ship a per-sensor configuration file in .xml format. The .xml file contains a main node and a sub-node which contains an entry. The LSC tables there contained can be re-used for libcamera, by parsing them opportunely. Add a script to utils/rkisp1/ to extract the LSC tables from Android configuration file for the RkISP1 platform, modeled after the requirements of the Rockchip closed source IQ tuning module. Compared to the Rockchip IQ LSC module the one implemented in libcamera is slightly simpler, and the parsing of the LSC table takes that into account by: - Only outputting tables for the larger found sensor resolution - Only outputting tables for "vignetting" value == 70 (ignoring the ones for vignetting values of 100) The script outputs to stdout a "LensShadingCorrection" section that can be directly pasted in a libcamera sensor configuration file. Signed-off-by: Jacopo Mondi --- utils/rkisp1/lsc_parse_android_config.py | 187 +++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100755 utils/rkisp1/lsc_parse_android_config.py diff --git a/utils/rkisp1/lsc_parse_android_config.py b/utils/rkisp1/lsc_parse_android_config.py new file mode 100755 index 000000000000..a7c2c160319d --- /dev/null +++ b/utils/rkisp1/lsc_parse_android_config.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python + +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2023, Jacopo Mondi - Ideas on Board Oy +# +# Parse Android .xml configuration file and extract the LSC tables. +# +# Print to standard output a "LensShadingCorrection" section, understandable by +# libcamera LSC algorithm, that can be pasted to the sensor configuration file. + +import argparse +import string +import sys +import re +import xml.etree.ElementTree as et + + +def sanitize(name): + return re.sub(r"[\n\t\s]*", "", name) + + +def split_table(table): + values = "" + for v in table.text.strip(' ').split(): + values += v.strip('[').strip(']') + ", " + return values + + +def print_cell(cell): + lsc_template = string.Template(''' #${name} - ${illuminant} + - ct: ${ct} + resolution: ${res} + r: [${red}] + gr: [${greenr}] + gb: [${greenb}] + b: [${blue}]''') + + illuminant = cell.find("illumination") + ct = illuminant_to_ct(illuminant) + + template_dict = { + 'name': sanitize(cell.find("name").text), + 'illuminant': sanitize(illuminant.text), + 'ct': ct, + 'res': sanitize(cell.find("resolution").text) + } + + red_table = cell.find("LSC_SAMPLES_red") + greenr_table = cell.find("LSC_SAMPLES_greenR") + greenb_table = cell.find("LSC_SAMPLES_greenB") + blue_table = cell.find("LSC_SAMPLES_blue") + + if red_table is None or greenr_table is None or greenb_table is None or blue_table is None: + return + + template_dict['red'] = split_table(red_table) + template_dict['greenr'] = split_table(greenr_table) + template_dict['greenb'] = split_table(greenb_table) + template_dict['blue'] = split_table(blue_table) + + return lsc_template.substitute(template_dict) + + +def illuminant_to_ct(illuminant): + # Standard CIE Illiminants to Color Temperature in Kelvin + # https://en.wikipedia.org/wiki/Standard_illuminant + # + # Not included (and then ignored when parsing the configuration file): + # - "Horizon" == D50 == 5003 + # - "BW" == ? + # - "PREFLASH" == ? + illuminants_dict = { + 'A': 2856, + 'D50': 5003, + 'D65': 6504, + 'D75': 7504, + 'F11_TL84': 4000, + 'F2_CWF': 4230, + } + + ill_key = sanitize(illuminant.text) + try: + ct = illuminants_dict[ill_key] + except KeyError: + return None + + return ct + + +# Make sure the cell is well formed and return it +def filter_cells(cell, res, lsc_cells): + name = cell.find("name") + resolution = cell.find("resolution") + illumination = cell.find("illumination") + vignetting = cell.find("vignetting") + + if name is None or resolution is None or \ + illumination is None or vignetting is None: + return + + # Skip tables for smaller sensor resolutions + if res != sanitize(resolution.text): + return + + # Skip tables for which we don't know how to translate the illuminant value + ct = illuminant_to_ct(illumination) + if ct is None: + return + + # Only pick tables with vignetting == 70 + if sanitize(vignetting.text) != "[70]": + return + + lsc_cells.append(cell) + + +# Get the "LSC" node +def find_lsc_table(root): + sensor = root.find('sensor') + if sensor is None: + print("Failed to find \"sensor\" node in config file") + raise Exception + + lsc = sensor.find('LSC') + if lsc is None: + print("Filed to find \"LSC\" node in config file") + raise Exception + + return lsc + +# libcamera LSC algorithm only operates on a single resolution. +# Find the largest sensor mode among the ones reported in the LSC tables + + +def parse_max_res(cells): + max_res = "" + max_size = 0 + + for cell in cells: + resolution = sanitize(cell.find("resolution").text) + [w, h] = resolution.split('x') + + area = int(w) * int(h) + if area > max_size: + max_res = resolution + + return max_res + + +def main(argv): + # Parse command line arguments. + parser = argparse.ArgumentParser( + description='Parse Android camera configuration file to extract LSC tables') + parser.add_argument('--file', '-f', required=True, + help='Path to the Android .xml configuration file') + args = parser.parse_args(argv[1:]) + + root = et.parse(args.file).getroot() + try: + lsc_node = find_lsc_table(root) + except Exception: + return 1 + + cells = lsc_node.findall("cell") + + max_res = parse_max_res(cells) + if max_res == "": + return + + lsc_cells = [] + for cell in cells: + filter_cells(cell, max_res, lsc_cells) + + lsc_section = ''' - LensShadingCorrection: + x-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ] + y-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ] + sets: +''' + + for cell in lsc_cells: + lsc_section += print_cell(cell) + "\n" + + print(lsc_section) + + +if __name__ == '__main__': + sys.exit(main(sys.argv))