Message ID | 20221022062310.2545463-7-paul.elder@ideasonboard.com |
---|---|
State | Accepted |
Headers | show |
Series |
|
Related | show |
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@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@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@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)
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@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)) + + +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)
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@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