[{"id":26577,"web_url":"https://patchwork.libcamera.org/comment/26577/","msgid":"<20230307094923.GB22236@pendragon.ideasonboard.com>","date":"2023-03-07T09:49:23","subject":"Re: [libcamera-devel] [PATCH 1/3] utils: rkisp1: Add script to\n\textract LSC tables from Android","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Jacopo,\n\nThank you for the patch.\n\nOn Mon, Mar 06, 2023 at 06:24:38PM +0100, Jacopo Mondi via libcamera-devel wrote:\n> Android ship a per-sensor configuration file in .xml format.\n\ns/ship/ships/\n\n> \n> The .xml file contains a main <matfile> node and a <sensor> sub-node\n> which contains an <LSC> entry. The LSC tables there contained can be\n> re-used for libcamera, by parsing them opportunely.\n> \n> Add a script to utils/rkisp1/ to extract the LSC tables from Android\n> configuration file for the RkISP1 platform, modeled after the\n> requirements of the Rockchip closed source IQ tuning module.\n> \n> Compared to the Rockchip IQ LSC module the one implemented in libcamera\n> is slightly simpler, and the parsing of the LSC table takes that into\n> account by:\n> - Only outputting tables for the larger found sensor resolution\n> - Only outputting tables for \"vignetting\" value == 70 (ignoring the ones\n>   for vignetting values of 100)\n> \n> The script outputs to stdout a \"LensShadingCorrection\" section that\n> can be directly pasted in a libcamera sensor configuration file.\n\nCould we generate a full tuning file instead ? I would imagine that\nother tuning data could be (later) extracted, it would be nice to\nprepare for that.\n\n> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n> ---\n>  utils/rkisp1/lsc_parse_android_config.py | 187 +++++++++++++++++++++++\n\nAnd we could already name the script in a more generic way, to hint that\nit converts a Rockchip tuning file to a libcamera tuning file.\n\nDo you know if the XML format is Android-specific ?\n\n>  1 file changed, 187 insertions(+)\n>  create mode 100755 utils/rkisp1/lsc_parse_android_config.py\n> \n> diff --git a/utils/rkisp1/lsc_parse_android_config.py b/utils/rkisp1/lsc_parse_android_config.py\n> new file mode 100755\n> index 000000000000..a7c2c160319d\n> --- /dev/null\n> +++ b/utils/rkisp1/lsc_parse_android_config.py\n> @@ -0,0 +1,187 @@\n> +#!/usr/bin/env python\n> +\n> +# SPDX-License-Identifier: GPL-2.0-or-later\n> +# Copyright (C) 2023, Jacopo Mondi - Ideas on Board Oy\n> +#\n> +# Parse Android .xml configuration file and extract the LSC tables.\n> +#\n> +# Print to standard output a \"LensShadingCorrection\" section, understandable by\n> +# libcamera LSC algorithm, that can be pasted to the sensor configuration file.\n> +\n> +import argparse\n> +import string\n> +import sys\n> +import re\n\nAlphabetical order please.\n\n> +import xml.etree.ElementTree as et\n> +\n> +\n> +def sanitize(name):\n> +    return re.sub(r\"[\\n\\t\\s]*\", \"\", name)\n\nWe use single quotes for strings when there's no specific reason to do\notherwise.\n\n> +\n> +\n> +def split_table(table):\n> +    values = \"\"\n> +    for v in table.text.strip(' ').split():\n> +        values += v.strip('[').strip(']') + \", \"\n> +    return values\n> +\n> +\n> +def print_cell(cell):\n> +    lsc_template = string.Template('''        #${name} - ${illuminant}\n> +        - ct: ${ct}\n> +          resolution: ${res}\n> +          r: [${red}]\n> +          gr: [${greenr}]\n> +          gb: [${greenb}]\n> +          b: [${blue}]''')\n> +\n> +    illuminant = cell.find(\"illumination\")\n> +    ct = illuminant_to_ct(illuminant)\n> +\n> +    template_dict = {\n> +        'name': sanitize(cell.find(\"name\").text),\n> +        'illuminant': sanitize(illuminant.text),\n> +        'ct': ct,\n> +        'res': sanitize(cell.find(\"resolution\").text)\n> +    }\n> +\n> +    red_table = cell.find(\"LSC_SAMPLES_red\")\n> +    greenr_table = cell.find(\"LSC_SAMPLES_greenR\")\n> +    greenb_table = cell.find(\"LSC_SAMPLES_greenB\")\n> +    blue_table = cell.find(\"LSC_SAMPLES_blue\")\n> +\n> +    if red_table is None or greenr_table is None or greenb_table is None or blue_table is None:\n> +        return\n> +\n> +    template_dict['red'] = split_table(red_table)\n> +    template_dict['greenr'] = split_table(greenr_table)\n> +    template_dict['greenb'] = split_table(greenb_table)\n> +    template_dict['blue'] = split_table(blue_table)\n> +\n> +    return lsc_template.substitute(template_dict)\n> +\n> +\n> +def illuminant_to_ct(illuminant):\n> +    # Standard CIE Illiminants to Color Temperature in Kelvin\n> +    # https://en.wikipedia.org/wiki/Standard_illuminant\n> +    #\n> +    # Not included (and then ignored when parsing the configuration file):\n> +    # - \"Horizon\" == D50 == 5003\n> +    # - \"BW\" == ?\n> +    # - \"PREFLASH\" == ?\n> +    illuminants_dict = {\n> +        'A': 2856,\n> +        'D50': 5003,\n> +        'D65': 6504,\n> +        'D75': 7504,\n> +        'F11_TL84': 4000,\n> +        'F2_CWF': 4230,\n> +    }\n> +\n> +    ill_key = sanitize(illuminant.text)\n> +    try:\n> +        ct = illuminants_dict[ill_key]\n> +    except KeyError:\n> +        return None\n> +\n> +    return ct\n> +\n> +\n> +# Make sure the cell is well formed and return it\n> +def filter_cells(cell, res, lsc_cells):\n> +    name = cell.find(\"name\")\n> +    resolution = cell.find(\"resolution\")\n> +    illumination = cell.find(\"illumination\")\n> +    vignetting = cell.find(\"vignetting\")\n> +\n> +    if name is None or resolution is None or \\\n> +       illumination is None or vignetting is None:\n> +        return\n> +\n> +    # Skip tables for smaller sensor resolutions\n> +    if res != sanitize(resolution.text):\n> +        return\n> +\n> +    # Skip tables for which we don't know how to translate the illuminant value\n> +    ct = illuminant_to_ct(illumination)\n> +    if ct is None:\n> +        return\n> +\n> +    # Only pick tables with vignetting == 70\n> +    if sanitize(vignetting.text) != \"[70]\":\n> +        return\n> +\n> +    lsc_cells.append(cell)\n> +\n> +\n> +# Get the \"LSC\" node\n> +def find_lsc_table(root):\n> +    sensor = root.find('sensor')\n> +    if sensor is None:\n> +        print(\"Failed to find \\\"sensor\\\" node in config file\")\n> +        raise Exception\n\n        raise RuntimeError('Failed to find \"sensor\" node in config file')\n\nand print the message in the caller. Same below.\n\n> +\n> +    lsc = sensor.find('LSC')\n> +    if lsc is None:\n> +        print(\"Filed to find \\\"LSC\\\" node in config file\")\n> +        raise Exception\n> +\n> +    return lsc\n> +\n> +# libcamera LSC algorithm only operates on a single resolution.\n> +# Find the largest sensor mode among the ones reported in the LSC tables\n> +\n> +\n> +def parse_max_res(cells):\n> +    max_res = \"\"\n> +    max_size = 0\n> +\n> +    for cell in cells:\n> +        resolution = sanitize(cell.find(\"resolution\").text)\n> +        [w, h] = resolution.split('x')\n> +\n> +        area = int(w) * int(h)\n> +        if area > max_size:\n> +            max_res = resolution\n> +\n> +    return max_res\n> +\n> +\n> +def main(argv):\n> +    # Parse command line arguments.\n> +    parser = argparse.ArgumentParser(\n> +        description='Parse Android camera configuration file to extract LSC tables')\n> +    parser.add_argument('--file', '-f', required=True,\n> +                        help='Path to the Android .xml configuration file')\n\nIt's quite traditional in command line tools to pass the input file as a\npositional argument instead of a named argument. Up to you.\n\n> +    args = parser.parse_args(argv[1:])\n> +\n> +    root = et.parse(args.file).getroot()\n\nIt would be nice to catch parse errors and print a human-readable\nmessage.\n\n> +    try:\n> +        lsc_node = find_lsc_table(root)\n> +    except Exception:\n> +        return 1\n> +\n> +    cells = lsc_node.findall(\"cell\")\n> +\n> +    max_res = parse_max_res(cells)\n> +    if max_res == \"\":\n> +        return\n> +\n> +    lsc_cells = []\n> +    for cell in cells:\n> +        filter_cells(cell, max_res, lsc_cells)\n> +\n> +    lsc_section = '''  - LensShadingCorrection:\n> +      x-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ]\n> +      y-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ]\n> +      sets:\n> +'''\n\nGiven that you're generating a rkisp1 tuning file, using the YamlOutput\nclass from libtuning would make sense.\n\n> +\n> +    for cell in lsc_cells:\n> +        lsc_section += print_cell(cell) + \"\\n\"\n> +\n> +    print(lsc_section)\n> +\n> +\n> +if __name__ == '__main__':\n> +    sys.exit(main(sys.argv))","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 3A789BE080\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue,  7 Mar 2023 09:49:22 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 75368626B4;\n\tTue,  7 Mar 2023 10:49:21 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id E720D6266A\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue,  7 Mar 2023 10:49:19 +0100 (CET)","from pendragon.ideasonboard.com\n\t(153.162-64-87.adsl-dyn.isp.belgacom.be [87.64.162.153])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 752BC4AD;\n\tTue,  7 Mar 2023 10:49:19 +0100 (CET)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1678182561;\n\tbh=qBdLQ6wPVgE2xcQWGw/3y4bfj4bP1czXGII74DPrHPg=;\n\th=Date:To:References:In-Reply-To:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc:\n\tFrom;\n\tb=0V3qYzBJrQqaGueH/6e1kdALcpq81tPxNE+eDuD3g0qJ9lB/evzd7WL17bOTcYUTC\n\toCNc3OWb9k2xjPPcEQq0ocoGZbqyf0B1t1/Ka/M6OnJJqd/3pheVR1hDBaGqg8z+KE\n\tRuP9q9Vr31868pxFdYrZCz6Dx88JFfabT4h8n2Zeogng1F/pF7ABGW24emwSwsv7lF\n\tOq9/OjMSDa8SNiNz8wOef7lpdbEZGHY7VPtamBhn4Qbk9lpN/4r/EzmHQuEYBkQXI+\n\tke/I6NhqpdE88ji2zcjmwiB48A2ZAK3fN1jNGHUt/wnOCl/KPHjnloL1/D0oXuwjAt\n\tpY9y0tziHeG+Q==","v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1678182559;\n\tbh=qBdLQ6wPVgE2xcQWGw/3y4bfj4bP1czXGII74DPrHPg=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=BSze4ITx8HzV4x5TbQjlK3tTnFf5SQpBFQ+GKLiAxCfvcGEDGHXkbmXdAR8zx+GSM\n\tME8HZaGU5TlaDUeOsP+E1nJtFUVsooBXRS6Yx2MAkdBJWmsD3ci6FYnAyRZ1YSqPdI\n\t2VwliAyGL1tF//5apu3ZePL2VcBc/TLC1rKTAu18="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"BSze4ITx\"; dkim-atps=neutral","Date":"Tue, 7 Mar 2023 11:49:23 +0200","To":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","Message-ID":"<20230307094923.GB22236@pendragon.ideasonboard.com>","References":"<20230306172440.57764-1-jacopo.mondi@ideasonboard.com>\n\t<20230306172440.57764-2-jacopo.mondi@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20230306172440.57764-2-jacopo.mondi@ideasonboard.com>","Subject":"Re: [libcamera-devel] [PATCH 1/3] utils: rkisp1: Add script to\n\textract LSC tables from Android","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","From":"Laurent Pinchart via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>","Reply-To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":26581,"web_url":"https://patchwork.libcamera.org/comment/26581/","msgid":"<20230307101205.GD22827@pendragon.ideasonboard.com>","date":"2023-03-07T10:12:05","subject":"Re: [libcamera-devel] [PATCH 1/3] utils: rkisp1: Add script to\n\textract LSC tables from Android","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Another comment.\n\nOn Tue, Mar 07, 2023 at 11:49:23AM +0200, Laurent Pinchart via libcamera-devel wrote:\n> Hi Jacopo,\n> \n> Thank you for the patch.\n> \n> On Mon, Mar 06, 2023 at 06:24:38PM +0100, Jacopo Mondi via libcamera-devel wrote:\n> > Android ship a per-sensor configuration file in .xml format.\n> \n> s/ship/ships/\n> \n> > \n> > The .xml file contains a main <matfile> node and a <sensor> sub-node\n> > which contains an <LSC> entry. The LSC tables there contained can be\n> > re-used for libcamera, by parsing them opportunely.\n> > \n> > Add a script to utils/rkisp1/ to extract the LSC tables from Android\n> > configuration file for the RkISP1 platform, modeled after the\n> > requirements of the Rockchip closed source IQ tuning module.\n> > \n> > Compared to the Rockchip IQ LSC module the one implemented in libcamera\n> > is slightly simpler, and the parsing of the LSC table takes that into\n> > account by:\n> > - Only outputting tables for the larger found sensor resolution\n> > - Only outputting tables for \"vignetting\" value == 70 (ignoring the ones\n> >   for vignetting values of 100)\n> > \n> > The script outputs to stdout a \"LensShadingCorrection\" section that\n> > can be directly pasted in a libcamera sensor configuration file.\n> \n> Could we generate a full tuning file instead ? I would imagine that\n> other tuning data could be (later) extracted, it would be nice to\n> prepare for that.\n> \n> > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n> > ---\n> >  utils/rkisp1/lsc_parse_android_config.py | 187 +++++++++++++++++++++++\n> \n> And we could already name the script in a more generic way, to hint that\n> it converts a Rockchip tuning file to a libcamera tuning file.\n> \n> Do you know if the XML format is Android-specific ?\n> \n> >  1 file changed, 187 insertions(+)\n> >  create mode 100755 utils/rkisp1/lsc_parse_android_config.py\n> > \n> > diff --git a/utils/rkisp1/lsc_parse_android_config.py b/utils/rkisp1/lsc_parse_android_config.py\n> > new file mode 100755\n> > index 000000000000..a7c2c160319d\n> > --- /dev/null\n> > +++ b/utils/rkisp1/lsc_parse_android_config.py\n> > @@ -0,0 +1,187 @@\n> > +#!/usr/bin/env python\n> > +\n> > +# SPDX-License-Identifier: GPL-2.0-or-later\n> > +# Copyright (C) 2023, Jacopo Mondi - Ideas on Board Oy\n> > +#\n> > +# Parse Android .xml configuration file and extract the LSC tables.\n> > +#\n> > +# Print to standard output a \"LensShadingCorrection\" section, understandable by\n> > +# libcamera LSC algorithm, that can be pasted to the sensor configuration file.\n> > +\n> > +import argparse\n> > +import string\n> > +import sys\n> > +import re\n> \n> Alphabetical order please.\n> \n> > +import xml.etree.ElementTree as et\n> > +\n> > +\n> > +def sanitize(name):\n> > +    return re.sub(r\"[\\n\\t\\s]*\", \"\", name)\n> \n> We use single quotes for strings when there's no specific reason to do\n> otherwise.\n> \n> > +\n> > +\n> > +def split_table(table):\n> > +    values = \"\"\n> > +    for v in table.text.strip(' ').split():\n> > +        values += v.strip('[').strip(']') + \", \"\n> > +    return values\n> > +\n> > +\n> > +def print_cell(cell):\n> > +    lsc_template = string.Template('''        #${name} - ${illuminant}\n\nMissing space after the #\n\n> > +        - ct: ${ct}\n> > +          resolution: ${res}\n> > +          r: [${red}]\n> > +          gr: [${greenr}]\n> > +          gb: [${greenb}]\n> > +          b: [${blue}]''')\n> > +\n> > +    illuminant = cell.find(\"illumination\")\n> > +    ct = illuminant_to_ct(illuminant)\n> > +\n> > +    template_dict = {\n> > +        'name': sanitize(cell.find(\"name\").text),\n> > +        'illuminant': sanitize(illuminant.text),\n> > +        'ct': ct,\n> > +        'res': sanitize(cell.find(\"resolution\").text)\n> > +    }\n> > +\n> > +    red_table = cell.find(\"LSC_SAMPLES_red\")\n> > +    greenr_table = cell.find(\"LSC_SAMPLES_greenR\")\n> > +    greenb_table = cell.find(\"LSC_SAMPLES_greenB\")\n> > +    blue_table = cell.find(\"LSC_SAMPLES_blue\")\n> > +\n> > +    if red_table is None or greenr_table is None or greenb_table is None or blue_table is None:\n> > +        return\n> > +\n> > +    template_dict['red'] = split_table(red_table)\n> > +    template_dict['greenr'] = split_table(greenr_table)\n> > +    template_dict['greenb'] = split_table(greenb_table)\n> > +    template_dict['blue'] = split_table(blue_table)\n> > +\n> > +    return lsc_template.substitute(template_dict)\n> > +\n> > +\n> > +def illuminant_to_ct(illuminant):\n> > +    # Standard CIE Illiminants to Color Temperature in Kelvin\n> > +    # https://en.wikipedia.org/wiki/Standard_illuminant\n> > +    #\n> > +    # Not included (and then ignored when parsing the configuration file):\n> > +    # - \"Horizon\" == D50 == 5003\n> > +    # - \"BW\" == ?\n> > +    # - \"PREFLASH\" == ?\n> > +    illuminants_dict = {\n> > +        'A': 2856,\n> > +        'D50': 5003,\n> > +        'D65': 6504,\n> > +        'D75': 7504,\n> > +        'F11_TL84': 4000,\n> > +        'F2_CWF': 4230,\n> > +    }\n> > +\n> > +    ill_key = sanitize(illuminant.text)\n> > +    try:\n> > +        ct = illuminants_dict[ill_key]\n> > +    except KeyError:\n> > +        return None\n> > +\n> > +    return ct\n> > +\n> > +\n> > +# Make sure the cell is well formed and return it\n> > +def filter_cells(cell, res, lsc_cells):\n> > +    name = cell.find(\"name\")\n> > +    resolution = cell.find(\"resolution\")\n> > +    illumination = cell.find(\"illumination\")\n> > +    vignetting = cell.find(\"vignetting\")\n> > +\n> > +    if name is None or resolution is None or \\\n> > +       illumination is None or vignetting is None:\n> > +        return\n> > +\n> > +    # Skip tables for smaller sensor resolutions\n> > +    if res != sanitize(resolution.text):\n> > +        return\n> > +\n> > +    # Skip tables for which we don't know how to translate the illuminant value\n> > +    ct = illuminant_to_ct(illumination)\n> > +    if ct is None:\n> > +        return\n> > +\n> > +    # Only pick tables with vignetting == 70\n> > +    if sanitize(vignetting.text) != \"[70]\":\n> > +        return\n> > +\n> > +    lsc_cells.append(cell)\n> > +\n> > +\n> > +# Get the \"LSC\" node\n> > +def find_lsc_table(root):\n> > +    sensor = root.find('sensor')\n> > +    if sensor is None:\n> > +        print(\"Failed to find \\\"sensor\\\" node in config file\")\n> > +        raise Exception\n> \n>         raise RuntimeError('Failed to find \"sensor\" node in config file')\n> \n> and print the message in the caller. Same below.\n> \n> > +\n> > +    lsc = sensor.find('LSC')\n> > +    if lsc is None:\n> > +        print(\"Filed to find \\\"LSC\\\" node in config file\")\n> > +        raise Exception\n> > +\n> > +    return lsc\n> > +\n> > +# libcamera LSC algorithm only operates on a single resolution.\n> > +# Find the largest sensor mode among the ones reported in the LSC tables\n> > +\n> > +\n> > +def parse_max_res(cells):\n> > +    max_res = \"\"\n> > +    max_size = 0\n> > +\n> > +    for cell in cells:\n> > +        resolution = sanitize(cell.find(\"resolution\").text)\n> > +        [w, h] = resolution.split('x')\n> > +\n> > +        area = int(w) * int(h)\n> > +        if area > max_size:\n> > +            max_res = resolution\n> > +\n> > +    return max_res\n> > +\n> > +\n> > +def main(argv):\n> > +    # Parse command line arguments.\n> > +    parser = argparse.ArgumentParser(\n> > +        description='Parse Android camera configuration file to extract LSC tables')\n> > +    parser.add_argument('--file', '-f', required=True,\n> > +                        help='Path to the Android .xml configuration file')\n> \n> It's quite traditional in command line tools to pass the input file as a\n> positional argument instead of a named argument. Up to you.\n> \n> > +    args = parser.parse_args(argv[1:])\n> > +\n> > +    root = et.parse(args.file).getroot()\n> \n> It would be nice to catch parse errors and print a human-readable\n> message.\n> \n> > +    try:\n> > +        lsc_node = find_lsc_table(root)\n> > +    except Exception:\n> > +        return 1\n> > +\n> > +    cells = lsc_node.findall(\"cell\")\n> > +\n> > +    max_res = parse_max_res(cells)\n> > +    if max_res == \"\":\n> > +        return\n> > +\n> > +    lsc_cells = []\n> > +    for cell in cells:\n> > +        filter_cells(cell, max_res, lsc_cells)\n> > +\n> > +    lsc_section = '''  - LensShadingCorrection:\n> > +      x-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ]\n> > +      y-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ]\n> > +      sets:\n> > +'''\n> \n> Given that you're generating a rkisp1 tuning file, using the YamlOutput\n> class from libtuning would make sense.\n> \n> > +\n> > +    for cell in lsc_cells:\n> > +        lsc_section += print_cell(cell) + \"\\n\"\n> > +\n> > +    print(lsc_section)\n> > +\n> > +\n> > +if __name__ == '__main__':\n> > +    sys.exit(main(sys.argv))\n> \n> -- \n> Regards,\n> \n> Laurent Pinchart","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 0EB11BDE17\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue,  7 Mar 2023 10:12:04 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 5C106626A7;\n\tTue,  7 Mar 2023 11:12:03 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 7271A6266A\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue,  7 Mar 2023 11:12:01 +0100 (CET)","from pendragon.ideasonboard.com\n\t(153.162-64-87.adsl-dyn.isp.belgacom.be [87.64.162.153])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id E467D4AD;\n\tTue,  7 Mar 2023 11:12:00 +0100 (CET)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1678183923;\n\tbh=Ba2ZJqMll5y0sjBH18CyzG36O8MGHDDzeLb0u3ofSiQ=;\n\th=Date:To:References:In-Reply-To:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:\n\tFrom;\n\tb=Vcj2OR1iltwPARUfMCDajC68kg2jEdjQeLrtSaeWBH6QS5aiLtOLj62FZrBEjrNi4\n\txCGXlMBbEKYApU3NXo9HFs+7kXvTzZta6CpQMx+yIzaDJQV7SX59QUQJISEVBCub3M\n\t6ktZKPpBeDADFwKr/ivb83IneRojGEnVgj9QmJ6AN+xL7eEN9fXTXXbFSddd9e1c33\n\t0IVXeF0zr8koxK+rcxHlARpTghXYEIrxPHX4+1UOfTRorhFnIR0ed766/JB5xVs42O\n\tBEnk5OMNDl4cwsEZ5K9DOUJEyiDXQ0HWbFMpc1PNV/hFGwvs9Xddye05MH6nfkmJSy\n\tqzD4St3vJkCwg==","v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1678183921;\n\tbh=Ba2ZJqMll5y0sjBH18CyzG36O8MGHDDzeLb0u3ofSiQ=;\n\th=Date:From:To:Subject:References:In-Reply-To:From;\n\tb=MQAbX03dR92ymOpr1e8yoRS6OSKnvYnzq+63I0vFhfLg6Z2M9fjM22uBO3zpF8rwB\n\t98QqTMR9LOh0683fv6T2ko9AAmpM/PvUXA9USZkKqqr9XKrd028kM3cSl4IOPJgZHT\n\tcnJYgjCDnb8s+Bq7il6n9jWlKwce3zW6YUkUmbfk="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"MQAbX03d\"; dkim-atps=neutral","Date":"Tue, 7 Mar 2023 12:12:05 +0200","To":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","Message-ID":"<20230307101205.GD22827@pendragon.ideasonboard.com>","References":"<20230306172440.57764-1-jacopo.mondi@ideasonboard.com>\n\t<20230306172440.57764-2-jacopo.mondi@ideasonboard.com>\n\t<20230307094923.GB22236@pendragon.ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20230307094923.GB22236@pendragon.ideasonboard.com>","Subject":"Re: [libcamera-devel] [PATCH 1/3] utils: rkisp1: Add script to\n\textract LSC tables from Android","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","From":"Laurent Pinchart via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>","Reply-To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":26586,"web_url":"https://patchwork.libcamera.org/comment/26586/","msgid":"<20230307134937.mevug26nao5c2xxm@uno.localdomain>","date":"2023-03-07T13:49:37","subject":"Re: [libcamera-devel] [PATCH 1/3] utils: rkisp1: Add script to\n\textract LSC tables from Android","submitter":{"id":143,"url":"https://patchwork.libcamera.org/api/people/143/","name":"Jacopo Mondi","email":"jacopo.mondi@ideasonboard.com"},"content":"Hi Laurent\n\nOn Tue, Mar 07, 2023 at 11:49:23AM +0200, Laurent Pinchart via libcamera-devel wrote:\n> Hi Jacopo,\n>\n> Thank you for the patch.\n>\n> On Mon, Mar 06, 2023 at 06:24:38PM +0100, Jacopo Mondi via libcamera-devel wrote:\n> > Android ship a per-sensor configuration file in .xml format.\n>\n> s/ship/ships/\n>\n> >\n> > The .xml file contains a main <matfile> node and a <sensor> sub-node\n> > which contains an <LSC> entry. The LSC tables there contained can be\n> > re-used for libcamera, by parsing them opportunely.\n> >\n> > Add a script to utils/rkisp1/ to extract the LSC tables from Android\n> > configuration file for the RkISP1 platform, modeled after the\n> > requirements of the Rockchip closed source IQ tuning module.\n> >\n> > Compared to the Rockchip IQ LSC module the one implemented in libcamera\n> > is slightly simpler, and the parsing of the LSC table takes that into\n> > account by:\n> > - Only outputting tables for the larger found sensor resolution\n> > - Only outputting tables for \"vignetting\" value == 70 (ignoring the ones\n> >   for vignetting values of 100)\n> >\n> > The script outputs to stdout a \"LensShadingCorrection\" section that\n> > can be directly pasted in a libcamera sensor configuration file.\n>\n> Could we generate a full tuning file instead ? I would imagine that\n> other tuning data could be (later) extracted, it would be nice to\n> prepare for that.\n>\n\nI'm not sure about this.. how should the other algorithms already\ndescribed in a config file be handled ? are we going to overwrite\neverything ?\n\n> > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n> > ---\n> >  utils/rkisp1/lsc_parse_android_config.py | 187 +++++++++++++++++++++++\n>\n> And we could already name the script in a more generic way, to hint that\n> it converts a Rockchip tuning file to a libcamera tuning file.\n>\n> Do you know if the XML format is Android-specific ?\n\nI presume is specific to what I think is the Rockchip 3A closed source library.\nThe IPU3 in example, uses a different (binary) format for the sensor\ntuning paramters. So I guess is the closed source 3A module that\ndefines the format.\n\nSo maybe yes, \"android\" should not be part of the name\n\nlsc_IQ_to_libcamera.py ?\n\n>\n> >  1 file changed, 187 insertions(+)\n> >  create mode 100755 utils/rkisp1/lsc_parse_android_config.py\n> >\n> > diff --git a/utils/rkisp1/lsc_parse_android_config.py b/utils/rkisp1/lsc_parse_android_config.py\n> > new file mode 100755\n> > index 000000000000..a7c2c160319d\n> > --- /dev/null\n> > +++ b/utils/rkisp1/lsc_parse_android_config.py\n> > @@ -0,0 +1,187 @@\n> > +#!/usr/bin/env python\n> > +\n> > +# SPDX-License-Identifier: GPL-2.0-or-later\n> > +# Copyright (C) 2023, Jacopo Mondi - Ideas on Board Oy\n> > +#\n> > +# Parse Android .xml configuration file and extract the LSC tables.\n> > +#\n> > +# Print to standard output a \"LensShadingCorrection\" section, understandable by\n> > +# libcamera LSC algorithm, that can be pasted to the sensor configuration file.\n> > +\n> > +import argparse\n> > +import string\n> > +import sys\n> > +import re\n>\n> Alphabetical order please.\n>\n> > +import xml.etree.ElementTree as et\n> > +\n> > +\n> > +def sanitize(name):\n> > +    return re.sub(r\"[\\n\\t\\s]*\", \"\", name)\n>\n> We use single quotes for strings when there's no specific reason to do\n> otherwise.\n>\n> > +\n> > +\n> > +def split_table(table):\n> > +    values = \"\"\n> > +    for v in table.text.strip(' ').split():\n> > +        values += v.strip('[').strip(']') + \", \"\n> > +    return values\n> > +\n> > +\n> > +def print_cell(cell):\n> > +    lsc_template = string.Template('''        #${name} - ${illuminant}\n> > +        - ct: ${ct}\n> > +          resolution: ${res}\n> > +          r: [${red}]\n> > +          gr: [${greenr}]\n> > +          gb: [${greenb}]\n> > +          b: [${blue}]''')\n> > +\n> > +    illuminant = cell.find(\"illumination\")\n> > +    ct = illuminant_to_ct(illuminant)\n> > +\n> > +    template_dict = {\n> > +        'name': sanitize(cell.find(\"name\").text),\n> > +        'illuminant': sanitize(illuminant.text),\n> > +        'ct': ct,\n> > +        'res': sanitize(cell.find(\"resolution\").text)\n> > +    }\n> > +\n> > +    red_table = cell.find(\"LSC_SAMPLES_red\")\n> > +    greenr_table = cell.find(\"LSC_SAMPLES_greenR\")\n> > +    greenb_table = cell.find(\"LSC_SAMPLES_greenB\")\n> > +    blue_table = cell.find(\"LSC_SAMPLES_blue\")\n> > +\n> > +    if red_table is None or greenr_table is None or greenb_table is None or blue_table is None:\n> > +        return\n> > +\n> > +    template_dict['red'] = split_table(red_table)\n> > +    template_dict['greenr'] = split_table(greenr_table)\n> > +    template_dict['greenb'] = split_table(greenb_table)\n> > +    template_dict['blue'] = split_table(blue_table)\n> > +\n> > +    return lsc_template.substitute(template_dict)\n> > +\n> > +\n> > +def illuminant_to_ct(illuminant):\n> > +    # Standard CIE Illiminants to Color Temperature in Kelvin\n> > +    # https://en.wikipedia.org/wiki/Standard_illuminant\n> > +    #\n> > +    # Not included (and then ignored when parsing the configuration file):\n> > +    # - \"Horizon\" == D50 == 5003\n> > +    # - \"BW\" == ?\n> > +    # - \"PREFLASH\" == ?\n> > +    illuminants_dict = {\n> > +        'A': 2856,\n> > +        'D50': 5003,\n> > +        'D65': 6504,\n> > +        'D75': 7504,\n> > +        'F11_TL84': 4000,\n> > +        'F2_CWF': 4230,\n> > +    }\n> > +\n> > +    ill_key = sanitize(illuminant.text)\n> > +    try:\n> > +        ct = illuminants_dict[ill_key]\n> > +    except KeyError:\n> > +        return None\n> > +\n> > +    return ct\n> > +\n> > +\n> > +# Make sure the cell is well formed and return it\n> > +def filter_cells(cell, res, lsc_cells):\n> > +    name = cell.find(\"name\")\n> > +    resolution = cell.find(\"resolution\")\n> > +    illumination = cell.find(\"illumination\")\n> > +    vignetting = cell.find(\"vignetting\")\n> > +\n> > +    if name is None or resolution is None or \\\n> > +       illumination is None or vignetting is None:\n> > +        return\n> > +\n> > +    # Skip tables for smaller sensor resolutions\n> > +    if res != sanitize(resolution.text):\n> > +        return\n> > +\n> > +    # Skip tables for which we don't know how to translate the illuminant value\n> > +    ct = illuminant_to_ct(illumination)\n> > +    if ct is None:\n> > +        return\n> > +\n> > +    # Only pick tables with vignetting == 70\n> > +    if sanitize(vignetting.text) != \"[70]\":\n> > +        return\n> > +\n> > +    lsc_cells.append(cell)\n> > +\n> > +\n> > +# Get the \"LSC\" node\n> > +def find_lsc_table(root):\n> > +    sensor = root.find('sensor')\n> > +    if sensor is None:\n> > +        print(\"Failed to find \\\"sensor\\\" node in config file\")\n> > +        raise Exception\n>\n>         raise RuntimeError('Failed to find \"sensor\" node in config file')\n>\n> and print the message in the caller. Same below.\n>\n> > +\n> > +    lsc = sensor.find('LSC')\n> > +    if lsc is None:\n> > +        print(\"Filed to find \\\"LSC\\\" node in config file\")\n> > +        raise Exception\n> > +\n> > +    return lsc\n> > +\n> > +# libcamera LSC algorithm only operates on a single resolution.\n> > +# Find the largest sensor mode among the ones reported in the LSC tables\n> > +\n> > +\n> > +def parse_max_res(cells):\n> > +    max_res = \"\"\n> > +    max_size = 0\n> > +\n> > +    for cell in cells:\n> > +        resolution = sanitize(cell.find(\"resolution\").text)\n> > +        [w, h] = resolution.split('x')\n> > +\n> > +        area = int(w) * int(h)\n> > +        if area > max_size:\n> > +            max_res = resolution\n> > +\n> > +    return max_res\n> > +\n> > +\n> > +def main(argv):\n> > +    # Parse command line arguments.\n> > +    parser = argparse.ArgumentParser(\n> > +        description='Parse Android camera configuration file to extract LSC tables')\n> > +    parser.add_argument('--file', '-f', required=True,\n> > +                        help='Path to the Android .xml configuration file')\n>\n> It's quite traditional in command line tools to pass the input file as a\n> positional argument instead of a named argument. Up to you.\n>\n> > +    args = parser.parse_args(argv[1:])\n> > +\n> > +    root = et.parse(args.file).getroot()\n>\n> It would be nice to catch parse errors and print a human-readable\n> message.\n>\n> > +    try:\n> > +        lsc_node = find_lsc_table(root)\n> > +    except Exception:\n> > +        return 1\n> > +\n> > +    cells = lsc_node.findall(\"cell\")\n> > +\n> > +    max_res = parse_max_res(cells)\n> > +    if max_res == \"\":\n> > +        return\n> > +\n> > +    lsc_cells = []\n> > +    for cell in cells:\n> > +        filter_cells(cell, max_res, lsc_cells)\n> > +\n> > +    lsc_section = '''  - LensShadingCorrection:\n> > +      x-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ]\n> > +      y-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ]\n> > +      sets:\n> > +'''\n>\n> Given that you're generating a rkisp1 tuning file, using the YamlOutput\n> class from libtuning would make sense.\n>\n\nIf we get to add more algorithms yes, it might make sense\n\n> > +\n> > +    for cell in lsc_cells:\n> > +        lsc_section += print_cell(cell) + \"\\n\"\n> > +\n> > +    print(lsc_section)\n> > +\n> > +\n> > +if __name__ == '__main__':\n> > +    sys.exit(main(sys.argv))\n>\n> --\n> Regards,\n>\n> Laurent Pinchart","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 1A602BE080\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue,  7 Mar 2023 13:49:43 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 7B5DB62693;\n\tTue,  7 Mar 2023 14:49:42 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 2EF226265E\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue,  7 Mar 2023 14:49:41 +0100 (CET)","from ideasonboard.com (host-79-47-54-87.retail.telecomitalia.it\n\t[79.47.54.87])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 5A7254AD;\n\tTue,  7 Mar 2023 14:49:40 +0100 (CET)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1678196982;\n\tbh=pwKdLlVLDDpUMMpzldwp9kptm6QhkydsEVAfiCixbWQ=;\n\th=Date:To:References:In-Reply-To:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc:\n\tFrom;\n\tb=KoKGiwxa00ohiHBC0QpUPv0263+AXF7abYzSZ8jIIsHLJTJ4BSMKNzy5ex65qGSSY\n\tHhXjW9BI/YGQiX/dxORCiMl+yVnL2U8oUmOyJBaC4iZ0Qek+t+Zv0zxofiwYC+vqK3\n\t7aqvKWyxVJrkXhFCVRGUd5lTwiPb3bWzHqqhgRMfpsSsOEQB24vT8P9Jm+3jSlQaFt\n\tnSeYfTyVJuQKLY7f44c48YH2uIz8NJQ9zZ+PY/iYJHnFu/Hy1T7uB4QwzgCTODQdz/\n\t7bOqDusjRvVQbIRI4WB21qfLhrGsK3S0voFPah7EdbcvU6HTZFrRm63MM/+xLLAB5U\n\tPnVQuisYqgwVw==","v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1678196980;\n\tbh=pwKdLlVLDDpUMMpzldwp9kptm6QhkydsEVAfiCixbWQ=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=Ol8t00xY0HJ5zln8EDGiNSuliZP253RKWSXbAX0wIoO9Lq8b52PSEF+DCxSfGLqdH\n\t9VasJbC4LcGXxDCT5uwoRScuU/Kb36ZZzEydyXvCbJi+nSkGi8ROfNJ5rlGDowD8lU\n\tV8/CCPp+P7MyYandsBwx5VSgijLg+v6j3QcVd1Gk="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"Ol8t00xY\"; dkim-atps=neutral","Date":"Tue, 7 Mar 2023 14:49:37 +0100","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Message-ID":"<20230307134937.mevug26nao5c2xxm@uno.localdomain>","References":"<20230306172440.57764-1-jacopo.mondi@ideasonboard.com>\n\t<20230306172440.57764-2-jacopo.mondi@ideasonboard.com>\n\t<20230307094923.GB22236@pendragon.ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20230307094923.GB22236@pendragon.ideasonboard.com>","Subject":"Re: [libcamera-devel] [PATCH 1/3] utils: rkisp1: Add script to\n\textract LSC tables from Android","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","From":"Jacopo Mondi via libcamera-devel <libcamera-devel@lists.libcamera.org>","Reply-To":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","Cc":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":26590,"web_url":"https://patchwork.libcamera.org/comment/26590/","msgid":"<20230307141529.GI22827@pendragon.ideasonboard.com>","date":"2023-03-07T14:15:29","subject":"Re: [libcamera-devel] [PATCH 1/3] utils: rkisp1: Add script to\n\textract LSC tables from Android","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Jacopo,\n\nOn Tue, Mar 07, 2023 at 02:49:37PM +0100, Jacopo Mondi wrote:\n> On Tue, Mar 07, 2023 at 11:49:23AM +0200, Laurent Pinchart via libcamera-devel wrote:\n> > On Mon, Mar 06, 2023 at 06:24:38PM +0100, Jacopo Mondi via libcamera-devel wrote:\n> > > Android ship a per-sensor configuration file in .xml format.\n> >\n> > s/ship/ships/\n> >\n> > > The .xml file contains a main <matfile> node and a <sensor> sub-node\n> > > which contains an <LSC> entry. The LSC tables there contained can be\n> > > re-used for libcamera, by parsing them opportunely.\n> > >\n> > > Add a script to utils/rkisp1/ to extract the LSC tables from Android\n> > > configuration file for the RkISP1 platform, modeled after the\n> > > requirements of the Rockchip closed source IQ tuning module.\n> > >\n> > > Compared to the Rockchip IQ LSC module the one implemented in libcamera\n> > > is slightly simpler, and the parsing of the LSC table takes that into\n> > > account by:\n> > > - Only outputting tables for the larger found sensor resolution\n> > > - Only outputting tables for \"vignetting\" value == 70 (ignoring the ones\n> > >   for vignetting values of 100)\n> > >\n> > > The script outputs to stdout a \"LensShadingCorrection\" section that\n> > > can be directly pasted in a libcamera sensor configuration file.\n> >\n> > Could we generate a full tuning file instead ? I would imagine that\n> > other tuning data could be (later) extracted, it would be nice to\n> > prepare for that.\n> \n> I'm not sure about this.. how should the other algorithms already\n> described in a config file be handled ? are we going to overwrite\n> everything ?\n\nI'd say the tool should output a full tuning file, skipping algorithms\nthat it doesn't handle, and then a human can pick individual algorithms\nfrom the output file and copy&paste them to the target tuning file.\n\n> > > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n> > > ---\n> > >  utils/rkisp1/lsc_parse_android_config.py | 187 +++++++++++++++++++++++\n> >\n> > And we could already name the script in a more generic way, to hint that\n> > it converts a Rockchip tuning file to a libcamera tuning file.\n> >\n> > Do you know if the XML format is Android-specific ?\n> \n> I presume is specific to what I think is the Rockchip 3A closed source library.\n> The IPU3 in example, uses a different (binary) format for the sensor\n> tuning paramters. So I guess is the closed source 3A module that\n> defines the format.\n\nThat's my guess too.\n\nhttps://gitlab.com/firefly-linux/external/camera_engine_rkaiq/-/tree/rk3588/firefly\nmay provide some interesting information.\n\n> So maybe yes, \"android\" should not be part of the name\n> \n> lsc_IQ_to_libcamera.py ?\n\nI'd go for rkisp1_aiq_to_libcamera.py (or with dashes instead of\nunderscores).\n\n> > >  1 file changed, 187 insertions(+)\n> > >  create mode 100755 utils/rkisp1/lsc_parse_android_config.py\n> > >\n> > > diff --git a/utils/rkisp1/lsc_parse_android_config.py b/utils/rkisp1/lsc_parse_android_config.py\n> > > new file mode 100755\n> > > index 000000000000..a7c2c160319d\n> > > --- /dev/null\n> > > +++ b/utils/rkisp1/lsc_parse_android_config.py\n> > > @@ -0,0 +1,187 @@\n> > > +#!/usr/bin/env python\n> > > +\n> > > +# SPDX-License-Identifier: GPL-2.0-or-later\n> > > +# Copyright (C) 2023, Jacopo Mondi - Ideas on Board Oy\n> > > +#\n> > > +# Parse Android .xml configuration file and extract the LSC tables.\n> > > +#\n> > > +# Print to standard output a \"LensShadingCorrection\" section, understandable by\n> > > +# libcamera LSC algorithm, that can be pasted to the sensor configuration file.\n> > > +\n> > > +import argparse\n> > > +import string\n> > > +import sys\n> > > +import re\n> >\n> > Alphabetical order please.\n> >\n> > > +import xml.etree.ElementTree as et\n> > > +\n> > > +\n> > > +def sanitize(name):\n> > > +    return re.sub(r\"[\\n\\t\\s]*\", \"\", name)\n> >\n> > We use single quotes for strings when there's no specific reason to do\n> > otherwise.\n> >\n> > > +\n> > > +\n> > > +def split_table(table):\n> > > +    values = \"\"\n> > > +    for v in table.text.strip(' ').split():\n> > > +        values += v.strip('[').strip(']') + \", \"\n> > > +    return values\n> > > +\n> > > +\n> > > +def print_cell(cell):\n> > > +    lsc_template = string.Template('''        #${name} - ${illuminant}\n> > > +        - ct: ${ct}\n> > > +          resolution: ${res}\n> > > +          r: [${red}]\n> > > +          gr: [${greenr}]\n> > > +          gb: [${greenb}]\n> > > +          b: [${blue}]''')\n> > > +\n> > > +    illuminant = cell.find(\"illumination\")\n> > > +    ct = illuminant_to_ct(illuminant)\n> > > +\n> > > +    template_dict = {\n> > > +        'name': sanitize(cell.find(\"name\").text),\n> > > +        'illuminant': sanitize(illuminant.text),\n> > > +        'ct': ct,\n> > > +        'res': sanitize(cell.find(\"resolution\").text)\n> > > +    }\n> > > +\n> > > +    red_table = cell.find(\"LSC_SAMPLES_red\")\n> > > +    greenr_table = cell.find(\"LSC_SAMPLES_greenR\")\n> > > +    greenb_table = cell.find(\"LSC_SAMPLES_greenB\")\n> > > +    blue_table = cell.find(\"LSC_SAMPLES_blue\")\n> > > +\n> > > +    if red_table is None or greenr_table is None or greenb_table is None or blue_table is None:\n> > > +        return\n> > > +\n> > > +    template_dict['red'] = split_table(red_table)\n> > > +    template_dict['greenr'] = split_table(greenr_table)\n> > > +    template_dict['greenb'] = split_table(greenb_table)\n> > > +    template_dict['blue'] = split_table(blue_table)\n> > > +\n> > > +    return lsc_template.substitute(template_dict)\n> > > +\n> > > +\n> > > +def illuminant_to_ct(illuminant):\n> > > +    # Standard CIE Illiminants to Color Temperature in Kelvin\n> > > +    # https://en.wikipedia.org/wiki/Standard_illuminant\n> > > +    #\n> > > +    # Not included (and then ignored when parsing the configuration file):\n> > > +    # - \"Horizon\" == D50 == 5003\n> > > +    # - \"BW\" == ?\n> > > +    # - \"PREFLASH\" == ?\n> > > +    illuminants_dict = {\n> > > +        'A': 2856,\n> > > +        'D50': 5003,\n> > > +        'D65': 6504,\n> > > +        'D75': 7504,\n> > > +        'F11_TL84': 4000,\n> > > +        'F2_CWF': 4230,\n> > > +    }\n> > > +\n> > > +    ill_key = sanitize(illuminant.text)\n> > > +    try:\n> > > +        ct = illuminants_dict[ill_key]\n> > > +    except KeyError:\n> > > +        return None\n> > > +\n> > > +    return ct\n> > > +\n> > > +\n> > > +# Make sure the cell is well formed and return it\n> > > +def filter_cells(cell, res, lsc_cells):\n> > > +    name = cell.find(\"name\")\n> > > +    resolution = cell.find(\"resolution\")\n> > > +    illumination = cell.find(\"illumination\")\n> > > +    vignetting = cell.find(\"vignetting\")\n> > > +\n> > > +    if name is None or resolution is None or \\\n> > > +       illumination is None or vignetting is None:\n> > > +        return\n> > > +\n> > > +    # Skip tables for smaller sensor resolutions\n> > > +    if res != sanitize(resolution.text):\n> > > +        return\n> > > +\n> > > +    # Skip tables for which we don't know how to translate the illuminant value\n> > > +    ct = illuminant_to_ct(illumination)\n> > > +    if ct is None:\n> > > +        return\n> > > +\n> > > +    # Only pick tables with vignetting == 70\n> > > +    if sanitize(vignetting.text) != \"[70]\":\n> > > +        return\n> > > +\n> > > +    lsc_cells.append(cell)\n> > > +\n> > > +\n> > > +# Get the \"LSC\" node\n> > > +def find_lsc_table(root):\n> > > +    sensor = root.find('sensor')\n> > > +    if sensor is None:\n> > > +        print(\"Failed to find \\\"sensor\\\" node in config file\")\n> > > +        raise Exception\n> >\n> >         raise RuntimeError('Failed to find \"sensor\" node in config file')\n> >\n> > and print the message in the caller. Same below.\n> >\n> > > +\n> > > +    lsc = sensor.find('LSC')\n> > > +    if lsc is None:\n> > > +        print(\"Filed to find \\\"LSC\\\" node in config file\")\n> > > +        raise Exception\n> > > +\n> > > +    return lsc\n> > > +\n> > > +# libcamera LSC algorithm only operates on a single resolution.\n> > > +# Find the largest sensor mode among the ones reported in the LSC tables\n> > > +\n> > > +\n> > > +def parse_max_res(cells):\n> > > +    max_res = \"\"\n> > > +    max_size = 0\n> > > +\n> > > +    for cell in cells:\n> > > +        resolution = sanitize(cell.find(\"resolution\").text)\n> > > +        [w, h] = resolution.split('x')\n> > > +\n> > > +        area = int(w) * int(h)\n> > > +        if area > max_size:\n> > > +            max_res = resolution\n> > > +\n> > > +    return max_res\n> > > +\n> > > +\n> > > +def main(argv):\n> > > +    # Parse command line arguments.\n> > > +    parser = argparse.ArgumentParser(\n> > > +        description='Parse Android camera configuration file to extract LSC tables')\n> > > +    parser.add_argument('--file', '-f', required=True,\n> > > +                        help='Path to the Android .xml configuration file')\n> >\n> > It's quite traditional in command line tools to pass the input file as a\n> > positional argument instead of a named argument. Up to you.\n> >\n> > > +    args = parser.parse_args(argv[1:])\n> > > +\n> > > +    root = et.parse(args.file).getroot()\n> >\n> > It would be nice to catch parse errors and print a human-readable\n> > message.\n> >\n> > > +    try:\n> > > +        lsc_node = find_lsc_table(root)\n> > > +    except Exception:\n> > > +        return 1\n> > > +\n> > > +    cells = lsc_node.findall(\"cell\")\n> > > +\n> > > +    max_res = parse_max_res(cells)\n> > > +    if max_res == \"\":\n> > > +        return\n> > > +\n> > > +    lsc_cells = []\n> > > +    for cell in cells:\n> > > +        filter_cells(cell, max_res, lsc_cells)\n> > > +\n> > > +    lsc_section = '''  - LensShadingCorrection:\n> > > +      x-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ]\n> > > +      y-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ]\n> > > +      sets:\n> > > +'''\n> >\n> > Given that you're generating a rkisp1 tuning file, using the YamlOutput\n> > class from libtuning would make sense.\n> \n> If we get to add more algorithms yes, it might make sense\n> \n> > > +\n> > > +    for cell in lsc_cells:\n> > > +        lsc_section += print_cell(cell) + \"\\n\"\n> > > +\n> > > +    print(lsc_section)\n> > > +\n> > > +\n> > > +if __name__ == '__main__':\n> > > +    sys.exit(main(sys.argv))","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 218FCBDE17\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue,  7 Mar 2023 14:15:28 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id DB3D9626A7;\n\tTue,  7 Mar 2023 15:15:27 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 49DE062693\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue,  7 Mar 2023 15:15:26 +0100 (CET)","from pendragon.ideasonboard.com\n\t(153.162-64-87.adsl-dyn.isp.belgacom.be [87.64.162.153])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id E084C4AD;\n\tTue,  7 Mar 2023 15:15:25 +0100 (CET)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1678198527;\n\tbh=ntijN34uyNzT3i3+2NLbr0fldjL3X9JKWTjcuVi/h7A=;\n\th=Date:To:References:In-Reply-To:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc:\n\tFrom;\n\tb=VBfErKFDfTDeXmkbWDgHVmu2JgV2k2HHpRRh/k0lGwIVcDGH3H2LfX9yRJe/UARd3\n\tcTUgvOSRAKwUGX+4jGXBxZLqmFok2QnS1mCdOgiETQa3qDj1RILCrt0GjfdS6J0Xzh\n\t43YRIlAwrqTTWloYZvyiWrSnJO0fPLGGdtIUI1WMgDqT2eFjjsjTsYNIOicx/02mBq\n\tnEZ0qqjj4AsVcOqjnEDfleAV+mc/ipQIyzA8k+K6kOr5BoaXZfoEHFD1zKID74s8Gt\n\truDpWe2n+BiesppTmowM0wj2N4VQ7CGk/Vc4BM38rZDYTH5a9ljob7ZYkxK9z1e1aN\n\tZT4jBjqyB1ymg==","v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1678198526;\n\tbh=ntijN34uyNzT3i3+2NLbr0fldjL3X9JKWTjcuVi/h7A=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=P87K2HSt9z/Khi24G95suqUgLOPt6E5SRs1oUzkra8Po8u5ZQT1tJHIAzEJuYGMJC\n\t8x0o3zBaiPKNcYyYgPWirZr4iUMwqhnxXPMTP0wkYYTDkNCCi/DSieOVMd6LmclsfS\n\tlko4VCkSiUO1kHyw3Nr+R9i5j8SPZsKYgxWe7PyA="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"P87K2HSt\"; dkim-atps=neutral","Date":"Tue, 7 Mar 2023 16:15:29 +0200","To":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","Message-ID":"<20230307141529.GI22827@pendragon.ideasonboard.com>","References":"<20230306172440.57764-1-jacopo.mondi@ideasonboard.com>\n\t<20230306172440.57764-2-jacopo.mondi@ideasonboard.com>\n\t<20230307094923.GB22236@pendragon.ideasonboard.com>\n\t<20230307134937.mevug26nao5c2xxm@uno.localdomain>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20230307134937.mevug26nao5c2xxm@uno.localdomain>","Subject":"Re: [libcamera-devel] [PATCH 1/3] utils: rkisp1: Add script to\n\textract LSC tables from Android","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","From":"Laurent Pinchart via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>","Reply-To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]