| Message ID | 20261029125844.170324-7-barnabas.pocze@ideasonboard.com |
|---|---|
| State | New |
| Headers | show |
| Series |
|
| Related | show |
Hi Barnabás On Thu, Oct 29, 2026 at 01:58:26PM +0100, Barnabás Pőcze wrote: > Move the tuning file parsing out of `AgcMeanLuminance`, and provide the > configuration via the constructor. This allows `AgcAlgorithm` to inspect > the configuration, specifically it can check if any exposure modes have > been defined. > > Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> > --- > src/ipa/libipa/agc.cpp | 254 +++++++++++++++++++++++- > src/ipa/libipa/agc_mean_luminance.cpp | 268 ++------------------------ > src/ipa/libipa/agc_mean_luminance.h | 22 +-- > 3 files changed, 275 insertions(+), 269 deletions(-) > > diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp > index 7438825d30..54aa8207d4 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,196 @@ 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. > + */ > +static constexpr double kDefaultRelativeLuminanceTarget = 0.16; > + > +int parseRelativeLuminanceTarget(AgcMeanLuminance::Configuration &config, const ValueNode &tuningData) > +{ > + auto &target = tuningData["relativeLuminanceTarget"]; > + 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 &tuningData) > +{ > + const ValueNode &constraintModes = tuningData[controls::AeConstraintMode.name()]; > + if (constraintModes.isDictionary()) { > + 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; > + } > + } > + > + /* > + * 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::AeConstraintModeNormal].push_back( > + std::move(constraint)); > + } > + > + return 0; > +} > + > +int parseExposureModes(AgcMeanLuminance::Configuration &config, const ValueNode &tuningData) > +{ > + const ValueNode &exposureModes = tuningData[controls::AeExposureMode.name()]; > + if (exposureModes.isDictionary()) { > + 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)); > + } > + } > + > + /* > + * 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::AeExposureModeNormal, > + std::vector<ExposureModeHelper::Stage>{}); > + > + return 0; > +} > + > +int parseTuningData(AgcMeanLuminance::Configuration &config, const ValueNode &tuningData) > +{ > + int ret; > + > + ret = parseRelativeLuminanceTarget(config, tuningData); > + if (ret) > + return ret; > + > + ret = parseConstraintModes(config, tuningData); > + if (ret) > + return ret; > + > + return parseExposureModes(config, tuningData); > +} > + > +} /* namespace */ > + > /** > * \class AgcAlgorithm > * \brief libIPA LSC algorithm algorithm > @@ -238,6 +430,57 @@ namespace agc { > * virtual function, they should simply call the identically named member > * function of AgcAlgorithm. > * > + * When a CameraSensorHelper is provided, AgcMeanLuminance is used and support for > + * exposure (controls::AeExposureMode) and constraint (controls::AeConstraintMode) > + * modes, and specifying the default luminance target is available via the tuning data. > + * > + * For controls::AeConstraintMode, the dictionary under the key "AeConstraintMode" > + * is parsed: per-mode setting dictionaries with the key being a value from > + * 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: > + * Normal: > + * lower: > + * qLo: 0.98 > + * qHi: 1.0 > + * yTarget: 0.5 > + * Highlight: > + * 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 controls::AeExposureMode, the dictionary under the key AeExposureMode > + * is parsed: per-mode setting dictionaries with the key being a value from > + * 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: > + * Normal: > + * exposureTime: [ 100, 10000, 30000, 60000, 120000 ] > + * gain: [ 2.0, 4.0, 6.0, 8.0, 10.0 ] > + * Short: > + * exposureTime: [ 100, 10000, 30000, 60000, 120000 ] > + * gain: [ 2.0, 4.0, 6.0, 8.0, 10.0 ] > + * > + * \endcode > + * > * \todo DigitalGain, DigitalGainMode > * \todo Expand documentation > */ > @@ -298,9 +541,8 @@ namespace agc { > * in ConfigurationParams::ctrlMap. > * > * If a CameraSensorHelper is provided in \a sensor, an instance of > - * AgcMeanLuminance is instantiated and the tuning data is loaded by > - * calling AgcMeanLuminance::parseTuningData(). Otherwise an instance > - * of AgcMSV is instantiated, and in this case the tuning data is ignored. > + * AgcMeanLuminance is instantiated. Otherwise an instance of AgcMSV > + * is instantiated, and in this case the tuning data is ignored. > * > * \return 0 on success, or a negative error code > * > @@ -310,10 +552,12 @@ 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 agcConfig; > + int ret = parseTuningData(agcConfig, tuningData); > if (ret) > return ret; > + > + impl_.emplace<AgcMeanLuminance>(std::move(agcConfig)); > } else { > impl_.emplace<AgcMSV>(); > } > diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp > index 4c47770424..4f2b5edef6 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 > * > @@ -148,17 +136,13 @@ static constexpr unsigned int kDefaultLuxLevel = 500; > * section of a histogram to a target value, where the target and the boundaries > * of the section of the histogram used in the calculation are taken from the > * values defined for the currently configured AeConstraintMode within the > - * tuning data. This class provides a helper function to parse those tuning data > - * to discover the constraints, and so requires a specific format for those > - * data which is described in \ref parseTuningData(). The gain from the first > - * stage is then clamped to the gain from this stage. > + * tuning data. The gain from the first stage is then clamped to the gain from > + * this stage. > * > * The final gain is used to adjust the effective exposure value of the image, > * and that new exposure value is divided into exposure time, analogue gain and > * digital gain according to the selected AeExposureMode. This class uses the > - * \ref ExposureModeHelper class to assist in that division, and expects the > - * data needed to initialise that class to be present in tuning data in a > - * format described in \ref parseTuningData(). > + * \ref ExposureModeHelper class to assist in that division. > * > * In order to be able to use this algorithm an IPA module needs to be able to > * do the following: > @@ -170,175 +154,23 @@ static constexpr unsigned int kDefaultLuxLevel = 500; > * > * IPA modules that want to use this class to implement their AEGC algorithm > * should derive AgcMeanLuminance::Traits and override the necessary functions. > - * The users must call parseTuningData() in init(), and must also call configure() > - * and setLimits() in configure(). They may then use calculateNewEv() in process(). > - * If the limits passed to setLimits() change for any reason (for example, in > - * response to a FrameDurationLimit control being passed in queueRequest()) then > - * setLimits() must be called again with the new values. > + * The users must call configure() and setLimits() in configure(). They may > + * then use calculateNewEv() in process(). If the limits passed to setLimits() > + * change for any reason (for example, in response to a FrameDurationLimit control > + * being passed in queueRequest()) then 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)) > { > } > > 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::AeConstraintModeNormal].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::AeExposureModeNormal, > - std::vector<ExposureModeHelper::Stage>{}); > - > - return 0; > -} > - > /** > * \brief Configure the exposure mode helpers > * \param[in] lineDuration The sensor line length > @@ -358,76 +190,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: > - * Normal: > - * lower: > - * qLo: 0.98 > - * qHi: 1.0 > - * yTarget: 0.5 > - * Highlight: > - * 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: > - * Normal: > - * exposureTime: [ 100, 10000, 30000, 60000, 120000 ] > - * gain: [ 2.0, 4.0, 6.0, 8.0, 10.0 ] > - * Short: > - * 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 > @@ -452,12 +214,12 @@ void AgcMeanLuminance::setLimits(utils::Duration minExposureTime, > > /** > * \fn AgcMeanLuminance::constraintModes() > - * \brief Get the constraint modes that have been parsed from tuning data > + * \brief Get the available constraint modes > */ > > /** > * \fn AgcMeanLuminance::exposureModeHelpers() > - * \brief Get the ExposureModeHelpers that have been parsed from tuning data > + * \brief Get the available ExposureModeHelpers > */ > > /** > 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 ¶ms, 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_; Not sure if this move is intentional, but it's just a detail Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> > std::map<int32_t, std::vector<AgcConstraint>> constraintModes_; > std::map<int32_t, ExposureModeHelper> exposureModeHelpers_; > }; > -- > 2.56.0 >
diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 7438825d30..54aa8207d4 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,196 @@ 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. + */ +static constexpr double kDefaultRelativeLuminanceTarget = 0.16; + +int parseRelativeLuminanceTarget(AgcMeanLuminance::Configuration &config, const ValueNode &tuningData) +{ + auto &target = tuningData["relativeLuminanceTarget"]; + 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 &tuningData) +{ + const ValueNode &constraintModes = tuningData[controls::AeConstraintMode.name()]; + if (constraintModes.isDictionary()) { + 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; + } + } + + /* + * 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::AeConstraintModeNormal].push_back( + std::move(constraint)); + } + + return 0; +} + +int parseExposureModes(AgcMeanLuminance::Configuration &config, const ValueNode &tuningData) +{ + const ValueNode &exposureModes = tuningData[controls::AeExposureMode.name()]; + if (exposureModes.isDictionary()) { + 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)); + } + } + + /* + * 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::AeExposureModeNormal, + std::vector<ExposureModeHelper::Stage>{}); + + return 0; +} + +int parseTuningData(AgcMeanLuminance::Configuration &config, const ValueNode &tuningData) +{ + int ret; + + ret = parseRelativeLuminanceTarget(config, tuningData); + if (ret) + return ret; + + ret = parseConstraintModes(config, tuningData); + if (ret) + return ret; + + return parseExposureModes(config, tuningData); +} + +} /* namespace */ + /** * \class AgcAlgorithm * \brief libIPA LSC algorithm algorithm @@ -238,6 +430,57 @@ namespace agc { * virtual function, they should simply call the identically named member * function of AgcAlgorithm. * + * When a CameraSensorHelper is provided, AgcMeanLuminance is used and support for + * exposure (controls::AeExposureMode) and constraint (controls::AeConstraintMode) + * modes, and specifying the default luminance target is available via the tuning data. + * + * For controls::AeConstraintMode, the dictionary under the key "AeConstraintMode" + * is parsed: per-mode setting dictionaries with the key being a value from + * 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: + * Normal: + * lower: + * qLo: 0.98 + * qHi: 1.0 + * yTarget: 0.5 + * Highlight: + * 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 controls::AeExposureMode, the dictionary under the key AeExposureMode + * is parsed: per-mode setting dictionaries with the key being a value from + * 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: + * Normal: + * exposureTime: [ 100, 10000, 30000, 60000, 120000 ] + * gain: [ 2.0, 4.0, 6.0, 8.0, 10.0 ] + * Short: + * exposureTime: [ 100, 10000, 30000, 60000, 120000 ] + * gain: [ 2.0, 4.0, 6.0, 8.0, 10.0 ] + * + * \endcode + * * \todo DigitalGain, DigitalGainMode * \todo Expand documentation */ @@ -298,9 +541,8 @@ namespace agc { * in ConfigurationParams::ctrlMap. * * If a CameraSensorHelper is provided in \a sensor, an instance of - * AgcMeanLuminance is instantiated and the tuning data is loaded by - * calling AgcMeanLuminance::parseTuningData(). Otherwise an instance - * of AgcMSV is instantiated, and in this case the tuning data is ignored. + * AgcMeanLuminance is instantiated. Otherwise an instance of AgcMSV + * is instantiated, and in this case the tuning data is ignored. * * \return 0 on success, or a negative error code * @@ -310,10 +552,12 @@ 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 agcConfig; + int ret = parseTuningData(agcConfig, tuningData); if (ret) return ret; + + impl_.emplace<AgcMeanLuminance>(std::move(agcConfig)); } else { impl_.emplace<AgcMSV>(); } diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp index 4c47770424..4f2b5edef6 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 * @@ -148,17 +136,13 @@ static constexpr unsigned int kDefaultLuxLevel = 500; * section of a histogram to a target value, where the target and the boundaries * of the section of the histogram used in the calculation are taken from the * values defined for the currently configured AeConstraintMode within the - * tuning data. This class provides a helper function to parse those tuning data - * to discover the constraints, and so requires a specific format for those - * data which is described in \ref parseTuningData(). The gain from the first - * stage is then clamped to the gain from this stage. + * tuning data. The gain from the first stage is then clamped to the gain from + * this stage. * * The final gain is used to adjust the effective exposure value of the image, * and that new exposure value is divided into exposure time, analogue gain and * digital gain according to the selected AeExposureMode. This class uses the - * \ref ExposureModeHelper class to assist in that division, and expects the - * data needed to initialise that class to be present in tuning data in a - * format described in \ref parseTuningData(). + * \ref ExposureModeHelper class to assist in that division. * * In order to be able to use this algorithm an IPA module needs to be able to * do the following: @@ -170,175 +154,23 @@ static constexpr unsigned int kDefaultLuxLevel = 500; * * IPA modules that want to use this class to implement their AEGC algorithm * should derive AgcMeanLuminance::Traits and override the necessary functions. - * The users must call parseTuningData() in init(), and must also call configure() - * and setLimits() in configure(). They may then use calculateNewEv() in process(). - * If the limits passed to setLimits() change for any reason (for example, in - * response to a FrameDurationLimit control being passed in queueRequest()) then - * setLimits() must be called again with the new values. + * The users must call configure() and setLimits() in configure(). They may + * then use calculateNewEv() in process(). If the limits passed to setLimits() + * change for any reason (for example, in response to a FrameDurationLimit control + * being passed in queueRequest()) then 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)) { } 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::AeConstraintModeNormal].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::AeExposureModeNormal, - std::vector<ExposureModeHelper::Stage>{}); - - return 0; -} - /** * \brief Configure the exposure mode helpers * \param[in] lineDuration The sensor line length @@ -358,76 +190,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: - * Normal: - * lower: - * qLo: 0.98 - * qHi: 1.0 - * yTarget: 0.5 - * Highlight: - * 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: - * Normal: - * exposureTime: [ 100, 10000, 30000, 60000, 120000 ] - * gain: [ 2.0, 4.0, 6.0, 8.0, 10.0 ] - * Short: - * 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 @@ -452,12 +214,12 @@ void AgcMeanLuminance::setLimits(utils::Duration minExposureTime, /** * \fn AgcMeanLuminance::constraintModes() - * \brief Get the constraint modes that have been parsed from tuning data + * \brief Get the available constraint modes */ /** * \fn AgcMeanLuminance::exposureModeHelpers() - * \brief Get the ExposureModeHelpers that have been parsed from tuning data + * \brief Get the available ExposureModeHelpers */ /** 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 ¶ms, 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_; };
Move the tuning file parsing out of `AgcMeanLuminance`, and provide the configuration via the constructor. This allows `AgcAlgorithm` to inspect the configuration, specifically it can check if any exposure modes have been defined. Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> --- src/ipa/libipa/agc.cpp | 254 +++++++++++++++++++++++- src/ipa/libipa/agc_mean_luminance.cpp | 268 ++------------------------ src/ipa/libipa/agc_mean_luminance.h | 22 +-- 3 files changed, 275 insertions(+), 269 deletions(-)