{"id":20215,"url":"https://patchwork.libcamera.org/api/1.1/patches/20215/?format=json","web_url":"https://patchwork.libcamera.org/patch/20215/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/1.1/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20240606101512.375178-2-david.plowman@raspberrypi.com>","date":"2024-06-06T10:15:07","name":"[1/6] utils: raspberrypi: ctt: Adapt tuning tool for both VC4 and PiSP","commit_ref":null,"pull_url":null,"state":"accepted","archived":false,"hash":"2325450f5c9474b4152197947f31d4765895e287","submitter":{"id":42,"url":"https://patchwork.libcamera.org/api/1.1/people/42/?format=json","name":"David Plowman","email":"david.plowman@raspberrypi.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/20215/mbox/","series":[{"id":4367,"url":"https://patchwork.libcamera.org/api/1.1/series/4367/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=4367","date":"2024-06-06T10:15:06","name":"Raspberry Pi Camera Tuning Tool updates","version":1,"mbox":"https://patchwork.libcamera.org/series/4367/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/20215/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/20215/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 C347FBD87C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu,  6 Jun 2024 10:15:35 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 6C88565463;\n\tThu,  6 Jun 2024 12:15:35 +0200 (CEST)","from mail-ej1-x635.google.com (mail-ej1-x635.google.com\n\t[IPv6:2a00:1450:4864:20::635])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 626CA634D5\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu,  6 Jun 2024 12:15:31 +0200 (CEST)","by mail-ej1-x635.google.com with SMTP id\n\ta640c23a62f3a-a69607c6ccaso78531466b.2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 06 Jun 2024 03:15:31 -0700 (PDT)","from pi5-davidp.pitowers.org\n\t([2001:4d4e:300:1f:c732:5d0a:406b:ae46])\n\tby smtp.gmail.com with ESMTPSA id\n\ta640c23a62f3a-a6c805c59a2sm75809866b.50.2024.06.06.03.15.27\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tThu, 06 Jun 2024 03:15:28 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=raspberrypi.com header.i=@raspberrypi.com\n\theader.b=\"I5HrGUOC\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google; t=1717668930; x=1718273730;\n\tdarn=lists.libcamera.org; \n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=MeYblDf8HMMD2zoYgUq87WnX+bB4pIxiav1uflgLzjI=;\n\tb=I5HrGUOC3WMis5YJ7y9eI+tST+LlcPviVuDPtMOoKB15OTH0hR19pu+LTtYtHbvkd0\n\tZatRZcwm5EBY3C3W2MkeTx0b10WRKjrs9G7Pq7U1iEpDryzN2U/AgZRTRPNpfV9jYDkR\n\tG7YZb8RNuDX8w+oYZZNE6f/LJNzQ0mVzFY01CegQeH5ilufiPklq6z1xXpgeRY/92raL\n\tFItvf0/1muC+u0pve9/6hAVtc4lhelDekzE6c13Gxi8wygSQgbnZjKD372MuymUMLI/h\n\tocFv1JpEdPU1YFmnTOxBIFxHtP0+pk6EM85hhWWCyrDX6JulW0+V3kwTJ4i3UOn+Rkss\n\tbI1g==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1717668930; x=1718273730;\n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc\n\t:subject:date:message-id:reply-to;\n\tbh=MeYblDf8HMMD2zoYgUq87WnX+bB4pIxiav1uflgLzjI=;\n\tb=iG7GNwQrgEV4mndwJ/Q4KC0j23IN0sIVvg/xsiQm1soTQhKZsYD25FMXYaMbmWsmjk\n\tK+c1AhhYeDHZ6Og+DTQhHBiIKcpQg47P3Xsy5zwKj6EvbbvZyC0otcFrTvYwqfiQfOpB\n\t95IwnPtVvvyunPjwUnDR/vBp2PV+yMnKqdEIHRMoM136l604QxJyhcFr4h+nRF8Kojfy\n\tKmniDDod58htrbcMkdsZPPITJjfEgDRf7Fp8YGjXdxdE7oqueSrwNNtO3WA3qsnEN/1q\n\tpkhXhJ2azERWcT0wC93S6NevT3JM49LUSsgINb9FADfoVNsxr3A9KrjBLsH/ffFvdGVh\n\tJjhA==","X-Gm-Message-State":"AOJu0YwoXBof+c/A75zxI3yIgpr7+YI1xynnpFGnYhrY5sVom6ZEOuY1\n\tEcSBSTEr/fbb9G3HXKHw3ML+hntz6j1gmQ2lf38Yv4zr0eGd4P+3bH/G04k8WAabFbI9u+BUxWp\n\t+","X-Google-Smtp-Source":"AGHT+IEaOUt+kGoVQQO2Nz6pNJLCa6k4Fv8ch+nJniDViVSzbw7F9er4mjeAED+AcgeJUHRybZEHSg==","X-Received":"by 2002:a17:906:3a88:b0:a6a:b1c7:ff33 with SMTP id\n\ta640c23a62f3a-a6ab1c80d12mr300632266b.8.1717668929398; \n\tThu, 06 Jun 2024 03:15:29 -0700 (PDT)","From":"David Plowman <david.plowman@raspberrypi.com>","To":"libcamera-devel@lists.libcamera.org","Cc":"David Plowman <david.plowman@raspberrypi.com>,\n\tNaushir Patuck <naush@raspberrypi.com>","Subject":"[PATCH 1/6] utils: raspberrypi: ctt: Adapt tuning tool for both VC4\n\tand PiSP","Date":"Thu,  6 Jun 2024 11:15:07 +0100","Message-Id":"<20240606101512.375178-2-david.plowman@raspberrypi.com>","X-Mailer":"git-send-email 2.39.2","In-Reply-To":"<20240606101512.375178-1-david.plowman@raspberrypi.com>","References":"<20240606101512.375178-1-david.plowman@raspberrypi.com>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","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>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"},"content":"The old ctt.py and alsc_only.py scripts are removed.\n\nInstead of ctt.py use ctt_vc4.py or ctt_pisp.py, depending on your\ntarget platform.\n\nInstead of alsc_only.py use alsc_vc4.py or alsc_pisp.py, again\naccording to your platform.\n\nSigned-off-by: David Plowman <david.plowman@raspberrypi.com>\nReviewed-by: Naushir Patuck <naush@raspberrypi.com>\n---\n utils/raspberrypi/ctt/alsc_pisp.py            |  37 +++\n .../ctt/{alsc_only.py => alsc_vc4.py}         |   9 +-\n utils/raspberrypi/ctt/ctt_alsc.py             |  75 +++---\n utils/raspberrypi/ctt/ctt_awb.py              |  11 +-\n utils/raspberrypi/ctt/ctt_ccm.py              |   6 +-\n utils/raspberrypi/ctt/ctt_pisp.py             | 233 ++++++++++++++++++\n .../raspberrypi/ctt/ctt_pretty_print_json.py  |   7 +-\n utils/raspberrypi/ctt/{ctt.py => ctt_run.py}  | 184 ++------------\n utils/raspberrypi/ctt/ctt_vc4.py              | 157 ++++++++++++\n 9 files changed, 511 insertions(+), 208 deletions(-)\n create mode 100755 utils/raspberrypi/ctt/alsc_pisp.py\n rename utils/raspberrypi/ctt/{alsc_only.py => alsc_vc4.py} (70%)\n create mode 100755 utils/raspberrypi/ctt/ctt_pisp.py\n rename utils/raspberrypi/ctt/{ctt.py => ctt_run.py} (82%)\n create mode 100755 utils/raspberrypi/ctt/ctt_vc4.py","diff":"diff --git a/utils/raspberrypi/ctt/alsc_pisp.py b/utils/raspberrypi/ctt/alsc_pisp.py\nnew file mode 100755\nindex 00000000..499aecd1\n--- /dev/null\n+++ b/utils/raspberrypi/ctt/alsc_pisp.py\n@@ -0,0 +1,37 @@\n+#!/usr/bin/env python3\n+#\n+# SPDX-License-Identifier: BSD-2-Clause\n+#\n+# Copyright (C) 2022, Raspberry Pi (Trading) Limited\n+#\n+# alsc_only.py - alsc tuning tool\n+\n+import sys\n+\n+from ctt_pisp import json_template, grid_size, target\n+from ctt_run import run_ctt\n+from ctt_tools import parse_input\n+\n+if __name__ == '__main__':\n+    \"\"\"\n+    initialise calibration\n+    \"\"\"\n+    if len(sys.argv) == 1:\n+        print(\"\"\"\n+    PiSP Lens Shading Camera Tuning Tool version 1.0\n+\n+    Required Arguments:\n+    '-i' : Calibration image directory.\n+    '-o' : Name of output json file.\n+\n+    Optional Arguments:\n+    '-c' : Config file for the CTT. If not passed, default parameters used.\n+    '-l' : Name of output log file. If not passed, 'ctt_log.txt' used.\n+              \"\"\")\n+        quit(0)\n+    else:\n+        \"\"\"\n+        parse input arguments\n+        \"\"\"\n+        json_output, directory, config, log_output = parse_input()\n+        run_ctt(json_output, directory, config, log_output, json_template, grid_size, target, alsc_only=True)\ndiff --git a/utils/raspberrypi/ctt/alsc_only.py b/utils/raspberrypi/ctt/alsc_vc4.py\nsimilarity index 70%\nrename from utils/raspberrypi/ctt/alsc_only.py\nrename to utils/raspberrypi/ctt/alsc_vc4.py\nindex 092aa40e..caf6a174 100755\n--- a/utils/raspberrypi/ctt/alsc_only.py\n+++ b/utils/raspberrypi/ctt/alsc_vc4.py\n@@ -6,8 +6,11 @@\n #\n # alsc tuning tool\n \n-from ctt import *\n+import sys\n \n+from ctt_vc4 import json_template, grid_size, target\n+from ctt_run import run_ctt\n+from ctt_tools import parse_input\n \n if __name__ == '__main__':\n     \"\"\"\n@@ -15,7 +18,7 @@ if __name__ == '__main__':\n     \"\"\"\n     if len(sys.argv) == 1:\n         print(\"\"\"\n-    Pisp Camera Tuning Tool version 1.0\n+    VC4 Lens Shading Camera Tuning Tool version 1.0\n \n     Required Arguments:\n     '-i' : Calibration image directory.\n@@ -31,4 +34,4 @@ if __name__ == '__main__':\n         parse input arguments\n         \"\"\"\n         json_output, directory, config, log_output = parse_input()\n-        run_ctt(json_output, directory, config, log_output, alsc_only=True)\n+        run_ctt(json_output, directory, config, log_output, json_template, grid_size, target, alsc_only=True)\ndiff --git a/utils/raspberrypi/ctt/ctt_alsc.py b/utils/raspberrypi/ctt/ctt_alsc.py\nindex b0201ac4..66ce8c14 100644\n--- a/utils/raspberrypi/ctt/ctt_alsc.py\n+++ b/utils/raspberrypi/ctt/ctt_alsc.py\n@@ -13,8 +13,9 @@ from mpl_toolkits.mplot3d import Axes3D\n \"\"\"\n preform alsc calibration on a set of images\n \"\"\"\n-def alsc_all(Cam, do_alsc_colour, plot):\n+def alsc_all(Cam, do_alsc_colour, plot, grid_size=(16, 12)):\n     imgs_alsc = Cam.imgs_alsc\n+    grid_w, grid_h = grid_size\n     \"\"\"\n     create list of colour temperatures and associated calibration tables\n     \"\"\"\n@@ -23,7 +24,7 @@ def alsc_all(Cam, do_alsc_colour, plot):\n     list_cb = []\n     list_cg = []\n     for Img in imgs_alsc:\n-        col, cr, cb, cg, size = alsc(Cam, Img, do_alsc_colour, plot)\n+        col, cr, cb, cg, size = alsc(Cam, Img, do_alsc_colour, plot, grid_size=grid_size)\n         list_col.append(col)\n         list_cr.append(cr)\n         list_cb.append(cb)\n@@ -68,11 +69,12 @@ def alsc_all(Cam, do_alsc_colour, plot):\n             t_b = np.where((100*t_b) % 1 >= 0.95, t_b-0.001, t_b)\n             t_r = np.round(t_r, 3)\n             t_b = np.round(t_b, 3)\n-            r_corners = (t_r[0], t_r[15], t_r[-1], t_r[-16])\n-            b_corners = (t_b[0], t_b[15], t_b[-1], t_b[-16])\n-            r_cen = t_r[5*16+7]+t_r[5*16+8]+t_r[6*16+7]+t_r[6*16+8]\n+            r_corners = (t_r[0], t_r[grid_w - 1], t_r[-1], t_r[-grid_w])\n+            b_corners = (t_b[0], t_b[grid_w - 1], t_b[-1], t_b[-grid_w])\n+            middle_pos = (grid_h // 2 - 1) * grid_w + grid_w - 1\n+            r_cen = t_r[middle_pos]+t_r[middle_pos + 1]+t_r[middle_pos + grid_w]+t_r[middle_pos + grid_w + 1]\n             r_cen = round(r_cen/4, 3)\n-            b_cen = t_b[5*16+7]+t_b[5*16+8]+t_b[6*16+7]+t_b[6*16+8]\n+            b_cen = t_b[middle_pos]+t_b[middle_pos + 1]+t_b[middle_pos + grid_w]+t_b[middle_pos + grid_w + 1]\n             b_cen = round(b_cen/4, 3)\n             Cam.log += '\\nRed table corners: {}'.format(r_corners)\n             Cam.log += '\\nRed table centre: {}'.format(r_cen)\n@@ -116,8 +118,9 @@ def alsc_all(Cam, do_alsc_colour, plot):\n \"\"\"\n calculate g/r and g/b for 32x32 points arranged in a grid for a single image\n \"\"\"\n-def alsc(Cam, Img, do_alsc_colour, plot=False):\n+def alsc(Cam, Img, do_alsc_colour, plot=False, grid_size=(16, 12)):\n     Cam.log += '\\nProcessing image: ' + Img.name\n+    grid_w, grid_h = grid_size\n     \"\"\"\n     get channel in correct order\n     \"\"\"\n@@ -128,24 +131,24 @@ def alsc(Cam, Img, do_alsc_colour, plot=False):\n     where w is a multiple of 32.\n     \"\"\"\n     w, h = Img.w/2, Img.h/2\n-    dx, dy = int(-(-(w-1)//16)), int(-(-(h-1)//12))\n+    dx, dy = int(-(-(w-1)//grid_w)), int(-(-(h-1)//grid_h))\n     \"\"\"\n     average the green channels into one\n     \"\"\"\n     av_ch_g = np.mean((channels[1:3]), axis=0)\n     if do_alsc_colour:\n         \"\"\"\n-        obtain 16x12 grid of intensities for each channel and subtract black level\n+        obtain grid_w x grid_h grid of intensities for each channel and subtract black level\n         \"\"\"\n-        g = get_16x12_grid(av_ch_g, dx, dy) - Img.blacklevel_16\n-        r = get_16x12_grid(channels[0], dx, dy) - Img.blacklevel_16\n-        b = get_16x12_grid(channels[3], dx, dy) - Img.blacklevel_16\n+        g = get_grid(av_ch_g, dx, dy, grid_size) - Img.blacklevel_16\n+        r = get_grid(channels[0], dx, dy, grid_size) - Img.blacklevel_16\n+        b = get_grid(channels[3], dx, dy, grid_size) - Img.blacklevel_16\n         \"\"\"\n         calculate ratios as 32 bit in order to be supported by medianBlur function\n         \"\"\"\n-        cr = np.reshape(g/r, (12, 16)).astype('float32')\n-        cb = np.reshape(g/b, (12, 16)).astype('float32')\n-        cg = np.reshape(1/g, (12, 16)).astype('float32')\n+        cr = np.reshape(g/r, (grid_h, grid_w)).astype('float32')\n+        cb = np.reshape(g/b, (grid_h, grid_w)).astype('float32')\n+        cg = np.reshape(1/g, (grid_h, grid_w)).astype('float32')\n         \"\"\"\n         median blur to remove peaks and save as float 64\n         \"\"\"\n@@ -164,7 +167,7 @@ def alsc(Cam, Img, do_alsc_colour, plot=False):\n             \"\"\"\n             note Y is plotted as -Y so plot has same axes as image\n             \"\"\"\n-            X, Y = np.meshgrid(range(16), range(12))\n+            X, Y = np.meshgrid(range(grid_w), range(grid_h))\n             ha.plot_surface(X, -Y, cr, cmap=cm.coolwarm, linewidth=0)\n             ha.set_title('ALSC Plot\\nImg: {}\\n\\ncr'.format(Img.str))\n             hb = hf.add_subplot(312, projection='3d')\n@@ -182,15 +185,15 @@ def alsc(Cam, Img, do_alsc_colour, plot=False):\n         \"\"\"\n         only perform calculations for luminance shading\n         \"\"\"\n-        g = get_16x12_grid(av_ch_g, dx, dy) - Img.blacklevel_16\n-        cg = np.reshape(1/g, (12, 16)).astype('float32')\n+        g = get_grid(av_ch_g, dx, dy, grid_size) - Img.blacklevel_16\n+        cg = np.reshape(1/g, (grid_h, grid_w)).astype('float32')\n         cg = cv2.medianBlur(cg, 3).astype('float64')\n         cg = cg/np.min(cg)\n \n         if plot:\n             hf = plt.figure(figssize=(8, 8))\n             ha = hf.add_subplot(1, 1, 1, projection='3d')\n-            X, Y = np.meashgrid(range(16), range(12))\n+            X, Y = np.meashgrid(range(grid_w), range(grid_h))\n             ha.plot_surface(X, -Y, cg, cmap=cm.coolwarm, linewidth=0)\n             ha.set_title('ALSC Plot (Luminance only!)\\nImg: {}\\n\\ncg').format(Img.str)\n             plt.show()\n@@ -199,21 +202,22 @@ def alsc(Cam, Img, do_alsc_colour, plot=False):\n \n \n \"\"\"\n-Compresses channel down to a 16x12 grid\n+Compresses channel down to a grid of the requested size\n \"\"\"\n-def get_16x12_grid(chan, dx, dy):\n+def get_grid(chan, dx, dy, grid_size):\n+    grid_w, grid_h = grid_size\n     grid = []\n     \"\"\"\n     since left and bottom border will not necessarily have rectangles of\n     dimension dx x dy, the 32nd iteration has to be handled separately.\n     \"\"\"\n-    for i in range(11):\n-        for j in range(15):\n+    for i in range(grid_h - 1):\n+        for j in range(grid_w - 1):\n             grid.append(np.mean(chan[dy*i:dy*(1+i), dx*j:dx*(1+j)]))\n-        grid.append(np.mean(chan[dy*i:dy*(1+i), 15*dx:]))\n-    for j in range(15):\n-        grid.append(np.mean(chan[11*dy:, dx*j:dx*(1+j)]))\n-    grid.append(np.mean(chan[11*dy:, 15*dx:]))\n+        grid.append(np.mean(chan[dy*i:dy*(1+i), (grid_w - 1)*dx:]))\n+    for j in range(grid_w - 1):\n+        grid.append(np.mean(chan[(grid_h - 1)*dy:, dx*j:dx*(1+j)]))\n+    grid.append(np.mean(chan[(grid_h - 1)*dy:, (grid_w - 1)*dx:]))\n     \"\"\"\n     return as np.array, ready for further manipulation\n     \"\"\"\n@@ -223,7 +227,7 @@ def get_16x12_grid(chan, dx, dy):\n \"\"\"\n obtains sigmas for red and blue, effectively a measure of the 'error'\n \"\"\"\n-def get_sigma(Cam, cal_cr_list, cal_cb_list):\n+def get_sigma(Cam, cal_cr_list, cal_cb_list, grid_size):\n     Cam.log += '\\nCalculating sigmas'\n     \"\"\"\n     provided colour alsc tables were generated for two different colour\n@@ -241,8 +245,8 @@ def get_sigma(Cam, cal_cr_list, cal_cb_list):\n     sigma_rs = []\n     sigma_bs = []\n     for i in range(len(cts)-1):\n-        sigma_rs.append(calc_sigma(cal_cr_list[i]['table'], cal_cr_list[i+1]['table']))\n-        sigma_bs.append(calc_sigma(cal_cb_list[i]['table'], cal_cb_list[i+1]['table']))\n+        sigma_rs.append(calc_sigma(cal_cr_list[i]['table'], cal_cr_list[i+1]['table'], grid_size))\n+        sigma_bs.append(calc_sigma(cal_cb_list[i]['table'], cal_cb_list[i+1]['table'], grid_size))\n         Cam.log += '\\nColour temperature interval {} - {} K'.format(cts[i], cts[i+1])\n         Cam.log += '\\nSigma red: {}'.format(sigma_rs[-1])\n         Cam.log += '\\nSigma blue: {}'.format(sigma_bs[-1])\n@@ -263,12 +267,13 @@ def get_sigma(Cam, cal_cr_list, cal_cb_list):\n \"\"\"\n calculate sigma from two adjacent gain tables\n \"\"\"\n-def calc_sigma(g1, g2):\n+def calc_sigma(g1, g2, grid_size):\n+    grid_w, grid_h = grid_size\n     \"\"\"\n     reshape into 16x12 matrix\n     \"\"\"\n-    g1 = np.reshape(g1, (12, 16))\n-    g2 = np.reshape(g2, (12, 16))\n+    g1 = np.reshape(g1, (grid_h, grid_w))\n+    g2 = np.reshape(g2, (grid_h, grid_w))\n     \"\"\"\n     apply gains to gain table\n     \"\"\"\n@@ -280,8 +285,8 @@ def calc_sigma(g1, g2):\n     neighbours, then append to list\n     \"\"\"\n     diffs = []\n-    for i in range(10):\n-        for j in range(14):\n+    for i in range(grid_h - 2):\n+        for j in range(grid_w - 2):\n             \"\"\"\n             note indexing is incremented by 1 since all patches on borders are\n             not counted\ndiff --git a/utils/raspberrypi/ctt/ctt_awb.py b/utils/raspberrypi/ctt/ctt_awb.py\nindex 5ba6f978..4af1fe41 100644\n--- a/utils/raspberrypi/ctt/ctt_awb.py\n+++ b/utils/raspberrypi/ctt/ctt_awb.py\n@@ -13,7 +13,7 @@ from scipy.optimize import fmin\n \"\"\"\n obtain piecewise linear approximation for colour curve\n \"\"\"\n-def awb(Cam, cal_cr_list, cal_cb_list, plot):\n+def awb(Cam, cal_cr_list, cal_cb_list, plot, grid_size):\n     imgs = Cam.imgs\n     \"\"\"\n     condense alsc calibration tables into one dictionary\n@@ -43,7 +43,7 @@ def awb(Cam, cal_cr_list, cal_cb_list, plot):\n         Note: if alsc is disabled then colour_cals will be set to None and the\n         function will just return the greyscale patches\n         \"\"\"\n-        r_patchs, b_patchs, g_patchs = get_alsc_patches(Img, colour_cals)\n+        r_patchs, b_patchs, g_patchs = get_alsc_patches(Img, colour_cals, grid_size=grid_size)\n         \"\"\"\n         calculate ratio of r, b to g\n         \"\"\"\n@@ -293,12 +293,13 @@ def awb(Cam, cal_cr_list, cal_cb_list, plot):\n \"\"\"\n obtain greyscale patches and perform alsc colour correction\n \"\"\"\n-def get_alsc_patches(Img, colour_cals, grey=True):\n+def get_alsc_patches(Img, colour_cals, grey=True, grid_size=(16, 12)):\n     \"\"\"\n     get patch centre coordinates, image colour and the actual\n     patches for each channel, remembering to subtract blacklevel\n     If grey then only greyscale patches considered\n     \"\"\"\n+    grid_w, grid_h = grid_size\n     if grey:\n         cen_coords = Img.cen_coords[3::4]\n         col = Img.col\n@@ -345,12 +346,12 @@ def get_alsc_patches(Img, colour_cals, grey=True):\n         bef_tabs = np.array(colour_cals[bef])\n         aft_tabs = np.array(colour_cals[aft])\n         col_tabs = (bef_tabs*db + aft_tabs*da)/(da+db)\n-    col_tabs = np.reshape(col_tabs, (2, 12, 16))\n+    col_tabs = np.reshape(col_tabs, (2, grid_h, grid_w))\n     \"\"\"\n     calculate dx, dy used to calculate alsc table\n     \"\"\"\n     w, h = Img.w/2, Img.h/2\n-    dx, dy = int(-(-(w-1)//16)), int(-(-(h-1)//12))\n+    dx, dy = int(-(-(w-1)//grid_w)), int(-(-(h-1)//grid_h))\n     \"\"\"\n     make list of pairs of gains for each patch by selecting the correct value\n     in alsc colour calibration table\ndiff --git a/utils/raspberrypi/ctt/ctt_ccm.py b/utils/raspberrypi/ctt/ctt_ccm.py\nindex 59753e33..07c943a8 100644\n--- a/utils/raspberrypi/ctt/ctt_ccm.py\n+++ b/utils/raspberrypi/ctt/ctt_ccm.py\n@@ -56,7 +56,7 @@ FInds colour correction matrices for list of images\n \"\"\"\n \n \n-def ccm(Cam, cal_cr_list, cal_cb_list):\n+def ccm(Cam, cal_cr_list, cal_cb_list, grid_size):\n     global matrix_selection_types, typenum\n     imgs = Cam.imgs\n     \"\"\"\n@@ -133,9 +133,7 @@ def ccm(Cam, cal_cr_list, cal_cb_list):\n         Note: if alsc is disabled then colour_cals will be set to None and no\n         the function will simply return the macbeth patches\n         \"\"\"\n-        r, b, g = get_alsc_patches(Img, colour_cals, grey=False)\n-        # 256 values for each patch of sRGB values\n-\n+        r, b, g = get_alsc_patches(Img, colour_cals, grey=False, grid_size=grid_size)\n         \"\"\"\n         do awb\n         Note: awb is done by measuring the macbeth chart in the image, rather\ndiff --git a/utils/raspberrypi/ctt/ctt_pisp.py b/utils/raspberrypi/ctt/ctt_pisp.py\nnew file mode 100755\nindex 00000000..f837e062\n--- /dev/null\n+++ b/utils/raspberrypi/ctt/ctt_pisp.py\n@@ -0,0 +1,233 @@\n+#!/usr/bin/env python3\n+#\n+# SPDX-License-Identifier: BSD-2-Clause\n+#\n+# Copyright (C) 2019, Raspberry Pi Ltd\n+#\n+# ctt_pisp.py - camera tuning tool for PiSP platforms\n+\n+import os\n+import sys\n+\n+from ctt_run import run_ctt\n+from ctt_tools import parse_input\n+\n+json_template = {\n+    \"rpi.black_level\": {\n+        \"black_level\": 4096\n+    },\n+    \"rpi.lux\": {\n+        \"reference_shutter_speed\": 10000,\n+        \"reference_gain\": 1,\n+        \"reference_aperture\": 1.0\n+    },\n+    \"rpi.dpc\": {\n+\t\"strength\": 1\n+    },\n+    \"rpi.noise\": {\n+    },\n+    \"rpi.geq\": {\n+    },\n+    \"rpi.denoise\":\n+    {\n+        \"sdn\":\n+        {\n+            \"deviation\": 1.6,\n+            \"strength\": 0.5,\n+            \"deviation2\": 3.2,\n+            \"deviation_no_tdn\": 3.2,\n+            \"strength_no_tdn\": 0.75\n+        },\n+        \"cdn\":\n+        {\n+            \"deviation\": 200,\n+            \"strength\": 0.3\n+        },\n+        \"tdn\":\n+        {\n+            \"deviation\": 0.8,\n+            \"threshold\": 0.05\n+        }\n+    },\n+    \"rpi.awb\": {\n+        \"priors\": [\n+            {\"lux\": 0, \"prior\": [2000, 1.0, 3000, 0.0, 13000, 0.0]},\n+            {\"lux\": 800, \"prior\": [2000, 0.0, 6000, 2.0, 13000, 2.0]},\n+            {\"lux\": 1500, \"prior\": [2000, 0.0, 4000, 1.0, 6000, 6.0, 6500, 7.0, 7000, 1.0, 13000, 1.0]}\n+        ],\n+        \"modes\": {\n+            \"auto\": {\"lo\": 2500, \"hi\": 7700},\n+            \"incandescent\": {\"lo\": 2500, \"hi\": 3000},\n+            \"tungsten\": {\"lo\": 3000, \"hi\": 3500},\n+            \"fluorescent\": {\"lo\": 4000, \"hi\": 4700},\n+            \"indoor\": {\"lo\": 3000, \"hi\": 5000},\n+            \"daylight\": {\"lo\": 5500, \"hi\": 6500},\n+            \"cloudy\": {\"lo\": 7000, \"hi\": 8000}\n+        },\n+        \"bayes\": 1\n+    },\n+    \"rpi.agc\": {\n+        \"metering_modes\": {\n+            \"centre-weighted\": {\n+\t\t\"weights\": [\n+\t\t    0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,\n+\t\t    0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0,\n+\t\t    1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1,\n+\t\t    1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1,\n+\t\t    1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1,\n+\t\t    1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1,\n+\t\t    1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1,\n+\t\t    1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1,\n+\t\t    1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1,\n+\t\t    1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1,\n+\t\t    1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1,\n+\t\t    1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1,\n+\t\t    1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1,\n+\t\t    0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0,\n+\t\t    0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0\n+\t\t]\n+            },\n+            \"spot\": {\n+\t\t\"weights\": [\n+\t\t    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n+\t\t    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n+\t\t    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n+\t\t    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n+\t\t    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n+\t\t    0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,\n+\t\t    0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0,\n+\t\t    0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0,\n+\t\t    0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0,\n+\t\t    0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,\n+\t\t    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n+\t\t    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n+\t\t    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n+\t\t    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n+\t\t    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n+\t\t]\n+            },\n+            \"matrix\": {\n+                \"weights\": [\n+\t\t    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n+\t\t    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n+\t\t    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n+\t\t    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n+\t\t    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n+\t\t    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n+\t\t    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n+\t\t    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n+\t\t    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n+\t\t    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n+\t\t    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n+\t\t    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n+\t\t    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n+\t\t    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n+\t\t    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1\n+\t\t]\n+            }\n+        },\n+        \"exposure_modes\": {\n+            \"normal\": {\n+                \"shutter\": [100, 10000, 30000, 60000, 66666],\n+                \"gain\": [1.0, 1.5, 2.0, 4.0, 8.0]\n+            },\n+            \"short\": {\n+                \"shutter\": [100, 5000, 10000, 20000, 60000],\n+                \"gain\": [1.0, 1.5, 2.0, 4.0, 8.0]\n+            },\n+            \"long\":\n+            {\n+                \"shutter\": [ 100, 10000, 30000, 60000, 90000, 120000 ],\n+                \"gain\": [ 1.0, 1.5, 2.0, 4.0, 8.0, 12.0 ]\n+            }\n+        },\n+        \"constraint_modes\": {\n+            \"normal\": [\n+                {\"bound\": \"LOWER\", \"q_lo\": 0.98, \"q_hi\": 1.0, \"y_target\": [0, 0.5, 1000, 0.5]}\n+            ],\n+            \"highlight\": [\n+                {\"bound\": \"LOWER\", \"q_lo\": 0.98, \"q_hi\": 1.0, \"y_target\": [0, 0.5, 1000, 0.5]},\n+                {\"bound\": \"UPPER\", \"q_lo\": 0.98, \"q_hi\": 1.0, \"y_target\": [0, 0.8, 1000, 0.8]}\n+            ]\n+        },\n+        \"y_target\": [0, 0.16, 1000, 0.165, 10000, 0.17]\n+    },\n+    \"rpi.alsc\": {\n+        'omega': 1.3,\n+        'n_iter': 100,\n+        'luminance_strength': 0.8,\n+    },\n+    \"rpi.contrast\": {\n+        \"ce_enable\": 1,\n+        \"gamma_curve\": [\n+            0,     0,\n+            1024,  5040,\n+            2048,  9338,\n+            3072,  12356,\n+            4096,  15312,\n+            5120,  18051,\n+            6144,  20790,\n+            7168,  23193,\n+            8192,  25744,\n+            9216,  27942,\n+            10240, 30035,\n+            11264, 32005,\n+            12288, 33975,\n+            13312, 35815,\n+            14336, 37600,\n+            15360, 39168,\n+            16384, 40642,\n+            18432, 43379,\n+            20480, 45749,\n+            22528, 47753,\n+            24576, 49621,\n+            26624, 51253,\n+            28672, 52698,\n+            30720, 53796,\n+            32768, 54876,\n+            36864, 57012,\n+            40960, 58656,\n+            45056, 59954,\n+            49152, 61183,\n+            53248, 62355,\n+            57344, 63419,\n+            61440, 64476,\n+            65535, 65535\n+        ]\n+    },\n+    \"rpi.ccm\": {\n+    },\n+    \"rpi.sharpen\": {\n+\t\"threshold\": 0.25,\n+\t\"limit\": 1.0,\n+\t\"strength\": 1.0\n+    }\n+}\n+\n+grid_size = (32, 32)\n+\n+target = 'pisp'\n+\n+if __name__ == '__main__':\n+    \"\"\"\n+    initialise calibration\n+    \"\"\"\n+    if len(sys.argv) == 1:\n+        print(\"\"\"\n+    PiSP Camera Tuning Tool version 1.0\n+\n+    Required Arguments:\n+    '-i' : Calibration image directory.\n+    '-o' : Name of output json file.\n+\n+    Optional Arguments:\n+    '-c' : Config file for the CTT. If not passed, default parameters used.\n+    '-l' : Name of output log file. If not passed, 'ctt_log.txt' used.\n+              \"\"\")\n+        quit(0)\n+    else:\n+        \"\"\"\n+        parse input arguments\n+        \"\"\"\n+        json_output, directory, config, log_output = parse_input()\n+        run_ctt(json_output, directory, config, log_output, json_template, grid_size, target)\ndiff --git a/utils/raspberrypi/ctt/ctt_pretty_print_json.py b/utils/raspberrypi/ctt/ctt_pretty_print_json.py\nindex 3e3b8475..5d16b2a6 100755\n--- a/utils/raspberrypi/ctt/ctt_pretty_print_json.py\n+++ b/utils/raspberrypi/ctt/ctt_pretty_print_json.py\n@@ -19,6 +19,7 @@ class Encoder(json.JSONEncoder):\n         self.indentation_level = 0\n         self.hard_break = 120\n         self.custom_elems = {\n+            'weights': 15,\n             'table': 16,\n             'luminance_lut': 16,\n             'ct_curve': 3,\n@@ -87,7 +88,7 @@ class Encoder(json.JSONEncoder):\n         return self.encode(o)\n \n \n-def pretty_print(in_json: dict) -> str:\n+def pretty_print(in_json: dict, custom_elems={}) -> str:\n \n     if 'version' not in in_json or \\\n        'target' not in in_json or \\\n@@ -95,7 +96,9 @@ def pretty_print(in_json: dict) -> str:\n        in_json['version'] < 2.0:\n         raise RuntimeError('Incompatible JSON dictionary has been provided')\n \n-    return json.dumps(in_json, cls=Encoder, indent=4, sort_keys=False)\n+    encoder = Encoder(indent=4, sort_keys=False)\n+    encoder.custom_elems |= custom_elems\n+    return encoder.encode(in_json) #json.dumps(in_json, cls=Encoder, indent=4, sort_keys=False)\n \n \n if __name__ == \"__main__\":\ndiff --git a/utils/raspberrypi/ctt/ctt.py b/utils/raspberrypi/ctt/ctt_run.py\nsimilarity index 82%\nrename from utils/raspberrypi/ctt/ctt.py\nrename to utils/raspberrypi/ctt/ctt_run.py\nindex bbe960b0..0c85d7db 100755\n--- a/utils/raspberrypi/ctt/ctt.py\n+++ b/utils/raspberrypi/ctt/ctt_run.py\n@@ -67,7 +67,7 @@ Camera object that is the backbone of the tuning tool.\n Input is the desired path of the output json.\n \"\"\"\n class Camera:\n-    def __init__(self, jfile):\n+    def __init__(self, jfile, json):\n         self.path = os.path.dirname(os.path.expanduser(__file__)) + '/'\n         if self.path == '/':\n             self.path = ''\n@@ -79,127 +79,15 @@ class Camera:\n         \"\"\"\n         initial json dict populated by uncalibrated values\n         \"\"\"\n-        self.json = {\n-            \"rpi.black_level\": {\n-                \"black_level\": 4096\n-            },\n-            \"rpi.dpc\": {\n-            },\n-            \"rpi.lux\": {\n-                \"reference_shutter_speed\": 10000,\n-                \"reference_gain\": 1,\n-                \"reference_aperture\": 1.0\n-            },\n-            \"rpi.noise\": {\n-            },\n-            \"rpi.geq\": {\n-            },\n-            \"rpi.sdn\": {\n-            },\n-            \"rpi.awb\": {\n-                \"priors\": [\n-                    {\"lux\": 0, \"prior\": [2000, 1.0, 3000, 0.0, 13000, 0.0]},\n-                    {\"lux\": 800, \"prior\": [2000, 0.0, 6000, 2.0, 13000, 2.0]},\n-                    {\"lux\": 1500, \"prior\": [2000, 0.0, 4000, 1.0, 6000, 6.0, 6500, 7.0, 7000, 1.0, 13000, 1.0]}\n-                ],\n-                \"modes\": {\n-                    \"auto\": {\"lo\": 2500, \"hi\": 8000},\n-                    \"incandescent\": {\"lo\": 2500, \"hi\": 3000},\n-                    \"tungsten\": {\"lo\": 3000, \"hi\": 3500},\n-                    \"fluorescent\": {\"lo\": 4000, \"hi\": 4700},\n-                    \"indoor\": {\"lo\": 3000, \"hi\": 5000},\n-                    \"daylight\": {\"lo\": 5500, \"hi\": 6500},\n-                    \"cloudy\": {\"lo\": 7000, \"hi\": 8600}\n-                },\n-                \"bayes\": 1\n-            },\n-            \"rpi.agc\": {\n-                \"metering_modes\": {\n-                    \"centre-weighted\": {\n-                        \"weights\": [3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0]\n-                    },\n-                    \"spot\": {\n-                        \"weights\": [2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n-                    },\n-                    \"matrix\": {\n-                        \"weights\": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]\n-                    }\n-                },\n-                \"exposure_modes\": {\n-                    \"normal\": {\n-                        \"shutter\": [100, 10000, 30000, 60000, 120000],\n-                        \"gain\": [1.0, 2.0, 4.0, 6.0, 6.0]\n-                    },\n-                    \"short\": {\n-                        \"shutter\": [100, 5000, 10000, 20000, 120000],\n-                        \"gain\": [1.0, 2.0, 4.0, 6.0, 6.0]\n-                    }\n-                },\n-                \"constraint_modes\": {\n-                    \"normal\": [\n-                        {\"bound\": \"LOWER\", \"q_lo\": 0.98, \"q_hi\": 1.0, \"y_target\": [0, 0.5, 1000, 0.5]}\n-                    ],\n-                    \"highlight\": [\n-                        {\"bound\": \"LOWER\", \"q_lo\": 0.98, \"q_hi\": 1.0, \"y_target\": [0, 0.5, 1000, 0.5]},\n-                        {\"bound\": \"UPPER\", \"q_lo\": 0.98, \"q_hi\": 1.0, \"y_target\": [0, 0.8, 1000, 0.8]}\n-                    ]\n-                },\n-                \"y_target\": [0, 0.16, 1000, 0.165, 10000, 0.17]\n-            },\n-            \"rpi.alsc\": {\n-                'omega': 1.3,\n-                'n_iter': 100,\n-                'luminance_strength': 0.7,\n-            },\n-            \"rpi.contrast\": {\n-                \"ce_enable\": 1,\n-                \"gamma_curve\": [\n-                    0,     0,\n-                    1024,  5040,\n-                    2048,  9338,\n-                    3072,  12356,\n-                    4096,  15312,\n-                    5120,  18051,\n-                    6144,  20790,\n-                    7168,  23193,\n-                    8192,  25744,\n-                    9216,  27942,\n-                    10240, 30035,\n-                    11264, 32005,\n-                    12288, 33975,\n-                    13312, 35815,\n-                    14336, 37600,\n-                    15360, 39168,\n-                    16384, 40642,\n-                    18432, 43379,\n-                    20480, 45749,\n-                    22528, 47753,\n-                    24576, 49621,\n-                    26624, 51253,\n-                    28672, 52698,\n-                    30720, 53796,\n-                    32768, 54876,\n-                    36864, 57012,\n-                    40960, 58656,\n-                    45056, 59954,\n-                    49152, 61183,\n-                    53248, 62355,\n-                    57344, 63419,\n-                    61440, 64476,\n-                    65535, 65535\n-                ]\n-            },\n-            \"rpi.ccm\": {\n-            },\n-            \"rpi.sharpen\": {\n-            }\n-        }\n+\n+        self.json = json\n+\n \n     \"\"\"\n     Perform colour correction calibrations by comparing macbeth patch colours\n     to standard macbeth chart colours.\n     \"\"\"\n-    def ccm_cal(self, do_alsc_colour):\n+    def ccm_cal(self, do_alsc_colour, grid_size):\n         if 'rpi.ccm' in self.disable:\n             return 1\n         print('\\nStarting CCM calibration')\n@@ -245,7 +133,7 @@ class Camera:\n         Do CCM calibration\n         \"\"\"\n         try:\n-            ccms = ccm(self, cal_cr_list, cal_cb_list)\n+            ccms = ccm(self, cal_cr_list, cal_cb_list, grid_size)\n         except ArithmeticError:\n             print('ERROR: Matrix is singular!\\nTake new pictures and try again...')\n             self.log += '\\nERROR: Singular matrix encountered during fit!'\n@@ -263,7 +151,7 @@ class Camera:\n     various colour temperatures, as well as providing a maximum 'wiggle room'\n     distance from this curve (transverse_neg/pos).\n     \"\"\"\n-    def awb_cal(self, greyworld, do_alsc_colour):\n+    def awb_cal(self, greyworld, do_alsc_colour, grid_size):\n         if 'rpi.awb' in self.disable:\n             return 1\n         print('\\nStarting AWB calibration')\n@@ -306,7 +194,7 @@ class Camera:\n         call calibration function\n         \"\"\"\n         plot = \"rpi.awb\" in self.plot\n-        awb_out = awb(self, cal_cr_list, cal_cb_list, plot)\n+        awb_out = awb(self, cal_cr_list, cal_cb_list, plot, grid_size)\n         ct_curve, transverse_neg, transverse_pos = awb_out\n         \"\"\"\n         write output to json\n@@ -324,7 +212,7 @@ class Camera:\n     colour channel seperately, and then partially corrects for vignetting.\n     The extent of the correction depends on the 'luminance_strength' parameter.\n     \"\"\"\n-    def alsc_cal(self, luminance_strength, do_alsc_colour):\n+    def alsc_cal(self, luminance_strength, do_alsc_colour, grid_size):\n         if 'rpi.alsc' in self.disable:\n             return 1\n         print('\\nStarting ALSC calibration')\n@@ -347,7 +235,7 @@ class Camera:\n         call calibration function\n         \"\"\"\n         plot = \"rpi.alsc\" in self.plot\n-        alsc_out = alsc_all(self, do_alsc_colour, plot)\n+        alsc_out = alsc_all(self, do_alsc_colour, plot, grid_size)\n         cal_cr_list, cal_cb_list, luminance_lut, av_corn = alsc_out\n         \"\"\"\n         write output to json and finish if not do_alsc_colour\n@@ -393,7 +281,7 @@ class Camera:\n         \"\"\"\n         obtain worst-case scenario residual sigmas\n         \"\"\"\n-        sigma_r, sigma_b = get_sigma(self, cal_cr_list, cal_cb_list)\n+        sigma_r, sigma_b = get_sigma(self, cal_cr_list, cal_cb_list, grid_size)\n         \"\"\"\n         write output to json\n         \"\"\"\n@@ -509,19 +397,20 @@ class Camera:\n     \"\"\"\n     writes the json dictionary to the raw json file then make pretty\n     \"\"\"\n-    def write_json(self):\n+    def write_json(self, version=2.0, target='bcm2835', grid_size=(16, 12)):\n         \"\"\"\n         Write json dictionary to file using our version 2 format\n         \"\"\"\n \n         out_json = {\n-            \"version\": 2.0,\n-            'target': 'bcm2835',\n+            \"version\": version,\n+            'target': target if target != 'vc4' else '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+            f.write(pretty_print(out_json,\n+                                 custom_elems={'table': grid_size[0], 'luminance_lut': grid_size[0]}))\n \n     \"\"\"\n     add a new section to the log file\n@@ -712,7 +601,7 @@ class Camera:\n             return 0\n \n \n-def run_ctt(json_output, directory, config, log_output, alsc_only=False):\n+def run_ctt(json_output, directory, config, log_output, json_template, grid_size, target, alsc_only=False):\n     \"\"\"\n     check input files are jsons\n     \"\"\"\n@@ -748,7 +637,7 @@ def run_ctt(json_output, directory, config, log_output, alsc_only=False):\n     greyworld = get_config(awb_d, \"greyworld\", 0, 'bool')\n     alsc_d = get_config(configs, \"alsc\", {}, 'dict')\n     do_alsc_colour = get_config(alsc_d, \"do_alsc_colour\", 1, 'bool')\n-    luminance_strength = get_config(alsc_d, \"luminance_strength\", 0.5, 'num')\n+    luminance_strength = get_config(alsc_d, \"luminance_strength\", 0.8, 'num')\n     blacklevel = get_config(configs, \"blacklevel\", -1, 'num')\n     macbeth_d = get_config(configs, \"macbeth\", {}, 'dict')\n     mac_small = get_config(macbeth_d, \"small\", 0, 'bool')\n@@ -772,7 +661,7 @@ def run_ctt(json_output, directory, config, log_output, alsc_only=False):\n     initialise tuning tool and load images\n     \"\"\"\n     try:\n-        Cam = Camera(json_output)\n+        Cam = Camera(json_output, json=json_template)\n         Cam.log_user_input(json_output, directory, config, log_output)\n         if alsc_only:\n             disable = set(Cam.json.keys()).symmetric_difference({\"rpi.alsc\"})\n@@ -794,14 +683,16 @@ def run_ctt(json_output, directory, config, log_output, alsc_only=False):\n             Cam.json['rpi.black_level']['black_level'] = Cam.blacklevel_16\n         Cam.json_remove(disable)\n         print('\\nSTARTING CALIBRATIONS')\n-        Cam.alsc_cal(luminance_strength, do_alsc_colour)\n+        Cam.alsc_cal(luminance_strength, do_alsc_colour, grid_size)\n         Cam.geq_cal()\n         Cam.lux_cal()\n         Cam.noise_cal()\n-        Cam.awb_cal(greyworld, do_alsc_colour)\n-        Cam.ccm_cal(do_alsc_colour)\n+        Cam.cac_cal(do_alsc_colour)\n+        Cam.awb_cal(greyworld, do_alsc_colour, grid_size)\n+        Cam.ccm_cal(do_alsc_colour, grid_size)\n+\n         print('\\nFINISHED CALIBRATIONS')\n-        Cam.write_json()\n+        Cam.write_json(target=target, grid_size=grid_size)\n         Cam.write_log(log_output)\n         print('\\nCalibrations written to: '+json_output)\n         if log_output is None:\n@@ -810,28 +701,3 @@ def run_ctt(json_output, directory, config, log_output, alsc_only=False):\n         pass\n     else:\n         Cam.write_log(log_output)\n-\n-\n-if __name__ == '__main__':\n-    \"\"\"\n-    initialise calibration\n-    \"\"\"\n-    if len(sys.argv) == 1:\n-        print(\"\"\"\n-    Pisp Camera Tuning Tool version 1.0\n-\n-    Required Arguments:\n-    '-i' : Calibration image directory.\n-    '-o' : Name of output json file.\n-\n-    Optional Arguments:\n-    '-c' : Config file for the CTT. If not passed, default parameters used.\n-    '-l' : Name of output log file. If not passed, 'ctt_log.txt' used.\n-              \"\"\")\n-        quit(0)\n-    else:\n-        \"\"\"\n-        parse input arguments\n-        \"\"\"\n-        json_output, directory, config, log_output = parse_input()\n-        run_ctt(json_output, directory, config, log_output)\ndiff --git a/utils/raspberrypi/ctt/ctt_vc4.py b/utils/raspberrypi/ctt/ctt_vc4.py\nnew file mode 100755\nindex 00000000..86acfd47\n--- /dev/null\n+++ b/utils/raspberrypi/ctt/ctt_vc4.py\n@@ -0,0 +1,157 @@\n+#!/usr/bin/env python3\n+#\n+# SPDX-License-Identifier: BSD-2-Clause\n+#\n+# Copyright (C) 2019, Raspberry Pi Ltd\n+#\n+# ctt_vc4.py - camera tuning tool for VC4 platforms\n+\n+import os\n+import sys\n+\n+from ctt_run import run_ctt\n+from ctt_tools import parse_input\n+\n+json_template = {\n+    \"rpi.black_level\": {\n+        \"black_level\": 4096\n+    },\n+    \"rpi.dpc\": {\n+    },\n+    \"rpi.lux\": {\n+        \"reference_shutter_speed\": 10000,\n+        \"reference_gain\": 1,\n+        \"reference_aperture\": 1.0\n+    },\n+    \"rpi.noise\": {\n+    },\n+    \"rpi.geq\": {\n+    },\n+    \"rpi.sdn\": {\n+    },\n+    \"rpi.awb\": {\n+        \"priors\": [\n+            {\"lux\": 0, \"prior\": [2000, 1.0, 3000, 0.0, 13000, 0.0]},\n+            {\"lux\": 800, \"prior\": [2000, 0.0, 6000, 2.0, 13000, 2.0]},\n+            {\"lux\": 1500, \"prior\": [2000, 0.0, 4000, 1.0, 6000, 6.0, 6500, 7.0, 7000, 1.0, 13000, 1.0]}\n+        ],\n+        \"modes\": {\n+            \"auto\": {\"lo\": 2500, \"hi\": 8000},\n+            \"incandescent\": {\"lo\": 2500, \"hi\": 3000},\n+            \"tungsten\": {\"lo\": 3000, \"hi\": 3500},\n+            \"fluorescent\": {\"lo\": 4000, \"hi\": 4700},\n+            \"indoor\": {\"lo\": 3000, \"hi\": 5000},\n+            \"daylight\": {\"lo\": 5500, \"hi\": 6500},\n+            \"cloudy\": {\"lo\": 7000, \"hi\": 8600}\n+        },\n+        \"bayes\": 1\n+    },\n+    \"rpi.agc\": {\n+        \"metering_modes\": {\n+            \"centre-weighted\": {\n+                \"weights\": [3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0]\n+            },\n+            \"spot\": {\n+                \"weights\": [2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n+            },\n+            \"matrix\": {\n+                \"weights\": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]\n+            }\n+        },\n+        \"exposure_modes\": {\n+            \"normal\": {\n+                \"shutter\": [100, 10000, 30000, 60000, 120000],\n+                \"gain\": [1.0, 2.0, 4.0, 6.0, 6.0]\n+            },\n+            \"short\": {\n+                \"shutter\": [100, 5000, 10000, 20000, 120000],\n+                \"gain\": [1.0, 2.0, 4.0, 6.0, 6.0]\n+            }\n+        },\n+        \"constraint_modes\": {\n+            \"normal\": [\n+                {\"bound\": \"LOWER\", \"q_lo\": 0.98, \"q_hi\": 1.0, \"y_target\": [0, 0.5, 1000, 0.5]}\n+            ],\n+            \"highlight\": [\n+                {\"bound\": \"LOWER\", \"q_lo\": 0.98, \"q_hi\": 1.0, \"y_target\": [0, 0.5, 1000, 0.5]},\n+                {\"bound\": \"UPPER\", \"q_lo\": 0.98, \"q_hi\": 1.0, \"y_target\": [0, 0.8, 1000, 0.8]}\n+            ]\n+        },\n+        \"y_target\": [0, 0.16, 1000, 0.165, 10000, 0.17]\n+    },\n+    \"rpi.alsc\": {\n+        'omega': 1.3,\n+        'n_iter': 100,\n+        'luminance_strength': 0.7,\n+    },\n+    \"rpi.contrast\": {\n+        \"ce_enable\": 1,\n+        \"gamma_curve\": [\n+            0,     0,\n+            1024,  5040,\n+            2048,  9338,\n+            3072,  12356,\n+            4096,  15312,\n+            5120,  18051,\n+            6144,  20790,\n+            7168,  23193,\n+            8192,  25744,\n+            9216,  27942,\n+            10240, 30035,\n+            11264, 32005,\n+            12288, 33975,\n+            13312, 35815,\n+            14336, 37600,\n+            15360, 39168,\n+            16384, 40642,\n+            18432, 43379,\n+            20480, 45749,\n+            22528, 47753,\n+            24576, 49621,\n+            26624, 51253,\n+            28672, 52698,\n+            30720, 53796,\n+            32768, 54876,\n+            36864, 57012,\n+            40960, 58656,\n+            45056, 59954,\n+            49152, 61183,\n+            53248, 62355,\n+            57344, 63419,\n+            61440, 64476,\n+            65535, 65535\n+        ]\n+    },\n+    \"rpi.ccm\": {\n+    },\n+    \"rpi.sharpen\": {\n+    }\n+}\n+\n+grid_size = (16, 12)\n+\n+target = 'bcm2835'\n+\n+if __name__ == '__main__':\n+    \"\"\"\n+    initialise calibration\n+    \"\"\"\n+    if len(sys.argv) == 1:\n+        print(\"\"\"\n+    VC4 Camera Tuning Tool version 1.0\n+\n+    Required Arguments:\n+    '-i' : Calibration image directory.\n+    '-o' : Name of output json file.\n+\n+    Optional Arguments:\n+    '-c' : Config file for the CTT. If not passed, default parameters used.\n+    '-l' : Name of output log file. If not passed, 'ctt_log.txt' used.\n+              \"\"\")\n+        quit(0)\n+    else:\n+        \"\"\"\n+        parse input arguments\n+        \"\"\"\n+        json_output, directory, config, log_output = parse_input()\n+        run_ctt(json_output, directory, config, log_output, json_template, grid_size, target)\n","prefixes":["1/6"]}