summaryrefslogtreecommitdiff
path: root/utils/tuning/libtuning/generators
diff options
context:
space:
mode:
Diffstat (limited to 'utils/tuning/libtuning/generators')
-rw-r--r--utils/tuning/libtuning/generators/__init__.py6
-rw-r--r--utils/tuning/libtuning/generators/generator.py15
-rw-r--r--utils/tuning/libtuning/generators/raspberrypi_output.py114
-rw-r--r--utils/tuning/libtuning/generators/yaml_output.py123
4 files changed, 258 insertions, 0 deletions
diff --git a/utils/tuning/libtuning/generators/__init__.py b/utils/tuning/libtuning/generators/__init__.py
new file mode 100644
index 00000000..f28b6149
--- /dev/null
+++ b/utils/tuning/libtuning/generators/__init__.py
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
+
+from libtuning.generators.raspberrypi_output import RaspberryPiOutput
+from libtuning.generators.yaml_output import YamlOutput
diff --git a/utils/tuning/libtuning/generators/generator.py b/utils/tuning/libtuning/generators/generator.py
new file mode 100644
index 00000000..77a8ba4a
--- /dev/null
+++ b/utils/tuning/libtuning/generators/generator.py
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
+#
+# Base class for a generator to convert dict to tuning file
+
+from pathlib import Path
+
+
+class Generator(object):
+ def __init__(self):
+ pass
+
+ def write(self, output_path: Path, output_dict: dict, output_order: list):
+ raise NotImplementedError
diff --git a/utils/tuning/libtuning/generators/raspberrypi_output.py b/utils/tuning/libtuning/generators/raspberrypi_output.py
new file mode 100644
index 00000000..47b49059
--- /dev/null
+++ b/utils/tuning/libtuning/generators/raspberrypi_output.py
@@ -0,0 +1,114 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright 2022 Raspberry Pi Ltd
+#
+# Generate tuning file in Raspberry Pi's json format
+#
+# (Copied from ctt_pretty_print_json.py)
+
+from .generator import Generator
+
+import json
+from pathlib import Path
+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
+ }
+
+ 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)
+
+
+class RaspberryPiOutput(Generator):
+ def __init__(self):
+ super().__init__()
+
+ def _pretty_print(self, 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)
+
+ 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(self._pretty_print(out_json))
diff --git a/utils/tuning/libtuning/generators/yaml_output.py b/utils/tuning/libtuning/generators/yaml_output.py
new file mode 100644
index 00000000..8f22d386
--- /dev/null
+++ b/utils/tuning/libtuning/generators/yaml_output.py
@@ -0,0 +1,123 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright 2022 Paul Elder <paul.elder@ideasonboard.com>
+#
+# Generate tuning file in YAML format
+
+from .generator import Generator
+
+from numbers import Number
+from pathlib import Path
+
+import libtuning.utils as utils
+
+
+class YamlOutput(Generator):
+ def __init__(self):
+ super().__init__()
+
+ def _stringify_number_list(self, listt: list):
+ line_wrap = 80
+
+ line = '[ ' + ', '.join([str(x) for x in listt]) + ' ]'
+ if len(line) <= line_wrap:
+ return [line]
+
+ out_lines = ['[']
+ line = ' '
+ for x in listt:
+ x_str = str(x)
+ # If the first number is longer than line_wrap, it'll add an extra line
+ if len(line) + len(x_str) > line_wrap:
+ out_lines.append(line)
+ line = f' {x_str},'
+ continue
+ line += f' {x_str},'
+ out_lines.append(line)
+ out_lines.append(']')
+
+ return out_lines
+
+ # @return Array of lines, and boolean of if all elements were numbers
+ def _stringify_list(self, listt: list):
+ out_lines = []
+
+ all_numbers = set([isinstance(x, Number) for x in listt]).issubset({True})
+
+ if all_numbers:
+ return self._stringify_number_list(listt), True
+
+ for value in listt:
+ if isinstance(value, Number):
+ out_lines.append(f'- {str(value)}')
+ elif isinstance(value, str):
+ out_lines.append(f'- "{value}"')
+ elif isinstance(value, list):
+ lines, all_numbers = self._stringify_list(value)
+
+ if all_numbers:
+ out_lines.append( f'- {lines[0]}')
+ out_lines += [f' {line}' for line in lines[1:]]
+ else:
+ out_lines.append( f'-')
+ out_lines += [f' {line}' for line in lines]
+ elif isinstance(value, dict):
+ lines = self._stringify_dict(value)
+ out_lines.append( f'- {lines[0]}')
+ out_lines += [f' {line}' for line in lines[1:]]
+
+ return out_lines, False
+
+ def _stringify_dict(self, dictt: dict):
+ out_lines = []
+
+ for key in dictt:
+ value = dictt[key]
+
+ if isinstance(value, Number):
+ out_lines.append(f'{key}: {str(value)}')
+ elif isinstance(value, str):
+ out_lines.append(f'{key}: "{value}"')
+ elif isinstance(value, list):
+ lines, all_numbers = self._stringify_list(value)
+
+ if all_numbers:
+ out_lines.append( f'{key}: {lines[0]}')
+ out_lines += [f'{" " * (len(key) + 2)}{line}' for line in lines[1:]]
+ else:
+ out_lines.append( f'{key}:')
+ out_lines += [f' {line}' for line in lines]
+ elif isinstance(value, dict):
+ lines = self._stringify_dict(value)
+ out_lines.append( f'{key}:')
+ out_lines += [f' {line}' for line in lines]
+
+ return out_lines
+
+ def write(self, output_file: Path, output_dict: dict, output_order: list):
+ out_lines = [
+ '%YAML 1.1',
+ '---',
+ 'version: 1',
+ # No need to condition this, as libtuning already guarantees that
+ # we have at least one module. Even if the module has no output,
+ # its prescence is meaningful.
+ 'algorithms:'
+ ]
+
+ for module in output_order:
+ out_lines.append(f' - {module.out_name}:')
+
+ if len(output_dict[module]) == 0:
+ continue
+
+ if not isinstance(output_dict[module], dict):
+ utils.eprint(f'Error: Output of {module.type} is not a dictionary')
+ continue
+
+ lines = self._stringify_dict(output_dict[module])
+ out_lines += [f' {line}' for line in lines]
+
+ with open(output_file, 'w', encoding='utf-8') as f:
+ for line in out_lines:
+ f.write(f'{line}\n')