[libcamera-devel] [PATCH v2 06/11] utils: libtuning: generators: Add raspberrypi output

Laurent Pinchart laurent.pinchart at ideasonboard.com
Wed Nov 9 11:34:25 CET 2022


Hi Paul,

Thank you for the patch.

On Sat, Oct 22, 2022 at 03:23:05PM +0900, Paul Elder via libcamera-devel wrote:
> Add a generator to libtuning for writing tuning output to a json file
> formatted the same way that raspberrypi's ctt formats them.
> 
> Signed-off-by: Paul Elder <paul.elder at ideasonboard.com>
> 
> ---
> Changes in v2:
> - add SPDX and copyright
> - fix style
> - move the 'rpi.' prefix of module names in the tuning output from here
>   to into the Modules' out_name field
> ---
>  utils/tuning/libtuning/generators/__init__.py |   5 +
>  .../generators/raspberrypi_output.py          | 115 ++++++++++++++++++
>  2 files changed, 120 insertions(+)
>  create mode 100644 utils/tuning/libtuning/generators/raspberrypi_output.py
> 
> diff --git a/utils/tuning/libtuning/generators/__init__.py b/utils/tuning/libtuning/generators/__init__.py
> index e69de29b..937aff30 100644
> --- a/utils/tuning/libtuning/generators/__init__.py
> +++ b/utils/tuning/libtuning/generators/__init__.py
> @@ -0,0 +1,5 @@
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +#
> +# Copyright (C) 2022, Paul Elder <paul.elder at ideasonboard.com>
> +
> +from libtuning.generators.raspberrypi_output import RaspberryPiOutput
> diff --git a/utils/tuning/libtuning/generators/raspberrypi_output.py b/utils/tuning/libtuning/generators/raspberrypi_output.py
> new file mode 100644
> index 00000000..e06aeddf
> --- /dev/null
> +++ b/utils/tuning/libtuning/generators/raspberrypi_output.py
> @@ -0,0 +1,115 @@
> +# SPDX-License-Identifier: BSD-2-Clause
> +#
> +# Copyright 2022 Raspberry Pi Ltd
> +#
> +# Script to pretty print a Raspberry Pi tuning config JSON structure in
> +# version 2.0 and later formats.
> +# (Copied from ctt_pretty_print_json.py)
> +
> +from .generator import Generator
> +
> +import json
> +from pathlib import Path
> +import textwrap
> +
> +
> +class RaspberryPiOutput(Generator):
> +    def __init__(self):
> +        super().__init__()
> +
> +    def _write(self, output_file: Path, output_dict: dict, output_order: list):
> +        # Write json dictionary to file using ctt's version 2 format
> +        out_json = {
> +            "version": 2.0,
> +            'target': 'bcm2835',
> +            "algorithms": [{f'{module.out_name}': output_dict[module]} for module in output_order]
> +        }
> +
> +        with open(output_file, 'w') as f:
> +            f.write(pretty_print(out_json))

Please move this class to the end of the file. I would also possibly
make the pretty_print function a (private) class member.

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

> +
> +
> +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
> +        }
> +
> +    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:
> +                # 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:
> +                    # 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:
> +            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')
> +
> +    return json.dumps(in_json, cls=Encoder, indent=4, sort_keys=False)

-- 
Regards,

Laurent Pinchart


More information about the libcamera-devel mailing list