From patchwork Thu Sep 24 12:18:32 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 28366 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 9E899BE173 for ; Thu, 24 Sep 2026 12:18:45 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7B0CE6895A; Thu, 24 Sep 2026 14:18:44 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="kx+VNG3I"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5DFD36849B for ; Thu, 24 Sep 2026 14:18:43 +0200 (CEST) Received: from ping.linuxembedded.co.uk (cpc89244-aztw30-2-0-cust6594.18-1.cable.virginm.net [86.31.185.195]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id CF62DC23; Thu, 24 Sep 2026 14:16:55 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1790252215; bh=cTiRPH4DL2M4iZxuAAqMz9bIcErg5pOjboSS69IJIWE=; h=From:Date:Subject:To:Cc:From; b=kx+VNG3IKZ6nohfCtrPpQlEUgTnD6SEUTVtG2zYzjTgvHnPjsTk5okL+WOS4LcSZ3 N5m5Ett5H0cuRNUtaLY8pp/heZpkEa8XU5Ylt+UQR7JLkJhtwDUJkBL1u5ci1qAF5P qghbnBimulHQuqD3wm0eLSvn4Xx1wcN543LsMRTE= From: Kieran Bingham Date: Thu, 24 Sep 2026 13:18:32 +0100 Subject: [PATCH v2] libcamera: Adding LensShadingCorrection maps and ToneCurve to controls metadata MIME-Version: 1.0 Message-Id: <20260924-articimaging-lsc-v2-1-37600e636bf1@ideasonboard.com> X-B4-Tracking: v=1; b=H4sIABcVtWoC/32NSwrDIBRFtxLeuBY1H2JH3UfJwOrTPGg0aJCWk L3XZgEdngP33B0yJsIMt2aHhIUyxVBBXhowsw4eGdnKILkcuBKc6bSRoUV7Cp69smESWyd7JYa xFVBna0JH7zP5mCrPlLeYPudDET/7J1YEE0z2rutG22un1J0s6hzDM+pkryYuMB3H8QV9m4elt wAAAA== X-Change-ID: 20260910-articimaging-lsc-2e3f25916831 To: libcamera-devel@lists.libcamera.org Cc: Michael Kunz X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1790252322; l=9710; i=kieran.bingham@ideasonboard.com; s=20260207; h=from:subject:message-id; bh=cLOLLAm6eaI0tBRPx92d8GWmvsUlUi8RznKSkc0iSaQ=; b=361mNJkIXvxlE28KWbT4mvNDbJFEEpNsYz7ZMUzfCse35OiY9x5rI3xU5EIf0FmR+n2EC44qh FxD3MJ7qPWFA4rUspKcmvjRsPpLrJGA00SYob5Sgw5lKuuprXzrU3Pt X-Developer-Key: i=kieran.bingham@ideasonboard.com; a=ed25519; pk=FVXKN7YuwHc6UtbRUeTMAmranfsQomA+vnilfglWdaY= 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" From: Michael Kunz Having the LensShadingCorrection maps and ToneCurves available in frame metadata allows creating a DNG file with all necessary information so that the DNG matches the JPEG image without colour casts. I opened a PR in raspberrypi/rpicam-apps (https://github.com/raspberrypi/rpicam-apps/pull/928) improving the colour accuracy of said DNG files created by these apps. The information needed is currently not provided by libcamera in the frame metadata. This patch intends to add LensShadingCorrection maps and ToneCurve to the controls metadata in libcamera, so that DNG files can be written with correct colours. Signed-off-by: Michael Kunz --- This patch is forwarded on behalf of Michael Kunz. It appears there is some issue on the mail server which is rejecting his posting. A double signed DKIM is being rejected by the OpenDKIM instance on the libcamera mail server. While we try to investigate this further, which might involve an upgrade to the mail server instance, I've applied the patch locally, compiled it and sending via git-send-email from me instead. Applying this patch had a conflict which required me to manually apply the hunks to src/ipa/rpi/common/ipa_base.cpp but I think everything here is enough to still continue to review and discussion. But I'll leave that to the patch! --- src/ipa/rpi/common/ipa_base.cpp | 50 ++++++++++++++++++++++++++++++++++--- src/ipa/rpi/common/ipa_base.h | 1 + src/ipa/rpi/common/meson.build | 2 +- src/ipa/rpi/controller/rpi/alsc.cpp | 3 +++ src/libcamera/control_ids_core.yaml | 42 +++++++++++++++++++++++++++++++ 5 files changed, 94 insertions(+), 4 deletions(-) --- base-commit: 0024628dae48701da79e7522c8a057ba6a855201 change-id: 20260910-articimaging-lsc-2e3f25916831 Best regards, diff --git a/src/ipa/rpi/common/ipa_base.cpp b/src/ipa/rpi/common/ipa_base.cpp index bc8e65cb0811..25ceb2492617 100644 --- a/src/ipa/rpi/common/ipa_base.cpp +++ b/src/ipa/rpi/common/ipa_base.cpp @@ -18,15 +18,18 @@ #include "controller/af_algorithm.h" #include "controller/af_status.h" #include "controller/agc_algorithm.h" +#include "controller/alsc_status.h" #include "controller/awb_algorithm.h" #include "controller/awb_status.h" #include "controller/black_level_status.h" #include "controller/ccm_algorithm.h" +#include "controller/contrast_status.h" #include "controller/ccm_status.h" #include "controller/contrast_algorithm.h" #include "controller/denoise_algorithm.h" #include "controller/hdr_algorithm.h" #include "controller/lux_status.h" +#include "controller/noise_status.h" #include "controller/sharpen_algorithm.h" #include "controller/statistics.h" @@ -80,6 +83,7 @@ const ControlInfoMap::Map ipaControls{ static_cast(defaultMaxFrameDuration.get()), std::span{ { static_cast(defaultMinFrameDuration.get()), static_cast(defaultMinFrameDuration.get()) } }) }, + { &controls::LensShadingCorrectionMapOutputEnable, ControlInfo(false, true, false) }, { &controls::draft::NoiseReductionMode, ControlInfo(controls::draft::NoiseReductionModeValues) }, { &controls::rpi::StatsOutputEnable, ControlInfo(false, true, false) }, }; @@ -121,7 +125,7 @@ LOG_DEFINE_CATEGORY(IPARPI) namespace ipa::RPi { IpaBase::IpaBase() - : controller_(), frameLengths_(FrameLengthsQueueSize, 0s), statsMetadataOutput_(false), + : controller_(), frameLengths_(FrameLengthsQueueSize, 0s), lscMapsOutput_(false), statsMetadataOutput_(false), stitchSwapBuffers_(false), frameCount_(0), mistrustCount_(0), lastRunTimestamp_(0), firstStart_(true), flickerState_({ 0, 0s }), awbEnabled_(true) { @@ -237,7 +241,6 @@ int32_t IpaBase::configure(const IPACameraSensorInfo &sensorInfo, const ConfigPa agcStatus.exposureTime = defaultExposureTime; agcStatus.analogueGain = defaultAnalogueGain; applyAGC(&agcStatus, ctrls); - } result->sensorControls = std::move(ctrls); @@ -772,8 +775,8 @@ static const std::map HdrModeTable = { void IpaBase::applyControls(const ControlList &controls) { - using RPiController::AgcAlgorithm; using RPiController::AfAlgorithm; + using RPiController::AgcAlgorithm; using RPiController::ContrastAlgorithm; using RPiController::DenoiseAlgorithm; using RPiController::HdrAlgorithm; @@ -1454,6 +1457,10 @@ void IpaBase::applyControls(const ControlList &controls) break; } + case controls::LENS_SHADING_CORRECTION_MAP_OUTPUT_ENABLE: + lscMapsOutput_ = ctrl.second.get(); + break; + case controls::rpi::STATS_OUTPUT_ENABLE: statsMetadataOutput_ = ctrl.second.get(); break; @@ -1647,6 +1654,43 @@ void IpaBase::reportMetadata(unsigned int ipaContext) libcameraMetadata_.set(controls::HdrChannel, controls::HdrChannelNone); } + NoiseStatus *noiseStatus = rpiMetadata.getLocked("noise.status"); + if (noiseStatus) { + float noiseProfile[] = { static_cast(noiseStatus->noiseSlope), + static_cast(noiseStatus->noiseConstant) }; + + libcameraMetadata_.set(controls::NoiseProfile, noiseProfile); + } + + ContrastStatus *contrastStatus = rpiMetadata.getLocked("contrast.status"); + if (contrastStatus && contrastStatus->gammaCurve.size() > 0) { + std::vector contrast; + contrast.reserve(contrastStatus->gammaCurve.size() * 2); + + contrastStatus->gammaCurve.map([&](double x, double y) { + contrast.emplace_back(static_cast(x)); + contrast.emplace_back(static_cast(y)); + }); + libcameraMetadata_.set(controls::ToneCurve, contrast); + } + + if (lscMapsOutput_) { + AlscStatus *alscStatus = rpiMetadata.getLocked("alsc.status"); + if (alscStatus) { + uint32_t elements = alscStatus->cols * alscStatus->rows; + std::vector map(3 * elements); + + std::copy(alscStatus->r.begin(), alscStatus->r.end(), map.begin() + 0 * elements); + std::copy(alscStatus->g.begin(), alscStatus->g.end(), map.begin() + 1 * elements); + std::copy(alscStatus->b.begin(), alscStatus->b.end(), map.begin() + 2 * elements); + + uint32_t sizeTable[] = { 3, alscStatus->cols, alscStatus->rows }; + libcameraMetadata_.set(controls::LensShadingCorrectionMaps, map); + libcameraMetadata_.set(controls::LensShadingCorrectionMapSize, sizeTable); + libcameraMetadata_.set(controls::LensShadingCorrectionMapOutputEnable, true); + } + } + metadataReady.emit(libcameraMetadata_); } diff --git a/src/ipa/rpi/common/ipa_base.h b/src/ipa/rpi/common/ipa_base.h index 0d7842f1f742..dbd6a8e5056f 100644 --- a/src/ipa/rpi/common/ipa_base.h +++ b/src/ipa/rpi/common/ipa_base.h @@ -68,6 +68,7 @@ protected: std::deque frameLengths_; utils::Duration lastTimeout_; ControlList libcameraMetadata_; + bool lscMapsOutput_; bool statsMetadataOutput_; /* Remember the HDR status after a mode switch. */ diff --git a/src/ipa/rpi/common/meson.build b/src/ipa/rpi/common/meson.build index 73d2ee732339..cb3555a802c0 100644 --- a/src/ipa/rpi/common/meson.build +++ b/src/ipa/rpi/common/meson.build @@ -5,7 +5,7 @@ rpi_ipa_common_sources = files([ ]) rpi_ipa_common_includes = [ - include_directories('..'), + include_directories('..','../..'), ] rpi_ipa_common_deps = [ diff --git a/src/ipa/rpi/controller/rpi/alsc.cpp b/src/ipa/rpi/controller/rpi/alsc.cpp index e13d6c6c2832..c0d39d1fe41b 100644 --- a/src/ipa/rpi/controller/rpi/alsc.cpp +++ b/src/ipa/rpi/controller/rpi/alsc.cpp @@ -409,6 +409,9 @@ void Alsc::prepare(Metadata *imageMetadata) status.r = prevSyncResults_[0].data(); status.g = prevSyncResults_[1].data(); status.b = prevSyncResults_[2].data(); + status.cols = config_.tableSize.width; + status.rows = config_.tableSize.height; + imageMetadata->set("alsc.status", status); /* * Put the results in the global metadata as well. This will be used by diff --git a/src/libcamera/control_ids_core.yaml b/src/libcamera/control_ids_core.yaml index 89991d03d79a..b61dcafa229f 100644 --- a/src/libcamera/control_ids_core.yaml +++ b/src/libcamera/control_ids_core.yaml @@ -1376,4 +1376,46 @@ controls: The nominal range is [-180, 180], where 0° leaves hues unchanged and the range wraps around continuously, with 180° == -180°. + - LensShadingCorrectionMapOutputEnable: + type: bool + direction: inout + description: | + Indicates if the lens shading correction maps should be set in frame- + metadata for each frame. Returns true if tables are successfully set. + + - LensShadingCorrectionMaps: + type: float + direction: out + description: | + A map giving the lens shading correction factors. Dimensions of the + returned table is [number of channels, sizeX, sizeY] (planar tables) + + size: [n] + + - LensShadingCorrectionMapSize: + type: uint32_t + direction: out + description: | + The number of channels/maps and the size of the lens shading correction + maps in pixels. E.g. [3, 32, 32] + + size: [3] + + - ToneCurve: + type: float + direction: out + description: | + A profile tone curve to apply on linear RGB to produce the requested + output colour space. + + size: [n] + + - NoiseProfile: + type: float + direction: out + description: | + The noise profile [Scale, Offset]. + + size: [2] + ...