[v3,3/4] ipa: libipa: agc_mean_luminance: Change luminance target to piecewise linear function
diff mbox series

Message ID 20251119132221.2088013-4-stefan.klug@ideasonboard.com
State New
Headers show
Series
  • libipa: agc: Fix constraints yTarget handling and add PWL support
Related show

Commit Message

Stefan Klug Nov. 19, 2025, 1:22 p.m. UTC
In some situations it is necessary to specify the target brightness
value depending on the overall lux level. Replace the float
relativeLuminanceTraget by a PWL. As the PWL loading code loads a plain
value as single point PWL, backwards compatibility to existing tuning
files is ensured.

While at it, order the class members in reverse xmas tree notation.

Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>

---

Changes in v3:
- Dropped reference to original patch from commit message. For the
  record, the old patch was https://patchwork.libcamera.org/patch/20231/
- Ordered member variables in reverse xmas tree order
- Fallback to kDefaultRelativeLuminanceTarget if relativeLuminanceTraget
  is missing in the tuning file
- Warn once if lux level is not available after a few frames

Changes in v2:
- Collected tags
- Dropped special handling of plain values, as this is now part of the
  yaml PWL loader.
---
 src/ipa/libipa/agc_mean_luminance.cpp | 67 ++++++++++++++++++++++++---
 src/ipa/libipa/agc_mean_luminance.h   | 14 ++++--
 src/ipa/rkisp1/algorithms/agc.cpp     |  1 +
 3 files changed, 72 insertions(+), 10 deletions(-)

Comments

Kieran Bingham Nov. 19, 2025, 2:09 p.m. UTC | #1
Quoting Stefan Klug (2025-11-19 13:22:12)
> In some situations it is necessary to specify the target brightness
> value depending on the overall lux level. Replace the float
> relativeLuminanceTraget by a PWL. As the PWL loading code loads a plain
> value as single point PWL, backwards compatibility to existing tuning
> files is ensured.
> 
> While at it, order the class members in reverse xmas tree notation.
> 
> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
> Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>

Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>

> 
> ---
> 
> Changes in v3:
> - Dropped reference to original patch from commit message. For the
>   record, the old patch was https://patchwork.libcamera.org/patch/20231/
> - Ordered member variables in reverse xmas tree order
> - Fallback to kDefaultRelativeLuminanceTarget if relativeLuminanceTraget
>   is missing in the tuning file
> - Warn once if lux level is not available after a few frames
> 
> Changes in v2:
> - Collected tags
> - Dropped special handling of plain values, as this is now part of the
>   yaml PWL loader.
> ---
>  src/ipa/libipa/agc_mean_luminance.cpp | 67 ++++++++++++++++++++++++---
>  src/ipa/libipa/agc_mean_luminance.h   | 14 ++++--
>  src/ipa/rkisp1/algorithms/agc.cpp     |  1 +
>  3 files changed, 72 insertions(+), 10 deletions(-)
> 
> diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp
> index 64f36bd75dd2..4f2b5fad2082 100644
> --- a/src/ipa/libipa/agc_mean_luminance.cpp
> +++ b/src/ipa/libipa/agc_mean_luminance.cpp
> @@ -54,6 +54,14 @@ static constexpr double kDefaultRelativeLuminanceTarget = 0.16;
>   */
>  static constexpr double kMaxRelativeLuminanceTarget = 0.95;
>  
> +/*
> + * Default lux level
> + *
> + * If no lux level or a zero lux level is specified, but PWLs are used to
> + * specify luminance targets, this default level is used.
> + */
> +static constexpr unsigned int kDefaultLuxLevel = 500;
> +
>  /**
>   * \struct AgcMeanLuminance::AgcConstraint
>   * \brief The boundaries and target for an AeConstraintMode constraint
> @@ -144,17 +152,30 @@ static constexpr double kMaxRelativeLuminanceTarget = 0.95;
>   */
>  
>  AgcMeanLuminance::AgcMeanLuminance()
> -       : exposureCompensation_(1.0), frameCount_(0), filteredExposure_(0s),
> -         relativeLuminanceTarget_(0)
> +       : filteredExposure_(0s), luxWarningEnabled_(true),
> +         exposureCompensation_(1.0), frameCount_(0), lux_(0)
>  {
>  }
>  
>  AgcMeanLuminance::~AgcMeanLuminance() = default;
>  
> -void AgcMeanLuminance::parseRelativeLuminanceTarget(const YamlObject &tuningData)
> +int AgcMeanLuminance::parseRelativeLuminanceTarget(const YamlObject &tuningData)
>  {
> -       relativeLuminanceTarget_ =
> -               tuningData["relativeLuminanceTarget"].get<double>(kDefaultRelativeLuminanceTarget);
> +       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;
>  }
>  
>  void AgcMeanLuminance::parseConstraint(const YamlObject &modeDict, int32_t id)
> @@ -325,6 +346,8 @@ void AgcMeanLuminance::configure(utils::Duration lineDuration,
>  {
>         for (auto &[id, helper] : exposureModeHelpers_)
>                 helper->configure(lineDuration, sensorHelper);
> +
> +       luxWarningEnabled_ = true;
>  }
>  
>  /**
> @@ -385,7 +408,9 @@ int AgcMeanLuminance::parseTuningData(const YamlObject &tuningData)
>  {
>         int ret;
>  
> -       parseRelativeLuminanceTarget(tuningData);
> +       ret = parseRelativeLuminanceTarget(tuningData);
> +       if (ret)
> +               return ret;
>  
>         ret = parseConstraintModes(tuningData);
>         if (ret)
> @@ -403,6 +428,16 @@ int AgcMeanLuminance::parseTuningData(const YamlObject &tuningData)
>   * AGC calculations. It is expressed as gain instead of EV.
>   */
>  
> +/**
> + * \fn AgcMeanLuminance::setLux(int lux)
> + * \brief Set the lux level
> + * \param[in] lux The lux level
> + *
> + * This function sets the lux level to be used in the AGC calculations. A value
> + * of 0 means no measurement and a default value of \a kDefaultLuxLevel is used
> + * if necessary.
> + */
> +
>  /**
>   * \brief Set the ExposureModeHelper limits for this class
>   * \param[in] minExposureTime Minimum exposure time to allow
> @@ -538,7 +573,25 @@ double AgcMeanLuminance::constraintClampGain(uint32_t constraintModeIndex,
>   */
>  double AgcMeanLuminance::effectiveYTarget() const
>  {
> -       return std::min(relativeLuminanceTarget_ * exposureCompensation_,
> +       double lux = lux_;
> +       if (relativeLuminanceTarget_.size() > 1 && lux_ == 0) {
> +               if (frameCount_ > 10 && luxWarningEnabled_) {
> +                       luxWarningEnabled_ = false;
> +                       LOG(AgcMeanLuminance, Warning)
> +                               << "Missing lux value for luminance target "
> +                                  "calculation, default to "
> +                               << kDefaultLuxLevel
> +                               << ". Note that the Lux algorithm must be "
> +                                  "included before the Agc algorithm.";
> +               }
> +
> +               lux = kDefaultLuxLevel;
> +       }
> +
> +       double luminanceTarget = relativeLuminanceTarget_.eval(
> +               relativeLuminanceTarget_.domain().clamp(lux));
> +
> +       return std::min(luminanceTarget * exposureCompensation_,
>                         kMaxRelativeLuminanceTarget);
>  }
>  
> diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h
> index e5f164c3186b..746b97b16ffe 100644
> --- a/src/ipa/libipa/agc_mean_luminance.h
> +++ b/src/ipa/libipa/agc_mean_luminance.h
> @@ -20,6 +20,7 @@
>  
>  #include "exposure_mode_helper.h"
>  #include "histogram.h"
> +#include "pwl.h"
>  
>  namespace libcamera {
>  
> @@ -50,6 +51,11 @@ public:
>                 exposureCompensation_ = gain;
>         }
>  
> +       void setLux(unsigned int lux)
> +       {
> +               lux_ = lux;
> +       }
> +
>         void setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime,
>                        double minGain, double maxGain, std::vector<AgcConstraint> constraints);
>  
> @@ -82,7 +88,7 @@ public:
>  private:
>         virtual double estimateLuminance(const double gain) const = 0;
>  
> -       void parseRelativeLuminanceTarget(const YamlObject &tuningData);
> +       int parseRelativeLuminanceTarget(const YamlObject &tuningData);
>         void parseConstraint(const YamlObject &modeDict, int32_t id);
>         int parseConstraintModes(const YamlObject &tuningData);
>         int parseExposureModes(const YamlObject &tuningData);
> @@ -92,10 +98,12 @@ private:
>                                    double gain);
>         utils::Duration filterExposure(utils::Duration exposureValue);
>  
> +       utils::Duration filteredExposure_;
> +       mutable bool luxWarningEnabled_;
>         double exposureCompensation_;
> +       Pwl relativeLuminanceTarget_;
>         uint64_t frameCount_;
> -       utils::Duration filteredExposure_;
> -       double relativeLuminanceTarget_;
> +       unsigned int lux_;
>  
>         std::vector<AgcConstraint> additionalConstraints_;
>         std::map<int32_t, std::vector<AgcConstraint>> constraintModes_;
> diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp
> index f5a3c917cb69..1ecaff680978 100644
> --- a/src/ipa/rkisp1/algorithms/agc.cpp
> +++ b/src/ipa/rkisp1/algorithms/agc.cpp
> @@ -618,6 +618,7 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
>                 effectiveExposureValue *= frameContext.agc.quantizationGain;
>  
>         setExposureCompensation(pow(2.0, frameContext.agc.exposureValue));
> +       setLux(frameContext.lux.lux);
>  
>         utils::Duration newExposureTime;
>         double aGain, qGain, dGain;
> -- 
> 2.51.0
>

Patch
diff mbox series

diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp
index 64f36bd75dd2..4f2b5fad2082 100644
--- a/src/ipa/libipa/agc_mean_luminance.cpp
+++ b/src/ipa/libipa/agc_mean_luminance.cpp
@@ -54,6 +54,14 @@  static constexpr double kDefaultRelativeLuminanceTarget = 0.16;
  */
 static constexpr double kMaxRelativeLuminanceTarget = 0.95;
 
+/*
+ * Default lux level
+ *
+ * If no lux level or a zero lux level is specified, but PWLs are used to
+ * specify luminance targets, this default level is used.
+ */
+static constexpr unsigned int kDefaultLuxLevel = 500;
+
 /**
  * \struct AgcMeanLuminance::AgcConstraint
  * \brief The boundaries and target for an AeConstraintMode constraint
@@ -144,17 +152,30 @@  static constexpr double kMaxRelativeLuminanceTarget = 0.95;
  */
 
 AgcMeanLuminance::AgcMeanLuminance()
-	: exposureCompensation_(1.0), frameCount_(0), filteredExposure_(0s),
-	  relativeLuminanceTarget_(0)
+	: filteredExposure_(0s), luxWarningEnabled_(true),
+	  exposureCompensation_(1.0), frameCount_(0), lux_(0)
 {
 }
 
 AgcMeanLuminance::~AgcMeanLuminance() = default;
 
-void AgcMeanLuminance::parseRelativeLuminanceTarget(const YamlObject &tuningData)
+int AgcMeanLuminance::parseRelativeLuminanceTarget(const YamlObject &tuningData)
 {
-	relativeLuminanceTarget_ =
-		tuningData["relativeLuminanceTarget"].get<double>(kDefaultRelativeLuminanceTarget);
+	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;
 }
 
 void AgcMeanLuminance::parseConstraint(const YamlObject &modeDict, int32_t id)
@@ -325,6 +346,8 @@  void AgcMeanLuminance::configure(utils::Duration lineDuration,
 {
 	for (auto &[id, helper] : exposureModeHelpers_)
 		helper->configure(lineDuration, sensorHelper);
+
+	luxWarningEnabled_ = true;
 }
 
 /**
@@ -385,7 +408,9 @@  int AgcMeanLuminance::parseTuningData(const YamlObject &tuningData)
 {
 	int ret;
 
-	parseRelativeLuminanceTarget(tuningData);
+	ret = parseRelativeLuminanceTarget(tuningData);
+	if (ret)
+		return ret;
 
 	ret = parseConstraintModes(tuningData);
 	if (ret)
@@ -403,6 +428,16 @@  int AgcMeanLuminance::parseTuningData(const YamlObject &tuningData)
  * AGC calculations. It is expressed as gain instead of EV.
  */
 
+/**
+ * \fn AgcMeanLuminance::setLux(int lux)
+ * \brief Set the lux level
+ * \param[in] lux The lux level
+ *
+ * This function sets the lux level to be used in the AGC calculations. A value
+ * of 0 means no measurement and a default value of \a kDefaultLuxLevel is used
+ * if necessary.
+ */
+
 /**
  * \brief Set the ExposureModeHelper limits for this class
  * \param[in] minExposureTime Minimum exposure time to allow
@@ -538,7 +573,25 @@  double AgcMeanLuminance::constraintClampGain(uint32_t constraintModeIndex,
  */
 double AgcMeanLuminance::effectiveYTarget() const
 {
-	return std::min(relativeLuminanceTarget_ * exposureCompensation_,
+	double lux = lux_;
+	if (relativeLuminanceTarget_.size() > 1 && lux_ == 0) {
+		if (frameCount_ > 10 && luxWarningEnabled_) {
+			luxWarningEnabled_ = false;
+			LOG(AgcMeanLuminance, Warning)
+				<< "Missing lux value for luminance target "
+				   "calculation, default to "
+				<< kDefaultLuxLevel
+				<< ". Note that the Lux algorithm must be "
+				   "included before the Agc algorithm.";
+		}
+
+		lux = kDefaultLuxLevel;
+	}
+
+	double luminanceTarget = relativeLuminanceTarget_.eval(
+		relativeLuminanceTarget_.domain().clamp(lux));
+
+	return std::min(luminanceTarget * exposureCompensation_,
 			kMaxRelativeLuminanceTarget);
 }
 
diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h
index e5f164c3186b..746b97b16ffe 100644
--- a/src/ipa/libipa/agc_mean_luminance.h
+++ b/src/ipa/libipa/agc_mean_luminance.h
@@ -20,6 +20,7 @@ 
 
 #include "exposure_mode_helper.h"
 #include "histogram.h"
+#include "pwl.h"
 
 namespace libcamera {
 
@@ -50,6 +51,11 @@  public:
 		exposureCompensation_ = gain;
 	}
 
+	void setLux(unsigned int lux)
+	{
+		lux_ = lux;
+	}
+
 	void setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime,
 		       double minGain, double maxGain, std::vector<AgcConstraint> constraints);
 
@@ -82,7 +88,7 @@  public:
 private:
 	virtual double estimateLuminance(const double gain) const = 0;
 
-	void parseRelativeLuminanceTarget(const YamlObject &tuningData);
+	int parseRelativeLuminanceTarget(const YamlObject &tuningData);
 	void parseConstraint(const YamlObject &modeDict, int32_t id);
 	int parseConstraintModes(const YamlObject &tuningData);
 	int parseExposureModes(const YamlObject &tuningData);
@@ -92,10 +98,12 @@  private:
 				   double gain);
 	utils::Duration filterExposure(utils::Duration exposureValue);
 
+	utils::Duration filteredExposure_;
+	mutable bool luxWarningEnabled_;
 	double exposureCompensation_;
+	Pwl relativeLuminanceTarget_;
 	uint64_t frameCount_;
-	utils::Duration filteredExposure_;
-	double relativeLuminanceTarget_;
+	unsigned int lux_;
 
 	std::vector<AgcConstraint> additionalConstraints_;
 	std::map<int32_t, std::vector<AgcConstraint>> constraintModes_;
diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp
index f5a3c917cb69..1ecaff680978 100644
--- a/src/ipa/rkisp1/algorithms/agc.cpp
+++ b/src/ipa/rkisp1/algorithms/agc.cpp
@@ -618,6 +618,7 @@  void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
 		effectiveExposureValue *= frameContext.agc.quantizationGain;
 
 	setExposureCompensation(pow(2.0, frameContext.agc.exposureValue));
+	setLux(frameContext.lux.lux);
 
 	utils::Duration newExposureTime;
 	double aGain, qGain, dGain;