[libcamera-devel] [PATCH v6 6/8] utils: raspberrypi: ctt: Output version 2.0 format tuning files

Laurent Pinchart laurent.pinchart at ideasonboard.com
Sat Jul 23 18:43:29 CEST 2022


Hi Naush,

Thank you for the patch.

On Mon, Jul 18, 2022 at 09:16:00AM +0100, Naushir Patuck via libcamera-devel wrote:
> Update the ctt_pretty_print_json.py script to generate the new version 2.0
> format camera tuning file. This script can be called through the command line
> to prettify an existing JSON file, or programatically by the CTT to format a
> new JSON config dictionary.
> 
> Update the CTT to produce a version 2.0 format json structure and use
> ctt_pretty_print_json.pretty_print to prettify the output.
> 
> Signed-off-by: Naushir Patuck <naush at raspberrypi.com>

Reviewed-by: Laurent Pinchart <laurent.pinchart at ideasonboard.com>

> ---
>  utils/raspberrypi/ctt/ctt.py                  |  18 +-
>  .../raspberrypi/ctt/ctt_pretty_print_json.py  | 194 +++++++++---------
>  2 files changed, 113 insertions(+), 99 deletions(-)
>  mode change 100644 => 100755 utils/raspberrypi/ctt/ctt_pretty_print_json.py
> 
> diff --git a/utils/raspberrypi/ctt/ctt.py b/utils/raspberrypi/ctt/ctt.py
> index 15064634c67f..0ec5b49016df 100755
> --- a/utils/raspberrypi/ctt/ctt.py
> +++ b/utils/raspberrypi/ctt/ctt.py
> @@ -15,7 +15,7 @@ from ctt_alsc import *
>  from ctt_lux import *
>  from ctt_noise import *
>  from ctt_geq import *
> -from ctt_pretty_print_json import *
> +from ctt_pretty_print_json import pretty_print
>  import random
>  import json
>  import re
> @@ -511,13 +511,17 @@ class Camera:
>      """
>      def write_json(self):
>          """
> -        Write json dictionary to file
> +        Write json dictionary to file using our version 2 format
>          """
> -        jstring = json.dumps(self.json, sort_keys=False)
> -        """
> -        make it pretty :)
> -        """
> -        pretty_print_json(jstring, self.jf)
> +
> +        out_json = {
> +            "version": 2.0,
> +            'target': 'bcm2835',
> +            "algorithms": [{name: data} for name, data in self.json.items()],
> +        }
> +
> +        with open(self.jf, 'w') as f:
> +            f.write(pretty_print(out_json))
>  
>      """
>      add a new section to the log file
> diff --git a/utils/raspberrypi/ctt/ctt_pretty_print_json.py b/utils/raspberrypi/ctt/ctt_pretty_print_json.py
> old mode 100644
> new mode 100755
> index d38ae6178524..ee5e8e10e437
> --- a/utils/raspberrypi/ctt/ctt_pretty_print_json.py
> +++ b/utils/raspberrypi/ctt/ctt_pretty_print_json.py
> @@ -1,106 +1,116 @@
> +#!/usr/bin/env python3
> +#
>  # SPDX-License-Identifier: BSD-2-Clause
>  #
> -# Copyright (C) 2019, Raspberry Pi (Trading) Limited
> +# Script to pretty print a Raspberry Pi tuning config JSON structure in
> +# version 2.0 and later formats.
>  #
> -# ctt_pretty_print_json.py - camera tuning tool JSON formatter
> -
> -import sys
> -
> -
> -class JSONPrettyPrinter(object):
> -    """
> -    Take a collapsed JSON file and make it more readable
> -    """
> -    def __init__(self, fout):
> -        self.state = {
> -            "indent": 0,
> -            "inarray": [False],
> -            "arraycount": [],
> -            "skipnewline": True,
> -            "need_indent": False,
> -            "need_space": False,
> +# Copyright 2022 Raspberry Pi Ltd.
> +
> +import argparse
> +import json
> +import textwrap
> +
> +
> +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
>          }
>  
> -        self.fout = fout
> -
> -    def newline(self):
> -        if not self.state["skipnewline"]:
> -            self.fout.write('\n')
> -            self.state["need_indent"] = True
> -            self.state["need_space"] = False
> -        self.state["skipnewline"] = True
> -
> -    def write(self, c):
> -        if self.state["need_indent"]:
> -            self.fout.write(' ' * self.state["indent"] * 4)
> -            self.state["need_indent"] = False
> -        if self.state["need_space"]:
> -            self.fout.write(' ')
> -            self.state["need_space"] = False
> -        self.fout.write(c)
> -        self.state["skipnewline"] = False
> -
> -    def process_char(self, c):
> -        if c == '{':
> -            self.newline()
> -            self.write(c)
> -            self.state["indent"] += 1
> -            self.newline()
> -        elif c == '}':
> -            self.state["indent"] -= 1
> -            self.newline()
> -            self.write(c)
> -        elif c == '[':
> -            self.newline()
> -            self.write(c)
> -            self.state["indent"] += 1
> -            self.newline()
> -            self.state["inarray"] = [True] + self.state["inarray"]
> -            self.state["arraycount"] = [0] + self.state["arraycount"]
> -        elif c == ']':
> -            self.state["indent"] -= 1
> -            self.newline()
> -            self.state["inarray"].pop(0)
> -            self.state["arraycount"].pop(0)
> -            self.write(c)
> -        elif c == ':':
> -            self.write(c)
> -            self.state["need_space"] = True
> -        elif c == ',':
> -            if not self.state["inarray"][0]:
> -                self.write(c)
> -                self.newline()
> +    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:
> -                self.write(c)
> -                self.state["arraycount"][0] += 1
> -                if self.state["arraycount"][0] == 16:
> -                    self.state["arraycount"][0] = 0
> -                    self.newline()
> +                # 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:
> -                    self.state["need_space"] = True
> -        elif c.isspace():
> -            pass
> +                    # 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:
> -            self.write(c)
> +            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')
>  
> -    def print(self, string):
> -        for c in string:
> -            self.process_char(c)
> -        self.newline()
> +    return json.dumps(in_json, cls=Encoder, indent=4, sort_keys=False)
>  
>  
> -def pretty_print_json(str_in, output_filename):
> -    with open(output_filename, "w") as fout:
> -        printer = JSONPrettyPrinter(fout)
> -        printer.print(str_in)
> +if __name__ == "__main__":
> +    parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, description=
> +                    'Prettify a version 2.0 camera tuning config JSON file.')
> +    parser.add_argument('input', type=str, help='Input tuning file.')
> +    parser.add_argument('output', type=str, nargs='?',
> +                        help='Output converted tuning file. If not provided, the input file will be updated in-place.',
> +                        default=None)
> +    args = parser.parse_args()
>  
> +    with open(args.input, 'r') as f:
> +        in_json = json.load(f)
>  
> -if __name__ == '__main__':
> -    if len(sys.argv) != 2:
> -        print("Usage: %s filename" % sys.argv[0])
> -        sys.exit(1)
> +    out_json = pretty_print(in_json)
>  
> -    input_filename = sys.argv[1]
> -    with open(input_filename, "r") as fin:
> -        printer = JSONPrettyPrinter(sys.stdout)
> -        printer.print(fin.read())
> +    with open(args.output if args.output is not None else args.input, 'w') as f:
> +        f.write(out_json)

-- 
Regards,

Laurent Pinchart


More information about the libcamera-devel mailing list