From patchwork Fri May 8 16:53:36 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Javier Tia X-Patchwork-Id: 26700 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 CB650BE173 for ; Fri, 8 May 2026 17:17:28 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 777AE6302B; Fri, 8 May 2026 19:17:28 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=jetm.me header.i=@jetm.me header.b="UECLH6VT"; dkim=pass (2048-bit key; unprotected) header.d=messagingengine.com header.i=@messagingengine.com header.b="E81jVt9X"; dkim-atps=neutral Received: from fout-a6-smtp.messagingengine.com (fout-a6-smtp.messagingengine.com [103.168.172.149]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id E647362FE1 for ; Fri, 8 May 2026 19:17:24 +0200 (CEST) Received: from phl-compute-02.internal (phl-compute-02.internal [10.202.2.42]) by mailfout.phl.internal (Postfix) with ESMTP id 42B95EC0073; Fri, 8 May 2026 13:17:24 -0400 (EDT) Received: from phl-imap-07 ([10.202.2.97]) by phl-compute-02.internal (MEProxy); Fri, 08 May 2026 13:17:24 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=jetm.me; h=cc:cc :content-transfer-encoding:content-type:content-type:date:date :from:from:in-reply-to:in-reply-to:message-id:mime-version :references:reply-to:subject:subject:to:to; s=fm2; t=1778260644; x=1778347044; bh=kmhZ2W1joAZD6ZezUaeHUltkj832ri3xQUgTXtRh59k=; b= UECLH6VTAGZWc6xF+2INKBSTWlkzVbpxQhvNpJFJYaZ5EHbYqNR1QlO7SpRvqlfi k06V3AK0zuVAWYSCGlDEqlr+kRz77qsDyHvHSv1TLMnok+sT9IG03pU26sep0JZH 0R4sIScqEEk5a0IABFfB18dEqA7M5Wkhn4TwmgLBXYkKd4FkGWkAVaRNOUIjqJFo +Lz1rbgXbdN1YFYVU/IrtL6b8RxCyFlIoTNpBELGZOnd4U8ZYXEdJ06CW9/FCn/E MZnczcUjuXUrpUmvLv39ChRRX/pOLgJWwicPehTir0oIOHujC1875D3Amh3TneIE /uVyEB3BnYfpFo6kVHfDDg== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-transfer-encoding :content-type:content-type:date:date:feedback-id:feedback-id :from:from:in-reply-to:in-reply-to:message-id:mime-version :references:reply-to:subject:subject:to:to:x-me-proxy :x-me-sender:x-me-sender:x-sasl-enc; s=fm3; t=1778260644; x= 1778347044; bh=kmhZ2W1joAZD6ZezUaeHUltkj832ri3xQUgTXtRh59k=; b=E 81jVt9XGGSS1ssVB1vXdQqU8KjISn+ZwUgOzTO597Z/PjUe6ENbZ2iXnP8HjX2ii SyOCeiveHgiUFyCpffiuSsgV8m1cyW96/qPdqgpKMuNUQ9VWfDuF4E3bKtdE9gSl j2tnmoYGyIDwLq+/ZlYStyRwd+BM6Jn5Rk1BPn2E8ifYxXDBJS9qDVja+r0yaT0p H3OG0T5/OWZ6XbhHCskqvPwSWrtCwOMN9LiW7qUDBbm8qbhu9z9fyOpkdVH9/Z+N lsB+WDG/36p2uW/J/Iva7uz1IkhBvC2TAte+XkGi6Zhsb/wE+nnzPsUPWwAiV3Na Sp+1wexfwvUElNDEZP8IA== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefhedrtddtgdduuddtledvucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfurfetoffkrfgpnffqhgenuceu rghilhhouhhtmecufedttdenucgopfhokfffucdluddtmdenucfjughrpefotggggffhvf ffufevjghfsehtkedttdertdejnecuhfhrohhmpeflrghvihgvrhcuvfhirgcuoehflhho shhssehjvghtmhdrmhgvqeenucggtffrrghtthgvrhhnpedtudejffejkeekteelueefvd ejvdeuhfefteehkeevtddvleduteekleetvdelhfenucevlhhushhtvghrufhiiigvpedu necurfgrrhgrmhepmhgrihhlfhhrohhmpehflhhoshhssehjvghtmhdrmhgvpdhnsggprh gtphhtthhopeejpdhmohguvgepshhmthhpohhuthdprhgtphhtthhopehrohgsvghrthdr mhgruggvrhestgholhhlrggsohhrrgdrtghomhdprhgtphhtthhopegsrghrnhgrsggrsh drphhotgiivgesihguvggrshhonhgsohgrrhgurdgtohhmpdhrtghpthhtohepkhhivghr rghnrdgsihhnghhhrghmsehiuggvrghsohhnsghorghrugdrtghomhdprhgtphhtthhope hlrghurhgvnhhtrdhpihhntghhrghrthesihguvggrshhonhgsohgrrhgurdgtohhmpdhr tghpthhtoheplhhisggtrghmvghrrgdquggvvhgvlheslhhishhtshdrlhhisggtrghmvg hrrgdrohhrghdprhgtphhtthhopehjohhhrghnnhgvshdrghhovgguvgesohhsshdrqhhu rghltghomhhmrdgtohhmpdhrtghpthhtohepmhiirghmrgiirghlsehrvgguhhgrthdrtg homh X-ME-Proxy: Feedback-ID: i9dde48b3:Fastmail Received: by mailuser.phl.internal (Postfix, from userid 501) id 201811EA006C; Fri, 8 May 2026 13:17:24 -0400 (EDT) X-Mailer: MessagingEngine.com Webmail Interface MIME-Version: 1.0 From: Javier Tia 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, laurent.pinchart@ideasonboard.com, barnabas.pocze@ideasonboard.com, johannes.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: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a Python script to extract CCMs and AWB chromaticity limits from Intel AIQB binary calibration files, producing a ready-to-use libcamera Simple IPA tuning YAML. AIQB is Intel's proprietary calibration format shipped with Windows camera drivers for Intel IPU6 sensors. Files for Alder Lake and Tiger Lake sensors are available in the ipu6-camera-hal repository under config/linux/ipu6ep/, or can be extracted from OEM Windows driver installers using p7zip and innoextract. The script supports record id=25 (advanced color matrices, float format with CCT in Kelvin directly) and falls back to record id=18 (integer matrices with autodetected scale). Record id=25 is preferred and present in all Alder Lake AIQB files examined. Tested against OV2740_CJFLE23_ADL.aiqb (Lenovo ThinkPad X1 Carbon Gen 10, extracted from n3ace31w.exe). Other AIQB files may require adjustments if the record layout differs. Signed-off-by: Javier Tia --- utils/tuning/parse_aiqb.py | 276 +++++++++++++++++++++++++++++++++++++ 1 file changed, 276 insertions(+) create mode 100644 utils/tuning/parse_aiqb.py diff --git a/utils/tuning/parse_aiqb.py b/utils/tuning/parse_aiqb.py new file mode 100644 index 00000000..12948f75 --- /dev/null +++ b/utils/tuning/parse_aiqb.py @@ -0,0 +1,276 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Parse an Intel AIQB (CPFF) binary to extract CCMs and AWB chromaticity +# limits for use in a libcamera Simple IPA tuning YAML file. +# +# Tested against OV2740_CJFLE23_ADL.aiqb (Alder Lake, Intel ipu6-camera-hal). +# Other sensors and AIQB versions may require adjustments to the record +# layout assumptions. +# +# AIQB files for Intel IPU6 sensors are available in the ipu6-camera-hal +# repository at config/linux/ipu6ep/. Alternatively, extract from the OEM +# Windows camera driver installer using p7zip and innoextract. +# +# Usage: +# python3 parse_aiqb.py .aiqb [--sensor-name ] \ +# [--black-level ] +# +# The black level is NOT extracted from the AIQB. Pass --black-level with the +# sensor's black level in 16-bit convention (value >> 8 = 8-bit black level). +# Check the sensor datasheet or kernel driver for the correct value. +# Example: OV2740 has 0x40 at 10-bit (64 ADU), which is 4096 in 16-bit conv. + +import argparse +import os +import struct +import sys + +# ia_mkn_record_header: size(u32), fmt_id(u8), key_id(u8), name_id(u16) +REC_HDR = struct.Struct(' approximate CCT in Kelvin +LIGHT_SOURCE_CCT = { + 1: 2856, # A - Incandescent/Tungsten + 4: 5003, # D50 + 5: 5503, # D55 + 6: 6504, # D65 + 7: 7504, # D75 + 8: 5454, # E (equal energy) + 9: 6430, # F1 daylight fluorescent + 10: 4230, # F2 cool white + 11: 3450, # F3 white + 12: 3000, # F4 warm white + 13: 6350, # F5 + 14: 4150, # F6 + 15: 6500, # F7 D65 sim + 16: 5000, # F8 D50 sim + 17: 4150, # F9 + 18: 5000, # F10 + 19: 4000, # F11 + 20: 3000, # F12 + 22: 2300, # HZ horizon +} + +# Record chain starts here in all AIQB files checked so far +FIRST_RECORD_OFFSET = 0x50 + +def walk_records(data): + records = {} + offset = FIRST_RECORD_OFFSET + while offset + REC_HDR_SIZE <= len(data): + size, fmt_id, key_id, name_id = REC_HDR.unpack_from(data, offset) + if size < REC_HDR_SIZE or offset + size > len(data): + break + records[name_id] = (offset, size) + offset += size + return records + +def extract_general_data(data, offset): + w, h, bd, co = struct.unpack_from(' 0.05: + print(f" WARNING: CCT={cct}K row sums {[round(s, 4) for s in row_sums]}") + print(f" CCT={cct}K R/G={rg:.4f} B/G={bg:.4f}") + valid_matrices.append((cct, vals, rg, bg)) + + max_gain_r = max_gain_b = None + if valid_matrices: + min_rg = min(m[2] for m in valid_matrices) + min_bg = min(m[3] for m in valid_matrices) + max_gain_r = round((1.0 / min_rg) * 1.1, 2) if min_rg > 0 else 2.5 + max_gain_b = round((1.0 / min_bg) * 1.1, 2) if min_bg > 0 else 3.2 + print(f"\nSuggested AWB maxGainR={max_gain_r}, maxGainB={max_gain_b} " + f"(from min R/G={min_rg:.4f}, min B/G={min_bg:.4f})") + + aiqb_name = os.path.basename(args.aiqb) + print("\n" + "=" * 60) + print(f"# {sensor_name}.yaml for libcamera Simple IPA") + print("# SPDX-License-Identifier: CC0-1.0") + print(f"# Calibrated from {aiqb_name}") + print("%YAML 1.1") + print("---") + print("version: 1") + print("algorithms:") + print(" - BlackLevel:") + if args.black_level: + print(f" blackLevel: {args.black_level}") + else: + print(" blackLevel: 0 # TODO: set correct value from sensor datasheet") + print(" - Awb:") + print(f" maxGainR: {max_gain_r}") + print(f" maxGainB: {max_gain_b}") + print(" speed: 0.25") + print(" - Ccm:") + print(" ccms:") + for cct, vals, rg, bg in sorted(valid_matrices): + print(f" - ct: {cct}") + print(f" ccm: {format_ccm(vals)}") + print(" - Adjust:") + print(" gamma: 2.2") + print(" contrast: 1.0") + print(" saturation: 1.0") + print(" - Agc:") + print("...") + +if __name__ == '__main__': + main()