From patchwork Wed Sep 16 19:27:54 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: James Alexander X-Patchwork-Id: 28310 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 D480CC3220 for ; Wed, 16 Sep 2026 19:28:02 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id BD6FE686F1; Wed, 16 Sep 2026 21:28:01 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=inspiredexperts.com header.i=@inspiredexperts.com header.b="EiVecI+r"; dkim-atps=neutral Received: from s1.inspiredexperts.com (s1.inspiredexperts.com [162.243.156.162]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2E46F68244 for ; Wed, 16 Sep 2026 21:28:00 +0200 (CEST) Received: from localhost (s1.inspiredexperts.com [127.0.0.1]) by s1.inspiredexperts.com (Postfix) with ESMTPSA id 35CFBD2D68A; Wed, 16 Sep 2026 13:27:56 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=inspiredexperts.com; s=default; t=1789586878; bh=bZ3juJYuBsPC7Z+UuVCn8mPiwy6Jhhdvg8OHudnBwro=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=EiVecI+rH40w6JRaIMe7KFWvIijvyzjEd7N67vhy4m6wdncotQpbRJtRCQVF+dOW1 6i8l+IlImM82BJOLkiI9YIXUTADRWn4ad4QeCEsw6CygyNRBhrtZUOvebO1hsDqgpd VezavyDbaeQutVJIkq5yonYje+bcfoItak8KYt3c= From: James Alexander To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Kieran Bingham , James Alexander Subject: [PATCH v2] ipa: softisp: adjust: Read default contrast from tuning Date: Wed, 16 Sep 2026 13:27:54 -0600 Message-ID: <20260916192754.312240-1-opensource@inspiredexperts.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260816204259.2845517-2-opensource@inspiredexperts.com> References: <20260816204259.2845517-2-opensource@inspiredexperts.com> MIME-Version: 1.0 X-Spam-Status: No, score=-1.0 required=5.0 tests=ALL_TRUSTED autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on s1.inspiredexperts.com 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" SoftISP tuning files can enable the Adjust algorithm, but they cannot select the default Contrast control value. This forces applications to supply the control on every request when a sensor needs a non-neutral default. Read an optional contrast value from the Adjust tuning section, validate it against the advertised control range, and use it as both the control default and initial state. Tuning files that omit the value retain the existing neutral default, and per-request controls remain authoritative. Add focused tests for the neutral fallback, a tuned default, a request override, and invalid tuning. Signed-off-by: James Alexander --- Changes in v2: - Rebase the change from the simple IPA onto the current softisp Adjust algorithm. - Limit the tuning option to contrast. Gamma is being moved to the common libipa GammaAlgorithm, and saturation remains tied to CCM behavior. - Reject malformed and out-of-range values instead of clamping them. - Add focused tests for fallback, tuning, request override, and validation. src/ipa/softisp/algorithms/adjust.cpp | 27 +++++- src/ipa/softisp/algorithms/adjust.h | 3 + test/ipa/meson.build | 1 + test/ipa/softisp/adjust.cpp | 125 ++++++++++++++++++++++++++ test/ipa/softisp/meson.build | 17 ++++ 5 files changed, 169 insertions(+), 4 deletions(-) create mode 100644 test/ipa/softisp/adjust.cpp create mode 100644 test/ipa/softisp/meson.build diff --git a/src/ipa/softisp/algorithms/adjust.cpp b/src/ipa/softisp/algorithms/adjust.cpp index 56e2cf0..8672033 100644 --- a/src/ipa/softisp/algorithms/adjust.cpp +++ b/src/ipa/softisp/algorithms/adjust.cpp @@ -8,6 +8,8 @@ #include "adjust.h" +#include + #include #include @@ -19,17 +21,34 @@ namespace libcamera { namespace ipa::softisp::algorithms { -constexpr float kDefaultContrast = 1.0f; constexpr float kDefaultSaturation = 1.0f; +constexpr float kMinContrast = 0.0f; +constexpr float kMaxContrast = 2.0f; + LOG_DEFINE_CATEGORY(IPASoftIspAdjust) -int Adjust::init(IPAContext &context, [[maybe_unused]] const ValueNode &tuningData) +int Adjust::init(IPAContext &context, const ValueNode &tuningData) { + const ValueNode &contrastNode = tuningData["contrast"]; + const std::optional contrast = contrastNode.get(); + if (contrastNode && !contrast) { + LOG(IPASoftIspAdjust, Error) << "Failed to parse contrast"; + return -EINVAL; + } + + defaultContrast_ = contrast.value_or(kDefaultContrast); + if (defaultContrast_ < kMinContrast || defaultContrast_ > kMaxContrast) { + LOG(IPASoftIspAdjust, Error) + << "Contrast must be in the range [" << kMinContrast + << ", " << kMaxContrast << "]"; + return -EINVAL; + } + context.ctrlMap[&controls::Gamma] = ControlInfo(0.1f, 10.0f, kDefaultGamma); context.ctrlMap[&controls::Contrast] = - ControlInfo(0.0f, 2.0f, kDefaultContrast); + ControlInfo(kMinContrast, kMaxContrast, defaultContrast_); if (context.ccmEnabled) context.ctrlMap[&controls::Saturation] = ControlInfo(0.0f, 2.0f, kDefaultSaturation); @@ -40,7 +59,7 @@ int Adjust::configure(IPAContext &context, [[maybe_unused]] const IPAConfigInfo &configInfo) { context.activeState.knobs.gamma = kDefaultGamma; - context.activeState.knobs.contrast = std::optional(); + context.activeState.knobs.contrast = defaultContrast_; context.activeState.knobs.saturation = std::optional(); return 0; diff --git a/src/ipa/softisp/algorithms/adjust.h b/src/ipa/softisp/algorithms/adjust.h index 1acf7cd..726e24c 100644 --- a/src/ipa/softisp/algorithms/adjust.h +++ b/src/ipa/softisp/algorithms/adjust.h @@ -18,6 +18,7 @@ namespace libcamera { namespace ipa::softisp::algorithms { constexpr float kDefaultGamma = 2.2f; +constexpr float kDefaultContrast = 1.0f; class Adjust : public Algorithm { @@ -43,6 +44,8 @@ public: private: void applySaturation(Matrix &ccm, float saturation); + + float defaultContrast_ = kDefaultContrast; }; } /* namespace ipa::softisp::algorithms */ diff --git a/test/ipa/meson.build b/test/ipa/meson.build index ceed15b..f249091 100644 --- a/test/ipa/meson.build +++ b/test/ipa/meson.build @@ -1,6 +1,7 @@ # SPDX-License-Identifier: CC0-1.0 subdir('libipa') +subdir('softisp') ipa_test = [ {'name': 'ipa_module_test', 'sources': ['ipa_module_test.cpp']}, diff --git a/test/ipa/softisp/adjust.cpp b/test/ipa/softisp/adjust.cpp new file mode 100644 index 0000000..24e862b --- /dev/null +++ b/test/ipa/softisp/adjust.cpp @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2026, James Alexander + * + * Soft ISP image adjustment algorithm tests + */ + +#include +#include +#include +#include + +#include + +#include "libcamera/internal/value_node.h" + +#include "algorithms/adjust.h" + +#include "test.h" + +using namespace libcamera; +using namespace libcamera::ipa::softisp; +using namespace libcamera::ipa::softisp::algorithms; + +namespace { + +constexpr float kEpsilon = 0.0001f; + +bool closeEnough(float lhs, float rhs) +{ + return std::abs(lhs - rhs) < kEpsilon; +} + +} /* namespace */ + +class AdjustTest : public Test +{ +protected: + int testDefaults() + { + IPAContext context(1); + ValueNode tuning; + Adjust adjust; + + if (adjust.init(context, tuning)) + return TestFail; + + IPAConfigInfo configInfo{}; + if (adjust.configure(context, configInfo)) + return TestFail; + + const float contrast = context.ctrlMap.at(&controls::Contrast).def().get(); + if (!closeEnough(contrast, 1.0f) || + !context.activeState.knobs.contrast || + !closeEnough(*context.activeState.knobs.contrast, 1.0f)) { + std::cerr << "Default contrast was not preserved" << std::endl; + return TestFail; + } + + return TestPass; + } + + int testTunedDefaultAndControl() + { + IPAContext context(1); + ValueNode tuning; + tuning.add("contrast", std::make_unique(1.15f)); + Adjust adjust; + + if (adjust.init(context, tuning)) + return TestFail; + + IPAConfigInfo configInfo{}; + if (adjust.configure(context, configInfo)) + return TestFail; + + const float contrast = context.ctrlMap.at(&controls::Contrast).def().get(); + if (!closeEnough(contrast, 1.15f) || + !context.activeState.knobs.contrast || + !closeEnough(*context.activeState.knobs.contrast, 1.15f)) { + std::cerr << "Tuned contrast default was not applied" << std::endl; + return TestFail; + } + + ControlList controlsList(controls::controls); + controlsList.set(controls::Contrast, 0.9f); + IPAFrameContext frameContext{}; + adjust.queueRequest(context, 0, frameContext, controlsList); + + if (!context.activeState.knobs.contrast || + !closeEnough(*context.activeState.knobs.contrast, 0.9f)) { + std::cerr << "Request control did not override tuned contrast" << std::endl; + return TestFail; + } + + return TestPass; + } + + int testInvalidTuning() + { + IPAContext context(1); + ValueNode tuning; + tuning.add("contrast", std::make_unique(3.0f)); + Adjust adjust; + + if (adjust.init(context, tuning) != -EINVAL) { + std::cerr << "Out-of-range contrast tuning was accepted" << std::endl; + return TestFail; + } + + return TestPass; + } + + int run() override + { + if (testDefaults() != TestPass || + testTunedDefaultAndControl() != TestPass || + testInvalidTuning() != TestPass) + return TestFail; + + return TestPass; + } +}; + +TEST_REGISTER(AdjustTest) diff --git a/test/ipa/softisp/meson.build b/test/ipa/softisp/meson.build new file mode 100644 index 0000000..78ad612 --- /dev/null +++ b/test/ipa/softisp/meson.build @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: CC0-1.0 + +softisp_adjust_test = executable( + 'softisp_adjust', + [ + 'adjust.cpp', + '../../../src/ipa/softisp/algorithms/adjust.cpp', + ], + dependencies : [libcamera_private, libipa_dep], + link_with : [test_libraries], + include_directories : [ + test_includes_internal, + include_directories('../../../src/ipa/softisp'), + ], +) + +test('softisp_adjust', softisp_adjust_test, suite : 'ipa')