Show a patch.

GET /api/1.1/patches/16673/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 16673,
    "url": "https://patchwork.libcamera.org/api/1.1/patches/16673/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/16673/",
    "project": {
        "id": 1,
        "url": "https://patchwork.libcamera.org/api/1.1/projects/1/?format=api",
        "name": "libcamera",
        "link_name": "libcamera",
        "list_id": "libcamera_core",
        "list_email": "libcamera-devel@lists.libcamera.org",
        "web_url": "",
        "scm_url": "",
        "webscm_url": ""
    },
    "msgid": "<20220718081602.32535-7-naush@raspberrypi.com>",
    "date": "2022-07-18T08:16:00",
    "name": "[libcamera-devel,v6,6/8] utils: raspberrypi: ctt: Output version 2.0 format tuning files",
    "commit_ref": null,
    "pull_url": null,
    "state": "accepted",
    "archived": false,
    "hash": "a819b24c662de15bda14bd5c7047d830efbffd50",
    "submitter": {
        "id": 34,
        "url": "https://patchwork.libcamera.org/api/1.1/people/34/?format=api",
        "name": "Naushir Patuck",
        "email": "naush@raspberrypi.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/16673/mbox/",
    "series": [
        {
            "id": 3293,
            "url": "https://patchwork.libcamera.org/api/1.1/series/3293/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=3293",
            "date": "2022-07-18T08:15:54",
            "name": "Replace boost JSON parser with libyaml in Raspberry Pi IPA",
            "version": 6,
            "mbox": "https://patchwork.libcamera.org/series/3293/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/16673/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/16673/checks/",
    "tags": {},
    "headers": {
        "Return-Path": "<libcamera-devel-bounces@lists.libcamera.org>",
        "X-Original-To": "parsemail@patchwork.libcamera.org",
        "Delivered-To": "parsemail@patchwork.libcamera.org",
        "Received": [
            "from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 93D15BD1F1\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 18 Jul 2022 08:16:15 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 255856332A;\n\tMon, 18 Jul 2022 10:16:15 +0200 (CEST)",
            "from mail-wr1-x436.google.com (mail-wr1-x436.google.com\n\t[IPv6:2a00:1450:4864:20::436])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 4019B6048A\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 18 Jul 2022 10:16:11 +0200 (CEST)",
            "by mail-wr1-x436.google.com with SMTP id bu1so15871437wrb.9\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 18 Jul 2022 01:16:11 -0700 (PDT)",
            "from naush-laptop.localdomain ([93.93.133.154])\n\tby smtp.gmail.com with ESMTPSA id\n\ta15-20020adffb8f000000b0021dbac444a7sm10206795wrr.59.2022.07.18.01.16.09\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tMon, 18 Jul 2022 01:16:09 -0700 (PDT)"
        ],
        "DKIM-Signature": [
            "v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1658132175;\n\tbh=y9QEoioXKnqCMjHNtfAWEm/WKvhWfBTuVbqwgrko3aI=;\n\th=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:\n\tFrom;\n\tb=bZSenOfYQDwyXtahjiGiRsK+t6qWHpRnu7/eR3yCbN8QTE/r0pDFy1dbX32c+Z1sR\n\t9OesYRq5XcWdJ1Jl3GIvtBtD5elFW6fb8ZCkJ1iQIffqeAkc0UDg/Jl1vZzBJ7rPTP\n\toAkUgRt++Sd5dkt3sh1sFr5Vkp6VRjsB8PocdhfTPk/LlV+/82smoUiUK/EcqczW5H\n\tr36HEndG2sSFSuEBcyJCFwFYqHJ+M4YGPxxZknHASm3ZenxeB3v2wZu2fNbXlOyWkW\n\tTLMq2/vUmIANx5vVUScN9d9ar3BqJImAN5UBDSJaEpYt7cPtRne6JZL9iUqq5H4zOo\n\tVN+FM3VHv91Rw==",
            "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google;\n\th=from:to:cc:subject:date:message-id:in-reply-to:references\n\t:mime-version:content-transfer-encoding;\n\tbh=lKai8tSSfjJFtWqCINHlH3zNlWNwg7ZRxTrsnBkmxuI=;\n\tb=NQVpsMWdMm0q/8QCP5E/llxsi5r2Ei37UFt+u8UuQzOorc0sMmNpQ1rK8Z69A8+D1w\n\tDVSc4lkIQW86aTa45gWlgaflHFWFqwMN0oXW0OuWtYF/PAo9eoQR6qr3WOqFDMVeORGO\n\tHx0svj/BMp6lKe9hCpZrDT0I2zEgyxUuTtyXu1k5v5uD7cP71TM5iIcq/fRnaar1IaM5\n\td0P0SHuQajphocfo0dFF2vTg5lP+AoHRo6cozC2pbivi8DFyvb2ti08fIMPk+Ebh44y7\n\tlSwODYtigfZoFUgQLy5HO8P1X+BItELP5ELvUUMUMofZoVriYtOiwu9rp+aTFmmSrmLi\n\tfLrw=="
        ],
        "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (2048-bit key; \n\tunprotected) header.d=raspberrypi.com\n\theader.i=@raspberrypi.com\n\theader.b=\"NQVpsMWd\"; dkim-atps=neutral",
        "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20210112;\n\th=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to\n\t:references:mime-version:content-transfer-encoding;\n\tbh=lKai8tSSfjJFtWqCINHlH3zNlWNwg7ZRxTrsnBkmxuI=;\n\tb=BaZ8YIVHBNZgRqhJ9DRtJWt5nQ98e/W/ntf4j+yvgcIhC/bP4x2bEWqcdg6KaH/ksD\n\t+ek0G00Iw0nkBe28Gb0b92LKoxKXcLG48BaoQjP1bLhIruN0a6PhxrlJDIPDVfeN6Mhz\n\tLBFrofuLHAuM3560a50AeHLbR1eqK27bFLGSCXz86EPbWANYV1GQ3peBbNjplzqlW0tP\n\tJajUkAzNvBTC3grx1yUiqVUGDesZ5DB5jm4u4lwpb8gWHxZkyZTPdWW3N0CfHE6nY6Y2\n\t/s3Cyyn3jLy4/qvckTu3C5WRU0Iag2fVaJlk6FJhJqxvUl7L57ToebTz72cJ6L4r231f\n\t+ULg==",
        "X-Gm-Message-State": "AJIora+u/pv6OlQh1H8mObCkADOY3VLfQDJoFkBgsQPsstuSdqyXcRhJ\n\tV9J39P6Uuh5w43d8wxf58ETgfCFTcrgU5Q==",
        "X-Google-Smtp-Source": "AGRyM1v4IGsjPC2MBEBjj7NcGmiUWD6Rr7+4yn0WSw+yZ9966MUDxibRAFUKlRMRlaZuJTJtHbAcxQ==",
        "X-Received": "by 2002:a05:6000:250:b0:21d:b3b5:3438 with SMTP id\n\tm16-20020a056000025000b0021db3b53438mr20242055wrz.203.1658132170604; \n\tMon, 18 Jul 2022 01:16:10 -0700 (PDT)",
        "To": "libcamera-devel@lists.libcamera.org",
        "Date": "Mon, 18 Jul 2022 09:16:00 +0100",
        "Message-Id": "<20220718081602.32535-7-naush@raspberrypi.com>",
        "X-Mailer": "git-send-email 2.25.1",
        "In-Reply-To": "<20220718081602.32535-1-naush@raspberrypi.com>",
        "References": "<20220718081602.32535-1-naush@raspberrypi.com>",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit",
        "Subject": "[libcamera-devel] [PATCH v6 6/8] utils: raspberrypi: ctt: Output\n\tversion 2.0 format tuning files",
        "X-BeenThere": "libcamera-devel@lists.libcamera.org",
        "X-Mailman-Version": "2.1.29",
        "Precedence": "list",
        "List-Id": "<libcamera-devel.lists.libcamera.org>",
        "List-Unsubscribe": "<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>",
        "List-Archive": "<https://lists.libcamera.org/pipermail/libcamera-devel/>",
        "List-Post": "<mailto:libcamera-devel@lists.libcamera.org>",
        "List-Help": "<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>",
        "List-Subscribe": "<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>",
        "From": "Naushir Patuck via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>",
        "Reply-To": "Naushir Patuck <naush@raspberrypi.com>",
        "Errors-To": "libcamera-devel-bounces@lists.libcamera.org",
        "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"
    },
    "content": "Update the ctt_pretty_print_json.py script to generate the new version 2.0\nformat camera tuning file. This script can be called through the command line\nto prettify an existing JSON file, or programatically by the CTT to format a\nnew JSON config dictionary.\n\nUpdate the CTT to produce a version 2.0 format json structure and use\nctt_pretty_print_json.pretty_print to prettify the output.\n\nSigned-off-by: Naushir Patuck <naush@raspberrypi.com>\n---\n utils/raspberrypi/ctt/ctt.py                  |  18 +-\n .../raspberrypi/ctt/ctt_pretty_print_json.py  | 194 +++++++++---------\n 2 files changed, 113 insertions(+), 99 deletions(-)\n mode change 100644 => 100755 utils/raspberrypi/ctt/ctt_pretty_print_json.py",
    "diff": "diff --git a/utils/raspberrypi/ctt/ctt.py b/utils/raspberrypi/ctt/ctt.py\nindex 15064634c67f..0ec5b49016df 100755\n--- a/utils/raspberrypi/ctt/ctt.py\n+++ b/utils/raspberrypi/ctt/ctt.py\n@@ -15,7 +15,7 @@ from ctt_alsc import *\n from ctt_lux import *\n from ctt_noise import *\n from ctt_geq import *\n-from ctt_pretty_print_json import *\n+from ctt_pretty_print_json import pretty_print\n import random\n import json\n import re\n@@ -511,13 +511,17 @@ class Camera:\n     \"\"\"\n     def write_json(self):\n         \"\"\"\n-        Write json dictionary to file\n+        Write json dictionary to file using our version 2 format\n         \"\"\"\n-        jstring = json.dumps(self.json, sort_keys=False)\n-        \"\"\"\n-        make it pretty :)\n-        \"\"\"\n-        pretty_print_json(jstring, self.jf)\n+\n+        out_json = {\n+            \"version\": 2.0,\n+            'target': 'bcm2835',\n+            \"algorithms\": [{name: data} for name, data in self.json.items()],\n+        }\n+\n+        with open(self.jf, 'w') as f:\n+            f.write(pretty_print(out_json))\n \n     \"\"\"\n     add a new section to the log file\ndiff --git a/utils/raspberrypi/ctt/ctt_pretty_print_json.py b/utils/raspberrypi/ctt/ctt_pretty_print_json.py\nold mode 100644\nnew mode 100755\nindex d38ae6178524..ee5e8e10e437\n--- a/utils/raspberrypi/ctt/ctt_pretty_print_json.py\n+++ b/utils/raspberrypi/ctt/ctt_pretty_print_json.py\n@@ -1,106 +1,116 @@\n+#!/usr/bin/env python3\n+#\n # SPDX-License-Identifier: BSD-2-Clause\n #\n-# Copyright (C) 2019, Raspberry Pi (Trading) Limited\n+# Script to pretty print a Raspberry Pi tuning config JSON structure in\n+# version 2.0 and later formats.\n #\n-# ctt_pretty_print_json.py - camera tuning tool JSON formatter\n-\n-import sys\n-\n-\n-class JSONPrettyPrinter(object):\n-    \"\"\"\n-    Take a collapsed JSON file and make it more readable\n-    \"\"\"\n-    def __init__(self, fout):\n-        self.state = {\n-            \"indent\": 0,\n-            \"inarray\": [False],\n-            \"arraycount\": [],\n-            \"skipnewline\": True,\n-            \"need_indent\": False,\n-            \"need_space\": False,\n+# Copyright 2022 Raspberry Pi Ltd.\n+\n+import argparse\n+import json\n+import textwrap\n+\n+\n+class Encoder(json.JSONEncoder):\n+\n+    def __init__(self, *args, **kwargs):\n+        super().__init__(*args, **kwargs)\n+        self.indentation_level = 0\n+        self.hard_break = 120\n+        self.custom_elems = {\n+            'table': 16,\n+            'luminance_lut': 16,\n+            'ct_curve': 3,\n+            'ccm': 3,\n+            'gamma_curve': 2,\n+            'y_target': 2,\n+            'prior': 2\n         }\n \n-        self.fout = fout\n-\n-    def newline(self):\n-        if not self.state[\"skipnewline\"]:\n-            self.fout.write('\\n')\n-            self.state[\"need_indent\"] = True\n-            self.state[\"need_space\"] = False\n-        self.state[\"skipnewline\"] = True\n-\n-    def write(self, c):\n-        if self.state[\"need_indent\"]:\n-            self.fout.write(' ' * self.state[\"indent\"] * 4)\n-            self.state[\"need_indent\"] = False\n-        if self.state[\"need_space\"]:\n-            self.fout.write(' ')\n-            self.state[\"need_space\"] = False\n-        self.fout.write(c)\n-        self.state[\"skipnewline\"] = False\n-\n-    def process_char(self, c):\n-        if c == '{':\n-            self.newline()\n-            self.write(c)\n-            self.state[\"indent\"] += 1\n-            self.newline()\n-        elif c == '}':\n-            self.state[\"indent\"] -= 1\n-            self.newline()\n-            self.write(c)\n-        elif c == '[':\n-            self.newline()\n-            self.write(c)\n-            self.state[\"indent\"] += 1\n-            self.newline()\n-            self.state[\"inarray\"] = [True] + self.state[\"inarray\"]\n-            self.state[\"arraycount\"] = [0] + self.state[\"arraycount\"]\n-        elif c == ']':\n-            self.state[\"indent\"] -= 1\n-            self.newline()\n-            self.state[\"inarray\"].pop(0)\n-            self.state[\"arraycount\"].pop(0)\n-            self.write(c)\n-        elif c == ':':\n-            self.write(c)\n-            self.state[\"need_space\"] = True\n-        elif c == ',':\n-            if not self.state[\"inarray\"][0]:\n-                self.write(c)\n-                self.newline()\n+    def encode(self, o, node_key=None):\n+        if isinstance(o, (list, tuple)):\n+            # Check if we are a flat list of numbers.\n+            if not any(isinstance(el, (list, tuple, dict)) for el in o):\n+                s = ', '.join(json.dumps(el) for el in o)\n+                if node_key in self.custom_elems.keys():\n+                    # Special case handling to specify number of elements in a row for tables, ccm, etc.\n+                    self.indentation_level += 1\n+                    sl = s.split(', ')\n+                    num = self.custom_elems[node_key]\n+                    chunk = [self.indent_str + ', '.join(sl[x:x + num]) for x in range(0, len(sl), num)]\n+                    t = ',\\n'.join(chunk)\n+                    self.indentation_level -= 1\n+                    output = f'\\n{self.indent_str}[\\n{t}\\n{self.indent_str}]'\n+                elif len(s) > self.hard_break - len(self.indent_str):\n+                    # Break a long list with wraps.\n+                    self.indentation_level += 1\n+                    t = textwrap.fill(s, self.hard_break, break_long_words=False,\n+                                      initial_indent=self.indent_str, subsequent_indent=self.indent_str)\n+                    self.indentation_level -= 1\n+                    output = f'\\n{self.indent_str}[\\n{t}\\n{self.indent_str}]'\n+                else:\n+                    # Smaller lists can remain on a single line.\n+                    output = f' [ {s} ]'\n+                return output\n             else:\n-                self.write(c)\n-                self.state[\"arraycount\"][0] += 1\n-                if self.state[\"arraycount\"][0] == 16:\n-                    self.state[\"arraycount\"][0] = 0\n-                    self.newline()\n+                # Sub-structures in the list case.\n+                self.indentation_level += 1\n+                output = [self.indent_str + self.encode(el) for el in o]\n+                self.indentation_level -= 1\n+                output = ',\\n'.join(output)\n+                return f' [\\n{output}\\n{self.indent_str}]'\n+\n+        elif isinstance(o, dict):\n+            self.indentation_level += 1\n+            output = []\n+            for k, v in o.items():\n+                if isinstance(v, dict) and len(v) == 0:\n+                    # Empty config block special case.\n+                    output.append(self.indent_str + f'{json.dumps(k)}: {{ }}')\n                 else:\n-                    self.state[\"need_space\"] = True\n-        elif c.isspace():\n-            pass\n+                    # Only linebreak if the next node is a config block.\n+                    sep = f'\\n{self.indent_str}' if isinstance(v, dict) else ''\n+                    output.append(self.indent_str + f'{json.dumps(k)}:{sep}{self.encode(v, k)}')\n+            output = ',\\n'.join(output)\n+            self.indentation_level -= 1\n+            return f'{{\\n{output}\\n{self.indent_str}}}'\n+\n         else:\n-            self.write(c)\n+            return ' ' + json.dumps(o)\n+\n+    @property\n+    def indent_str(self) -> str:\n+        return ' ' * self.indentation_level * self.indent\n+\n+    def iterencode(self, o, **kwargs):\n+        return self.encode(o)\n+\n+\n+def pretty_print(in_json: dict) -> str:\n+\n+    if 'version' not in in_json or \\\n+       'target' not in in_json or \\\n+       'algorithms' not in in_json or \\\n+       in_json['version'] < 2.0:\n+        raise RuntimeError('Incompatible JSON dictionary has been provided')\n \n-    def print(self, string):\n-        for c in string:\n-            self.process_char(c)\n-        self.newline()\n+    return json.dumps(in_json, cls=Encoder, indent=4, sort_keys=False)\n \n \n-def pretty_print_json(str_in, output_filename):\n-    with open(output_filename, \"w\") as fout:\n-        printer = JSONPrettyPrinter(fout)\n-        printer.print(str_in)\n+if __name__ == \"__main__\":\n+    parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, description=\n+                    'Prettify a version 2.0 camera tuning config JSON file.')\n+    parser.add_argument('input', type=str, help='Input tuning file.')\n+    parser.add_argument('output', type=str, nargs='?',\n+                        help='Output converted tuning file. If not provided, the input file will be updated in-place.',\n+                        default=None)\n+    args = parser.parse_args()\n \n+    with open(args.input, 'r') as f:\n+        in_json = json.load(f)\n \n-if __name__ == '__main__':\n-    if len(sys.argv) != 2:\n-        print(\"Usage: %s filename\" % sys.argv[0])\n-        sys.exit(1)\n+    out_json = pretty_print(in_json)\n \n-    input_filename = sys.argv[1]\n-    with open(input_filename, \"r\") as fin:\n-        printer = JSONPrettyPrinter(sys.stdout)\n-        printer.print(fin.read())\n+    with open(args.output if args.output is not None else args.input, 'w') as f:\n+        f.write(out_json)\n",
    "prefixes": [
        "libcamera-devel",
        "v6",
        "6/8"
    ]
}