diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp
index 5808381fd84dc49fd8548e4d00b2fb4b7015f509..8427c48f65b2e6e200f834db506939c6c85fd2a3 100644
--- a/src/ipa/rkisp1/algorithms/lsc.cpp
+++ b/src/ipa/rkisp1/algorithms/lsc.cpp
@@ -72,38 +72,127 @@ namespace {
 
 constexpr int kColourTemperatureQuantization = 10;
 
-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);
+	LensShadingCorrection::Components sampleForCrop(const Rectangle &cropRectangle,
+							Span<const double> xSizes,
+							Span<const double> ySizes) override;
 
 private:
-	std::vector<double> sizesListToPositions(const std::vector<double> &sizes);
 	std::vector<uint16_t> samplePolynomial(const LscPolynomial &poly,
 					       Span<const double> xPositions,
 					       Span<const double> yPositions,
 					       const Rectangle &cropRectangle);
 
+	std::vector<double> sizesListToPositions(Span<const double> sizes);
+
+	LscPolynomial pr_;
+	LscPolynomial pgr_;
+	LscPolynomial pgb_;
+	LscPolynomial pb_;
+};
+
+LensShadingCorrection::Components
+LscPolynomialShadingDescriptor::sampleForCrop(const Rectangle &cropRectangle,
+					      Span<const double> xSizes,
+					      Span<const double> ySizes)
+{
+	std::vector<double> xPos = sizesListToPositions(xSizes);
+	std::vector<double> yPos = sizesListToPositions(ySizes);
+
+	return {
+		.r = samplePolynomial(pr_, xPos, yPos, cropRectangle),
+		.gr = samplePolynomial(pgr_, xPos, yPos, cropRectangle),
+		.gb = samplePolynomial(pgb_, xPos, yPos, cropRectangle),
+		.b = samplePolynomial(pb_, xPos, yPos, cropRectangle)
+	};
+}
+
+std::vector<uint16_t>
+LscPolynomialShadingDescriptor::samplePolynomial(const LscPolynomial &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;
+}
+
+/*
+ * 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(Span<const 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) {
@@ -117,7 +206,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>();
@@ -135,12 +223,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()) {
@@ -151,70 +236,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,
-							    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::min(std::max(v, 1024), 4095);
-			samples.push_back(v);
-		}
+	LensShadingCorrection::Components
+	sampleForCrop([[maybe_unused]] const Rectangle &cropRectangle,
+		      [[maybe_unused]] Span<const double> xSizes,
+		      [[maybe_unused]] Span<const double> ySizes)
+	{
+		LOG(RkISP1Lsc, Warning)
+			<< "Tabular LSC data doesn't support resampling.";
+		return 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,
@@ -222,7 +270,7 @@ private:
 };
 
 int LscTableLoader::parseLscData(const YamlObject &yamlSets,
-				 std::map<unsigned int, LensShadingCorrection::Components> &lscData)
+				 LensShadingCorrection::ShadingDescriptorMap &lscData)
 {
 	const auto &sets = yamlSets.asList();
 
@@ -236,8 +284,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");
@@ -250,6 +297,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()) {
@@ -341,7 +391,7 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context,
 		return -EINVAL;
 	}
 
-	std::map<unsigned int, Components> lscData;
+	ShadingDescriptorMap lscData;
 	int ret = 0;
 
 	std::string type = tuningData["type"].get<std::string>("table");
@@ -351,10 +401,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 '"
@@ -365,7 +416,7 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context,
 	if (ret)
 		return ret;
 
-	sets_.setData(std::move(lscData));
+	shadingDescriptors_ = std::move(lscData);
 
 	return 0;
 }
@@ -401,6 +452,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 (const auto &[t, descriptor] : shadingDescriptors_)
+		shadingData[t] = descriptor->sampleForCrop(configInfo.analogCrop,
+							   xSize_, ySize_);
+
+	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 7b68dda1a0d4257c345e0f1b77aa498f8183c92f..3097740a6cb2ce9063a4ba087856987a489b0ab6 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,22 @@ public:
 		std::vector<uint16_t> b;
 	};
 
+	class ShadingDescriptor
+	{
+	public:
+		virtual ~ShadingDescriptor() = default;
+		virtual Components sampleForCrop(const Rectangle &cropRectangle,
+						 Span<const double> xSizes,
+						 Span<const double> ySizes) = 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);
 
+	ShadingDescriptorMap shadingDescriptors_;
 	ipa::Interpolator<Components> sets_;
 	std::vector<double> xSize_;
 	std::vector<double> ySize_;
