[v3,29/35] ipa: libipa: lsc: Template LscAlgorithm with register format
diff mbox series

Message ID 20260706-libipa-algorithms-v3-29-968757b038bb@ideasonboard.com
State New
Headers show
Series
  • ipa: libipa: Introduce libipa algorithms
Related show

Commit Message

Jacopo Mondi July 6, 2026, 8:01 a.m. UTC
The LscPolynomial::samplePolynomial function expands the radial
polynomial assuming the RkISP1 Q<2, 10> register format:

       /*
	* The hardware uses 2.10 fixed point format and limits
	* the legal values to [1..3.999]. Scale and clamp the
	* sampled value accordingly.
	*/
       int v = static_cast<int>(
	       poly.sampleAtNormalizedPixelPos(xp, yp) *
	       1024);
       v = std::clamp(v, 1024, 4095);

In order to remove all assumptions about the platform register format
template the LscAlgorithm class with the fixed point format of the
ISP Lsc engine registers.

       float sample = static_cast<float>
		       (poly.sampleAtNormalizedPixelPos(xp, yp));

       samples.push_back(U(sample).quantized());

In order to template the LscPolynomial class, template the whole class
hierarchy including the LscTable class that doesn't need (yet) the
template argument but will use it once re-sampling LscTable will be
implemented.

Split all involved classes (LscAlgorithm, LscTable and LscPolynomial) in
two classes: a Lsc*Base class for non-templated function implementations
and a Lsc* class for templated functions implementations.

Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
---
 src/ipa/libipa/lsc.cpp            | 151 +++++++++++++-------------------------
 src/ipa/libipa/lsc.h              |  81 +++++++++++++++++---
 src/ipa/libipa/lsc_base.cpp       |   1 +
 src/ipa/libipa/lsc_base.h         |   1 +
 src/ipa/libipa/lsc_polynomial.cpp |  90 +++++++----------------
 src/ipa/libipa/lsc_polynomial.h   |  67 +++++++++++++++--
 src/ipa/libipa/lsc_table.cpp      |  49 +++++++++----
 src/ipa/libipa/lsc_table.h        |  35 ++++++---
 src/ipa/rkisp1/algorithms/lsc.h   |   3 +-
 9 files changed, 270 insertions(+), 208 deletions(-)

Patch
diff mbox series

diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp
index c1207292518a..621c04d354a8 100644
--- a/src/ipa/libipa/lsc.cpp
+++ b/src/ipa/libipa/lsc.cpp
@@ -7,13 +7,6 @@ 
 
 #include "lsc.h"
 
-#include <libcamera/base/log.h>
-
-#include <libcamera/control_ids.h>
-
-#include "lsc_polynomial.h"
-#include "lsc_table.h"
-
 /**
  * \file lsc.h
  * \brief libipa lsc algorithm
@@ -48,9 +41,56 @@  namespace lsc {
 
 } /* namespace lsc */
 
+/**
+ * \class LscAlgorithmBase
+ * \brief Base class for LscAlgorithm
+ *
+ * Base class for LscAlgorithm for non-templated functions implementation
+ */
+
+/**
+ * \brief Queue a request to the lsc algorithm
+ * \param[in] state The lsc active state
+ * \param[in] context The lsc frame context
+ * \param[in] controls The list of controls associated with a Request
+ *
+ * Queue a new list of \a controls to the lsc algorithm.
+ * The only supported control is controls::LensShadingCorrectionEnable.
+ */
+void LscAlgorithmBase::queueRequest(lsc::ActiveState &state,
+				    lsc::FrameContext &context,
+				    const ControlList &controls)
+{
+	const auto &lscEnable = controls.get(controls::LensShadingCorrectionEnable);
+	if (lscEnable && *lscEnable != state.enabled) {
+		state.enabled = *lscEnable;
+
+		LOG(Lsc, Debug)
+			<< (state.enabled ? "Enabling" : "Disabling") << " Lsc";
+
+		context.update = true;
+	}
+
+	context.enabled = state.enabled;
+}
+
+/**
+ * \brief Populate the list of lsc metadata
+ * \param[in] context The lsc frame context
+ * \param[in] metadata The list of metadata
+ *
+ * Populates the list of \a metadata with controls handled by the LscAlgorithm
+ * class. The only supported metadata is controls::LensShadingCorrectionEnable.
+ */
+void LscAlgorithmBase::process(lsc::FrameContext &context, ControlList &metadata)
+{
+	metadata.set(controls::LensShadingCorrectionEnable, context.enabled);
+}
+
 /**
  * \class LscAlgorithm
  * \brief libIPA lsc algorithm implementation
+ * \tparam U The fixedpoint lsc engine register format
  *
  * Due to the optical characteristics of the lens, the light intensity received
  * by the sensor is not uniform. The Lens Shading Correction algorithm applies
@@ -200,6 +240,7 @@  namespace lsc {
  */
 
 /**
+ * \fn LscAlgorithm::init()
  * \param[in] tuningData The tuning data
  * \param[in] controls The IPA list of supported controls
  * \param[in] descriptor The lsc engine descriptor
@@ -209,42 +250,9 @@  namespace lsc {
  *
  * \return 0 on success, a negative error code otherwise
  */
-int LscAlgorithm::init(const ValueNode &tuningData, ControlInfoMap::Map &controls,
-		       const LscDescriptor &descriptor)
-{
-	polynomial_ = false;
-
-	std::string type = tuningData["type"].get<std::string>("table");
-	if (type == "table") {
-		impl_ = std::make_unique<LscTable>();
-		LOG(Lsc, Debug) << "Using table-based Lsc";
-	} else if (type == "polynomial") {
-		impl_ = std::make_unique<LscPolynomial>();
-		polynomial_ = true;
-		LOG(Lsc, Debug) << "Using polynomial Lsc";
-	} else {
-		LOG(Lsc, Error) << "Unsupported Lsc algorithm '"
-				<< type << "'";
-		return -EINVAL;
-	}
-
-	const ValueNode &yamlSets = tuningData["sets"];
-	if (!yamlSets.isList()) {
-		LOG(Lsc, Error) << "'sets' parameter not found in tuning file";
-		return -EINVAL;
-	}
-
-	int ret = impl_->parseLscData(yamlSets, descriptor);
-	if (ret)
-		return ret;
-
-	controls[&controls::LensShadingCorrectionEnable] =
-		ControlInfo(false, true, true);
-
-	return 0;
-}
 
 /**
+ * \fn LscAlgorithm::configure()
  * \param[in] state The lsc active state
  * \param[in] analogCrop The current sensor analog crop rectangle
  * \param[in] xPos List of horizontal positions of the LSC grid nodes
@@ -265,67 +273,6 @@  int LscAlgorithm::init(const ValueNode &tuningData, ControlInfoMap::Map &control
  *
  * \return 0 on success, a negative error code otherwise
  */
-int LscAlgorithm::configure(lsc::ActiveState &state, const Rectangle &analogCrop,
-			    const std::vector<double> &xPos,
-			    const std::vector<double> &yPos)
-{
-	LOG(Lsc, Debug) << "Sample Lsc data for " << analogCrop;
-	lsc::ComponentsMap lscData =
-		impl_->sampleForCrop(analogCrop, xPos, yPos);
-
-	/*
-	 * Retain a copy of the components table.
-	 *
-	 * We could avoid a copy here if getComponents() could
-	 * return sets_.data() but I wasn't able to work around the
-	 * compiler refusing it.
-	 */
-	lscData_ = lscData;
-
-	sets_.setData(std::move(lscData));
-	state.enabled = true;
-
-	return 0;
-}
-
-/**
- * \brief Queue a request to the lsc algorithm
- * \param[in] state The lsc active state
- * \param[in] context The lsc frame context
- * \param[in] controls The list of controls associated with a Request
- *
- * Queue a new list of \a controls to the lsc algorithm.
- * The only supported control is controls::LensShadingCorrectionEnable.
- */
-void LscAlgorithm::queueRequest(lsc::ActiveState &state,
-				lsc::FrameContext &context,
-				const ControlList &controls)
-{
-	const auto &lscEnable = controls.get(controls::LensShadingCorrectionEnable);
-	if (lscEnable && *lscEnable != state.enabled) {
-		state.enabled = *lscEnable;
-
-		LOG(Lsc, Debug)
-			<< (state.enabled ? "Enabling" : "Disabling") << " Lsc";
-
-		context.update = true;
-	}
-
-	context.enabled = state.enabled;
-}
-
-/**
- * \brief Populate the list of lsc metadata
- * \param[in] context The lsc frame context
- * \param[in] metadata The list of metadata
- *
- * Populates the list of \a metadata with controls handled by the LscAlgorithm
- * class. The only supported metadata is controls::LensShadingCorrectionEnable.
- */
-void LscAlgorithm::process(lsc::FrameContext &context, ControlList &metadata)
-{
-	metadata.set(controls::LensShadingCorrectionEnable, context.enabled);
-}
 
 /**
  * \fn LscAlgorithm::interpolateComponents
diff --git a/src/ipa/libipa/lsc.h b/src/ipa/libipa/lsc.h
index 9fe8ad67ea33..d48336b169f4 100644
--- a/src/ipa/libipa/lsc.h
+++ b/src/ipa/libipa/lsc.h
@@ -8,18 +8,25 @@ 
 #pragma once
 
 #include <memory>
+#include <string>
 #include <vector>
 
+#include <libcamera/base/log.h>
+
+#include <libcamera/control_ids.h>
 #include <libcamera/controls.h>
 #include <libcamera/geometry.h>
 
 #include "libcamera/internal/value_node.h"
 
 #include "interpolator.h"
-#include "lsc_base.h"
+#include "lsc_polynomial.h"
+#include "lsc_table.h"
 
 namespace libcamera {
 
+LOG_DECLARE_CATEGORY(Lsc)
+
 namespace ipa {
 
 namespace lsc {
@@ -35,19 +42,75 @@  struct FrameContext {
 
 } /* namespace lsc */
 
-class LscAlgorithm
+class LscAlgorithmBase
+{
+public:
+	void queueRequest(lsc::ActiveState &state, lsc::FrameContext &context,
+			  const ControlList &controls);
+	void process(lsc::FrameContext &context, ControlList &metadata);
+};
+
+template<typename U>
+class LscAlgorithm : public LscAlgorithmBase
 {
 public:
 	int init(const ValueNode &tuningData, ControlInfoMap::Map &controls,
-		 const LscDescriptor &descriptor);
+		 const LscDescriptor &descriptor)
+	{
+		polynomial_ = false;
+
+		std::string type = tuningData["type"].get<std::string>("table");
+		if (type == "table") {
+			impl_ = std::make_unique<LscTable<U>>();
+			LOG(Lsc, Debug) << "Using table-based Lsc";
+		} else if (type == "polynomial") {
+			impl_ = std::make_unique<LscPolynomial<U>>();
+			polynomial_ = true;
+			LOG(Lsc, Debug) << "Using polynomial Lsc";
+		} else {
+			LOG(Lsc, Error) << "Unsupported Lsc algorithm '"
+					<< type << "'";
+			return -EINVAL;
+		}
+
+		const ValueNode &yamlSets = tuningData["sets"];
+		if (!yamlSets.isList()) {
+			LOG(Lsc, Error) << "'sets' parameter not found in tuning file";
+			return -EINVAL;
+		}
+
+		int ret = impl_->parseLscData(yamlSets, descriptor);
+		if (ret)
+			return ret;
+
+		controls[&controls::LensShadingCorrectionEnable] =
+			ControlInfo(false, true, true);
+
+		return 0;
+	}
 
 	int configure(lsc::ActiveState &state, const Rectangle &analogCrop,
 		      const std::vector<double> &xPos,
-		      const std::vector<double> &yPos);
-
-	void queueRequest(lsc::ActiveState &state, lsc::FrameContext &context,
-			  const ControlList &controls);
-	void process(lsc::FrameContext &context, ControlList &metadata);
+		      const std::vector<double> &yPos)
+	{
+		LOG(Lsc, Debug) << "Sample Lsc data for " << analogCrop;
+		lsc::ComponentsMap lscData =
+			impl_->sampleForCrop(analogCrop, xPos, yPos);
+
+		/*
+		 * Retain a copy of the components table.
+		 *
+		 * We could avoid a copy here if getComponents() could
+		 * return sets_.data() but I wasn't able to work around the
+		 * compiler refusing it.
+		 */
+		lscData_ = lscData;
+
+		sets_.setData(std::move(lscData));
+		state.enabled = true;
+
+		return 0;
+	}
 
 	const lsc::Components interpolateComponents(unsigned int ct)
 	{
@@ -60,7 +123,7 @@  public:
 	}
 
 private:
-	std::unique_ptr<LscImplementation> impl_;
+	std::unique_ptr<LscImplementation<U>> impl_;
 	Interpolator<lsc::Components> sets_;
 	lsc::ComponentsMap lscData_;
 	bool polynomial_;
diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp
index 7de6ee70e7a0..0eeb99f6881a 100644
--- a/src/ipa/libipa/lsc_base.cpp
+++ b/src/ipa/libipa/lsc_base.cpp
@@ -86,6 +86,7 @@  void Interpolator<lsc::Components>::
 /**
  * \class LscImplementation
  * \brief Pure virtual base class for lsc algorithm implementations
+ * \tparam U The fixedpoint lsc engine register format
  *
  * Defines the interface for the lsc algorithm implementation.
  */
diff --git a/src/ipa/libipa/lsc_base.h b/src/ipa/libipa/lsc_base.h
index 48307455362a..721fd137ef21 100644
--- a/src/ipa/libipa/lsc_base.h
+++ b/src/ipa/libipa/lsc_base.h
@@ -57,6 +57,7 @@  struct LscDescriptor {
 	Size sensorSize;
 };
 
+template<typename U>
 class LscImplementation
 {
 public:
diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp
index 013a4469546e..2b743ed53be7 100644
--- a/src/ipa/libipa/lsc_polynomial.cpp
+++ b/src/ipa/libipa/lsc_polynomial.cpp
@@ -113,14 +113,10 @@  void Polynomial::setReferenceImageSize(const Size &size)
 } /* namespace lsc */
 
 /**
- * \class LscPolynomial
- * \brief Radial Polynomial lsc algorithm implementation
+ * \class LscPolynomialBase
+ * \brief Base class for LscPolynomial
  *
- * Polynomial-based lsc algorithm implementation. The LscPolynomial class
- * implements lsc support using Polynomial to represent the shading artifacts
- * map.
- *
- * \sa LscImplementation
+ * Base class for LscPolynomial for non-templated functions.
  */
 
 /**
@@ -132,8 +128,8 @@  void Polynomial::setReferenceImageSize(const Size &size)
  *
  * \return 0 on success or a negative error number otherwise
  */
-int LscPolynomial::parseLscData(const ValueNode &sets,
-				const LscDescriptor &descriptor)
+int LscPolynomialBase::parseLscData(const ValueNode &sets,
+				    const LscDescriptor &descriptor)
 {
 	for (const auto &set : sets.asList()) {
 		uint32_t ct = set["ct"].get<uint32_t>(0);
@@ -174,6 +170,27 @@  int LscPolynomial::parseLscData(const ValueNode &sets,
 }
 
 /**
+ * \var LscPolynomialBase::lscData_
+ * \brief The polynomial lsc data
+ *
+ * Maps colour temperatures to per-colour radial polynomial definitions.
+ */
+
+/**
+ * \class LscPolynomial
+ * \brief Radial Polynomial lsc algorithm implementation
+ * \tparam U The fixedpoint format used to convert gain values generated by
+ * polynomial expansion to the register format
+ *
+ * Polynomial-based lsc algorithm implementation. The LscPolynomial class
+ * implements lsc support using Polynomial to represent the shading artifacts
+ * map.
+ *
+ * \sa LscImplementation
+ */
+
+/**
+ * \fn LscPolynomial::sampleForCrop()
  * \brief Re-sample the lsc components for \a cropRectangle
  * \param[in] cropRectangle The sensor analogue crop rectangle
  * \param[in] xPos List of horizontal positions of the lsc grid nodes
@@ -195,62 +212,7 @@  int LscPolynomial::parseLscData(const ValueNode &sets,
  * [0, 0.0625, 0.125, 0.1875, ... , 1]. It is expected that the first position
  * is 0 and the last position is 1.
  */
-lsc::ComponentsMap
-LscPolynomial::sampleForCrop(const Rectangle &cropRectangle,
-			     std::vector<double> xPos, std::vector<double> yPos)
-{
-
-	lsc::ComponentsMap components;
-
-	for (const auto &[t, c] : lscData_) {
-		lsc::Components comp;
-
-		for (const auto &[k, p] : c) {
-			comp.emplace(std::piecewise_construct,
-				     std::forward_as_tuple(k),
-				     std::forward_as_tuple(samplePolynomial(p, xPos, yPos,
-									    cropRectangle)));
-		}
 
-		components[t] = comp;
-	}
-
-	return components;
-}
-
-std::vector<uint16_t>
-LscPolynomial::samplePolynomial(const lsc::Polynomial &poly,
-				Span<const double> xPositions,
-				Span<const double> yPositions,
-				const Rectangle &cropRectangle)
-{
-	double m = poly.getM();
-	double x0 = cropRectangle.x / m;
-	double y0 = cropRectangle.y / m;
-	double w = cropRectangle.width / m;
-	double h = cropRectangle.height / m;
-	std::vector<uint16_t> samples;
-
-	samples.reserve(xPositions.size() * yPositions.size());
-
-	for (double y : yPositions) {
-		for (double x : xPositions) {
-			double xp = x0 + x * w;
-			double yp = y0 + y * h;
-			/*
-			 * The hardware uses 2.10 fixed point format and limits
-			 * the legal values to [1..3.999]. Scale and clamp the
-			 * sampled value accordingly.
-			 */
-			int v = static_cast<int>(
-				poly.sampleAtNormalizedPixelPos(xp, yp) *
-				1024);
-			v = std::clamp(v, 1024, 4095);
-			samples.push_back(v);
-		}
-	}
-	return samples;
-}
 
 } /* namespace ipa */
 
diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h
index 6d19e46a50b7..04d7465b3af0 100644
--- a/src/ipa/libipa/lsc_polynomial.h
+++ b/src/ipa/libipa/lsc_polynomial.h
@@ -8,6 +8,7 @@ 
 
 #include <array>
 #include <map>
+#include <tuple>
 #include <vector>
 
 #include <libcamera/base/span.h>
@@ -50,26 +51,80 @@  private:
 
 } /* namespace lsc */
 
-class LscPolynomial : public LscImplementation
+class LscPolynomialBase
 {
 private:
 	using PolynomialComponents = std::map<std::string, lsc::Polynomial>;
 	using PolynomialComponentsMap = std::map<unsigned int, PolynomialComponents>;
 
+protected:
+	int parseLscData(const ValueNode &yamlSets,
+			 const LscDescriptor &descriptor);
+
+	PolynomialComponentsMap lscData_;
+};
+
+template<typename U>
+class LscPolynomial : public LscPolynomialBase, public LscImplementation<U>
+{
 public:
-	int parseLscData(const ValueNode &sets,
-			 const LscDescriptor &descriptor) override;
+	int parseLscData(const ValueNode &yamlSets,
+			 const LscDescriptor &descriptor) override
+	{
+		return LscPolynomialBase::parseLscData(yamlSets, descriptor);
+	}
 
 	lsc::ComponentsMap
 	sampleForCrop(const Rectangle &cropRectangle,
-		      std::vector<double> xPos, std::vector<double> yPos) override;
+		      std::vector<double> xPos, std::vector<double> yPos) override
+	{
+		lsc::ComponentsMap components;
+
+		for (const auto &[t, c] : lscData_) {
+			lsc::Components comp;
+
+			for (const auto &[k, p] : c) {
+				comp.emplace(std::piecewise_construct,
+					     std::forward_as_tuple(k),
+					     std::forward_as_tuple(samplePolynomial(p, xPos, yPos,
+										    cropRectangle)));
+			}
+
+			components[t] = comp;
+		}
+
+		return components;
+	}
 
 private:
 	std::vector<uint16_t> samplePolynomial(const lsc::Polynomial &poly,
 					       Span<const double> xPositions,
 					       Span<const double> yPositions,
-					       const Rectangle &cropRectangle);
-	PolynomialComponentsMap lscData_;
+					       const Rectangle &cropRectangle)
+	{
+		double m = poly.getM();
+		double x0 = cropRectangle.x / m;
+		double y0 = cropRectangle.y / m;
+		double w = cropRectangle.width / m;
+		double h = cropRectangle.height / m;
+		std::vector<uint16_t> samples;
+
+		samples.reserve(xPositions.size() * yPositions.size());
+
+		for (double y : yPositions) {
+			for (double x : xPositions) {
+				double xp = x0 + x * w;
+				double yp = y0 + y * h;
+
+				float sample = static_cast<float>
+						(poly.sampleAtNormalizedPixelPos(xp, yp));
+
+				samples.push_back(U(sample).quantized());
+			}
+		}
+
+		return samples;
+	}
 };
 
 } /* namespace ipa */
diff --git a/src/ipa/libipa/lsc_table.cpp b/src/ipa/libipa/lsc_table.cpp
index 415c1cc0ccd4..bb3c76b12332 100644
--- a/src/ipa/libipa/lsc_table.cpp
+++ b/src/ipa/libipa/lsc_table.cpp
@@ -14,13 +14,10 @@  LOG_DEFINE_CATEGORY(LscTable)
 namespace ipa {
 
 /**
- * \class LscTable
- * \brief Table based lsc algorithm implementation
- *
- * Table based lsc algorithm implementation. The LscTable class implements lsc
- * support using tabular lsc data.
+ * \class LscTableBase
+ * \brief Base class for LscTable
  *
- * \sa LscImplementation
+ * Base class for LscTable for non-templated functions.
  */
 
 /**
@@ -32,8 +29,8 @@  namespace ipa {
  *
  * \return 0 on success or a negative error number otherwise
  */
-int LscTable::parseLscData(const ValueNode &sets,
-			   const LscDescriptor &descriptor)
+int LscTableBase::parseLscData(const ValueNode &sets,
+			       const LscDescriptor &descriptor)
 {
 	for (const auto &set : sets.asList()) {
 		uint32_t ct = set["ct"].get<uint32_t>(0);
@@ -51,8 +48,8 @@  int LscTable::parseLscData(const ValueNode &sets,
 	return 0;
 }
 
-int LscTable::parseLscComponent(const ValueNode &yamlSet,
-				unsigned int ct, const LscDescriptor &descriptor)
+int LscTableBase::parseLscComponent(const ValueNode &yamlSet,
+				    unsigned int ct, const LscDescriptor &descriptor)
 {
 	lsc::Components component;
 	for (auto &k : descriptor.keys) {
@@ -82,10 +79,10 @@  int LscTable::parseLscComponent(const ValueNode &yamlSet,
 	return 0;
 }
 
-std::vector<uint16_t> LscTable::parseTable(const ValueNode &tuningData,
-					   const char *prop,
-					   unsigned int numHCells,
-					   unsigned int numVCells)
+std::vector<uint16_t> LscTableBase::parseTable(const ValueNode &tuningData,
+					       const char *prop,
+					       unsigned int numHCells,
+					       unsigned int numVCells)
 {
 	unsigned int lscNumSamples = numHCells * numVCells;
 
@@ -102,6 +99,30 @@  std::vector<uint16_t> LscTable::parseTable(const ValueNode &tuningData,
 	return table;
 }
 
+/**
+ * \var LscTableBase::lscData_
+ * \brief The tabular lsc data
+ *
+ * Maps colour temperatures to per-colour channel vector of gains
+ */
+
+/**
+ * \class LscTable
+ * \brief Table based lsc algorithm implementation
+ * \tparam U The fixedpoint lsc engine register format
+ *
+ * Table based lsc algorithm implementation. The LscTable class implements lsc
+ * support using tabular lsc data.
+ *
+ * \sa LscImplementation
+ */
+
+/**
+ * \fn LscTable::parseLscData()
+ *
+ * \copydoc LscTableBase::parseLscData()
+ */
+
 } /* namespace ipa */
 
 } /* namespace libcamera */
diff --git a/src/ipa/libipa/lsc_table.h b/src/ipa/libipa/lsc_table.h
index 3fe52afbad7b..65e04b09c3ff 100644
--- a/src/ipa/libipa/lsc_table.h
+++ b/src/ipa/libipa/lsc_table.h
@@ -23,11 +23,32 @@  LOG_DECLARE_CATEGORY(LscTable)
 
 namespace ipa {
 
-class LscTable : public LscImplementation
+class LscTableBase
+{
+protected:
+	int parseLscData(const ValueNode &sets,
+			 const LscDescriptor &descriptor);
+
+private:
+	int parseLscComponent(const ValueNode &yamlSet,
+			      unsigned int ct, const LscDescriptor &descriptor);
+	std::vector<uint16_t> parseTable(const ValueNode &tuningData,
+					 const char *prop,
+					 unsigned int numHCells,
+					 unsigned int numVCells);
+protected:
+	lsc::ComponentsMap lscData_;
+};
+
+template<typename U>
+class LscTable : public LscTableBase, public LscImplementation<U>
 {
 public:
 	int parseLscData(const ValueNode &sets,
-			 const LscDescriptor &descriptor) override;
+			 const LscDescriptor &descriptor) override
+	{
+		return LscTableBase::parseLscData(sets, descriptor);
+	}
 
 	lsc::ComponentsMap
 	sampleForCrop([[maybe_unused]] const Rectangle &cropRectangle,
@@ -38,16 +59,6 @@  public:
 			<< "Tabular LSC data doesn't support resampling";
 		return lscData_;
 	}
-
-private:
-	int parseLscComponent(const ValueNode &yamlSet,
-			      unsigned int ct, const LscDescriptor &descriptor);
-	std::vector<uint16_t> parseTable(const ValueNode &tuningData,
-					 const char *prop,
-					 unsigned int numHCells,
-					 unsigned int numVCells);
-
-	lsc::ComponentsMap lscData_;
 };
 
 } /* namespace ipa */
diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h
index e7d3091a36c5..31156f56cd21 100644
--- a/src/ipa/rkisp1/algorithms/lsc.h
+++ b/src/ipa/rkisp1/algorithms/lsc.h
@@ -13,6 +13,7 @@ 
 
 #include "libcamera/internal/value_node.h"
 
+#include "libipa/fixedpoint.h"
 #include "libipa/lsc.h"
 
 #include "algorithm.h"
@@ -58,7 +59,7 @@  private:
 	unsigned int lastAppliedCt_;
 	unsigned int lastAppliedQuantizedCt_;
 
-	LscAlgorithm lscAlgo_;
+	LscAlgorithm<UQ<2, 10>> lscAlgo_;
 };
 
 } /* namespace ipa::rkisp1::algorithms */