Patch Detail
Show a patch.
GET /api/patches/26700/?format=api
{ "id": 26700, "url": "https://patchwork.libcamera.org/api/patches/26700/?format=api", "web_url": "https://patchwork.libcamera.org/patch/26700/", "project": { "id": 1, "url": "https://patchwork.libcamera.org/api/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": "<20260508171724.201811EA006C@mailuser.phl.internal>", "date": "2026-05-08T16:53:36", "name": "[v2,3/3] utils: tuning: Add AIQB parser for Intel IPU6 sensors", "commit_ref": null, "pull_url": null, "state": "superseded", "archived": false, "hash": "5428c4d4afe922bdbdc5c30208a9d7e036aa3a50", "submitter": { "id": 261, "url": "https://patchwork.libcamera.org/api/people/261/?format=api", "name": "Javier Tia", "email": "floss@jetm.me" }, "delegate": null, "mbox": "https://patchwork.libcamera.org/patch/26700/mbox/", "series": [ { "id": 5926, "url": "https://patchwork.libcamera.org/api/series/5926/?format=api", "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=5926", "date": "2026-05-08T16:51:31", "name": "ipa: simple: OV2740 tuning file and swstats sumShift cleanup", "version": 2, "mbox": "https://patchwork.libcamera.org/series/5926/mbox/" } ], "comments": "https://patchwork.libcamera.org/api/patches/26700/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/26700/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 CB650BE173\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 8 May 2026 17:17:28 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 777AE6302B;\n\tFri, 8 May 2026 19:17:28 +0200 (CEST)", "from fout-a6-smtp.messagingengine.com\n\t(fout-a6-smtp.messagingengine.com [103.168.172.149])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id E647362FE1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 8 May 2026 19:17:24 +0200 (CEST)", "from phl-compute-02.internal (phl-compute-02.internal\n\t[10.202.2.42])\n\tby mailfout.phl.internal (Postfix) with ESMTP id 42B95EC0073;\n\tFri, 8 May 2026 13:17:24 -0400 (EDT)", "from phl-imap-07 ([10.202.2.97])\n\tby phl-compute-02.internal (MEProxy); Fri, 08 May 2026 13:17:24 -0400", "by mailuser.phl.internal (Postfix, from userid 501)\n\tid 201811EA006C; Fri, 8 May 2026 13:17:24 -0400 (EDT)" ], "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=jetm.me header.i=@jetm.me header.b=\"UECLH6VT\";\n\tdkim=pass (2048-bit key;\n\tunprotected) header.d=messagingengine.com\n\theader.i=@messagingengine.com header.b=\"E81jVt9X\"; \n\tdkim-atps=neutral", "DKIM-Signature": [ "v=1; a=rsa-sha256; c=relaxed/relaxed; d=jetm.me; h=cc:cc\n\t:content-transfer-encoding:content-type:content-type:date:date\n\t:from:from:in-reply-to:in-reply-to:message-id:mime-version\n\t:references:reply-to:subject:subject:to:to; s=fm2; t=1778260644;\n\tx=1778347044; bh=kmhZ2W1joAZD6ZezUaeHUltkj832ri3xQUgTXtRh59k=; b=\n\tUECLH6VTAGZWc6xF+2INKBSTWlkzVbpxQhvNpJFJYaZ5EHbYqNR1QlO7SpRvqlfi\n\tk06V3AK0zuVAWYSCGlDEqlr+kRz77qsDyHvHSv1TLMnok+sT9IG03pU26sep0JZH\n\t0R4sIScqEEk5a0IABFfB18dEqA7M5Wkhn4TwmgLBXYkKd4FkGWkAVaRNOUIjqJFo\n\t+Lz1rbgXbdN1YFYVU/IrtL6b8RxCyFlIoTNpBELGZOnd4U8ZYXEdJ06CW9/FCn/E\n\tMZnczcUjuXUrpUmvLv39ChRRX/pOLgJWwicPehTir0oIOHujC1875D3Amh3TneIE\n\t/uVyEB3BnYfpFo6kVHfDDg==", "v=1; a=rsa-sha256; c=relaxed/relaxed; d=\n\tmessagingengine.com; h=cc:cc:content-transfer-encoding\n\t:content-type:content-type:date:date:feedback-id:feedback-id\n\t:from:from:in-reply-to:in-reply-to:message-id:mime-version\n\t:references:reply-to:subject:subject:to:to:x-me-proxy\n\t:x-me-sender:x-me-sender:x-sasl-enc; s=fm3; t=1778260644; x=\n\t1778347044; bh=kmhZ2W1joAZD6ZezUaeHUltkj832ri3xQUgTXtRh59k=; b=E\n\t81jVt9XGGSS1ssVB1vXdQqU8KjISn+ZwUgOzTO597Z/PjUe6ENbZ2iXnP8HjX2ii\n\tSyOCeiveHgiUFyCpffiuSsgV8m1cyW96/qPdqgpKMuNUQ9VWfDuF4E3bKtdE9gSl\n\tj2tnmoYGyIDwLq+/ZlYStyRwd+BM6Jn5Rk1BPn2E8ifYxXDBJS9qDVja+r0yaT0p\n\tH3OG0T5/OWZ6XbhHCskqvPwSWrtCwOMN9LiW7qUDBbm8qbhu9z9fyOpkdVH9/Z+N\n\tlsB+WDG/36p2uW/J/Iva7uz1IkhBvC2TAte+XkGi6Zhsb/wE+nnzPsUPWwAiV3Na\n\tSp+1wexfwvUElNDEZP8IA==" ], "X-ME-Sender": "<xms:pBr-aT2o3cLUy0MTPxehMpHjrSFeOi8VLNL-3o3QDemjnngIcb_Rtg>\n\t<xme:pBr-ac5DUvcWJyImUuaLxZXNHwzM3uRrNQ2aVBZCfWiY-IRkBGf-Ezzg8TMAzDtDk\n\tYaNbrVN_O41BU_3ADdTb2k65woIzz13UYfVKtO3_dU5LyqnH0ouGw>", "X-ME-Proxy-Cause": "gggruggvucftvghtrhhoucdtuddrgeefhedrtddtgdduuddtledvucetufdoteggodetrf\n\tdotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfurfetoffkrfgpnffqhgenuceu\n\trghilhhouhhtmecufedttdenucgopfhokfffucdluddtmdenucfjughrpefotggggffhvf\n\tffufevjghfsehtkedttdertdejnecuhfhrohhmpeflrghvihgvrhcuvfhirgcuoehflhho\n\tshhssehjvghtmhdrmhgvqeenucggtffrrghtthgvrhhnpedtudejffejkeekteelueefvd\n\tejvdeuhfefteehkeevtddvleduteekleetvdelhfenucevlhhushhtvghrufhiiigvpedu\n\tnecurfgrrhgrmhepmhgrihhlfhhrohhmpehflhhoshhssehjvghtmhdrmhgvpdhnsggprh\n\tgtphhtthhopeejpdhmohguvgepshhmthhpohhuthdprhgtphhtthhopehrohgsvghrthdr\n\tmhgruggvrhestgholhhlrggsohhrrgdrtghomhdprhgtphhtthhopegsrghrnhgrsggrsh\n\tdrphhotgiivgesihguvggrshhonhgsohgrrhgurdgtohhmpdhrtghpthhtohepkhhivghr\n\trghnrdgsihhnghhhrghmsehiuggvrghsohhnsghorghrugdrtghomhdprhgtphhtthhope\n\thlrghurhgvnhhtrdhpihhntghhrghrthesihguvggrshhonhgsohgrrhgurdgtohhmpdhr\n\ttghpthhtoheplhhisggtrghmvghrrgdquggvvhgvlheslhhishhtshdrlhhisggtrghmvg\n\thrrgdrohhrghdprhgtphhtthhopehjohhhrghnnhgvshdrghhovgguvgesohhsshdrqhhu\n\trghltghomhhmrdgtohhmpdhrtghpthhtohepmhiirghmrgiirghlsehrvgguhhgrthdrtg\n\thomh", "X-ME-Proxy": "<xmx:pBr-aVd2b8PVlYKCM-W1OEAWAsYBbTFZNSzJAlW5BoqmoTba1ekl0Q>\n\t<xmx:pBr-adTsSDfyW1q04yHwk1S7NNLtYRDBkwoFxAKcGH2V1DbMPpYG7w>\n\t<xmx:pBr-aeapHa4XrubB4ObF2-CQ96zq8rFza2xlyqq0NmAAf4l7uU4L-Q>\n\t<xmx:pBr-aQf2tmuEIdNDASWyfpc_dI9ywdRlb0l-6JKolHiIyW6nq-LZrw>\n\t<xmx:pBr-aWf4x5PgNtSOtwmRHDpnOVIgvnHq5WMhMm2WVzzg1DPyha12min0>", "Feedback-ID": "i9dde48b3:Fastmail", "X-Mailer": "MessagingEngine.com Webmail Interface", "Content-Type": "text/plain; charset=\"utf-8\"", "MIME-Version": "1.0", "Content-Transfer-Encoding": "8bit", "From": "Javier Tia <floss@jetm.me>", "To": "libcamera-devel@lists.libcamera.org", "Date": "Fri, 08 May 2026 10:53:36 -0600", "Subject": "[PATCH v2 3/3] utils: tuning: Add AIQB parser for Intel IPU6 sensors", "Cc": "mzamazal@redhat.com, kieran.bingham@ideasonboard.com,\n\tlaurent.pinchart@ideasonboard.com, barnabas.pocze@ideasonboard.com,\n\tjohannes.goede@oss.qualcomm.com, robert.mader@collabora.com", "In-Reply-To": "<177826063718.39714.13674874482653763631@jetm.me>", "References": "<177826063718.39714.13674874482653763631@jetm.me>", "Message-Id": "<20260508171724.201811EA006C@mailuser.phl.internal>", "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": "Add a Python script to extract CCMs and AWB chromaticity limits from\nIntel AIQB binary calibration files, producing a ready-to-use\nlibcamera Simple IPA tuning YAML.\n\nAIQB is Intel's proprietary calibration format shipped with Windows\ncamera drivers for Intel IPU6 sensors. Files for Alder Lake and Tiger\nLake sensors are available in the ipu6-camera-hal repository under\nconfig/linux/ipu6ep/, or can be extracted from OEM Windows driver\ninstallers using p7zip and innoextract.\n\nThe script supports record id=25 (advanced color matrices, float\nformat with CCT in Kelvin directly) and falls back to record id=18\n(integer matrices with autodetected scale). Record id=25 is preferred\nand present in all Alder Lake AIQB files examined.\n\nTested against OV2740_CJFLE23_ADL.aiqb (Lenovo ThinkPad X1 Carbon\nGen 10, extracted from n3ace31w.exe). Other AIQB files may require\nadjustments if the record layout differs.\n\nSigned-off-by: Javier Tia <floss@jetm.me>\n---\n utils/tuning/parse_aiqb.py | 276 +++++++++++++++++++++++++++++++++++++\n 1 file changed, 276 insertions(+)\n create mode 100644 utils/tuning/parse_aiqb.py", "diff": "diff --git a/utils/tuning/parse_aiqb.py b/utils/tuning/parse_aiqb.py\nnew file mode 100644\nindex 00000000..12948f75\n--- /dev/null\n+++ b/utils/tuning/parse_aiqb.py\n@@ -0,0 +1,276 @@\n+#!/usr/bin/env python3\n+# SPDX-License-Identifier: GPL-2.0-or-later\n+#\n+# Parse an Intel AIQB (CPFF) binary to extract CCMs and AWB chromaticity\n+# limits for use in a libcamera Simple IPA tuning YAML file.\n+#\n+# Tested against OV2740_CJFLE23_ADL.aiqb (Alder Lake, Intel ipu6-camera-hal).\n+# Other sensors and AIQB versions may require adjustments to the record\n+# layout assumptions.\n+#\n+# AIQB files for Intel IPU6 sensors are available in the ipu6-camera-hal\n+# repository at config/linux/ipu6ep/. Alternatively, extract from the OEM\n+# Windows camera driver installer using p7zip and innoextract.\n+#\n+# Usage:\n+# python3 parse_aiqb.py <sensor>.aiqb [--sensor-name <name>] \\\n+# [--black-level <value>]\n+#\n+# The black level is NOT extracted from the AIQB. Pass --black-level with the\n+# sensor's black level in 16-bit convention (value >> 8 = 8-bit black level).\n+# Check the sensor datasheet or kernel driver for the correct value.\n+# Example: OV2740 has 0x40 at 10-bit (64 ADU), which is 4096 in 16-bit conv.\n+\n+import argparse\n+import os\n+import struct\n+import sys\n+\n+# ia_mkn_record_header: size(u32), fmt_id(u8), key_id(u8), name_id(u16)\n+REC_HDR = struct.Struct('<IBBH')\n+REC_HDR_SIZE = 8\n+\n+# cmc_name_id enum values\n+CMC_GENERAL_DATA = 2\n+CMC_SENSITIVITY = 7\n+CMC_COLOR_MATRICES = 18\n+CMC_ADV_COLOR_MATRICES = 25\n+\n+# cmc_color_matrix_t (84 bytes):\n+# int32 light_src_type\n+# uint16 r_per_g, b_per_g\n+# uint16 cie_x, cie_y\n+# int32 matrix_accurate[9]\n+# int32 matrix_preferred[9]\n+COLOR_MATRIX = struct.Struct('<i HH HH 9i 9i')\n+assert COLOR_MATRIX.size == 84\n+\n+# Light source enum -> approximate CCT in Kelvin\n+LIGHT_SOURCE_CCT = {\n+ 1: 2856, # A - Incandescent/Tungsten\n+ 4: 5003, # D50\n+ 5: 5503, # D55\n+ 6: 6504, # D65\n+ 7: 7504, # D75\n+ 8: 5454, # E (equal energy)\n+ 9: 6430, # F1 daylight fluorescent\n+ 10: 4230, # F2 cool white\n+ 11: 3450, # F3 white\n+ 12: 3000, # F4 warm white\n+ 13: 6350, # F5\n+ 14: 4150, # F6\n+ 15: 6500, # F7 D65 sim\n+ 16: 5000, # F8 D50 sim\n+ 17: 4150, # F9\n+ 18: 5000, # F10\n+ 19: 4000, # F11\n+ 20: 3000, # F12\n+ 22: 2300, # HZ horizon\n+}\n+\n+# Record chain starts here in all AIQB files checked so far\n+FIRST_RECORD_OFFSET = 0x50\n+\n+def walk_records(data):\n+ records = {}\n+ offset = FIRST_RECORD_OFFSET\n+ while offset + REC_HDR_SIZE <= len(data):\n+ size, fmt_id, key_id, name_id = REC_HDR.unpack_from(data, offset)\n+ if size < REC_HDR_SIZE or offset + size > len(data):\n+ break\n+ records[name_id] = (offset, size)\n+ offset += size\n+ return records\n+\n+def extract_general_data(data, offset):\n+ w, h, bd, co = struct.unpack_from('<HHHH', data, offset + REC_HDR_SIZE)\n+ return {'width': w, 'height': h, 'bit_depth': bd, 'color_order': co}\n+\n+def extract_sensitivity(data, offset):\n+ iso, = struct.unpack_from('<H', data, offset + REC_HDR_SIZE)\n+ return iso\n+\n+def extract_color_matrices(data, offset):\n+ num, = struct.unpack_from('<H', data, offset + REC_HDR_SIZE)\n+ matrices = []\n+ mat_offset = offset + REC_HDR_SIZE + 2\n+ for i in range(num):\n+ fields = COLOR_MATRIX.unpack_from(data, mat_offset + i * 84)\n+ light_src = fields[0]\n+ matrices.append({\n+ 'light_src': light_src,\n+ 'cct': LIGHT_SOURCE_CCT.get(light_src),\n+ 'r_per_g_raw': fields[1],\n+ 'b_per_g_raw': fields[2],\n+ 'matrix_raw': fields[5:14],\n+ })\n+ return matrices\n+\n+def extract_advanced_color_matrices(data, offset):\n+ \"\"\"Parse cmc_advanced_color_matrix_correction (record id=25).\n+\n+ Layout after the 8-byte record header:\n+ uint16 num_light_srcs\n+ uint16 num_sectors\n+ uint32 hue_of_sectors[num_sectors]\n+ Per light source (24-byte cmc_acm_color_matrices_info_v101_t):\n+ uint32 src_type\n+ float r_per_g\n+ float b_per_g\n+ float cie_x\n+ float cie_y\n+ uint32 cct (Kelvin, directly)\n+ float traditional[9] (3x3 CCM, rows sum to 1.0)\n+ float advanced[num_sectors][9] (per-sector CCMs, skipped)\n+ \"\"\"\n+ pos = offset + REC_HDR_SIZE\n+ num_ls, num_sectors = struct.unpack_from('<HH', data, pos)\n+ pos += 4\n+ pos += num_sectors * 4 # skip hue_of_sectors\n+\n+ INFO = struct.Struct('<IffffI') # 24 bytes\n+ CCM = struct.Struct('<9f') # 36 bytes\n+\n+ matrices = []\n+ for _ in range(num_ls):\n+ src_type, rg, bg, cie_x, cie_y, cct_k = INFO.unpack_from(data, pos)\n+ pos += 24\n+ trad = CCM.unpack_from(data, pos)\n+ pos += 36\n+ pos += num_sectors * 36 # skip per-sector advanced CCMs\n+ matrices.append({\n+ 'light_src': src_type,\n+ 'cct': cct_k,\n+ 'r_per_g': rg,\n+ 'b_per_g': bg,\n+ 'matrix_float': trad,\n+ })\n+ return matrices\n+\n+def guess_ccm_scale(matrices):\n+ for scale in (8192, 4096, 2048, 1024):\n+ errors = []\n+ for m in matrices:\n+ for row in range(3):\n+ s = sum(m['matrix_raw'][row * 3:(row + 1) * 3]) / scale\n+ errors.append(abs(s - 1.0))\n+ if max(errors) < 0.05:\n+ return scale\n+ return 8192\n+\n+def format_ccm(vals):\n+ rows = []\n+ for row in range(3):\n+ r = vals[row * 3:(row + 1) * 3]\n+ rows.append(f\" {r[0]:8.4f}, {r[1]:8.4f}, {r[2]:8.4f}\")\n+ return '[\\n' + ',\\n'.join(rows) + ' ]'\n+\n+def main():\n+ parser = argparse.ArgumentParser(\n+ description='Parse Intel AIQB binary for libcamera Simple IPA YAML',\n+ epilog='Tested on OV2740_CJFLE23_ADL.aiqb only. Other sensors may '\n+ 'require adjustments.')\n+ parser.add_argument('aiqb', help='Path to .aiqb file')\n+ parser.add_argument('--sensor-name',\n+ help='Sensor name for YAML output (default: derived '\n+ 'from filename)')\n+ parser.add_argument('--black-level', type=int, default=0,\n+ help='Black level in 16-bit convention, e.g. 4096 for '\n+ 'OV2740 (default: 0 = unknown, emits placeholder)')\n+ parser.add_argument('--ccm-scale', type=int, default=0,\n+ help='Integer CCM scale for record id=18 (0=autodetect)')\n+ args = parser.parse_args()\n+\n+ sensor_name = args.sensor_name or os.path.basename(args.aiqb).split('_')[0].lower()\n+\n+ with open(args.aiqb, 'rb') as f:\n+ data = f.read()\n+\n+ print(f\"File: {args.aiqb} ({len(data)} bytes)\")\n+\n+ records = walk_records(data)\n+ print(f\"Records found: {sorted(records.keys())}\\n\")\n+\n+ if CMC_GENERAL_DATA in records:\n+ gd = extract_general_data(data, records[CMC_GENERAL_DATA][0])\n+ print(f\"Sensor: {gd['width']}x{gd['height']}, {gd['bit_depth']}-bit, \"\n+ f\"color_order={gd['color_order']}\")\n+\n+ if CMC_SENSITIVITY in records:\n+ iso = extract_sensitivity(data, records[CMC_SENSITIVITY][0])\n+ print(f\"Base ISO: {iso}\")\n+\n+ adv_mode = False\n+ if CMC_ADV_COLOR_MATRICES in records:\n+ matrices = extract_advanced_color_matrices(data, records[CMC_ADV_COLOR_MATRICES][0])\n+ adv_mode = True\n+ print(f\"\\nAdvanced color matrices (id=25, {len(matrices)} entries, float CCMs):\")\n+ elif CMC_COLOR_MATRICES in records:\n+ matrices = extract_color_matrices(data, records[CMC_COLOR_MATRICES][0])\n+ print(f\"\\nColor matrices (id=18, {len(matrices)} entries):\")\n+ else:\n+ print(\"ERROR: no color_matrices record found (id=18 or id=25)\")\n+ sys.exit(1)\n+\n+ ccm_scale = args.ccm_scale or (1 if adv_mode else guess_ccm_scale(matrices))\n+\n+ valid_matrices = []\n+ for m in matrices:\n+ cct = m['cct']\n+ if not cct:\n+ continue\n+ if adv_mode:\n+ vals = list(m['matrix_float'])\n+ rg = m['r_per_g']\n+ bg = m['b_per_g']\n+ else:\n+ vals = [v / ccm_scale for v in m['matrix_raw']]\n+ rg = m['r_per_g_raw'] / 256.0\n+ bg = m['b_per_g_raw'] / 256.0\n+ row_sums = [sum(vals[r * 3:(r + 1) * 3]) for r in range(3)]\n+ if max(abs(s - 1.0) for s in row_sums) > 0.05:\n+ print(f\" WARNING: CCT={cct}K row sums {[round(s, 4) for s in row_sums]}\")\n+ print(f\" CCT={cct}K R/G={rg:.4f} B/G={bg:.4f}\")\n+ valid_matrices.append((cct, vals, rg, bg))\n+\n+ max_gain_r = max_gain_b = None\n+ if valid_matrices:\n+ min_rg = min(m[2] for m in valid_matrices)\n+ min_bg = min(m[3] for m in valid_matrices)\n+ max_gain_r = round((1.0 / min_rg) * 1.1, 2) if min_rg > 0 else 2.5\n+ max_gain_b = round((1.0 / min_bg) * 1.1, 2) if min_bg > 0 else 3.2\n+ print(f\"\\nSuggested AWB maxGainR={max_gain_r}, maxGainB={max_gain_b} \"\n+ f\"(from min R/G={min_rg:.4f}, min B/G={min_bg:.4f})\")\n+\n+ aiqb_name = os.path.basename(args.aiqb)\n+ print(\"\\n\" + \"=\" * 60)\n+ print(f\"# {sensor_name}.yaml for libcamera Simple IPA\")\n+ print(\"# SPDX-License-Identifier: CC0-1.0\")\n+ print(f\"# Calibrated from {aiqb_name}\")\n+ print(\"%YAML 1.1\")\n+ print(\"---\")\n+ print(\"version: 1\")\n+ print(\"algorithms:\")\n+ print(\" - BlackLevel:\")\n+ if args.black_level:\n+ print(f\" blackLevel: {args.black_level}\")\n+ else:\n+ print(\" blackLevel: 0 # TODO: set correct value from sensor datasheet\")\n+ print(\" - Awb:\")\n+ print(f\" maxGainR: {max_gain_r}\")\n+ print(f\" maxGainB: {max_gain_b}\")\n+ print(\" speed: 0.25\")\n+ print(\" - Ccm:\")\n+ print(\" ccms:\")\n+ for cct, vals, rg, bg in sorted(valid_matrices):\n+ print(f\" - ct: {cct}\")\n+ print(f\" ccm: {format_ccm(vals)}\")\n+ print(\" - Adjust:\")\n+ print(\" gamma: 2.2\")\n+ print(\" contrast: 1.0\")\n+ print(\" saturation: 1.0\")\n+ print(\" - Agc:\")\n+ print(\"...\")\n+\n+if __name__ == '__main__':\n+ main()\n", "prefixes": [ "v2", "3/3" ] }