From patchwork Wed Jul 27 02:38:12 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 16822 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id DBC1CBE173 for ; Wed, 27 Jul 2022 02:38:34 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3FBE56332A; Wed, 27 Jul 2022 04:38:34 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1658889514; bh=J17VEa9ido8On/0VbfT25SuSYSoEfdZ+cSN4prEujs4=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=N4YQLPOXLY4BGH3qibt6EnXIFjXoIJ4RHxa1adovnymT+Gwj7KoWwPcZdivJHVbQI /EH1kQXivKTq5K+DGTUSvJz2U6VvzX2Ylj+eyng8ztQjXOBcVxJ9BphpkWYGsCRBew EJALW3OSrkvzyDTWTYJnvDwvX9PQIjPQl+y7m4kgnvqyKa6kAVHylyFCHBIr5lKftS a388o1oPpcKRuJFRyvwec6CtJDar3xF5waNLYcNlswR0QmI5HgohEI66Ebg8IM5BQS KEsooFv2N8IcK0Cx3/HGdgnT51nRih2hBGYuyvCd4nXHfYx+w1LCivcMou6JHzDpfG Rk5xTZ7PBn9gQ== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id D91F263315 for ; Wed, 27 Jul 2022 04:38:29 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="UcWBPjFT"; dkim-atps=neutral Received: from pendragon.ideasonboard.com (62-78-145-57.bb.dnainternet.fi [62.78.145.57]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 74F88835; Wed, 27 Jul 2022 04:38:29 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1658889509; bh=J17VEa9ido8On/0VbfT25SuSYSoEfdZ+cSN4prEujs4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=UcWBPjFTJ0YW0zZDi4gelsIlVOBuXM+55JMvq2Ltvvw4p4tAIDlAfVcvf9aonthW/ iPw4caM+3+fQmYjrppyQTh2Pnh5IWOjIR7Bba/V7xhEqRRAdVWOjkGXiqZQoNXRc7F oiZcmBgoWvOtq1PaXlBGcg9HVRPVik9/WJ0SwQFU= To: libcamera-devel@lists.libcamera.org Date: Wed, 27 Jul 2022 05:38:12 +0300 Message-Id: <20220727023816.30008-11-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20220727023816.30008-1-laurent.pinchart@ideasonboard.com> References: <20220727023816.30008-1-laurent.pinchart@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v7 10/14] utils: raspberrypi: ctt: Output version 2.0 format tuning files X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Laurent Pinchart via libcamera-devel From: Laurent Pinchart Reply-To: Laurent Pinchart Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" From: Naushir Patuck 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 Reviewed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart --- utils/raspberrypi/ctt/ctt.py | 18 +- .../raspberrypi/ctt/ctt_pretty_print_json.py | 188 +++++++++--------- 2 files changed, 110 insertions(+), 96 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 13765b5d52f6..df98db80ad4f 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 0176aec63028..3e3b84752907 --- 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 Ltd +# Copyright 2022 Raspberry Pi Ltd # -# ctt_pretty_print_json.py - camera tuning tool JSON formatter +# Script to pretty print a Raspberry Pi tuning config JSON structure in +# version 2.0 and later formats. -import sys +import argparse +import json +import textwrap -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, +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) - def print(self, string): - for c in string: - self.process_char(c) - self.newline() + @property + def indent_str(self) -> str: + return ' ' * self.indentation_level * self.indent + def iterencode(self, o, **kwargs): + return self.encode(o) -def pretty_print_json(str_in, output_filename): - with open(output_filename, "w") as fout: - printer = JSONPrettyPrinter(fout) - printer.print(str_in) +def pretty_print(in_json: dict) -> str: -if __name__ == '__main__': - if len(sys.argv) != 2: - print("Usage: %s filename" % sys.argv[0]) - sys.exit(1) + 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') - input_filename = sys.argv[1] - with open(input_filename, "r") as fin: - printer = JSONPrettyPrinter(sys.stdout) - printer.print(fin.read()) + return json.dumps(in_json, cls=Encoder, indent=4, sort_keys=False) + + +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) + + out_json = pretty_print(in_json) + + with open(args.output if args.output is not None else args.input, 'w') as f: + f.write(out_json)