Message ID | 20240424124917.1250837-4-dan.scally@ideasonboard.com |
---|---|
State | Superseded |
Headers | show |
Series |
|
Related | show |
Hi Dan On Wed, Apr 24, 2024 at 01:49:12PM +0100, Daniel Scally wrote: > From: Paul Elder <paul.elder@ideasonboard.com> > > Add a helper for managing exposure modes and splitting exposure times > into shutter and gain values. > > Reviewed-by: Paul Elder <paul.elder@ideasonboard.com> > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com> > Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com> > --- > Changes in v3: > > - Referred to "shutter time" instead of "shutter" > - Removed ::init() and swapped its functionality to the constructor > - Replaced the parameter to the constructor (ex-::init()) with a Span > instead of a vector. > - Lots of documentation updates and function renaming > Changes in v2: > > - Expanded the documentation > - Dropped the overloads for fixed shutter / gain - the same > functionality is instead done by setting min and max shutter and gain > to the same value > - Changed ::init() to consume a vector of pairs instead of two separate > vectors > - Reworked splitExposure() > > src/ipa/libipa/exposure_mode_helper.cpp | 246 ++++++++++++++++++++++++ > src/ipa/libipa/exposure_mode_helper.h | 53 +++++ > src/ipa/libipa/meson.build | 2 + > 3 files changed, 301 insertions(+) > create mode 100644 src/ipa/libipa/exposure_mode_helper.cpp > create mode 100644 src/ipa/libipa/exposure_mode_helper.h > > diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp > new file mode 100644 > index 00000000..47a300f3 > --- /dev/null > +++ b/src/ipa/libipa/exposure_mode_helper.cpp > @@ -0,0 +1,246 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com> > + * > + * exposure_mode_helper.cpp - Helper class that performs computations relating to exposure > + */ > +#include "exposure_mode_helper.h" > + > +#include <algorithm> > + > +#include <libcamera/base/log.h> > + > +/** > + * \file exposure_mode_helper.h > + * \brief Helper class that performs computations relating to exposure > + * > + * AEGC algorithms have a need to split exposure between shutter time, analogue > + * and digital gain. Multiple implementations do so based on paired stages of > + * shutter time and gain limits; provide a helper to avoid duplicating the code. > + */ > + > +namespace libcamera { > + > +using namespace std::literals::chrono_literals; > + > +LOG_DEFINE_CATEGORY(ExposureModeHelper) > + > +namespace ipa { > + > +/** > + * \class ExposureModeHelper > + * \brief Class for splitting exposure into shutter time and total gain > + * > + * The ExposureModeHelper class provides a standard interface through which an > + * AEGC algorithm can divide exposure between shutter time and gain. It is > + * configured with a set of shutter time and gain pairs and works by initially > + * fixing gain at 1.0 and increasing shutter time up to the shutter time value > + * from the first pair in the set in an attempt to meet the required exposure > + * value. > + * > + * If the required exposure is not achievable by the first shutter time value > + * alone it ramps gain up to the value from the first pair in the set. If the > + * required exposure is still not met it then allows shutter time to ramp up to > + * the shutter time value from the second pair in the set, and continues in this > + * vein until either the required exposure time is met, or else the hardware's > + * shutter time or gain limits are reached. > + * > + * This method allows users to strike a balance between a well-exposed image and > + * an acceptable frame-rate, as opposed to simply maximising shutter time > + * followed by gain. The same helpers can be used to perform the latter > + * operation if needed by passing an empty set of pairs to the initialisation > + * function. > + * > + * The gain values may exceed a camera sensor's analogue gain limits if either > + * it or the IPA is also capable of digital gain. The configure() function must > + * be called with the hardware's limits to inform the helper of those > + * constraints. Any gain that is needed will be applied as analogue gain first > + * until the hardware's limit is reached, following which digital gain will be > + * used. > + */ > + > +/** > + * \brief Construct an ExposureModeHelper instance > + * \param[in] stages The vector of paired shutter time and gain limits > + * > + * The input stages are shutter time and _total_ gain pairs; the gain > + * encompasses both analogue and digital gain. > + * > + * The vector of stages may be empty. In that case, the helper will simply use > + * the runtime limits set through setShutterGainLimits() instead. > + */ > +ExposureModeHelper::ExposureModeHelper(const Span<std::pair<utils::Duration, double>> stages) > +{ > + minShutter_ = 0us; > + maxShutter_ = 0us; > + minGain_ = 0; > + maxGain_ = 0; > + > + for (const auto &[s, g] : stages) { > + shutters_.push_back(s); > + gains_.push_back(g); > + } > +} > + > +/** > + * \brief Set the shutter time and gain limits > + * \param[in] minShutter The minimum shutter time supported > + * \param[in] maxShutter The maximum shutter time supported > + * \param[in] minGain The minimum analogue gain supported > + * \param[in] maxGain The maximum analogue gain supported > + * > + * This function configures the shutter time and analogue gain limits that need > + * to be adhered to as the helper divides up exposure. Note that this function > + * *must* be called whenever those limits change and before splitExposure() is > + * used. > + * > + * If the algorithm using the helpers needs to indicate that either shutter time > + * or analogue gain or both should be fixed it can do so by setting both the > + * minima and maxima to the same value. > + */ > +void ExposureModeHelper::setLimits(utils::Duration minShutter, > + utils::Duration maxShutter, > + double minGain, double maxGain) > +{ > + minShutter_ = minShutter; > + maxShutter_ = maxShutter; > + minGain_ = minGain; > + maxGain_ = maxGain; > +} > + > +utils::Duration ExposureModeHelper::clampShutter(utils::Duration shutter) const > +{ > + return std::clamp(shutter, minShutter_, maxShutter_); > +} > + > +double ExposureModeHelper::clampGain(double gain) const > +{ > + return std::clamp(gain, minGain_, maxGain_); > +} > + > +/** > + * \brief Split exposure time into shutter time and gain Ok, I'll accept that what you pass in as exposure value (in agc_mean_value.cpp) is here an exposure time > + * \param[in] exposure Exposure time > + * > + * This function divides a given exposure time into shutter time, analogue and > + * digital gain by iterating through stages of shutter time and gain limits. At > + * each stage the current stage's shutter time limit is multiplied by the > + * previous stage's gain limit (or 1.0 initially) to see if the combination of > + * the two can meet the required exposure time. If they cannot then the current > + * stage's shutter time limit is multiplied by the same stage's gain limit to > + * see if that combination can meet the required exposure time. If they cannot > + * then the function moves to consider the next stage. > + * > + * When a combination of shutter time and gain _stage_ limits are found that are > + * sufficient to meet the required exposure time, the function attempts to > + * reduce shutter time as much as possible whilst fixing gain and still meeting > + * the exposure time. If a _runtime_ limit prevents shutter time from being > + * lowered enough to meet the exposure time with gain fixed at the stage limit, > + * gain is also lowered to compensate. > + * > + * Once the shutter time and gain values are ascertained, gain is assigned as > + * analogue gain as much as possible, with digital gain only in use if the > + * maximum analogue gain runtime limit is unable to accomodate the exposure accommodate ? Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> Thanks j > + * value. > + * > + * If no combination of shutter time and gain limits is found that meets the > + * required exposure time, the helper falls-back to simply maximising the > + * shutter time first, followed by analogue gain, followed by digital gain. > + * > + * \return Tuple of shutter time, analogue gain, and digital gain > + */ > +std::tuple<utils::Duration, double, double> > +ExposureModeHelper::splitExposure(utils::Duration exposure) const > +{ > + ASSERT(maxShutter_); > + ASSERT(maxGain_); > + > + bool gainFixed = minGain_ == maxGain_; > + bool shutterFixed = minShutter_ == maxShutter_; > + > + /* > + * There's no point entering the loop if we cannot change either gain > + * nor shutter anyway. > + */ > + if (shutterFixed && gainFixed) > + return { minShutter_, minGain_, exposure / (minShutter_ * minGain_) }; > + > + utils::Duration shutter; > + double stageGain; > + double gain; > + > + for (unsigned int stage = 0; stage < gains_.size(); stage++) { > + double lastStageGain = stage == 0 ? 1.0 : clampGain(gains_[stage - 1]); > + utils::Duration stageShutter = clampShutter(shutters_[stage]); > + stageGain = clampGain(gains_[stage]); > + > + /* > + * We perform the clamping on both shutter and gain in case the > + * helper has had limits set that prevent those values being > + * lowered beyond a certain minimum...this can happen at runtime > + * for various reasons and so would not be known when the stage > + * limits are initialised. > + */ > + > + if (stageShutter * lastStageGain >= exposure) { > + shutter = clampShutter(exposure / clampGain(lastStageGain)); > + gain = clampGain(exposure / shutter); > + > + return { shutter, gain, exposure / (shutter * gain) }; > + } > + > + if (stageShutter * stageGain >= exposure) { > + shutter = clampShutter(exposure / clampGain(stageGain)); > + gain = clampGain(exposure / shutter); > + > + return { shutter, gain, exposure / (shutter * gain) }; > + } > + } > + > + /* > + * From here on all we can do is max out the shutter time, followed by > + * the analogue gain. If we still haven't achieved the target we send > + * the rest of the exposure time to digital gain. If we were given no > + * stages to use then set stageGain to 1.0 so that shutter time is maxed > + * before gain touched at all. > + */ > + if (gains_.empty()) > + stageGain = 1.0; > + > + shutter = clampShutter(exposure / clampGain(stageGain)); > + gain = clampGain(exposure / shutter); > + > + return { shutter, gain, exposure / (shutter * gain) }; > +} > + > +/** > + * \fn ExposureModeHelper::minShutter() > + * \brief Retrieve the configured minimum shutter time limit set through > + * setShutterGainLimits() > + * \return The minShutter_ value > + */ > + > +/** > + * \fn ExposureModeHelper::maxShutter() > + * \brief Retrieve the configured maximum shutter time set through > + * setShutterGainLimits() > + * \return The maxShutter_ value > + */ > + > +/** > + * \fn ExposureModeHelper::minGain() > + * \brief Retrieve the configured minimum gain set through > + * setShutterGainLimits() > + * \return The minGain_ value > + */ > + > +/** > + * \fn ExposureModeHelper::maxGain() > + * \brief Retrieve the configured maximum gain set through > + * setShutterGainLimits() > + * \return The maxGain_ value > + */ > + > +} /* namespace ipa */ > + > +} /* namespace libcamera */ > diff --git a/src/ipa/libipa/exposure_mode_helper.h b/src/ipa/libipa/exposure_mode_helper.h > new file mode 100644 > index 00000000..0ffc164e > --- /dev/null > +++ b/src/ipa/libipa/exposure_mode_helper.h > @@ -0,0 +1,53 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com> > + * > + * exposure_mode_helper.h - Helper class that performs computations relating to exposure > + */ > + > +#pragma once > + > +#include <tuple> > +#include <utility> > +#include <vector> > + > +#include <libcamera/base/span.h> > +#include <libcamera/base/utils.h> > + > +namespace libcamera { > + > +namespace ipa { > + > +class ExposureModeHelper > +{ > +public: > + ExposureModeHelper(const Span<std::pair<utils::Duration, double>> stages); > + ~ExposureModeHelper() = default; > + > + void setLimits(utils::Duration minShutter, utils::Duration maxShutter, > + double minGain, double maxGain); > + > + std::tuple<utils::Duration, double, double> > + splitExposure(utils::Duration exposure) const; > + > + utils::Duration minShutter() const { return minShutter_; } > + utils::Duration maxShutter() const { return maxShutter_; } > + double minGain() const { return minGain_; } > + double maxGain() const { return maxGain_; } > + > +private: > + utils::Duration clampShutter(utils::Duration shutter) const; > + double clampGain(double gain) const; > + > + std::vector<utils::Duration> shutters_; > + std::vector<double> gains_; > + > + utils::Duration minShutter_; > + utils::Duration maxShutter_; > + double minGain_; > + double maxGain_; > +}; > + > +} /* namespace ipa */ > + > +} /* namespace libcamera */ > diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build > index 016b8e0e..37fbd177 100644 > --- a/src/ipa/libipa/meson.build > +++ b/src/ipa/libipa/meson.build > @@ -3,6 +3,7 @@ > libipa_headers = files([ > 'algorithm.h', > 'camera_sensor_helper.h', > + 'exposure_mode_helper.h', > 'fc_queue.h', > 'histogram.h', > 'module.h', > @@ -11,6 +12,7 @@ libipa_headers = files([ > libipa_sources = files([ > 'algorithm.cpp', > 'camera_sensor_helper.cpp', > + 'exposure_mode_helper.cpp', > 'fc_queue.cpp', > 'histogram.cpp', > 'module.cpp', > -- > 2.34.1 >
diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp new file mode 100644 index 00000000..47a300f3 --- /dev/null +++ b/src/ipa/libipa/exposure_mode_helper.cpp @@ -0,0 +1,246 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com> + * + * exposure_mode_helper.cpp - Helper class that performs computations relating to exposure + */ +#include "exposure_mode_helper.h" + +#include <algorithm> + +#include <libcamera/base/log.h> + +/** + * \file exposure_mode_helper.h + * \brief Helper class that performs computations relating to exposure + * + * AEGC algorithms have a need to split exposure between shutter time, analogue + * and digital gain. Multiple implementations do so based on paired stages of + * shutter time and gain limits; provide a helper to avoid duplicating the code. + */ + +namespace libcamera { + +using namespace std::literals::chrono_literals; + +LOG_DEFINE_CATEGORY(ExposureModeHelper) + +namespace ipa { + +/** + * \class ExposureModeHelper + * \brief Class for splitting exposure into shutter time and total gain + * + * The ExposureModeHelper class provides a standard interface through which an + * AEGC algorithm can divide exposure between shutter time and gain. It is + * configured with a set of shutter time and gain pairs and works by initially + * fixing gain at 1.0 and increasing shutter time up to the shutter time value + * from the first pair in the set in an attempt to meet the required exposure + * value. + * + * If the required exposure is not achievable by the first shutter time value + * alone it ramps gain up to the value from the first pair in the set. If the + * required exposure is still not met it then allows shutter time to ramp up to + * the shutter time value from the second pair in the set, and continues in this + * vein until either the required exposure time is met, or else the hardware's + * shutter time or gain limits are reached. + * + * This method allows users to strike a balance between a well-exposed image and + * an acceptable frame-rate, as opposed to simply maximising shutter time + * followed by gain. The same helpers can be used to perform the latter + * operation if needed by passing an empty set of pairs to the initialisation + * function. + * + * The gain values may exceed a camera sensor's analogue gain limits if either + * it or the IPA is also capable of digital gain. The configure() function must + * be called with the hardware's limits to inform the helper of those + * constraints. Any gain that is needed will be applied as analogue gain first + * until the hardware's limit is reached, following which digital gain will be + * used. + */ + +/** + * \brief Construct an ExposureModeHelper instance + * \param[in] stages The vector of paired shutter time and gain limits + * + * The input stages are shutter time and _total_ gain pairs; the gain + * encompasses both analogue and digital gain. + * + * The vector of stages may be empty. In that case, the helper will simply use + * the runtime limits set through setShutterGainLimits() instead. + */ +ExposureModeHelper::ExposureModeHelper(const Span<std::pair<utils::Duration, double>> stages) +{ + minShutter_ = 0us; + maxShutter_ = 0us; + minGain_ = 0; + maxGain_ = 0; + + for (const auto &[s, g] : stages) { + shutters_.push_back(s); + gains_.push_back(g); + } +} + +/** + * \brief Set the shutter time and gain limits + * \param[in] minShutter The minimum shutter time supported + * \param[in] maxShutter The maximum shutter time supported + * \param[in] minGain The minimum analogue gain supported + * \param[in] maxGain The maximum analogue gain supported + * + * This function configures the shutter time and analogue gain limits that need + * to be adhered to as the helper divides up exposure. Note that this function + * *must* be called whenever those limits change and before splitExposure() is + * used. + * + * If the algorithm using the helpers needs to indicate that either shutter time + * or analogue gain or both should be fixed it can do so by setting both the + * minima and maxima to the same value. + */ +void ExposureModeHelper::setLimits(utils::Duration minShutter, + utils::Duration maxShutter, + double minGain, double maxGain) +{ + minShutter_ = minShutter; + maxShutter_ = maxShutter; + minGain_ = minGain; + maxGain_ = maxGain; +} + +utils::Duration ExposureModeHelper::clampShutter(utils::Duration shutter) const +{ + return std::clamp(shutter, minShutter_, maxShutter_); +} + +double ExposureModeHelper::clampGain(double gain) const +{ + return std::clamp(gain, minGain_, maxGain_); +} + +/** + * \brief Split exposure time into shutter time and gain + * \param[in] exposure Exposure time + * + * This function divides a given exposure time into shutter time, analogue and + * digital gain by iterating through stages of shutter time and gain limits. At + * each stage the current stage's shutter time limit is multiplied by the + * previous stage's gain limit (or 1.0 initially) to see if the combination of + * the two can meet the required exposure time. If they cannot then the current + * stage's shutter time limit is multiplied by the same stage's gain limit to + * see if that combination can meet the required exposure time. If they cannot + * then the function moves to consider the next stage. + * + * When a combination of shutter time and gain _stage_ limits are found that are + * sufficient to meet the required exposure time, the function attempts to + * reduce shutter time as much as possible whilst fixing gain and still meeting + * the exposure time. If a _runtime_ limit prevents shutter time from being + * lowered enough to meet the exposure time with gain fixed at the stage limit, + * gain is also lowered to compensate. + * + * Once the shutter time and gain values are ascertained, gain is assigned as + * analogue gain as much as possible, with digital gain only in use if the + * maximum analogue gain runtime limit is unable to accomodate the exposure + * value. + * + * If no combination of shutter time and gain limits is found that meets the + * required exposure time, the helper falls-back to simply maximising the + * shutter time first, followed by analogue gain, followed by digital gain. + * + * \return Tuple of shutter time, analogue gain, and digital gain + */ +std::tuple<utils::Duration, double, double> +ExposureModeHelper::splitExposure(utils::Duration exposure) const +{ + ASSERT(maxShutter_); + ASSERT(maxGain_); + + bool gainFixed = minGain_ == maxGain_; + bool shutterFixed = minShutter_ == maxShutter_; + + /* + * There's no point entering the loop if we cannot change either gain + * nor shutter anyway. + */ + if (shutterFixed && gainFixed) + return { minShutter_, minGain_, exposure / (minShutter_ * minGain_) }; + + utils::Duration shutter; + double stageGain; + double gain; + + for (unsigned int stage = 0; stage < gains_.size(); stage++) { + double lastStageGain = stage == 0 ? 1.0 : clampGain(gains_[stage - 1]); + utils::Duration stageShutter = clampShutter(shutters_[stage]); + stageGain = clampGain(gains_[stage]); + + /* + * We perform the clamping on both shutter and gain in case the + * helper has had limits set that prevent those values being + * lowered beyond a certain minimum...this can happen at runtime + * for various reasons and so would not be known when the stage + * limits are initialised. + */ + + if (stageShutter * lastStageGain >= exposure) { + shutter = clampShutter(exposure / clampGain(lastStageGain)); + gain = clampGain(exposure / shutter); + + return { shutter, gain, exposure / (shutter * gain) }; + } + + if (stageShutter * stageGain >= exposure) { + shutter = clampShutter(exposure / clampGain(stageGain)); + gain = clampGain(exposure / shutter); + + return { shutter, gain, exposure / (shutter * gain) }; + } + } + + /* + * From here on all we can do is max out the shutter time, followed by + * the analogue gain. If we still haven't achieved the target we send + * the rest of the exposure time to digital gain. If we were given no + * stages to use then set stageGain to 1.0 so that shutter time is maxed + * before gain touched at all. + */ + if (gains_.empty()) + stageGain = 1.0; + + shutter = clampShutter(exposure / clampGain(stageGain)); + gain = clampGain(exposure / shutter); + + return { shutter, gain, exposure / (shutter * gain) }; +} + +/** + * \fn ExposureModeHelper::minShutter() + * \brief Retrieve the configured minimum shutter time limit set through + * setShutterGainLimits() + * \return The minShutter_ value + */ + +/** + * \fn ExposureModeHelper::maxShutter() + * \brief Retrieve the configured maximum shutter time set through + * setShutterGainLimits() + * \return The maxShutter_ value + */ + +/** + * \fn ExposureModeHelper::minGain() + * \brief Retrieve the configured minimum gain set through + * setShutterGainLimits() + * \return The minGain_ value + */ + +/** + * \fn ExposureModeHelper::maxGain() + * \brief Retrieve the configured maximum gain set through + * setShutterGainLimits() + * \return The maxGain_ value + */ + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/exposure_mode_helper.h b/src/ipa/libipa/exposure_mode_helper.h new file mode 100644 index 00000000..0ffc164e --- /dev/null +++ b/src/ipa/libipa/exposure_mode_helper.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com> + * + * exposure_mode_helper.h - Helper class that performs computations relating to exposure + */ + +#pragma once + +#include <tuple> +#include <utility> +#include <vector> + +#include <libcamera/base/span.h> +#include <libcamera/base/utils.h> + +namespace libcamera { + +namespace ipa { + +class ExposureModeHelper +{ +public: + ExposureModeHelper(const Span<std::pair<utils::Duration, double>> stages); + ~ExposureModeHelper() = default; + + void setLimits(utils::Duration minShutter, utils::Duration maxShutter, + double minGain, double maxGain); + + std::tuple<utils::Duration, double, double> + splitExposure(utils::Duration exposure) const; + + utils::Duration minShutter() const { return minShutter_; } + utils::Duration maxShutter() const { return maxShutter_; } + double minGain() const { return minGain_; } + double maxGain() const { return maxGain_; } + +private: + utils::Duration clampShutter(utils::Duration shutter) const; + double clampGain(double gain) const; + + std::vector<utils::Duration> shutters_; + std::vector<double> gains_; + + utils::Duration minShutter_; + utils::Duration maxShutter_; + double minGain_; + double maxGain_; +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index 016b8e0e..37fbd177 100644 --- a/src/ipa/libipa/meson.build +++ b/src/ipa/libipa/meson.build @@ -3,6 +3,7 @@ libipa_headers = files([ 'algorithm.h', 'camera_sensor_helper.h', + 'exposure_mode_helper.h', 'fc_queue.h', 'histogram.h', 'module.h', @@ -11,6 +12,7 @@ libipa_headers = files([ libipa_sources = files([ 'algorithm.cpp', 'camera_sensor_helper.cpp', + 'exposure_mode_helper.cpp', 'fc_queue.cpp', 'histogram.cpp', 'module.cpp',