[RFC,v4,07/23] ipa: libipa: agc: Move tuning file parsing
diff mbox series

Message ID 20261026132045.269007-8-barnabas.pocze@ideasonboard.com
State New
Headers show
Series
  • ipa: libipa: agc: Take exposure margin into account
Related show

Commit Message

Barnabás Pőcze Oct. 26, 2026, 1:20 p.m. UTC
Move the tuning file parsing out of `AgcMeanLuminance`, and provide the
configuration via the constructor.

Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
---
 src/ipa/libipa/agc.cpp                | 257 +++++++++++++++++++++++++-
 src/ipa/libipa/agc_mean_luminance.cpp | 246 +-----------------------
 src/ipa/libipa/agc_mean_luminance.h   |  22 +--
 3 files changed, 273 insertions(+), 252 deletions(-)

Patch
diff mbox series

diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp
index e23cb13cb7..a27813b8bf 100644
--- a/src/ipa/libipa/agc.cpp
+++ b/src/ipa/libipa/agc.cpp
@@ -26,6 +26,8 @@ 
 
 #include <libcamera/ipa/core_ipa_interface.h>
 
+#include "exposure_mode_helper.h"
+
 /**
  * \file agc.h
  * \brief libipa AGC algorithm
@@ -222,6 +224,253 @@  namespace agc {
 
 } /* namespace agc */
 
+namespace {
+
+/*
+ * Default relative luminance target
+ *
+ * This value should be chosen so that when the camera points at a grey target,
+ * the resulting image brightness looks "right". Custom values can be passed
+ * as the relativeLuminanceTarget value in sensor tuning files.
+ */
+constexpr double kDefaultRelativeLuminanceTarget = 0.16;
+
+int parseRelativeLuminanceTarget(AgcMeanLuminance::Configuration &config, const ValueNode &tuningData)
+{
+	auto &target = tuningData;
+	if (!target) {
+		config.relativeLuminanceTarget = { { { { 0.0, kDefaultRelativeLuminanceTarget } } } };
+		return 0;
+	}
+
+	auto pwl = target.get<Pwl>();
+	if (!pwl) {
+		LOG(Agc, Error)
+			<< "Failed to load relative luminance target";
+		return -EINVAL;
+	}
+
+	config.relativeLuminanceTarget = std::move(*pwl);
+	return 0;
+}
+
+int parseConstraint(AgcMeanLuminance::Configuration &config, const ValueNode &modeDict, int32_t id)
+{
+	for (const auto &[boundName, content] : modeDict.asDict()) {
+		if (boundName != "upper" && boundName != "lower") {
+			LOG(Agc, Warning)
+				<< "Ignoring unknown constraint bound '" << boundName << "'";
+			continue;
+		}
+
+		unsigned int idx = static_cast<unsigned int>(boundName == "upper");
+		auto bound = static_cast<AgcMeanLuminance::AgcConstraint::Bound>(idx);
+		double qLo = content["qLo"].get<double>().value_or(0.98);
+		double qHi = content["qHi"].get<double>().value_or(1.0);
+		auto yTarget = content["yTarget"].get<Pwl>();
+		if (!yTarget) {
+			LOG(Agc, Error)
+				<< "Failed to parse yTarget";
+			return -EINVAL;
+		}
+
+		AgcMeanLuminance::AgcConstraint constraint = {
+			bound,
+			qLo,
+			qHi,
+			std::move(*yTarget),
+		};
+		auto &constraints = config.constraintModes[id];
+
+		if (idx)
+			constraints.push_back(std::move(constraint));
+		else
+			constraints.insert(constraints.begin(), std::move(constraint));
+	}
+
+	return 0;
+}
+
+int parseConstraintModes(AgcMeanLuminance::Configuration &config, const ValueNode &constraintModes)
+{
+	if (!constraintModes.isDictionary())
+		return 0;
+
+	for (const auto &[modeName, modeDict] : constraintModes.asDict()) {
+		auto it = controls::AeConstraintModeNameValueMap.find(modeName);
+		if (it == controls::AeConstraintModeNameValueMap.end()) {
+			LOG(Agc, Warning)
+				<< "Skipping unknown constraint mode '" << modeName << "'";
+			continue;
+		}
+
+		if (!modeDict.isDictionary()) {
+			LOG(Agc, Error)
+				<< "Invalid constraint mode '" << modeName << "'";
+			return -EINVAL;
+		}
+
+		int ret = parseConstraint(config, modeDict, it->second);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+int parseExposureModes(AgcMeanLuminance::Configuration &config, const ValueNode &exposureModes)
+{
+	if (!exposureModes.isDictionary())
+		return 0;
+
+	for (const auto &[modeName, modeValues] : exposureModes.asDict()) {
+		auto it = controls::AeExposureModeNameValueMap.find(modeName);
+		if (it == controls::AeExposureModeNameValueMap.end()) {
+			LOG(Agc, Warning)
+				<< "Skipping unknown exposure mode '" << modeName << "'";
+			continue;
+		}
+
+		if (!modeValues.isDictionary()) {
+			LOG(Agc, Error)
+				<< "Invalid exposure mode '" << modeName << "'";
+			return -EINVAL;
+		}
+
+		std::vector<uint32_t> exposureTimes =
+			modeValues["exposureTime"].get<std::vector<uint32_t>>().value_or(utils::defopt);
+		std::vector<double> gains =
+			modeValues["gain"].get<std::vector<double>>().value_or(utils::defopt);
+
+		if (exposureTimes.size() != gains.size()) {
+			LOG(Agc, Error)
+				<< "Exposure time and gain array sizes unequal";
+			return -EINVAL;
+		}
+
+		if (exposureTimes.empty()) {
+			LOG(Agc, Error)
+				<< "Exposure time and gain arrays are empty";
+			return -EINVAL;
+		}
+
+		std::vector<ExposureModeHelper::Stage> stages;
+		for (size_t i = 0; i < exposureTimes.size(); i++) {
+			stages.push_back({
+				.exposureTime = std::chrono::microseconds(exposureTimes[i]),
+				.gain = gains[i],
+			});
+		}
+
+		config.exposureModeHelpers.try_emplace(it->second, std::move(stages));
+	}
+
+	return 0;
+}
+
+/**
+ * \brief Parse tuning data for AeConstraintMode and AeExposureMode controls
+ * \param[in] tuningData the ValueNode representing the tuning data
+ *
+ * This function parses tuning data to build the list of allowed values for the
+ * AeConstraintMode and AeExposureMode controls. Those tuning data must provide
+ * the data in a specific format; the Agc algorithm's tuning data should contain
+ * a dictionary called AeConstraintMode containing per-mode setting dictionaries
+ * with the key being a value from \ref controls::AeConstraintModeNameValueMap.
+ * The yTarget can either be provided as single value or as array in which case
+ * it is interpreted as a PWL mapping lux levels to yTarget values. Each mode
+ * dict may contain either a "lower" or "upper" key or both, for example:
+ *
+ * \code{.unparsed}
+ * algorithms:
+ *   - Agc:
+ *       AeConstraintMode:
+ *         ConstraintNormal:
+ *           lower:
+ *             qLo: 0.98
+ *             qHi: 1.0
+ *             yTarget: 0.5
+ *         ConstraintHighlight:
+ *           lower:
+ *             qLo: 0.98
+ *             qHi: 1.0
+ *             yTarget: 0.5
+ *           upper:
+ *             qLo: 0.98
+ *             qHi: 1.0
+ *             yTarget: [ 100, 0.8, 20000, 0.5 ]
+ *
+ * \endcode
+ *
+ * For the AeExposureMode control the data should contain a dictionary called
+ * AeExposureMode containing per-mode setting dictionaries with the key being a
+ * value from \ref controls::AeExposureModeNameValueMap. Each mode dict should
+ * contain an array of exposure times with the key "exposureTime" and an array
+ * of gain values with the key "gain", in this format:
+ *
+ * \code{.unparsed}
+ * algorithms:
+ *   - Agc:
+ *       AeExposureMode:
+ *         ExposureNormal:
+ *           exposureTime: [ 100, 10000, 30000, 60000, 120000 ]
+ *           gain: [ 2.0, 4.0, 6.0, 8.0, 10.0 ]
+ *         ExposureShort:
+ *           exposureTime: [ 100, 10000, 30000, 60000, 120000 ]
+ *           gain: [ 2.0, 4.0, 6.0, 8.0, 10.0 ]
+ *
+ * \endcode
+ *
+ * \return 0 on success or a negative error code
+ */
+int parseTuningData(AgcMeanLuminance::Configuration &config, const ValueNode &tuningData)
+{
+	int ret;
+
+	ret = parseRelativeLuminanceTarget(config, tuningData["relativeLuminanceTarget"]);
+	if (ret)
+		return ret;
+
+	ret = parseConstraintModes(config, tuningData[controls::AeConstraintMode.name()]);
+	if (ret)
+		return ret;
+
+	return parseExposureModes(config, tuningData[controls::AeExposureMode.name()]);
+}
+
+void fillMissing(AgcMeanLuminance::Configuration &config)
+{
+	/*
+	 * If we don't have any exposure modes in the tuning data we create an
+	 * ExposureModeHelper using an empty vector of stages. This will result
+	 * in the ExposureModeHelper simply driving the exposure time as high as
+	 * possible before touching gain.
+	 */
+	if (config.exposureModeHelpers.empty())
+		config.exposureModeHelpers.try_emplace(
+			controls::ExposureNormal,
+			std::vector<ExposureModeHelper::Stage>{});
+
+	/*
+	 * If the tuning data file contains no constraints then we use the
+	 * default constraint that the IPU3/RkISP1 Agc algorithms were adhering
+	 * to anyway before centralisation; this constraint forces the top 2% of
+	 * the histogram to be at least 0.5.
+	 */
+	if (config.constraintModes.empty()) {
+		AgcMeanLuminance::AgcConstraint constraint = {
+			AgcMeanLuminance::AgcConstraint::Bound::Lower,
+			0.98,
+			1.0,
+			Pwl({ { { 0.0, 0.5 } } }),
+		};
+
+		config.constraintModes[controls::ConstraintNormal].push_back(std::move(constraint));
+	}
+}
+
+} /* namespace */
+
 /**
  * \class AgcAlgorithm
  * \brief libIPA LSC algorithm algorithm
@@ -310,10 +559,14 @@  int AgcAlgorithm::init(const ValueNode &tuningData, CameraSensorHelper *sensor,
 		       const ConfigurationParams &config)
 {
 	if (sensor) {
-		auto &impl = impl_.emplace<AgcMeanLuminance>();
-		int ret = impl.parseTuningData(tuningData);
+		AgcMeanLuminance::Configuration agcMLconfig;
+		int ret = parseTuningData(agcMLconfig, tuningData);
 		if (ret)
 			return ret;
+
+		fillMissing(agcMLconfig);
+
+		impl_.emplace<AgcMeanLuminance>(std::move(agcMLconfig));
 	} else {
 		impl_.emplace<AgcMSV>();
 	}
diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp
index eff639fb67..8b35eab6aa 100644
--- a/src/ipa/libipa/agc_mean_luminance.cpp
+++ b/src/ipa/libipa/agc_mean_luminance.cpp
@@ -9,7 +9,6 @@ 
 
 #include <algorithm>
 #include <cmath>
-#include <span>
 
 #include <libcamera/base/log.h>
 #include <libcamera/base/utils.h>
@@ -18,8 +17,6 @@ 
 
 #include "exposure_mode_helper.h"
 
-using namespace libcamera::controls;
-
 /**
  * \file agc_mean_luminance.h
  * \brief Class implementing mean luminance AEGC
@@ -39,15 +36,6 @@  namespace ipa {
  */
 static constexpr uint32_t kNumStartupFrames = 10;
 
-/*
- * Default relative luminance target
- *
- * This value should be chosen so that when the camera points at a grey target,
- * the resulting image brightness looks "right". Custom values can be passed
- * as the relativeLuminanceTarget value in sensor tuning files.
- */
-static constexpr double kDefaultRelativeLuminanceTarget = 0.16;
-
 /*
  * Maximum relative luminance target
  *
@@ -177,168 +165,18 @@  static constexpr unsigned int kDefaultLuxLevel = 500;
  * setLimits() must be called again with the new values.
  */
 
-AgcMeanLuminance::AgcMeanLuminance()
-	: filteredExposure_(0s), luxWarningEnabled_(true), frameCount_(0)
+AgcMeanLuminance::AgcMeanLuminance(Configuration &&config)
+	: filteredExposure_(0s), luxWarningEnabled_(true), frameCount_(0),
+	  relativeLuminanceTarget_(std::move(config.relativeLuminanceTarget)),
+	  constraintModes_(std::move(config.constraintModes)),
+	  exposureModeHelpers_(std::move(config.exposureModeHelpers))
 {
+	ASSERT(!constraintModes_.empty());
+	ASSERT(!exposureModeHelpers_.empty());
 }
 
 AgcMeanLuminance::~AgcMeanLuminance() = default;
 
-int AgcMeanLuminance::parseRelativeLuminanceTarget(const ValueNode &tuningData)
-{
-	auto &target = tuningData["relativeLuminanceTarget"];
-	if (!target) {
-		relativeLuminanceTarget_ = { { { { 0.0, kDefaultRelativeLuminanceTarget } } } };
-		return 0;
-	}
-
-	auto pwl = target.get<Pwl>();
-	if (!pwl) {
-		LOG(AgcMeanLuminance, Error)
-			<< "Failed to load relative luminance target.";
-		return -EINVAL;
-	}
-
-	relativeLuminanceTarget_ = std::move(*pwl);
-	return 0;
-}
-
-int AgcMeanLuminance::parseConstraint(const ValueNode &modeDict, int32_t id)
-{
-	for (const auto &[boundName, content] : modeDict.asDict()) {
-		if (boundName != "upper" && boundName != "lower") {
-			LOG(AgcMeanLuminance, Warning)
-				<< "Ignoring unknown constraint bound '" << boundName << "'";
-			continue;
-		}
-
-		unsigned int idx = static_cast<unsigned int>(boundName == "upper");
-		AgcConstraint::Bound bound = static_cast<AgcConstraint::Bound>(idx);
-		double qLo = content["qLo"].get<double>().value_or(0.98);
-		double qHi = content["qHi"].get<double>().value_or(1.0);
-		auto yTarget = content["yTarget"].get<Pwl>();
-		if (!yTarget) {
-			LOG(AgcMeanLuminance, Error)
-				<< "Failed to parse yTarget";
-			return -EINVAL;
-		}
-
-		AgcConstraint constraint = { bound, qLo, qHi, std::move(*yTarget) };
-		auto &constraints = constraintModes_[id];
-
-		if (idx)
-			constraints.push_back(std::move(constraint));
-		else
-			constraints.insert(constraints.begin(), std::move(constraint));
-	}
-
-	return 0;
-}
-
-int AgcMeanLuminance::parseConstraintModes(const ValueNode &tuningData)
-{
-	const ValueNode &constraintModes = tuningData[controls::AeConstraintMode.name()];
-	if (constraintModes.isDictionary()) {
-		for (const auto &[modeName, modeDict] : constraintModes.asDict()) {
-			auto it = AeConstraintModeNameValueMap.find(modeName);
-			if (it == AeConstraintModeNameValueMap.end()) {
-				LOG(AgcMeanLuminance, Warning)
-					<< "Skipping unknown constraint mode '" << modeName << "'";
-				continue;
-			}
-
-			if (!modeDict.isDictionary()) {
-				LOG(AgcMeanLuminance, Error)
-					<< "Invalid constraint mode '" << modeName << "'";
-				return -EINVAL;
-			}
-
-			int ret = parseConstraint(modeDict, it->second);
-			if (ret)
-				return ret;
-		}
-	}
-
-	/*
-	 * If the tuning data file contains no constraints then we use the
-	 * default constraint that the IPU3/RkISP1 Agc algorithms were adhering
-	 * to anyway before centralisation; this constraint forces the top 2% of
-	 * the histogram to be at least 0.5.
-	 */
-	if (constraintModes_.empty()) {
-		AgcConstraint constraint = {
-			AgcConstraint::Bound::Lower,
-			0.98,
-			1.0,
-			Pwl({ { { 0.0, 0.5 } } })
-		};
-
-		constraintModes_[controls::ConstraintNormal].push_back(std::move(constraint));
-	}
-
-	return 0;
-}
-
-int AgcMeanLuminance::parseExposureModes(const ValueNode &tuningData)
-{
-	const ValueNode &exposureModes = tuningData[controls::AeExposureMode.name()];
-	if (exposureModes.isDictionary()) {
-		for (const auto &[modeName, modeValues] : exposureModes.asDict()) {
-			auto it = AeExposureModeNameValueMap.find(modeName);
-			if (it == AeExposureModeNameValueMap.end()) {
-				LOG(AgcMeanLuminance, Warning)
-					<< "Skipping unknown exposure mode '" << modeName << "'";
-				continue;
-			}
-
-			if (!modeValues.isDictionary()) {
-				LOG(AgcMeanLuminance, Error)
-					<< "Invalid exposure mode '" << modeName << "'";
-				return -EINVAL;
-			}
-
-			std::vector<uint32_t> exposureTimes =
-				modeValues["exposureTime"].get<std::vector<uint32_t>>().value_or(utils::defopt);
-			std::vector<double> gains =
-				modeValues["gain"].get<std::vector<double>>().value_or(utils::defopt);
-
-			if (exposureTimes.size() != gains.size()) {
-				LOG(AgcMeanLuminance, Error)
-					<< "Exposure time and gain array sizes unequal";
-				return -EINVAL;
-			}
-
-			if (exposureTimes.empty()) {
-				LOG(AgcMeanLuminance, Error)
-					<< "Exposure time and gain arrays are empty";
-				return -EINVAL;
-			}
-
-			std::vector<ExposureModeHelper::Stage> stages;
-			for (size_t i = 0; i < exposureTimes.size(); i++) {
-				stages.push_back({
-					.exposureTime = std::chrono::microseconds(exposureTimes[i]),
-					.gain = gains[i],
-				});
-			}
-
-			exposureModeHelpers_.try_emplace(it->second, std::move(stages));
-		}
-	}
-
-	/*
-	 * If we don't have any exposure modes in the tuning data we create an
-	 * ExposureModeHelper using an empty vector of stages. This will result
-	 * in the ExposureModeHelper simply driving the exposure time as high as
-	 * possible before touching gain.
-	 */
-	if (exposureModeHelpers_.empty())
-		exposureModeHelpers_.try_emplace(controls::ExposureNormal,
-						 std::vector<ExposureModeHelper::Stage>{});
-
-	return 0;
-}
-
 /**
  * \brief Configure the exposure mode helpers
  * \param[in] lineDuration The sensor line length
@@ -358,76 +196,6 @@  void AgcMeanLuminance::configure(utils::Duration lineDuration,
 	frameCount_ = 0;
 }
 
-/**
- * \brief Parse tuning data for AeConstraintMode and AeExposureMode controls
- * \param[in] tuningData the ValueNode representing the tuning data
- *
- * This function parses tuning data to build the list of allowed values for the
- * AeConstraintMode and AeExposureMode controls. Those tuning data must provide
- * the data in a specific format; the Agc algorithm's tuning data should contain
- * a dictionary called AeConstraintMode containing per-mode setting dictionaries
- * with the key being a value from \ref controls::AeConstraintModeNameValueMap.
- * The yTarget can either be provided as single value or as array in which case
- * it is interpreted as a PWL mapping lux levels to yTarget values. Each mode
- * dict may contain either a "lower" or "upper" key or both, for example:
- *
- * \code{.unparsed}
- * algorithms:
- *   - Agc:
- *       AeConstraintMode:
- *         ConstraintNormal:
- *           lower:
- *             qLo: 0.98
- *             qHi: 1.0
- *             yTarget: 0.5
- *         ConstraintHighlight:
- *           lower:
- *             qLo: 0.98
- *             qHi: 1.0
- *             yTarget: 0.5
- *           upper:
- *             qLo: 0.98
- *             qHi: 1.0
- *             yTarget: [ 100, 0.8, 20000, 0.5 ]
- *
- * \endcode
- *
- * For the AeExposureMode control the data should contain a dictionary called
- * AeExposureMode containing per-mode setting dictionaries with the key being a
- * value from \ref controls::AeExposureModeNameValueMap. Each mode dict should
- * contain an array of exposure times with the key "exposureTime" and an array
- * of gain values with the key "gain", in this format:
- *
- * \code{.unparsed}
- * algorithms:
- *   - Agc:
- *       AeExposureMode:
- *         ExposureNormal:
- *           exposureTime: [ 100, 10000, 30000, 60000, 120000 ]
- *           gain: [ 2.0, 4.0, 6.0, 8.0, 10.0 ]
- *         ExposureShort:
- *           exposureTime: [ 100, 10000, 30000, 60000, 120000 ]
- *           gain: [ 2.0, 4.0, 6.0, 8.0, 10.0 ]
- *
- * \endcode
- *
- * \return 0 on success or a negative error code
- */
-int AgcMeanLuminance::parseTuningData(const ValueNode &tuningData)
-{
-	int ret;
-
-	ret = parseRelativeLuminanceTarget(tuningData);
-	if (ret)
-		return ret;
-
-	ret = parseConstraintModes(tuningData);
-	if (ret)
-		return ret;
-
-	return parseExposureModes(tuningData);
-}
-
 /**
  * \brief Set the ExposureModeHelper limits for this class
  * \param[in] minExposureTime Minimum exposure time to allow
diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h
index f799618ffc..c32bf44a2e 100644
--- a/src/ipa/libipa/agc_mean_luminance.h
+++ b/src/ipa/libipa/agc_mean_luminance.h
@@ -8,13 +8,10 @@ 
 #pragma once
 
 #include <map>
-#include <memory>
 #include <vector>
 
 #include <libcamera/base/utils.h>
 
-#include "libcamera/internal/value_node.h"
-
 #include "exposure_mode_helper.h"
 #include "histogram.h"
 #include "pwl.h"
@@ -26,9 +23,6 @@  namespace ipa {
 class AgcMeanLuminance
 {
 public:
-	AgcMeanLuminance();
-	~AgcMeanLuminance();
-
 	struct AgcConstraint {
 		enum class Bound {
 			Lower = 0,
@@ -40,6 +34,15 @@  public:
 		Pwl yTarget;
 	};
 
+	struct Configuration {
+		Pwl relativeLuminanceTarget;
+		std::map<int32_t, std::vector<AgcConstraint>> constraintModes;
+		std::map<int32_t, ExposureModeHelper> exposureModeHelpers;
+	};
+
+	AgcMeanLuminance(Configuration &&config);
+	~AgcMeanLuminance();
+
 	struct Traits {
 		virtual ~Traits() = default;
 		virtual double estimateLuminance(double gain) const = 0;
@@ -80,20 +83,17 @@  public:
 	double effectiveYTarget(double lux, double exposureCompensation) const;
 
 private:
-	int parseRelativeLuminanceTarget(const ValueNode &tuningData);
-	int parseConstraint(const ValueNode &modeDict, int32_t id);
-	int parseConstraintModes(const ValueNode &tuningData);
-	int parseExposureModes(const ValueNode &tuningData);
 	double estimateInitialGain(const Traits &traits, double yTarget) const;
 	double constraintClampGain(const Params &params, double gain) const;
 	utils::Duration filterExposure(utils::Duration exposureValue);
 
 	utils::Duration filteredExposure_;
 	mutable bool luxWarningEnabled_;
-	Pwl relativeLuminanceTarget_;
 	uint64_t frameCount_;
 
 	std::vector<AgcConstraint> additionalConstraints_;
+
+	Pwl relativeLuminanceTarget_;
 	std::map<int32_t, std::vector<AgcConstraint>> constraintModes_;
 	std::map<int32_t, ExposureModeHelper> exposureModeHelpers_;
 };