[v1,10/12] ipa: rkisp1: lsc: Resample polynomial lens shading tables at configure time
diff mbox series

Message ID 20251014075252.2876485-11-stefan.klug@ideasonboard.com
State New
Headers show
Series
  • Add resampling support for polynomial LSC data
Related show

Commit Message

Stefan Klug Oct. 14, 2025, 7:52 a.m. UTC
The lens shading correction is always applied based on the sensor crop
bounds. This leads to incorrect lens shading correction for analog crops
that do not cover the whole sensor.

To fix that, we need to adapt the lens shading table for the selected
analog crop at configure time. Introduce an abstract ShadingDescriptor
class that holds the lens shading information that can then be sampled
at configure time for a specific crop rectangle.

Resampling for a specific crop is only implemented for polynomial lsc
data. For tabular data, a warning is logged and the unmodified table is
returned. This matches the current functionality for tabular data and is
a huge improvement for polynomial data.

Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
---
 src/ipa/rkisp1/algorithms/lsc.cpp | 230 +++++++++++++++++++-----------
 src/ipa/rkisp1/algorithms/lsc.h   |  14 ++
 2 files changed, 157 insertions(+), 87 deletions(-)

Comments

Barnabás Pőcze Oct. 14, 2025, 8:59 a.m. UTC | #1
Hi


2025. 10. 14. 9:52 keltezéssel, Stefan Klug írta:
> The lens shading correction is always applied based on the sensor crop
> bounds. This leads to incorrect lens shading correction for analog crops
> that do not cover the whole sensor.
> 
> To fix that, we need to adapt the lens shading table for the selected
> analog crop at configure time. Introduce an abstract ShadingDescriptor
> class that holds the lens shading information that can then be sampled
> at configure time for a specific crop rectangle.
> 
> Resampling for a specific crop is only implemented for polynomial lsc
> data. For tabular data, a warning is logged and the unmodified table is
> returned. This matches the current functionality for tabular data and is
> a huge improvement for polynomial data.
> 
> Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
> ---
>   src/ipa/rkisp1/algorithms/lsc.cpp | 230 +++++++++++++++++++-----------
>   src/ipa/rkisp1/algorithms/lsc.h   |  14 ++
>   2 files changed, 157 insertions(+), 87 deletions(-)
> 
> diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp
> index 29cd7efd83ef..68cf35064182 100644
> --- a/src/ipa/rkisp1/algorithms/lsc.cpp
> +++ b/src/ipa/rkisp1/algorithms/lsc.cpp
> @@ -70,38 +70,124 @@ namespace ipa::rkisp1::algorithms {
>   
>   LOG_DEFINE_CATEGORY(RkISP1Lsc)
>   
> -class LscPolynomialLoader
> +class LscPolynomialShadingDescriptor : public LensShadingCorrection::ShadingDescriptor

If you're already here, could you put all "private" Lsc* types into an anon namespace?
I feel we should strive to have everything translation-unit local in there.


>   {
>   public:
> -	LscPolynomialLoader(const Size &sensorSize,
> -			    const Rectangle &cropRectangle,
> -			    const std::vector<double> &xSizes,
> -			    const std::vector<double> &ySizes)
> -		: sensorSize_(sensorSize),
> -		  cropRectangle_(cropRectangle),
> -		  xSizes_(xSizes),
> -		  ySizes_(ySizes)
> +	LscPolynomialShadingDescriptor(const LscPolynomial &pr, const LscPolynomial &pgr,
> +				       const LscPolynomial &pgb, const LscPolynomial &pb)
> +		: pr_(pr), pgr_(pgr), pgb_(pgb), pb_(pb)
>   	{
>   	}
>   
> -	int parseLscData(const YamlObject &yamlSets,
> -			 std::map<unsigned int, LensShadingCorrection::Components> &lscData);
> +	void sampleForCrop(const Rectangle &cropRectangle,
> +			   const std::vector<double> &xSizes,
> +			   const std::vector<double> &ySizes,
> +			   LensShadingCorrection::Components &components) override;
>   
>   private:
> -	std::vector<double> sizesListToPositions(const std::vector<double> &sizes);
>   	std::vector<uint16_t> samplePolynomial(const LscPolynomial &poly,
>   					       const std::vector<double> &xPositions,
>   					       const std::vector<double> &yPositions,
>   					       const Rectangle &cropRectangle);
>   
> +	std::vector<double> sizesListToPositions(const std::vector<double> &sizes);
> +
> +	LscPolynomial pr_;
> +	LscPolynomial pgr_;
> +	LscPolynomial pgb_;
> +	LscPolynomial pb_;
> +};
> [...]
> @@ -393,6 +441,14 @@ int LensShadingCorrection::configure(IPAContext &context,
>   		yGrad_[i] = std::round(32768 / ySizes_[i]);
>   	}
>   
> +	LOG(RkISP1Lsc, Debug) << "Sample LSC data for " << configInfo.analogCrop;
> +	std::map<unsigned int, LensShadingCorrection::Components> shadingData;
> +	for (auto const &[t, descriptor] : shadingDescriptors_)

auto const or const auto is the preferred?


> +		descriptor->sampleForCrop(configInfo.analogCrop, xSize_, ySize_,
> +					  shadingData[t]);
> +
> +	sets_.setData(std::move(shadingData));
> +
>   	context.configuration.lsc.enabled = true;
>   	return 0;
>   }
> diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h
> index 7b68dda1a0d4..43fee337931f 100644
> --- a/src/ipa/rkisp1/algorithms/lsc.h
> +++ b/src/ipa/rkisp1/algorithms/lsc.h
> @@ -8,6 +8,7 @@
>   #pragma once
>   
>   #include <map>
> +#include <memory>
>   
>   #include "libipa/interpolator.h"
>   
> @@ -36,10 +37,23 @@ public:
>   		std::vector<uint16_t> b;
>   	};
>   
> +	class ShadingDescriptor
> +	{
> +	public:
> +		virtual ~ShadingDescriptor() = default;
> +		virtual void sampleForCrop(const Rectangle &cropRectangle,
> +					   const std::vector<double> &xSizes,
> +					   const std::vector<double> &ySizes,
> +					   Components &components) = 0;
> +	};
> +
> +	using ShadingDescriptorMap = std::map<unsigned int, std::unique_ptr<ShadingDescriptor>>;


Since the set of possibilities appears quite closed to me. I was wondering if maybe `std::variant`
is something worth trying.

That is,

   std::map<unsigned int, std::variant<Poly, Table>> shadingDescriptors_;

or even

   std::variant<
     std::map<unsigned int, Table>,
     std::map<unsigned int, Poly>
   > shadingDescriptors_;

and then in the first case

   for (auto const &[t, descriptor] : shadingDescriptors_) {
     std::visit([&](auto &x) {
       x.sampleForCrop(...);
     }, descriptor);
   }

and in the second case

   std::visit([&](auto &shadingDescriptors) {
     for (const auto &[t, descriptor] : shadingDescriptors) {
       descriptor.sampleForCrop(...);
     }
   }, shadingDescriptors_);


Thoughts?


Regards,
Barnabás Pőcze


> +
>   private:
>   	void setParameters(rkisp1_cif_isp_lsc_config &config);
>   	void copyTable(rkisp1_cif_isp_lsc_config &config, const Components &set0);
>   
> +	std::map<unsigned int, std::unique_ptr<ShadingDescriptor>> shadingDescriptors_;
>   	ipa::Interpolator<Components> sets_;
>   	std::vector<double> xSize_;
>   	std::vector<double> ySize_;

Patch
diff mbox series

diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp
index 29cd7efd83ef..68cf35064182 100644
--- a/src/ipa/rkisp1/algorithms/lsc.cpp
+++ b/src/ipa/rkisp1/algorithms/lsc.cpp
@@ -70,38 +70,124 @@  namespace ipa::rkisp1::algorithms {
 
 LOG_DEFINE_CATEGORY(RkISP1Lsc)
 
-class LscPolynomialLoader
+class LscPolynomialShadingDescriptor : public LensShadingCorrection::ShadingDescriptor
 {
 public:
-	LscPolynomialLoader(const Size &sensorSize,
-			    const Rectangle &cropRectangle,
-			    const std::vector<double> &xSizes,
-			    const std::vector<double> &ySizes)
-		: sensorSize_(sensorSize),
-		  cropRectangle_(cropRectangle),
-		  xSizes_(xSizes),
-		  ySizes_(ySizes)
+	LscPolynomialShadingDescriptor(const LscPolynomial &pr, const LscPolynomial &pgr,
+				       const LscPolynomial &pgb, const LscPolynomial &pb)
+		: pr_(pr), pgr_(pgr), pgb_(pgb), pb_(pb)
 	{
 	}
 
-	int parseLscData(const YamlObject &yamlSets,
-			 std::map<unsigned int, LensShadingCorrection::Components> &lscData);
+	void sampleForCrop(const Rectangle &cropRectangle,
+			   const std::vector<double> &xSizes,
+			   const std::vector<double> &ySizes,
+			   LensShadingCorrection::Components &components) override;
 
 private:
-	std::vector<double> sizesListToPositions(const std::vector<double> &sizes);
 	std::vector<uint16_t> samplePolynomial(const LscPolynomial &poly,
 					       const std::vector<double> &xPositions,
 					       const std::vector<double> &yPositions,
 					       const Rectangle &cropRectangle);
 
+	std::vector<double> sizesListToPositions(const std::vector<double> &sizes);
+
+	LscPolynomial pr_;
+	LscPolynomial pgr_;
+	LscPolynomial pgb_;
+	LscPolynomial pb_;
+};
+
+void LscPolynomialShadingDescriptor::sampleForCrop(const Rectangle &cropRectangle,
+						   const std::vector<double> &xSizes,
+						   const std::vector<double> &ySizes,
+						   LensShadingCorrection::Components &components)
+{
+	std::vector<double> xPos = sizesListToPositions(xSizes);
+	std::vector<double> yPos = sizesListToPositions(ySizes);
+	components.r = samplePolynomial(pr_, xPos, yPos, cropRectangle);
+	components.gr = samplePolynomial(pgr_, xPos, yPos, cropRectangle);
+	components.gb = samplePolynomial(pgb_, xPos, yPos, cropRectangle);
+	components.b = samplePolynomial(pb_, xPos, yPos, cropRectangle);
+}
+
+std::vector<uint16_t>
+LscPolynomialShadingDescriptor::samplePolynomial(const LscPolynomial &poly,
+						 const std::vector<double> &xPositions,
+						 const std::vector<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::min(std::max(v, 1024), 4095);
+			samples.push_back(v);
+		}
+	}
+	return samples;
+}
+
+/*
+ * The rkisp1 LSC grid spacing is defined by the cell sizes on the first half of
+ * the grid. This is then mirrored in hardware to the other half. See
+ * parseSizes() for further details. For easier handling, this function converts
+ * the cell sizes of half the grid to a list of position of the whole grid (on
+ * one axis). Example:
+ *
+ * input:   | 0.2 | 0.3 |
+ * output: 0.0   0.2   0.5   0.8   1.0
+ */
+std::vector<double>
+LscPolynomialShadingDescriptor::sizesListToPositions(const std::vector<double> &sizes)
+{
+	const int half = sizes.size();
+	std::vector<double> positions(half * 2 + 1);
+	double x = 0.0;
+
+	positions[half] = 0.5;
+	for (int i = 1; i <= half; i++) {
+		x += sizes[half - i];
+		positions[half - i] = 0.5 - x;
+		positions[half + i] = 0.5 + x;
+	}
+
+	return positions;
+}
+
+class LscPolynomialLoader
+{
+public:
+	LscPolynomialLoader(const Size &sensorSize) : sensorSize_(sensorSize)
+	{
+	}
+
+	int parseLscData(const YamlObject &yamlSets,
+			 LensShadingCorrection::ShadingDescriptorMap &lscData);
+
+private:
 	Size sensorSize_;
-	Rectangle cropRectangle_;
-	const std::vector<double> &xSizes_;
-	const std::vector<double> &ySizes_;
 };
 
 int LscPolynomialLoader::parseLscData(const YamlObject &yamlSets,
-				      std::map<unsigned int, LensShadingCorrection::Components> &lscData)
+				      LensShadingCorrection::ShadingDescriptorMap &lscData)
 {
 	const auto &sets = yamlSets.asList();
 	for (const auto &yamlSet : sets) {
@@ -115,7 +201,6 @@  int LscPolynomialLoader::parseLscData(const YamlObject &yamlSets,
 			return -EINVAL;
 		}
 
-		LensShadingCorrection::Components &set = lscData[ct];
 		pr = yamlSet["r"].get<LscPolynomial>();
 		pgr = yamlSet["gr"].get<LscPolynomial>();
 		pgb = yamlSet["gb"].get<LscPolynomial>();
@@ -133,12 +218,9 @@  int LscPolynomialLoader::parseLscData(const YamlObject &yamlSets,
 		pgb->setReferenceImageSize(sensorSize_);
 		pb->setReferenceImageSize(sensorSize_);
 
-		std::vector<double> xPos(sizesListToPositions(xSizes_));
-		std::vector<double> yPos(sizesListToPositions(ySizes_));
-		set.r = samplePolynomial(*pr, xPos, yPos, cropRectangle_);
-		set.gr = samplePolynomial(*pgr, xPos, yPos, cropRectangle_);
-		set.gb = samplePolynomial(*pgb, xPos, yPos, cropRectangle_);
-		set.b = samplePolynomial(*pb, xPos, yPos, cropRectangle_);
+		lscData.emplace(
+			ct, std::make_unique<LscPolynomialShadingDescriptor>(
+				    *pr, *pgr, *pgb, *pb));
 	}
 
 	if (lscData.empty()) {
@@ -149,70 +231,33 @@  int LscPolynomialLoader::parseLscData(const YamlObject &yamlSets,
 	return 0;
 }
 
-/*
- * The rkisp1 LSC grid spacing is defined by the cell sizes on the first half of
- * the grid. This is then mirrored in hardware to the other half. See
- * parseSizes() for further details. For easier handling, this function converts
- * the cell sizes of half the grid to a list of position of the whole grid (on
- * one axis). Example:
- *
- * input:   | 0.2 | 0.3 |
- * output: 0.0   0.2   0.5   0.8   1.0
- */
-std::vector<double> LscPolynomialLoader::sizesListToPositions(const std::vector<double> &sizes)
+class LscTableShadingDescriptor : public LensShadingCorrection::ShadingDescriptor
 {
-	const int half = sizes.size();
-	std::vector<double> positions(half * 2 + 1);
-	double x = 0.0;
-
-	positions[half] = 0.5;
-	for (int i = 1; i <= half; i++) {
-		x += sizes[half - i];
-		positions[half - i] = 0.5 - x;
-		positions[half + i] = 0.5 + x;
+public:
+	LscTableShadingDescriptor(LensShadingCorrection::Components components)
+		: lscData_(std::move(components))
+	{
 	}
 
-	return positions;
-}
-
-std::vector<uint16_t> LscPolynomialLoader::samplePolynomial(const LscPolynomial &poly,
-							    const std::vector<double> &xPositions,
-							    const std::vector<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::min(std::max(v, 1024), 4095);
-			samples.push_back(v);
-		}
+	void sampleForCrop([[maybe_unused]] const Rectangle &cropRectangle,
+			   [[maybe_unused]] const std::vector<double> &xSizes,
+			   [[maybe_unused]] const std::vector<double> &ySizes,
+			   LensShadingCorrection::Components &components)
+	{
+		LOG(RkISP1Lsc, Warning)
+			<< "Tabular LSC data doesn't support resampling.";
+		components = lscData_;
 	}
-	return samples;
-}
+
+private:
+	LensShadingCorrection::Components lscData_;
+};
 
 class LscTableLoader
 {
 public:
 	int parseLscData(const YamlObject &yamlSets,
-			 std::map<unsigned int, LensShadingCorrection::Components> &lscData);
+			 LensShadingCorrection::ShadingDescriptorMap &lscData);
 
 private:
 	std::vector<uint16_t> parseTable(const YamlObject &tuningData,
@@ -220,7 +265,7 @@  private:
 };
 
 int LscTableLoader::parseLscData(const YamlObject &yamlSets,
-				 std::map<unsigned int, LensShadingCorrection::Components> &lscData)
+				 LensShadingCorrection::ShadingDescriptorMap &lscData)
 {
 	const auto &sets = yamlSets.asList();
 
@@ -234,8 +279,7 @@  int LscTableLoader::parseLscData(const YamlObject &yamlSets,
 			return -EINVAL;
 		}
 
-		LensShadingCorrection::Components &set = lscData[ct];
-
+		LensShadingCorrection::Components set;
 		set.r = parseTable(yamlSet, "r");
 		set.gr = parseTable(yamlSet, "gr");
 		set.gb = parseTable(yamlSet, "gb");
@@ -248,6 +292,9 @@  int LscTableLoader::parseLscData(const YamlObject &yamlSets,
 				<< " is missing tables";
 			return -EINVAL;
 		}
+
+		lscData.emplace(
+			ct, std::make_unique<LscTableShadingDescriptor>(std::move(set)));
 	}
 
 	if (lscData.empty()) {
@@ -334,7 +381,7 @@  int LensShadingCorrection::init([[maybe_unused]] IPAContext &context,
 	}
 
 	int ret = 0;
-	std::map<unsigned int, Components> lscData;
+	std::map<unsigned int, std::unique_ptr<ShadingDescriptor>> lscData;
 
 	std::string type = tuningData["type"].get<std::string>("table");
 	if (type == "table") {
@@ -343,10 +390,11 @@  int LensShadingCorrection::init([[maybe_unused]] IPAContext &context,
 		ret = loader.parseLscData(yamlSets, lscData);
 	} else if (type == "polynomial") {
 		LOG(RkISP1Lsc, Debug) << "Loading polynomial LSC data.";
-		auto loader = LscPolynomialLoader(context.sensorInfo.activeAreaSize,
-						  context.sensorInfo.analogCrop,
-						  xSize_,
-						  ySize_);
+		/*
+		 * \todo: Most likely the reference frame should be native_size.
+		 * Let's wait how the internal discussions progress.
+		 */
+		auto loader = LscPolynomialLoader(context.sensorInfo.activeAreaSize);
 		ret = loader.parseLscData(yamlSets, lscData);
 	} else {
 		LOG(RkISP1Lsc, Error) << "Unsupported LSC data type '"
@@ -357,7 +405,7 @@  int LensShadingCorrection::init([[maybe_unused]] IPAContext &context,
 	if (ret)
 		return ret;
 
-	sets_.setData(std::move(lscData));
+	shadingDescriptors_.swap(lscData);
 
 	return 0;
 }
@@ -393,6 +441,14 @@  int LensShadingCorrection::configure(IPAContext &context,
 		yGrad_[i] = std::round(32768 / ySizes_[i]);
 	}
 
+	LOG(RkISP1Lsc, Debug) << "Sample LSC data for " << configInfo.analogCrop;
+	std::map<unsigned int, LensShadingCorrection::Components> shadingData;
+	for (auto const &[t, descriptor] : shadingDescriptors_)
+		descriptor->sampleForCrop(configInfo.analogCrop, xSize_, ySize_,
+					  shadingData[t]);
+
+	sets_.setData(std::move(shadingData));
+
 	context.configuration.lsc.enabled = true;
 	return 0;
 }
diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h
index 7b68dda1a0d4..43fee337931f 100644
--- a/src/ipa/rkisp1/algorithms/lsc.h
+++ b/src/ipa/rkisp1/algorithms/lsc.h
@@ -8,6 +8,7 @@ 
 #pragma once
 
 #include <map>
+#include <memory>
 
 #include "libipa/interpolator.h"
 
@@ -36,10 +37,23 @@  public:
 		std::vector<uint16_t> b;
 	};
 
+	class ShadingDescriptor
+	{
+	public:
+		virtual ~ShadingDescriptor() = default;
+		virtual void sampleForCrop(const Rectangle &cropRectangle,
+					   const std::vector<double> &xSizes,
+					   const std::vector<double> &ySizes,
+					   Components &components) = 0;
+	};
+
+	using ShadingDescriptorMap = std::map<unsigned int, std::unique_ptr<ShadingDescriptor>>;
+
 private:
 	void setParameters(rkisp1_cif_isp_lsc_config &config);
 	void copyTable(rkisp1_cif_isp_lsc_config &config, const Components &set0);
 
+	std::map<unsigned int, std::unique_ptr<ShadingDescriptor>> shadingDescriptors_;
 	ipa::Interpolator<Components> sets_;
 	std::vector<double> xSize_;
 	std::vector<double> ySize_;