summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJacopo Mondi <jacopo.mondi@ideasonboard.com>2023-03-06 17:44:39 +0100
committerJacopo Mondi <jacopo.mondi@ideasonboard.com>2023-03-10 10:13:14 +0100
commitaa341469e626a4cdaf8b5cb974cc14652d30a2a5 (patch)
tree66206bd85dc37d5b2aa470191ec967cdf785d820
parent83f0ea3bee555ef0fbae8f305e177712b74337b4 (diff)
utils: rkisp1: Add script to extract LSC tables from Android
Android ship a per-sensor configuration file in .xml format. 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. Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
-rwxr-xr-xutils/rkisp1/lsc_parse_android_config.py187
1 files changed, 187 insertions, 0 deletions
diff --git a/utils/rkisp1/lsc_parse_android_config.py b/utils/rkisp1/lsc_parse_android_config.py
new file mode 100755
index 00000000..a7c2c160
--- /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))