[v13,3/7] ipa: simple: Add LSC algorithm
diff mbox series

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

Commit Message

Milan Zamazal Aug. 10, 2026, 8:19 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/simple/algorithms/lsc.cpp             | 96 +++++++++++++++++++
 src/ipa/simple/algorithms/lsc.h               | 55 +++++++++++
 src/ipa/simple/algorithms/meson.build         |  1 +
 src/ipa/simple/ipa_context.h                  |  5 +
 src/ipa/simple/soft_simple.cpp                |  2 +
 src/libcamera/software_isp/debayer.cpp        |  8 ++
 7 files changed, 178 insertions(+), 1 deletion(-)
 create mode 100644 src/ipa/simple/algorithms/lsc.cpp
 create mode 100644 src/ipa/simple/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/simple/algorithms/lsc.cpp b/src/ipa/simple/algorithms/lsc.cpp
new file mode 100644
index 000000000..d515be3c2
--- /dev/null
+++ b/src/ipa/simple/algorithms/lsc.cpp
@@ -0,0 +1,96 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Lens shading correction
+ */
+
+#include "lsc.h"
+
+#include <libcamera/base/log.h>
+
+namespace libcamera {
+
+namespace ipa::soft::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;
+			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::soft::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/simple/algorithms/lsc.h b/src/ipa/simple/algorithms/lsc.h
new file mode 100644
index 000000000..13d9f2bca
--- /dev/null
+++ b/src/ipa/simple/algorithms/lsc.h
@@ -0,0 +1,55 @@ 
+/* 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 {
+
+namespace soft::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 soft::algorithms */
+
+} /* namespace ipa */
+
+} /* namespace libcamera */
diff --git a/src/ipa/simple/algorithms/meson.build b/src/ipa/simple/algorithms/meson.build
index 73c637220..c9f6e5590 100644
--- a/src/ipa/simple/algorithms/meson.build
+++ b/src/ipa/simple/algorithms/meson.build
@@ -6,4 +6,5 @@  soft_simple_ipa_algorithms = files([
     'agc.cpp',
     'blc.cpp',
     'ccm.cpp',
+    'lsc.cpp',
 ])
diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h
index ff312ae8f..23c2cfd0a 100644
--- a/src/ipa/simple/ipa_context.h
+++ b/src/ipa/simple/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::soft */
diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp
index 629e1a32d..f48ef76a5 100644
--- a/src/ipa/simple/soft_simple.cpp
+++ b/src/ipa/simple/soft_simple.cpp
@@ -163,6 +163,8 @@  int IPASoftSimple::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