[libcamera-devel] [PATCH 1/3] utils: rkisp1: Add script to extract LSC tables from Android
Laurent Pinchart
laurent.pinchart at ideasonboard.com
Tue Mar 7 10:49:23 CET 2023
Hi Jacopo,
Thank you for the patch.
On Mon, Mar 06, 2023 at 06:24:38PM +0100, Jacopo Mondi via libcamera-devel wrote:
> Android ship a per-sensor configuration file in .xml format.
s/ship/ships/
>
> The .xml file contains a main <matfile> node and a <sensor> sub-node
> which contains an <LSC> 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.
Could we generate a full tuning file instead ? I would imagine that
other tuning data could be (later) extracted, it would be nice to
prepare for that.
> Signed-off-by: Jacopo Mondi <jacopo.mondi at ideasonboard.com>
> ---
> utils/rkisp1/lsc_parse_android_config.py | 187 +++++++++++++++++++++++
And we could already name the script in a more generic way, to hint that
it converts a Rockchip tuning file to a libcamera tuning file.
Do you know if the XML format is Android-specific ?
> 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
Alphabetical order please.
> +import xml.etree.ElementTree as et
> +
> +
> +def sanitize(name):
> + return re.sub(r"[\n\t\s]*", "", name)
We use single quotes for strings when there's no specific reason to do
otherwise.
> +
> +
> +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
raise RuntimeError('Failed to find "sensor" node in config file')
and print the message in the caller. Same below.
> +
> + 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')
It's quite traditional in command line tools to pass the input file as a
positional argument instead of a named argument. Up to you.
> + args = parser.parse_args(argv[1:])
> +
> + root = et.parse(args.file).getroot()
It would be nice to catch parse errors and print a human-readable
message.
> + 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:
> +'''
Given that you're generating a rkisp1 tuning file, using the YamlOutput
class from libtuning would make sense.
> +
> + for cell in lsc_cells:
> + lsc_section += print_cell(cell) + "\n"
> +
> + print(lsc_section)
> +
> +
> +if __name__ == '__main__':
> + sys.exit(main(sys.argv))
--
Regards,
Laurent Pinchart
More information about the libcamera-devel
mailing list