[v14,3/6] ipa: simple: Add LSC algorithm
diff mbox series

Message ID 20260821125958.95928-4-mzamazal@redhat.com
State New
Headers show
Series
  • LSC for SoftISP simple pipeline
Related show

Commit Message

Milan Zamazal Aug. 21, 2026, 12:59 p.m. UTC
From: Xander Pronk <xander.c.pronk@gmail.com>

The algorithm is based on the common libipa lens shading correction
implementation.  The grid values obtained from the libipa algorithm are
passed to the debayer algorithm as an array and used as an an RGB
texture.

Notes on the implementation:

- The overall idea is to keep things simple, to not make the LSC
  computation unnecessarily expensive.

- LscAlgorithm accepts only quantised types.  UQ<2,6> is used, to be
  converted to float in debayering.

- The limit of 100 degrees to consider a temperature change noticeable
  is arbitrary.

Co-developed-by: Rick ten Wolde <rick_libcamera@wolde.info>
Signed-off-by: Rick ten Wolde <rick_libcamera@wolde.info>
Signed-off-by: Xander Pronk <xander.c.pronk@gmail.com>
Signed-off-by: Milan Zamazal <mzamazal@redhat.com>
---
 .../internal/software_isp/debayer_params.h    | 12 ++-
 src/ipa/softisp/algorithms/lsc.cpp            | 97 +++++++++++++++++++
 src/ipa/softisp/algorithms/lsc.h              | 51 ++++++++++
 src/ipa/softisp/algorithms/meson.build        |  1 +
 src/ipa/softisp/ipa_context.h                 |  5 +
 src/ipa/softisp/softisp.cpp                   |  2 +
 src/libcamera/software_isp/debayer.cpp        |  8 ++
 7 files changed, 175 insertions(+), 1 deletion(-)
 create mode 100644 src/ipa/softisp/algorithms/lsc.cpp
 create mode 100644 src/ipa/softisp/algorithms/lsc.h

Patch
diff mbox series

diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h
index 93dcf42e6..b7860c9dd 100644
--- a/include/libcamera/internal/software_isp/debayer_params.h
+++ b/include/libcamera/internal/software_isp/debayer_params.h
@@ -35,7 +35,17 @@  struct DebayerParams {
 	static constexpr unsigned int kLscValuesPerCell = 4;
 	using LscLookupTable =
 		std::array<uint8_t, kLscGridSize * kLscGridSize * kLscValuesPerCell>;
-	LscLookupTable lscLut{};
+	static constexpr auto identityLscLut = [] {
+		LscLookupTable lut = {};
+		/* lut.fill(64) could be used, but it fails with older gcc versions */
+		for (size_t i = 0; kLscValuesPerCell * i < lut.size(); i++) {
+			lut[i * kLscValuesPerCell + 0] = 64; /* == UQ<2, 6>(1.0f).quantized() */
+			lut[i * kLscValuesPerCell + 1] = 64;
+			lut[i * kLscValuesPerCell + 2] = 64;
+		}
+		return lut;
+	}();
+	LscLookupTable lscLut = identityLscLut;
 	uint64_t lscLutVersion = 0;
 };
 
diff --git a/src/ipa/softisp/algorithms/lsc.cpp b/src/ipa/softisp/algorithms/lsc.cpp
new file mode 100644
index 000000000..e05e1bb89
--- /dev/null
+++ b/src/ipa/softisp/algorithms/lsc.cpp
@@ -0,0 +1,97 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Lens shading correction
+ */
+
+#include "lsc.h"
+
+#include <libcamera/base/log.h>
+
+namespace libcamera {
+
+namespace ipa::softisp::algorithms {
+
+LOG_DEFINE_CATEGORY(IPASoftLsc)
+
+int Lsc::init(IPAContext &context, const ValueNode &tuningData)
+{
+	static constexpr unsigned int kGridSize = DebayerParams::kLscGridSize;
+
+	for (unsigned int i = 0; i < kGridSize; i++)
+		gridPos_.push_back(static_cast<double>(i) / (kGridSize - 1));
+
+	return lscAlgo_.init(tuningData, context.ctrlMap,
+			     { .keys = { "r", "g", "b" },
+			       .numHSamples = kGridSize,
+			       .numVSamples = kGridSize,
+			       .sensorSize = context.sensorInfo.activeAreaSize });
+}
+
+int Lsc::configure(IPAContext &context,
+		   [[maybe_unused]] const IPAConfigInfo &configInfo)
+{
+	return lscAlgo_.configure(context.activeState.lsc,
+				  context.sensorInfo.analogCrop,
+				  gridPos_, gridPos_);
+}
+
+void Lsc::prepare([[maybe_unused]] IPAContext &context,
+		  [[maybe_unused]] const uint32_t frame,
+		  IPAFrameContext &frameContext,
+		  DebayerParams *params)
+{
+	unsigned int ct = frameContext.awb.colourTemperature;
+	constexpr unsigned int minTemperatureChange = 100;
+
+	if (!frameContext.lsc.enabled) {
+		if (lastAppliedCt_ != 0) {
+			params->lscLut = DebayerParams::identityLscLut;
+			params->lscLutVersion++;
+			lastAppliedCt_ = 0;
+		}
+		return;
+	}
+
+	if (utils::abs_diff(ct, lastAppliedCt_) < minTemperatureChange)
+		return;
+
+	const auto &set = lscAlgo_.interpolateComponents(ct);
+
+	const auto &red = set.at("r");
+	const auto &green = set.at("g");
+	const auto &blue = set.at("b");
+
+	DebayerParams::LscLookupTable &lut = params->lscLut;
+	constexpr unsigned int gridSize = DebayerParams::kLscGridSize;
+	for (unsigned int i = 0, j = 0; i < gridSize * gridSize; i++) {
+		lut[j++] = red[i];
+		lut[j++] = green[i];
+		lut[j++] = blue[i];
+		lut[j++] = 0; /* padding */
+	}
+	params->lscLutVersion++;
+
+	lastAppliedCt_ = ct;
+}
+
+void Lsc::queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame,
+		       IPAFrameContext &frameContext, const ControlList &controls)
+{
+	lscAlgo_.queueRequest(context.activeState.lsc, frameContext.lsc,
+			      controls);
+}
+
+void Lsc::process([[maybe_unused]] IPAContext &context,
+		  [[maybe_unused]] const uint32_t frame,
+		  IPAFrameContext &frameContext,
+		  [[maybe_unused]] const SwIspStats *stats,
+		  ControlList &metadata)
+{
+	lscAlgo_.process(frameContext.lsc, metadata);
+}
+
+REGISTER_IPA_ALGORITHM(Lsc, "Lsc")
+
+} /* namespace ipa::softisp::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/softisp/algorithms/lsc.h b/src/ipa/softisp/algorithms/lsc.h
new file mode 100644
index 000000000..83ec5aec2
--- /dev/null
+++ b/src/ipa/softisp/algorithms/lsc.h
@@ -0,0 +1,51 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Lens shading correction
+ */
+
+#pragma once
+
+#include <libipa/interpolator.h>
+
+#include "libipa/fixedpoint.h"
+#include "libipa/lsc.h"
+
+#include "algorithm.h"
+#include "ipa_context.h"
+
+namespace libcamera {
+
+namespace ipa::softisp::algorithms {
+
+class Lsc : public Algorithm
+{
+public:
+	Lsc() = default;
+	~Lsc() = default;
+
+	int init(IPAContext &context, const ValueNode &tuningData) override;
+	int configure(IPAContext &context,
+		      const IPAConfigInfo &configInfo) override;
+	void queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame,
+			  IPAFrameContext &frameContext, const ControlList &controls) override;
+	void prepare(IPAContext &context,
+		     const uint32_t frame,
+		     IPAFrameContext &frameContext,
+		     DebayerParams *params) override;
+	void process([[maybe_unused]] IPAContext &context,
+		     [[maybe_unused]] const uint32_t frame,
+		     IPAFrameContext &frameContext,
+		     [[maybe_unused]] const SwIspStats *stats,
+		     ControlList &metadata) override;
+
+private:
+	LscAlgorithm<UQ<2, 6>> lscAlgo_;
+
+	std::vector<double> gridPos_;
+
+	unsigned int lastAppliedCt_ = 0;
+};
+
+} /* namespace ipa::softisp::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/softisp/algorithms/meson.build b/src/ipa/softisp/algorithms/meson.build
index d240409e5..d98f4b1f4 100644
--- a/src/ipa/softisp/algorithms/meson.build
+++ b/src/ipa/softisp/algorithms/meson.build
@@ -6,4 +6,5 @@  softisp_ipa_algorithms = files([
     'agc.cpp',
     'blc.cpp',
     'ccm.cpp',
+    'lsc.cpp',
 ])
diff --git a/src/ipa/softisp/ipa_context.h b/src/ipa/softisp/ipa_context.h
index 2eed32f88..e9c3eef0e 100644
--- a/src/ipa/softisp/ipa_context.h
+++ b/src/ipa/softisp/ipa_context.h
@@ -19,6 +19,7 @@ 
 #include <libipa/awb.h>
 #include <libipa/ccm.h>
 #include <libipa/fc_queue.h>
+#include "libipa/lsc.h"
 
 #include "core_ipa_interface.h"
 
@@ -61,6 +62,8 @@  struct IPAActiveState {
 		std::optional<float> contrast;
 		std::optional<float> saturation;
 	} knobs;
+
+	ipa::lsc::ActiveState lsc;
 };
 
 struct IPAFrameContext : public FrameContext {
@@ -75,6 +78,7 @@  struct IPAFrameContext : public FrameContext {
 	float gamma;
 	std::optional<float> contrast;
 	std::optional<float> saturation;
+	ipa::lsc::FrameContext lsc;
 };
 
 struct IPAContext {
@@ -89,6 +93,7 @@  struct IPAContext {
 	FCQueue<IPAFrameContext> frameContexts;
 	ControlInfoMap::Map ctrlMap;
 	bool ccmEnabled = false;
+	ipa::lsc::ActiveState lsc;
 };
 
 } /* namespace ipa::softisp */
diff --git a/src/ipa/softisp/softisp.cpp b/src/ipa/softisp/softisp.cpp
index aec04c528..41d3e23d8 100644
--- a/src/ipa/softisp/softisp.cpp
+++ b/src/ipa/softisp/softisp.cpp
@@ -163,6 +163,8 @@  int IPASoftIsp::init(const IPASettings &settings,
 		params_->gamma = 1.0 / algorithms::kDefaultGamma;
 		params_->contrastExp = 1.0;
 		params_->gains = { { 1.0, 1.0, 1.0 } };
+		params_->lscLutVersion = 0;
+		params_->lscLut = DebayerParams::identityLscLut;
 		/* combinedMatrix is reset for each frame. */
 	}
 
diff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp
index 82cdf1dc2..6ebf7ed1a 100644
--- a/src/libcamera/software_isp/debayer.cpp
+++ b/src/libcamera/software_isp/debayer.cpp
@@ -66,6 +66,14 @@  namespace libcamera {
  * \brief Lens shading lookup table
  */
 
+/**
+ * \var DebayerParams::identityLscLut
+ * \brief Lens shading lookup table of identity mapping
+ *
+ * This table can be used to initialise the lens shading lookup table or to
+ * not apply any real lens shading correction.
+ */
+
 /**
  * \var DebayerParams::lscLutVersion
  * \brief Incremented on each \a lscLut change