| Message ID | 20261109094954.341480-5-barnabas.pocze@ideasonboard.com |
|---|---|
| State | New |
| Headers | show |
| Series |
|
| Related | show |
Hi Barnabás, Quoting Barnabás Pőcze (2026-11-09 10:49:39) > 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. I'm not fully convinced that AgcAlgorithm should parse these, as the parameters are all fully AgcMeanLuminance specific. I didn't try it out but am wondering why these functions can't be static members of AgcMeanLuminance. So that the config parsing becomes: AgcMeanLuminance::Configuration agcConfig; int ret = AgcMeanLuminance::parseTuningData(agcConfig, tuningData); if (ret) return ret; impl_.emplace<AgcMeanLuminance>(std::move(agcConfig)); I didn't fully check, but wouldn't that also work with the changes from the next patch? Afaik these params will never be applied to AgcMSV so why the central parsing? Best regards, Stefan > > Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> > Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> > --- > src/ipa/libipa/agc.cpp | 305 +++++++++++++++++++++++- > src/ipa/libipa/agc_mean_luminance.cpp | 318 ++------------------------ > src/ipa/libipa/agc_mean_luminance.h | 22 +- > 3 files changed, 326 insertions(+), 319 deletions(-) > > diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp > index 182dd7380e..38bf90879d 100644 > --- a/src/ipa/libipa/agc.cpp > +++ b/src/ipa/libipa/agc.cpp > @@ -14,6 +14,8 @@ > #include <optional> > #include <ratio> > #include <span> > +#include <string_view> > +#include <tuple> > #include <variant> > > #include <linux/v4l2-controls.h> > @@ -26,6 +28,8 @@ > > #include <libcamera/ipa/core_ipa_interface.h> > > +#include "exposure_mode_helper.h" > + > /** > * \file agc.h > * \brief libipa AGC algorithm > @@ -222,6 +226,245 @@ 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) > +{ > + static constexpr std::tuple<std::string_view, std::string_view, controls::AeConstraintModeEnum> compat[] = { > + { "ConstraintNormal", "Normal", controls::AeConstraintModeNormal }, > + { "ConstraintHighlight", "Highlight", controls::AeConstraintModeHighlight }, > + { "ConstraintShadows", "Shadows", controls::AeConstraintModeShadows }, > + { "ConstraintCustom", "Custom", controls::AeConstraintModeCustom }, > + }; > + > + const ValueNode &constraintModes = tuningData[controls::AeConstraintMode.name()]; > + if (constraintModes.isDictionary()) { > + for (const auto &[modeName, modeDict] : constraintModes.asDict()) { > + std::optional<controls::AeConstraintModeEnum> modeValue; > + > + auto it = controls::AeConstraintModeNameValueMap.find(modeName); > + if (it == controls::AeConstraintModeNameValueMap.end()) { > + for (const auto &[oldName, newName, v] : compat) { > + if (modeName != oldName) > + continue; > + > + modeValue = v; > + > + LOG(Agc, Warning) > + << "Constraint mode '" << oldName << "' has been renamed to '" > + << newName << "', please fix the tuning file"; > + break; > + } > + } else { > + modeValue = static_cast<controls::AeConstraintModeEnum>(it->second); > + } > + > + if (!modeValue) { > + 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, *modeValue); > + 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) > +{ > + static constexpr std::tuple<std::string_view, std::string_view, controls::AeExposureModeEnum> compat[] = { > + { "ExposureNormal", "Normal", controls::AeExposureModeNormal }, > + { "ExposureShort", "Short", controls::AeExposureModeShort }, > + { "ExposureLong", "Long", controls::AeExposureModeLong }, > + { "ExposureCustom", "Custom", controls::AeExposureModeCustom }, > + }; > + > + const ValueNode &exposureModes = tuningData[controls::AeExposureMode.name()]; > + if (exposureModes.isDictionary()) { > + for (const auto &[modeName, modeValues] : exposureModes.asDict()) { > + std::optional<controls::AeExposureModeEnum> modeValue; > + > + auto it = controls::AeExposureModeNameValueMap.find(modeName); > + if (it == controls::AeExposureModeNameValueMap.end()) { > + for (const auto &[oldName, newName, v] : compat) { > + if (modeName != oldName) > + continue; > + > + modeValue = v; > + > + LOG(Agc, Warning) > + << "Exposure mode '" << oldName << "' has been renamed to '" > + << newName << "', please fix the tuning file"; > + break; > + } > + } else { > + modeValue = static_cast<controls::AeExposureModeEnum>(it->second); > + } > + > + if (!modeValue) { > + 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(*modeValue, 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 +481,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 +592,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 +603,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 a763a3b94d..5b700e73d9 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,225 +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) > -{ > - static constexpr std::tuple<std::string_view, std::string_view, controls::AeConstraintModeEnum> compat[] = { > - { "ConstraintNormal", "Normal", controls::AeConstraintModeNormal }, > - { "ConstraintHighlight", "Highlight", controls::AeConstraintModeHighlight }, > - { "ConstraintShadows", "Shadows", controls::AeConstraintModeShadows }, > - { "ConstraintCustom", "Custom", controls::AeConstraintModeCustom }, > - }; > - > - const ValueNode &constraintModes = tuningData[controls::AeConstraintMode.name()]; > - if (constraintModes.isDictionary()) { > - for (const auto &[modeName, modeDict] : constraintModes.asDict()) { > - std::optional<controls::AeConstraintModeEnum> modeValue; > - > - auto it = AeConstraintModeNameValueMap.find(modeName); > - if (it == AeConstraintModeNameValueMap.end()) { > - for (const auto &[oldName, newName, v] : compat) { > - if (modeName != oldName) > - continue; > - > - modeValue = v; > - > - LOG(AgcMeanLuminance, Warning) > - << "Constraint mode '" << oldName << "' has been renamed to '" > - << newName << "', please fix the tuning file"; > - break; > - } > - } else { > - modeValue = static_cast<controls::AeConstraintModeEnum>(it->second); > - } > - > - if (!modeValue) { > - 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, *modeValue); > - 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) > -{ > - static constexpr std::tuple<std::string_view, std::string_view, controls::AeExposureModeEnum> compat[] = { > - { "ExposureNormal", "Normal", controls::AeExposureModeNormal }, > - { "ExposureShort", "Short", controls::AeExposureModeShort }, > - { "ExposureLong", "Long", controls::AeExposureModeLong }, > - { "ExposureCustom", "Custom", controls::AeExposureModeCustom }, > - }; > - > - const ValueNode &exposureModes = tuningData[controls::AeExposureMode.name()]; > - if (exposureModes.isDictionary()) { > - for (const auto &[modeName, modeValues] : exposureModes.asDict()) { > - std::optional<controls::AeExposureModeEnum> modeValue; > - > - auto it = AeExposureModeNameValueMap.find(modeName); > - if (it == AeExposureModeNameValueMap.end()) { > - for (const auto &[oldName, newName, v] : compat) { > - if (modeName != oldName) > - continue; > - > - modeValue = v; > - > - LOG(AgcMeanLuminance, Warning) > - << "Exposure mode '" << oldName << "' has been renamed to '" > - << newName << "', please fix the tuning file"; > - break; > - } > - } else { > - modeValue = static_cast<controls::AeExposureModeEnum>(it->second); > - } > - > - if (!modeValue) { > - 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(*modeValue, 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 > @@ -408,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 > @@ -502,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_; > }; > -- > 2.56.0 >
2026. 11. 09. 13:49 keltezéssel, Stefan Klug írta: > Hi Barnabás, > > Quoting Barnabás Pőcze (2026-11-09 10:49:39) >> 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. > > I'm not fully convinced that AgcAlgorithm should parse these, as the > parameters are all fully AgcMeanLuminance specific. I didn't try it out > but am wondering why these functions can't be static members of > AgcMeanLuminance. So that the config parsing becomes: > > > AgcMeanLuminance::Configuration agcConfig; > int ret = AgcMeanLuminance::parseTuningData(agcConfig, tuningData); > if (ret) > return ret; > > impl_.emplace<AgcMeanLuminance>(std::move(agcConfig)); > > I didn't fully check, but wouldn't that also work with the changes from > the next patch? > > Afaik these params will never be applied to AgcMSV so why the central > parsing? The main reason is that it needs to be known whether the tuning file contains any exposure modes. What I wanted to avoid is adding some kind of ad-hoc flag to signal that. So by moving everything here, and moving the fallback handling to a separate function, this is easily doable. As an alternative, I can add the separate static parser functions, and then move the fallback logic to the constructor. Thoughts? > > Best regards, > Stefan > >> >> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> >> Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> >> --- >> src/ipa/libipa/agc.cpp | 305 +++++++++++++++++++++++- >> src/ipa/libipa/agc_mean_luminance.cpp | 318 ++------------------------ >> src/ipa/libipa/agc_mean_luminance.h | 22 +- >> 3 files changed, 326 insertions(+), 319 deletions(-) >> >> diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp >> index 182dd7380e..38bf90879d 100644 >> --- a/src/ipa/libipa/agc.cpp >> +++ b/src/ipa/libipa/agc.cpp >> @@ -14,6 +14,8 @@ >> #include <optional> >> #include <ratio> >> #include <span> >> +#include <string_view> >> +#include <tuple> >> #include <variant> >> >> #include <linux/v4l2-controls.h> >> @@ -26,6 +28,8 @@ >> >> #include <libcamera/ipa/core_ipa_interface.h> >> >> +#include "exposure_mode_helper.h" >> + >> /** >> * \file agc.h >> * \brief libipa AGC algorithm >> @@ -222,6 +226,245 @@ 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) >> +{ >> + static constexpr std::tuple<std::string_view, std::string_view, controls::AeConstraintModeEnum> compat[] = { >> + { "ConstraintNormal", "Normal", controls::AeConstraintModeNormal }, >> + { "ConstraintHighlight", "Highlight", controls::AeConstraintModeHighlight }, >> + { "ConstraintShadows", "Shadows", controls::AeConstraintModeShadows }, >> + { "ConstraintCustom", "Custom", controls::AeConstraintModeCustom }, >> + }; >> + >> + const ValueNode &constraintModes = tuningData[controls::AeConstraintMode.name()]; >> + if (constraintModes.isDictionary()) { >> + for (const auto &[modeName, modeDict] : constraintModes.asDict()) { >> + std::optional<controls::AeConstraintModeEnum> modeValue; >> + >> + auto it = controls::AeConstraintModeNameValueMap.find(modeName); >> + if (it == controls::AeConstraintModeNameValueMap.end()) { >> + for (const auto &[oldName, newName, v] : compat) { >> + if (modeName != oldName) >> + continue; >> + >> + modeValue = v; >> + >> + LOG(Agc, Warning) >> + << "Constraint mode '" << oldName << "' has been renamed to '" >> + << newName << "', please fix the tuning file"; >> + break; >> + } >> + } else { >> + modeValue = static_cast<controls::AeConstraintModeEnum>(it->second); >> + } >> + >> + if (!modeValue) { >> + 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, *modeValue); >> + 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) >> +{ >> + static constexpr std::tuple<std::string_view, std::string_view, controls::AeExposureModeEnum> compat[] = { >> + { "ExposureNormal", "Normal", controls::AeExposureModeNormal }, >> + { "ExposureShort", "Short", controls::AeExposureModeShort }, >> + { "ExposureLong", "Long", controls::AeExposureModeLong }, >> + { "ExposureCustom", "Custom", controls::AeExposureModeCustom }, >> + }; >> + >> + const ValueNode &exposureModes = tuningData[controls::AeExposureMode.name()]; >> + if (exposureModes.isDictionary()) { >> + for (const auto &[modeName, modeValues] : exposureModes.asDict()) { >> + std::optional<controls::AeExposureModeEnum> modeValue; >> + >> + auto it = controls::AeExposureModeNameValueMap.find(modeName); >> + if (it == controls::AeExposureModeNameValueMap.end()) { >> + for (const auto &[oldName, newName, v] : compat) { >> + if (modeName != oldName) >> + continue; >> + >> + modeValue = v; >> + >> + LOG(Agc, Warning) >> + << "Exposure mode '" << oldName << "' has been renamed to '" >> + << newName << "', please fix the tuning file"; >> + break; >> + } >> + } else { >> + modeValue = static_cast<controls::AeExposureModeEnum>(it->second); >> + } >> + >> + if (!modeValue) { >> + 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(*modeValue, 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 +481,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 +592,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 +603,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 a763a3b94d..5b700e73d9 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,225 +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) >> -{ >> - static constexpr std::tuple<std::string_view, std::string_view, controls::AeConstraintModeEnum> compat[] = { >> - { "ConstraintNormal", "Normal", controls::AeConstraintModeNormal }, >> - { "ConstraintHighlight", "Highlight", controls::AeConstraintModeHighlight }, >> - { "ConstraintShadows", "Shadows", controls::AeConstraintModeShadows }, >> - { "ConstraintCustom", "Custom", controls::AeConstraintModeCustom }, >> - }; >> - >> - const ValueNode &constraintModes = tuningData[controls::AeConstraintMode.name()]; >> - if (constraintModes.isDictionary()) { >> - for (const auto &[modeName, modeDict] : constraintModes.asDict()) { >> - std::optional<controls::AeConstraintModeEnum> modeValue; >> - >> - auto it = AeConstraintModeNameValueMap.find(modeName); >> - if (it == AeConstraintModeNameValueMap.end()) { >> - for (const auto &[oldName, newName, v] : compat) { >> - if (modeName != oldName) >> - continue; >> - >> - modeValue = v; >> - >> - LOG(AgcMeanLuminance, Warning) >> - << "Constraint mode '" << oldName << "' has been renamed to '" >> - << newName << "', please fix the tuning file"; >> - break; >> - } >> - } else { >> - modeValue = static_cast<controls::AeConstraintModeEnum>(it->second); >> - } >> - >> - if (!modeValue) { >> - 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, *modeValue); >> - 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) >> -{ >> - static constexpr std::tuple<std::string_view, std::string_view, controls::AeExposureModeEnum> compat[] = { >> - { "ExposureNormal", "Normal", controls::AeExposureModeNormal }, >> - { "ExposureShort", "Short", controls::AeExposureModeShort }, >> - { "ExposureLong", "Long", controls::AeExposureModeLong }, >> - { "ExposureCustom", "Custom", controls::AeExposureModeCustom }, >> - }; >> - >> - const ValueNode &exposureModes = tuningData[controls::AeExposureMode.name()]; >> - if (exposureModes.isDictionary()) { >> - for (const auto &[modeName, modeValues] : exposureModes.asDict()) { >> - std::optional<controls::AeExposureModeEnum> modeValue; >> - >> - auto it = AeExposureModeNameValueMap.find(modeName); >> - if (it == AeExposureModeNameValueMap.end()) { >> - for (const auto &[oldName, newName, v] : compat) { >> - if (modeName != oldName) >> - continue; >> - >> - modeValue = v; >> - >> - LOG(AgcMeanLuminance, Warning) >> - << "Exposure mode '" << oldName << "' has been renamed to '" >> - << newName << "', please fix the tuning file"; >> - break; >> - } >> - } else { >> - modeValue = static_cast<controls::AeExposureModeEnum>(it->second); >> - } >> - >> - if (!modeValue) { >> - 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(*modeValue, 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 >> @@ -408,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 >> @@ -502,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_; >> }; >> -- >> 2.56.0 >>
Quoting Barnabás Pőcze (2026-11-09 14:18:50) > 2026. 11. 09. 13:49 keltezéssel, Stefan Klug írta: > > Hi Barnabás, > > > > Quoting Barnabás Pőcze (2026-11-09 10:49:39) > >> 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. > > > > I'm not fully convinced that AgcAlgorithm should parse these, as the > > parameters are all fully AgcMeanLuminance specific. I didn't try it out > > but am wondering why these functions can't be static members of > > AgcMeanLuminance. So that the config parsing becomes: > > > > > > AgcMeanLuminance::Configuration agcConfig; > > int ret = AgcMeanLuminance::parseTuningData(agcConfig, tuningData); > > if (ret) > > return ret; > > > > impl_.emplace<AgcMeanLuminance>(std::move(agcConfig)); > > > > I didn't fully check, but wouldn't that also work with the changes from > > the next patch? > > > > Afaik these params will never be applied to AgcMSV so why the central > > parsing? > > The main reason is that it needs to be known whether the tuning file contains > any exposure modes. What I wanted to avoid is adding some kind of ad-hoc flag > to signal that. So by moving everything here, and moving the fallback handling > to a separate function, this is easily doable. > > As an alternative, I can add the separate static parser functions, and then > move the fallback logic to the constructor. Thoughts? Yes, or keep addFallback in AgcAlgorithm. I don't have any particular preference, but it seems a reasonable general behavior. So I currently don't see a use case that requires it to be moved out of AgcMeanLuminance. Or am I missing something bigger that benefits from having that logic inside AgcAlgorithm? Best regards, Stefan > > > > > > Best regards, > > Stefan > > > >> > >> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> > >> Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> > >> --- > >> src/ipa/libipa/agc.cpp | 305 +++++++++++++++++++++++- > >> src/ipa/libipa/agc_mean_luminance.cpp | 318 ++------------------------ > >> src/ipa/libipa/agc_mean_luminance.h | 22 +- > >> 3 files changed, 326 insertions(+), 319 deletions(-) > >> > >> diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp > >> index 182dd7380e..38bf90879d 100644 > >> --- a/src/ipa/libipa/agc.cpp > >> +++ b/src/ipa/libipa/agc.cpp > >> @@ -14,6 +14,8 @@ > >> #include <optional> > >> #include <ratio> > >> #include <span> > >> +#include <string_view> > >> +#include <tuple> > >> #include <variant> > >> > >> #include <linux/v4l2-controls.h> > >> @@ -26,6 +28,8 @@ > >> > >> #include <libcamera/ipa/core_ipa_interface.h> > >> > >> +#include "exposure_mode_helper.h" > >> + > >> /** > >> * \file agc.h > >> * \brief libipa AGC algorithm > >> @@ -222,6 +226,245 @@ 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) > >> +{ > >> + static constexpr std::tuple<std::string_view, std::string_view, controls::AeConstraintModeEnum> compat[] = { > >> + { "ConstraintNormal", "Normal", controls::AeConstraintModeNormal }, > >> + { "ConstraintHighlight", "Highlight", controls::AeConstraintModeHighlight }, > >> + { "ConstraintShadows", "Shadows", controls::AeConstraintModeShadows }, > >> + { "ConstraintCustom", "Custom", controls::AeConstraintModeCustom }, > >> + }; > >> + > >> + const ValueNode &constraintModes = tuningData[controls::AeConstraintMode.name()]; > >> + if (constraintModes.isDictionary()) { > >> + for (const auto &[modeName, modeDict] : constraintModes.asDict()) { > >> + std::optional<controls::AeConstraintModeEnum> modeValue; > >> + > >> + auto it = controls::AeConstraintModeNameValueMap.find(modeName); > >> + if (it == controls::AeConstraintModeNameValueMap.end()) { > >> + for (const auto &[oldName, newName, v] : compat) { > >> + if (modeName != oldName) > >> + continue; > >> + > >> + modeValue = v; > >> + > >> + LOG(Agc, Warning) > >> + << "Constraint mode '" << oldName << "' has been renamed to '" > >> + << newName << "', please fix the tuning file"; > >> + break; > >> + } > >> + } else { > >> + modeValue = static_cast<controls::AeConstraintModeEnum>(it->second); > >> + } > >> + > >> + if (!modeValue) { > >> + 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, *modeValue); > >> + 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) > >> +{ > >> + static constexpr std::tuple<std::string_view, std::string_view, controls::AeExposureModeEnum> compat[] = { > >> + { "ExposureNormal", "Normal", controls::AeExposureModeNormal }, > >> + { "ExposureShort", "Short", controls::AeExposureModeShort }, > >> + { "ExposureLong", "Long", controls::AeExposureModeLong }, > >> + { "ExposureCustom", "Custom", controls::AeExposureModeCustom }, > >> + }; > >> + > >> + const ValueNode &exposureModes = tuningData[controls::AeExposureMode.name()]; > >> + if (exposureModes.isDictionary()) { > >> + for (const auto &[modeName, modeValues] : exposureModes.asDict()) { > >> + std::optional<controls::AeExposureModeEnum> modeValue; > >> + > >> + auto it = controls::AeExposureModeNameValueMap.find(modeName); > >> + if (it == controls::AeExposureModeNameValueMap.end()) { > >> + for (const auto &[oldName, newName, v] : compat) { > >> + if (modeName != oldName) > >> + continue; > >> + > >> + modeValue = v; > >> + > >> + LOG(Agc, Warning) > >> + << "Exposure mode '" << oldName << "' has been renamed to '" > >> + << newName << "', please fix the tuning file"; > >> + break; > >> + } > >> + } else { > >> + modeValue = static_cast<controls::AeExposureModeEnum>(it->second); > >> + } > >> + > >> + if (!modeValue) { > >> + 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(*modeValue, 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 +481,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 +592,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 +603,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 a763a3b94d..5b700e73d9 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,225 +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) > >> -{ > >> - static constexpr std::tuple<std::string_view, std::string_view, controls::AeConstraintModeEnum> compat[] = { > >> - { "ConstraintNormal", "Normal", controls::AeConstraintModeNormal }, > >> - { "ConstraintHighlight", "Highlight", controls::AeConstraintModeHighlight }, > >> - { "ConstraintShadows", "Shadows", controls::AeConstraintModeShadows }, > >> - { "ConstraintCustom", "Custom", controls::AeConstraintModeCustom }, > >> - }; > >> - > >> - const ValueNode &constraintModes = tuningData[controls::AeConstraintMode.name()]; > >> - if (constraintModes.isDictionary()) { > >> - for (const auto &[modeName, modeDict] : constraintModes.asDict()) { > >> - std::optional<controls::AeConstraintModeEnum> modeValue; > >> - > >> - auto it = AeConstraintModeNameValueMap.find(modeName); > >> - if (it == AeConstraintModeNameValueMap.end()) { > >> - for (const auto &[oldName, newName, v] : compat) { > >> - if (modeName != oldName) > >> - continue; > >> - > >> - modeValue = v; > >> - > >> - LOG(AgcMeanLuminance, Warning) > >> - << "Constraint mode '" << oldName << "' has been renamed to '" > >> - << newName << "', please fix the tuning file"; > >> - break; > >> - } > >> - } else { > >> - modeValue = static_cast<controls::AeConstraintModeEnum>(it->second); > >> - } > >> - > >> - if (!modeValue) { > >> - 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, *modeValue); > >> - 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) > >> -{ > >> - static constexpr std::tuple<std::string_view, std::string_view, controls::AeExposureModeEnum> compat[] = { > >> - { "ExposureNormal", "Normal", controls::AeExposureModeNormal }, > >> - { "ExposureShort", "Short", controls::AeExposureModeShort }, > >> - { "ExposureLong", "Long", controls::AeExposureModeLong }, > >> - { "ExposureCustom", "Custom", controls::AeExposureModeCustom }, > >> - }; > >> - > >> - const ValueNode &exposureModes = tuningData[controls::AeExposureMode.name()]; > >> - if (exposureModes.isDictionary()) { > >> - for (const auto &[modeName, modeValues] : exposureModes.asDict()) { > >> - std::optional<controls::AeExposureModeEnum> modeValue; > >> - > >> - auto it = AeExposureModeNameValueMap.find(modeName); > >> - if (it == AeExposureModeNameValueMap.end()) { > >> - for (const auto &[oldName, newName, v] : compat) { > >> - if (modeName != oldName) > >> - continue; > >> - > >> - modeValue = v; > >> - > >> - LOG(AgcMeanLuminance, Warning) > >> - << "Exposure mode '" << oldName << "' has been renamed to '" > >> - << newName << "', please fix the tuning file"; > >> - break; > >> - } > >> - } else { > >> - modeValue = static_cast<controls::AeExposureModeEnum>(it->second); > >> - } > >> - > >> - if (!modeValue) { > >> - 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(*modeValue, 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 > >> @@ -408,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 > >> @@ -502,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_; > >> }; > >> -- > >> 2.56.0 > >> >
diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 182dd7380e..38bf90879d 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -14,6 +14,8 @@ #include <optional> #include <ratio> #include <span> +#include <string_view> +#include <tuple> #include <variant> #include <linux/v4l2-controls.h> @@ -26,6 +28,8 @@ #include <libcamera/ipa/core_ipa_interface.h> +#include "exposure_mode_helper.h" + /** * \file agc.h * \brief libipa AGC algorithm @@ -222,6 +226,245 @@ 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) +{ + static constexpr std::tuple<std::string_view, std::string_view, controls::AeConstraintModeEnum> compat[] = { + { "ConstraintNormal", "Normal", controls::AeConstraintModeNormal }, + { "ConstraintHighlight", "Highlight", controls::AeConstraintModeHighlight }, + { "ConstraintShadows", "Shadows", controls::AeConstraintModeShadows }, + { "ConstraintCustom", "Custom", controls::AeConstraintModeCustom }, + }; + + const ValueNode &constraintModes = tuningData[controls::AeConstraintMode.name()]; + if (constraintModes.isDictionary()) { + for (const auto &[modeName, modeDict] : constraintModes.asDict()) { + std::optional<controls::AeConstraintModeEnum> modeValue; + + auto it = controls::AeConstraintModeNameValueMap.find(modeName); + if (it == controls::AeConstraintModeNameValueMap.end()) { + for (const auto &[oldName, newName, v] : compat) { + if (modeName != oldName) + continue; + + modeValue = v; + + LOG(Agc, Warning) + << "Constraint mode '" << oldName << "' has been renamed to '" + << newName << "', please fix the tuning file"; + break; + } + } else { + modeValue = static_cast<controls::AeConstraintModeEnum>(it->second); + } + + if (!modeValue) { + 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, *modeValue); + 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) +{ + static constexpr std::tuple<std::string_view, std::string_view, controls::AeExposureModeEnum> compat[] = { + { "ExposureNormal", "Normal", controls::AeExposureModeNormal }, + { "ExposureShort", "Short", controls::AeExposureModeShort }, + { "ExposureLong", "Long", controls::AeExposureModeLong }, + { "ExposureCustom", "Custom", controls::AeExposureModeCustom }, + }; + + const ValueNode &exposureModes = tuningData[controls::AeExposureMode.name()]; + if (exposureModes.isDictionary()) { + for (const auto &[modeName, modeValues] : exposureModes.asDict()) { + std::optional<controls::AeExposureModeEnum> modeValue; + + auto it = controls::AeExposureModeNameValueMap.find(modeName); + if (it == controls::AeExposureModeNameValueMap.end()) { + for (const auto &[oldName, newName, v] : compat) { + if (modeName != oldName) + continue; + + modeValue = v; + + LOG(Agc, Warning) + << "Exposure mode '" << oldName << "' has been renamed to '" + << newName << "', please fix the tuning file"; + break; + } + } else { + modeValue = static_cast<controls::AeExposureModeEnum>(it->second); + } + + if (!modeValue) { + 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(*modeValue, 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 +481,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 +592,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 +603,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 a763a3b94d..5b700e73d9 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,225 +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) -{ - static constexpr std::tuple<std::string_view, std::string_view, controls::AeConstraintModeEnum> compat[] = { - { "ConstraintNormal", "Normal", controls::AeConstraintModeNormal }, - { "ConstraintHighlight", "Highlight", controls::AeConstraintModeHighlight }, - { "ConstraintShadows", "Shadows", controls::AeConstraintModeShadows }, - { "ConstraintCustom", "Custom", controls::AeConstraintModeCustom }, - }; - - const ValueNode &constraintModes = tuningData[controls::AeConstraintMode.name()]; - if (constraintModes.isDictionary()) { - for (const auto &[modeName, modeDict] : constraintModes.asDict()) { - std::optional<controls::AeConstraintModeEnum> modeValue; - - auto it = AeConstraintModeNameValueMap.find(modeName); - if (it == AeConstraintModeNameValueMap.end()) { - for (const auto &[oldName, newName, v] : compat) { - if (modeName != oldName) - continue; - - modeValue = v; - - LOG(AgcMeanLuminance, Warning) - << "Constraint mode '" << oldName << "' has been renamed to '" - << newName << "', please fix the tuning file"; - break; - } - } else { - modeValue = static_cast<controls::AeConstraintModeEnum>(it->second); - } - - if (!modeValue) { - 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, *modeValue); - 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) -{ - static constexpr std::tuple<std::string_view, std::string_view, controls::AeExposureModeEnum> compat[] = { - { "ExposureNormal", "Normal", controls::AeExposureModeNormal }, - { "ExposureShort", "Short", controls::AeExposureModeShort }, - { "ExposureLong", "Long", controls::AeExposureModeLong }, - { "ExposureCustom", "Custom", controls::AeExposureModeCustom }, - }; - - const ValueNode &exposureModes = tuningData[controls::AeExposureMode.name()]; - if (exposureModes.isDictionary()) { - for (const auto &[modeName, modeValues] : exposureModes.asDict()) { - std::optional<controls::AeExposureModeEnum> modeValue; - - auto it = AeExposureModeNameValueMap.find(modeName); - if (it == AeExposureModeNameValueMap.end()) { - for (const auto &[oldName, newName, v] : compat) { - if (modeName != oldName) - continue; - - modeValue = v; - - LOG(AgcMeanLuminance, Warning) - << "Exposure mode '" << oldName << "' has been renamed to '" - << newName << "', please fix the tuning file"; - break; - } - } else { - modeValue = static_cast<controls::AeExposureModeEnum>(it->second); - } - - if (!modeValue) { - 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(*modeValue, 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 @@ -408,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 @@ -502,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_; };