From patchwork Thu Sep 10 14:50:06 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 28219 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 366B9BDCBE for ; Thu, 10 Sep 2026 14:50:25 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 22A386865C; Thu, 10 Sep 2026 16:50:24 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ae9/JmMt"; 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 C9E1668615 for ; Thu, 10 Sep 2026 16:50:22 +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 622115B3; Thu, 10 Sep 2026 16:48:45 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1789051725; bh=8UyM7CtL/wDode/2LKzV5ooCJGggPkO1gTSc7bOKIcs=; h=From:Date:Subject:To:Cc:From; b=ae9/JmMt2E8y+1djOwGFU3z2ofoOmNCHR2Hb7LY56WItBubRxGcekPD6d6BMXd0da NGE/ycIcOY+owiRmfKxy+HXQYZUtwPOg4H+CIAjgCkZAWZCWUZpRrIjxKjym2Yju+N 29VmaK7oZb+U3xF1iHHI9+Fa/n59xu3vNuSQvWn0= From: Kieran Bingham Date: Thu, 10 Sep 2026 15:50:06 +0100 Subject: [PATCH] libcamera: Adding LensShadingCorrection maps and ToneCurve to controls metadata MIME-Version: 1.0 Message-Id: <20260910-articimaging-lsc-v1-1-25f448d5af99@ideasonboard.com> X-B4-Tracking: v=1; b=H4sIAJ3DomoC/x3MTQqAIBBA4avIrBP8IcmuEi3ERhsoC40IwrsnL b/Fey8UzIQFRvZCxpsKHalBdgz86lJETkszKKGMsFJwly/ytLtIKfKteK5QB9VbaQYtoWVnxkD Pv5zmWj8gKVMMYgAAAA== X-Change-ID: 20260910-articimaging-lsc-2e3f25916831 To: libcamera-devel@lists.libcamera.org Cc: Michael Kunz , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1789051822; l=9766; i=kieran.bingham@ideasonboard.com; s=20260207; h=from:subject:message-id; bh=pgt/1NnHzYwdg4fX4TouxKXM/AMlji+JUtFtXwVn+kc=; b=w4E2brTA/ZuvtVYfElnNG5XRF9iRlVOFS9xlbrWKEGu0g1id6FDG2f1zjI/sDmh/knZGZb0UA 8myJ+4vY0VvDCZYSBj43cSUwYdSsEFGeW0i9uQsQmaXESM6zrVkkW+Z 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 Signed-off-by: Kieran Bingham Reviewed-by: David Plowman --- 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 | 38 ++++++++++++++++++++++++++++ 5 files changed, 90 insertions(+), 4 deletions(-) --- base-commit: c08caf6672fca8ea613ccb3a36e5f8e926b84903 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..988c1f5b2ad2 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/ccm_status.h" #include "controller/contrast_algorithm.h" +#include "controller/contrast_status.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::EnableLensShadingCorrectionMapOutput, 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::ENABLE_LENS_SHADING_CORRECTION_MAP_OUTPUT: + 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::EnableLensShadingCorrectionMapOutput, 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..608681f3e395 100644 --- a/src/libcamera/control_ids_core.yaml +++ b/src/libcamera/control_ids_core.yaml @@ -1376,4 +1376,42 @@ controls: The nominal range is [-180, 180], where 0° leaves hues unchanged and the range wraps around continuously, with 180° == -180°. + - EnableLensShadingCorrectionMapOutput: + 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. Dimesnions 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 - as it seems it has the sRGB + gamma curve baked in. + size: [n] + + - NoiseProfile: + type: float + direction: out + description: | + The noise profile [Scale, Offset]. + size: [2] + ...