[v3,01/35] ipa: libipa: awb: Reimplement AwbAlgorithm
diff mbox series

Message ID 20260706-libipa-algorithms-v3-1-968757b038bb@ideasonboard.com
State Superseded
Headers show
Series
  • ipa: libipa: Introduce libipa algorithms
Related show

Commit Message

Jacopo Mondi July 6, 2026, 8:01 a.m. UTC
The current implementation of AwbAlgorithm is the following one:

AwbAlgorithm o---- RkISP1Awb
    ^
    |
 |-----|
 |     |
Bayes Grey

The platform-specific Awb implementation instantiate one of
the Bayes or Grey Awb helper classes and both inherit from the
AwbAlgorithm base class.

The handling of libcamera controls is split between the base and the
derived (mostly Bayes) class.

We want instead the AwbAlgorithm to handle all-things-libcamera and let
the Bayes and Grey classes implement the logic. In order to achieve that
use composition also in the AwbAlgorithm class and introduce an
AwbImplementation base class from which Bayes and Grey derive from.

AwbImplementation o---- AwbAlgorithmBase
    ^                           ^
    |                           |
 |-----|                AwbAlgorithm<Q<x,y>> o---- RkISP1Awb
 |     |
Bayes Grey

The AwbAlgorithm<> class is instantiated with a Q<x, y> template
argument that represents the platform-specific register format which
is used to initialize controls and clamp gains to the hardware limits.

Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
Tested-by: Daniel Scally <dan.scally@ideasonboard.com>
---
 src/ipa/libipa/awb.cpp            | 472 ++++++++++++++++++++++++++++++++------
 src/ipa/libipa/awb.h              | 109 +++++++--
 src/ipa/libipa/awb_bayes.cpp      |  52 ++---
 src/ipa/libipa/awb_bayes.h        |  12 +-
 src/ipa/libipa/awb_grey.cpp       |  25 +-
 src/ipa/libipa/awb_grey.h         |   6 +-
 src/ipa/rkisp1/algorithms/awb.cpp | 360 +++++++++--------------------
 src/ipa/rkisp1/algorithms/awb.h   |  18 +-
 src/ipa/rkisp1/ipa_context.h      |  29 +--
 9 files changed, 672 insertions(+), 411 deletions(-)

Comments

Stefan Klug July 7, 2026, 6:31 a.m. UTC | #1
Hi Jacopo,

Thank you for the patch.

Quoting Jacopo Mondi (2026-07-06 10:01:21)
> The current implementation of AwbAlgorithm is the following one:
> 
> AwbAlgorithm o---- RkISP1Awb
>     ^
>     |
>  |-----|
>  |     |
> Bayes Grey
> 
> The platform-specific Awb implementation instantiate one of
> the Bayes or Grey Awb helper classes and both inherit from the
> AwbAlgorithm base class.
> 
> The handling of libcamera controls is split between the base and the
> derived (mostly Bayes) class.
> 
> We want instead the AwbAlgorithm to handle all-things-libcamera and let
> the Bayes and Grey classes implement the logic. In order to achieve that
> use composition also in the AwbAlgorithm class and introduce an
> AwbImplementation base class from which Bayes and Grey derive from.
> 
> AwbImplementation o---- AwbAlgorithmBase
>     ^                           ^
>     |                           |
>  |-----|                AwbAlgorithm<Q<x,y>> o---- RkISP1Awb
>  |     |
> Bayes Grey
> 
> The AwbAlgorithm<> class is instantiated with a Q<x, y> template
> argument that represents the platform-specific register format which
> is used to initialize controls and clamp gains to the hardware limits.
> 
> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
> Tested-by: Daniel Scally <dan.scally@ideasonboard.com>
> ---
>  src/ipa/libipa/awb.cpp            | 472 ++++++++++++++++++++++++++++++++------
>  src/ipa/libipa/awb.h              | 109 +++++++--
>  src/ipa/libipa/awb_bayes.cpp      |  52 ++---
>  src/ipa/libipa/awb_bayes.h        |  12 +-
>  src/ipa/libipa/awb_grey.cpp       |  25 +-
>  src/ipa/libipa/awb_grey.h         |   6 +-
>  src/ipa/rkisp1/algorithms/awb.cpp | 360 +++++++++--------------------
>  src/ipa/rkisp1/algorithms/awb.h   |  18 +-
>  src/ipa/rkisp1/ipa_context.h      |  29 +--
>  9 files changed, 672 insertions(+), 411 deletions(-)
> 
> diff --git a/src/ipa/libipa/awb.cpp b/src/ipa/libipa/awb.cpp
> index 29a78069c52c..95291840d8fb 100644
> --- a/src/ipa/libipa/awb.cpp
> +++ b/src/ipa/libipa/awb.cpp
> @@ -2,7 +2,7 @@
>  /*
>   * Copyright (C) 2024 Ideas on Board Oy
>   *
> - * Generic AWB algorithms
> + * libIPA Awb algorithms
>   */
>  
>  #include "awb.h"
> @@ -11,9 +11,16 @@
>  
>  #include <libcamera/control_ids.h>
>  
> +#include "awb_bayes.h"
> +#include "awb_grey.h"
> +
> +constexpr int32_t kMinColourTemperature = 2500;
> +constexpr int32_t kMaxColourTemperature = 10000;
> +constexpr int32_t kDefaultColourTemperature = 5000;
> +
>  /**
>   * \file awb.h
> - * \brief Base classes for AWB algorithms
> + * \brief libipa awb implementation
>   */
>  
>  namespace libcamera {
> @@ -22,30 +29,60 @@ LOG_DEFINE_CATEGORY(Awb)
>  
>  namespace ipa {
>  
> +namespace awb {
> +
>  /**
> - * \class AwbResult
> - * \brief The result of an AWB calculation
> + * \struct Context
> + * \brief Awb gains and colour temperature
> + *
> + * \var Context::gains
> + * \brief The white balance gains
>   *
> - * This class holds the result of an auto white balance calculation.
> + * \var Context::temperatureK
> + * \brief The colour temperature, in Kelvin
>   */
>  
>  /**
> - * \var AwbResult::gains
> - * \brief The calculated white balance gains
> + * \struct ActiveState
> + * \brief Active awb state shared across frames
> + *
> + * \var ActiveState::manual
> + * \brief The most recent manually requested awb state
> + *
> + * \var ActiveState::automatic
> + * \brief The most recent automatically calculated awb state
> + *
> + * \var ActiveState::autoEnabled
> + * \brief True when automatic awb is selected
>   */
>  
>  /**
> - * \var AwbResult::colourTemperature
> - * \brief The calculated colour temperature in Kelvin
> + * \struct FrameContext
> + * \brief Per-frame awb state
> + *
> + * \var FrameContext::autoEnabled
> + * \brief True when automatic awb is in use
>   */
>  
> +} /* namespace awb */
> +
>  /**
>   * \class AwbStats
> - * \brief An abstraction class wrapping hardware-specific AWB statistics
> + * \brief An abstraction class wrapping hardware-specific awb statistics

Out of curiosity. I was told that abbreviations like AWB are always
capitalized in writing. Is that incorrect?

>   *
> - * IPA modules using an AWB algorithm based on the AwbAlgorithm class need to
> - * implement this class to give the algorithm access to the hardware-specific
> - * statistics data.
> + * IPA modules shall provide a derived implementation of this class to give the
> + * awb algorithm access to the hardware-specific statistics data in a common
> + * format.
> + */
> +
> +/**
> + * \fn AwbStats::AwbStats()
> + * \brief Construct an empty AwbStat
> + */
> +
> +/**
> + * AwbStat::~AwbStat
> + * \brief Virtual class destructor
>   */
>  
>  /**
> @@ -57,7 +94,7 @@ namespace ipa {
>   * applied. To keep the actual implementations computationally inexpensive,
>   * the squared colour error shall be returned.
>   *
> - * If the AWB statistics provide multiple zones, the average of the individual
> + * If the awb statistics provide multiple zones, the average of the individual
>   * squared errors shall be returned. Averaging/normalizing is necessary so that
>   * the numeric dimensions are the same on all hardware platforms.
>   *
> @@ -65,79 +102,328 @@ namespace ipa {
>   */
>  
>  /**
> - * \fn AwbStats::rgbMeans()
> - * \brief Get RGB means of the statistics
> + * \fn AwbStats::valid()
> + * \brief Retrieve if the awb statistics are valid
>   *
> - * Fetch the RGB means from the statistics. The values of each channel are
> - * dimensionless and only the ratios are used for further calculations. This is
> - * used by the simple grey world model to calculate the gains to apply.
> + * If the colour mean values are too small, the AwbAlgorithm class doesn't have
> + * enough information to meaningfully calculate white-balance gains. Freeze the
> + * algorithm in that case.
> + *
> + * \return True if the awb statistics are valid, false otherwise
> + */
> +
> +/**
> + * \fn AwbStats::rgbMeans
> + * \brief Retrieve the RGB means values
> + *
> + * Fetch the RGB means from the statistics in the [0, 1] range.
>   *
>   * \return The RGB means
>   */
>  
>  /**
> - * \class AwbAlgorithm
> - * \brief A base class for auto white balance algorithms
> + * \var AwbStats::rgbMeans_
> + * \brief Mean values of colour channels
> + */
> +
> +/**
> + * \var AwbStats::rg_
> + * \brief Red/Green ratio
> + */
> +
> +/**
> + * \var AwbStats::bg_
> + * \brief Blue/Green ratio
> + */
> +
> +/**
> + * \class AwbImplementation
> + * \brief Pure virtual base class for awb algorithms implementations
> + *
> + * The AwbImplementation class defines the interface for the awb algorithm
> + * implementations.
>   *
> - * This class is a base class for auto white balance algorithms. It defines an
> - * interface for the algorithms to implement, and is used by the IPAs to
> - * interact with the concrete implementation.
> + * It is currently implemented by the AwbGrey and AwbBayes classes.
> + *
> + * The interface defines an init() function to initialize the algorithm with the
> + * content of the tuning file and two functions to compute colour gains
> + * according to the algorithm operating mode (auto or manual) in use.
> + *
> + * The calculateAwb() function calculates colour gains given a set of statistics
> + * provided by the IPA module. It is used when the algorithm operates in auto
> + * mode and gains are dynamically computed given a new set of statistics from
> + * the awb engine.
> + *
> + * The gainsFromColourTemperature() function instead computes the white balance
> + * gains from a colour temperature. This function does not take any statistics
> + * into account. It is used to compute the colour gains when the user manually
> + * specifies a colour temperature.
>   */
>  
>  /**
> - * \fn AwbAlgorithm::init()
> - * \brief Initialize the algorithm with the given tuning data
> - * \param[in] tuningData The tuning data to use for the algorithm
> + * \struct AwbImplementation::Result
> + * \brief The result of an awb computation
>   *
> - * \return 0 on success, a negative error code otherwise
> + * \var AwbImplementation::Result::gains
> + * \brief The computed white balance gains
> + *
> + * \var AwbImplementation::Result::temperatureK
> + * \brief The computed colour temperature, in Kelvin
>   */
>  
>  /**
> - * \fn AwbAlgorithm::calculateAwb()
> - * \brief Calculate AWB data from the given statistics
> - * \param[in] stats The statistics to use for the calculation
> - * \param[in] lux The lux value of the scene
> + * \fn AwbImplementation::~AwbImplementation
> + * \brief Virtual class destructor
> + */
> +
> +/**
> + * \fn AwbImplementation::init()
> + * \param[in] tuningData
> + * \brief Initialize the algorithm by parsing \a tuningData
> + */
> +
> +/**
> + * \fn AwbImplementation::calculateAwb()
> + * \param[in] stats The awb statistics
> + * \param[in] lux The estimated lux level
> + * \param[in] range The colour temperature search limits
>   *
> - * Calculate an AwbResult object from the given statistics and lux value. A \a
> - * lux value of 0 means it is unknown or invalid and the algorithm shall ignore
> - * it.
> + * Calculate a new set of colour gains and a colour temperature given a new
> + * set of statistics \a stats, an estimated luminance \a lux and a range of
> + * colour temperature to limit the search.
>   *
> - * \return The AWB result
> + * \return A Result computed using the new statistics
> + */
> +/**
> + * \fn AwbImplementation::gainsFromColourTemperature()
> + * \param[in] temperatureK Colour temperature in Kelvin
> + *
> + * Calculate a new set of colour gains for a new manually specified colour
> + * temperature \a temperatureK.
> + *
> + * \return The RGB gain values adjusted to \a temperatureK or std::nullopt
> + */
> +
> +/**
> + * \class AwbAlgorithmBase
> + * \brief Base class for AwbAlgorithm for non-templated functions implementation
> + *
> + * Base class for AwbAlgorithm where non-templated functions are implemented.
> + * IPA implementations shall use AwbAlgorithm and not this class.
>   */
>  
>  /**
> - * \fn AwbAlgorithm::gainsFromColourTemperature()
> - * \brief Compute white balance gains from a colour temperature
> - * \param[in] colourTemperature The colour temperature in Kelvin
> + * \brief Initialize the algorithm with the given tuning data
> + * \param[in] tuningData The tuning data to use for the algorithm
>   *
> - * Compute the white balance gains from a \a colourTemperature. This function
> - * does not take any statistics into account. It is used to compute the colour
> - * gains when the user manually specifies a colour temperature.
> + * Parse \a tuningData to initialize the awb algorithm and register controls.
> + * IPA modules are expected to call this function as part of their
> + * implementation of Algorithm::init().
>   *
> - * \return The colour gains or std::nullopt if the conversion is not possible
> + * \return 0 on success, a negative error code otherwise
> + */
> +int AwbAlgorithmBase::init(const ValueNode &tuningData)
> +{
> +       bayes_ = false;
> +
> +       if (!tuningData.contains("algorithm"))
> +               LOG(Awb, Info) << "No Awb algorithm specified, using grey world";
> +
> +       auto mode = tuningData["algorithm"].get<std::string>("grey");
> +       if (mode == "grey") {
> +               impl_ = std::make_unique<AwbGrey>();
> +       } else if (mode == "bayes") {
> +               impl_ = std::make_unique<AwbBayes>();
> +               bayes_ = true;
> +       } else {
> +               LOG(Awb, Error) << "Unknown Awb algorithm: " << mode;
> +               return -EINVAL;
> +       }
> +
> +       LOG(Awb, Debug) << "Using Awb algorithm: " << mode;
> +
> +       int ret = impl_->init(tuningData);
> +       if (ret)
> +               return ret;
> +
> +       controls_[&controls::ColourTemperature] =
> +               ControlInfo(kMinColourTemperature, kMaxColourTemperature,
> +                           kDefaultColourTemperature);
> +       controls_[&controls::AwbEnable] = ControlInfo(false, true);
> +
> +       return parseModeConfigs(tuningData, controls::AwbAuto);
> +}
> +
> +/**
> + * \brief Configure the awb algorithm
> + * \param[in] state The awb active state
> + * \return 0 if successful, an error code otherwise
>   */
> +int AwbAlgorithmBase::configure(awb::ActiveState &state)
> +{
> +       state.manual.gains = RGB<double>{ 1.0 };
> +       auto gains = impl_->gainsFromColourTemperature(kDefaultColourTemperature);
> +       if (gains)
> +               state.automatic.gains = *gains;
> +       else
> +               state.automatic.gains = RGB<double>{ 1.0 };
> +
> +       state.autoEnabled = true;
> +       state.manual.temperatureK = kDefaultColourTemperature;
> +       state.automatic.temperatureK = kDefaultColourTemperature;
> +
> +       return 0;
> +}
>  
>  /**
> - * \fn AwbAlgorithm::controls()
> - * \brief Get the controls info map for this algorithm
> + * \brief Queue a Request to the awb algorithm
> + * \param[in] state The awb active state
> + * \param[in] frame The frame number
> + * \param[in] frameContext The awb frame context
> + * \param[in] controls The list of controls part of the Request
>   *
> - * \return The controls info map
> + * Queue a new Request to the awb algorithm and modify its behaviour according
> + * to the provided controls.
> + *
> + * The currently handled controls are:
> + * - controls::AwbEnable
> + * - controls::AwbMode
> + * - controls::ColourGains
> + * - controls::ColourTemperature
>   */
> +void AwbAlgorithmBase::queueRequest(awb::ActiveState &state,
> +                                   [[maybe_unused]] const uint32_t frame,
> +                                   awb::FrameContext &frameContext,
> +                                   const ControlList &controls)
> +{
> +       const auto &awbEnable = controls.get(controls::AwbEnable);
> +       if (awbEnable && *awbEnable != state.autoEnabled) {
> +               state.autoEnabled = *awbEnable;
> +
> +               LOG(Awb, Debug)
> +                       << (*awbEnable ? "Enabling" : "Disabling") << " Awb";
> +       }
> +
> +       auto mode = controls.get(controls::AwbMode);
> +       if (mode) {
> +               auto it = modes_.find(static_cast<controls::AwbModeEnum>(*mode));
> +               if (it == modes_.end()) {
> +                       LOG(Awb, Error) << "Unsupported Awb mode " << *mode;
> +                       return;
> +               }
> +
> +               currentMode_ = &it->second;
> +       }
> +
> +       frameContext.autoEnabled = state.autoEnabled;
> +
> +       if (frameContext.autoEnabled)
> +               return;
> +
> +       const auto &colourGains = controls.get(controls::ColourGains);
> +       const auto &colourTemperature = controls.get(controls::ColourTemperature);
> +       bool update = false;
> +       if (colourGains) {
> +               state.manual.gains.r() = (*colourGains)[0];
> +               state.manual.gains.b() = (*colourGains)[1];
> +               /*
> +                * \todo Colour temperature reported in metadata is now
> +                * incorrect, as we can't deduce the temperature from the gains.
> +                * This will be fixed with the bayes AWB algorithm.
> +                */
> +               update = true;
> +       } else if (colourTemperature) {
> +               state.manual.temperatureK = *colourTemperature;
> +               const auto &gains = impl_->gainsFromColourTemperature(*colourTemperature);
> +               if (gains) {
> +                       state.manual.gains.r() = gains->r();
> +                       state.manual.gains.b() = gains->b();
> +                       update = true;
> +               }
> +       }
> +
> +       if (update)
> +               LOG(Awb, Debug)
> +                       << "Set colour gains to " << state.manual.gains;
> +
> +       frameContext.gains = state.manual.gains;
> +       frameContext.temperatureK = state.manual.temperatureK;
> +}
>  
>  /**
> - * \fn AwbAlgorithm::handleControls()
> - * \param[in] controls The controls to handle
> - * \brief Handle the controls supplied in a request
> + * \brief Set the gains and colour temperature values in \a frameContext
> + * \param[in] state The awb active state
> + * \param[in] frameContext The awb frame context
> + *
> + * If auto mode is enabled, take the most recently computed gains and use them
> + * for the current frame. Otherwise, if in manual mode, gains and colour
> + * temperature for a frame are set at queueRequest() time.
>   */
> +void AwbAlgorithmBase::prepare(awb::ActiveState &state,
> +                              awb::FrameContext &frameContext)
> +{
> +       if (frameContext.autoEnabled) {
> +               frameContext.gains = state.automatic.gains;
> +               frameContext.temperatureK = state.automatic.temperatureK;
> +       }
> +}
>  
>  /**
> + * \brief Process awb statistics to calculate gains and populate metadata
> + * \param[in] state The awb active state
> + * \param[in] frameContext The awb frame context
> + * \param[in] stats The awb statistics
> + * \param[in] lux The lux value as estimated by the IPA module
> + * \param[out] metadata The metadata list
> + *
> + * Process \a stats to calculate new gains and colour temperature and populate
> + * \a metadata with the results.
> + */
> +void AwbAlgorithmBase::process(awb::ActiveState &state,
> +                              awb::FrameContext &frameContext,
> +                              const AwbStats &stats, unsigned int lux,
> +                              ControlList &metadata)
> +{
> +       if (!stats.valid())
> +               return;
> +
> +       auto awbResult = impl_->calculateAwb(stats, lux, { currentMode_->ctLo,
> +                                                          currentMode_->ctHi });
> +
> +       /*
> +        * Clamp the gain values to the hardware, according to the gainMin_
> +        * and gainMax_ values.
> +        */
> +       awbResult.gains = awbResult.gains.clamp(gainMin_, gainMax_);
> +
> +       /* Smooth color gains adjustments. */
> +       double speed = 0.2;
> +       double ct = awbResult.temperatureK;
> +       ct = ct * speed + state.automatic.temperatureK * (1 - speed);
> +
> +       state.automatic.temperatureK = awbResult.temperatureK;
> +       state.automatic.gains = awbResult.gains * speed +
> +                               state.automatic.gains * (1 - speed);
> +
> +       /* Populate metadata. */
> +       metadata.set(controls::AwbEnable, frameContext.autoEnabled);
> +       metadata.set(controls::ColourGains, { static_cast<float>(frameContext.gains.r()),
> +                                             static_cast<float>(frameContext.gains.b()) });
> +       metadata.set(controls::ColourTemperature, frameContext.temperatureK);
> +
> +       LOG(Awb, Debug) << std::showpoint << "Means " << stats.rgbMeans()
> +                       << ", gains " << state.automatic.gains
> +                       << ", temp " << state.automatic.temperatureK << "K";
> +}
> +
> +/*
>   * \brief Parse the mode configurations from the tuning data
>   * \param[in] tuningData the ValueNode representing the tuning data
>   * \param[in] def The default value for the AwbMode control
>   *
>   * Utility function to parse the tuning data for an AwbMode entry and read all
> - * provided modes. It adds controls::AwbMode to AwbAlgorithm::controls_ and
> - * populates AwbAlgorithm::modes_. For a list of possible modes see \ref
> + * provided modes. It adds controls::AwbMode to AwbAlgorithmBase::controls_ and
> + * populates AwbAlgorithmBase::modes_. For a list of possible modes see \ref
>   * controls::AwbModeEnum.
>   *
>   * Each mode entry must contain a "lo" and "hi" key to specify the lower and
> @@ -156,15 +442,23 @@ namespace ipa {
>   *       ...
>   * \endcode
>   *
> - * If \a def is supplied but not contained in the the \a tuningData, -EINVAL is
> + * If \a def is supplied but not contained in the \a tuningData, -EINVAL is
>   * returned.
>   *
> + * AwbModes are only used by the AwbBayes implementation.
> + *
>   * \sa controls::AwbModeEnum
>   * \return Zero on success, negative error code otherwise
>   */
> -int AwbAlgorithm::parseModeConfigs(const ValueNode &tuningData,
> -                                  const ControlValue &def)
> +int AwbAlgorithmBase::parseModeConfigs(const ValueNode &tuningData,
> +                                      const ControlValue &def)
>  {
> +       if (!bayes_) {
> +               /* AwbGrey does not support and does not use modes. */
> +               currentMode_ = &AwbGreyMode;
> +               return 0;
> +       }
> +
>         std::vector<ControlValue> availableModes;
>  
>         const ValueNode &modes = tuningData[controls::AwbMode.name()];
> @@ -227,37 +521,83 @@ int AwbAlgorithm::parseModeConfigs(const ValueNode &tuningData,
>         }
>  
>         controls_[&controls::AwbMode] = ControlInfo(availableModes, def);
> +       currentMode_ = &modes_[controls::AwbAuto];
>  
>         return 0;
>  }
>  
>  /**
> - * \class AwbAlgorithm::ModeConfig
> - * \brief Holds the configuration of a single AWB mode
> + * \var AwbAlgorithmBase::controls_
> + * \brief Controls info map for the controls registered by the awb algorithm
> + */
> +
> +/**
> + * \var AwbAlgorithmBase::gainMin_
> + * \brief The minimum supported gain value
> + *
> + * Minimum gain value used to clamp the awb algorithm calculation results in the
> + * range supported by the platform awb engine.
>   *
> - * AWB modes limit the regulation of the AWB algorithm to a specific range of
> - * colour temperatures.
> + * The min and max gain values are initialized by AwbAlgorithm::init().
>   */
>  
>  /**
> - * \var AwbAlgorithm::ModeConfig::ctLo
> + * \var AwbAlgorithmBase::gainMax_
> + * \brief The maximum supported gain value
> + *
> + * Maximum gain value used to clamp the awb algorithm calculation results in the
> + * range supported by the platform awb engine.
> + *
> + * The min and max gain values are initialized by AwbAlgorithm::init().
> + */
> +
> +/*
> + * \class AwbAlgorithmBase::ModeConfig
> + * \brief Holds the configuration of a single awb mode
> + *
> + * AWB modes limit the regulation of the awb algorithm to a specific range of
> + * colour temperatures. Use by AwbBayes only.
> + */
> +
> +/*
> + * \var AwbAlgorithmBase::ModeConfig::ctLo
>   * \brief The lowest valid colour temperature of that mode
>   */
>  
> -/**
> - * \var AwbAlgorithm::ModeConfig::ctHi
> +/*
> + * \var AwbAlgorithmBase::ModeConfig::ctHi
>   * \brief The highest valid colour temperature of that mode
>   */
>  
> +/*
> + * \var AwbAlgorithmBase::modes_
> + * \brief Map of all configured modes
> + * \sa AwbAlgorithmBase::parseModeConfigs
> + */
> +
>  /**
> - * \var AwbAlgorithm::controls_
> - * \brief Controls info map for the controls provided by the algorithm
> + * \class AwbAlgorithm
> + * \brief The libipa awb algorithm
> + * \tparam Q The fixedpoint register representation of gain values
> + *
> + * Implement the awb algorithm for libipa.
> + *
> + * The AwbAlgorithm class implements an interface similar in spirit to the one
> + * of the Algorithm class. IPA modules are expected to store an instance of
> + * AwbAlgorithm as class member, template it with the awb engine gain register
> + * representation and call its function in their implementations of the
> + * Algorithm interface.
> + *
> + * The AwbAlgorithm instantiates an AwbImplementation implementation (AwbGrey or
> + * AwbBayes) at init() time by parsing the tuning data and uses it to compute
> + * the RGB gains and estimate a colour temperature given a set of statistics
> + * in the form of a AwbStats derived implementation.
>   */
>  
>  /**
> - * \var AwbAlgorithm::modes_
> - * \brief Map of all configured modes
> - * \sa AwbAlgorithm::parseModeConfigs
> + * \fn AwbAlgorithm::init()
> + * \param[in] controls The info map of the IPA controls
> + * \copydoc AwbAlgorithmBase::init()
>   */
>  
>  } /* namespace ipa */
> diff --git a/src/ipa/libipa/awb.h b/src/ipa/libipa/awb.h
> index 09c00e47d604..72f46367a136 100644
> --- a/src/ipa/libipa/awb.h
> +++ b/src/ipa/libipa/awb.h
> @@ -2,11 +2,12 @@
>  /*
>   * Copyright (C) 2024 Ideas on Board Oy
>   *
> - * Generic AWB algorithms
> + * libIPA AWB algorithms
>   */
>  
>  #pragma once
>  
> +#include <array>
>  #include <map>
>  #include <optional>
>  
> @@ -20,46 +21,118 @@ namespace libcamera {
>  
>  namespace ipa {
>  
> -struct AwbResult {
> +namespace awb {
> +
> +struct Context {
>         RGB<double> gains;
> -       double colourTemperature;
> +       unsigned int temperatureK;
>  };
>  
> +struct ActiveState {
> +       Context manual;
> +       Context automatic;
> +
> +       bool autoEnabled;
> +};
> +
> +struct FrameContext : public Context {
> +       bool autoEnabled;
> +};
> +
> +} /* namespace awb */
> +
>  struct AwbStats {
> +       AwbStats() = default;
> +       virtual ~AwbStats() = default;
> +
>         virtual double computeColourError(const RGB<double> &gains) const = 0;
> -       virtual RGB<double> rgbMeans() const = 0;
> +       virtual bool valid() const = 0;
> +       RGB<double> rgbMeans() const { return rgbMeans_; }
>  
>  protected:
> -       ~AwbStats() = default;
> +       RGB<double> rgbMeans_;
> +       double rg_;
> +       double bg_;

I think these (rg_ and bg_) are not used anymore and can be dropped.
Personally I'd also leave the rgbMeans_ member in the platform
implementation and keep it as interface only - up to you.

>  };
>  
> -class AwbAlgorithm
> +class AwbImplementation
>  {
>  public:
> -       virtual ~AwbAlgorithm() = default;
> +       struct Result {
> +               RGB<double> gains;
> +               unsigned int temperatureK;
> +       };
>  
> +       virtual ~AwbImplementation() = default;
>         virtual int init(const ValueNode &tuningData) = 0;
> -       virtual AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) = 0;
> -       virtual std::optional<RGB<double>> gainsFromColourTemperature(double colourTemperature) = 0;
> +       virtual Result calculateAwb(const AwbStats &stats, unsigned int lux,
> +                                   std::array<double, 2> range) = 0;
> +       virtual std::optional<RGB<double>>
> +       gainsFromColourTemperature(double temperatureK) = 0;
> +};
>  
> -       const ControlInfoMap::Map &controls() const
> -       {
> -               return controls_;
> -       }
> +class AwbAlgorithmBase
> +{
> +public:
> +       int init(const ValueNode &tuningData);
>  
> -       virtual void handleControls([[maybe_unused]] const ControlList &controls) {}
> +       int configure(awb::ActiveState &state);
> +
> +       void queueRequest(awb::ActiveState &state,
> +                         const uint32_t frame,
> +                         awb::FrameContext &frameContext,
> +                         const ControlList &controls);
> +
> +       void prepare(awb::ActiveState &state, awb::FrameContext &frameContext);
> +
> +       void process(awb::ActiveState &state, awb::FrameContext &frameContext,
> +                    const AwbStats &stats, unsigned int lux,
> +                    ControlList &metadata);
>  
>  protected:
> -       int parseModeConfigs(const ValueNode &tuningData,
> -                            const ControlValue &def = {});
> +       AwbAlgorithmBase() = default;
> +
> +       ControlInfoMap::Map controls_;
> +       float gainMin_;
> +       float gainMax_;
>  
> +private:
>         struct ModeConfig {
>                 double ctHi;
>                 double ctLo;
>         };
>  
> -       ControlInfoMap::Map controls_;
> -       std::map<controls::AwbModeEnum, AwbAlgorithm::ModeConfig> modes_;
> +       /* AwbGrey does not support modes; */
> +       static constexpr ModeConfig AwbGreyMode = { 0.0, 0.0 };
> +
> +       int parseModeConfigs(const ValueNode &tuningData,
> +                            const ControlValue &def = {});
> +
> +       std::map<controls::AwbModeEnum, AwbAlgorithmBase::ModeConfig> modes_;
> +       const ModeConfig *currentMode_ = nullptr;
> +       std::unique_ptr<AwbImplementation> impl_;
> +       bool bayes_ = false;
> +};
> +
> +template<typename Q>
> +class AwbAlgorithm : public AwbAlgorithmBase
> +{
> +public:
> +       int init(const ValueNode &tuningData, ControlInfoMap::Map &controls)
> +       {
> +               AwbAlgorithmBase::init(tuningData);
> +
> +               gainMin_ = std::max(Q::TraitsType::min, 1.0f);
> +               gainMax_ = Q::TraitsType::max;
> +
> +               controls_[&controls::ColourGains] =
> +                       ControlInfo(gainMin_, gainMax_,
> +                                   Span<const float, 2>{ { 1.0f, 1.0f } });
> +
> +               controls.insert(controls_.begin(), controls_.end());
> +
> +               return 0;
> +       }
>  };
>  
>  } /* namespace ipa */
> diff --git a/src/ipa/libipa/awb_bayes.cpp b/src/ipa/libipa/awb_bayes.cpp
> index 9fd85e5a4505..2c10f9c4c82d 100644
> --- a/src/ipa/libipa/awb_bayes.cpp
> +++ b/src/ipa/libipa/awb_bayes.cpp
> @@ -140,11 +140,6 @@ void Interpolator<Pwl>::interpolate(const Pwl &a, const Pwl &b, Pwl &dest, doubl
>   * \brief How far to wander off CT curve towards "more green"
>   */
>  
> -/**
> - * \var AwbBayes::currentMode_
> - * \brief The currently selected mode
> - */
> -
>  int AwbBayes::init(const ValueNode &tuningData)
>  {
>         int ret = colourGainCurve_.readYaml(tuningData["colourGains"], "ct", "gains");
> @@ -172,14 +167,6 @@ int AwbBayes::init(const ValueNode &tuningData)
>                 return ret;
>         }
>  
> -       ret = parseModeConfigs(tuningData, controls::AwbAuto);
> -       if (ret) {
> -               LOG(Awb, Error)
> -                       << "Failed to parse mode parameter from tuning file";
> -               return ret;
> -       }
> -       currentMode_ = &modes_[controls::AwbAuto];
> -
>         transversePos_ = tuningData["transversePos"].get<double>(0.01);
>         transverseNeg_ = tuningData["transverseNeg"].get<double>(0.01);
>         if (transversePos_ <= 0 || transverseNeg_ <= 0) {
> @@ -260,18 +247,16 @@ int AwbBayes::readPriors(const ValueNode &tuningData)
>         return 0;
>  }
>  
> -void AwbBayes::handleControls(const ControlList &controls)
> -{
> -       auto mode = controls.get(controls::AwbMode);
> -       if (mode) {
> -               auto it = modes_.find(static_cast<controls::AwbModeEnum>(*mode));
> -               if (it != modes_.end())
> -                       currentMode_ = &it->second;
> -               else
> -                       LOG(Awb, Error) << "Unsupported AWB mode " << *mode;
> -       }
> -}
> -
> +/**
> + * \brief Compute white balance gains from a colour temperature
> + * \param[in] colourTemperature The colour temperature in Kelvin
> + *
> + * Compute the white balance gains from a \a colourTemperature. This function
> + * does not take any statistics into account. It simply interpolates the colour
> + * gains curve on a new colour temperature.
> + *
> + * \return The RGB gain values adjusted to \a temperatureK
> + */
>  std::optional<RGB<double>> AwbBayes::gainsFromColourTemperature(double colourTemperature)
>  {
>         /*
> @@ -283,7 +268,9 @@ std::optional<RGB<double>> AwbBayes::gainsFromColourTemperature(double colourTem
>         return RGB<double>{ { gains[0], 1.0, gains[1] } };
>  }
>  
> -AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux)
> +AwbImplementation::Result
> +AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux,
> +                      std::array<double, 2> range)
>  {
>         ipa::Pwl prior;
>         if (lux > 0) {
> @@ -295,7 +282,7 @@ AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux)
>                 prior.append(0, 1.0);
>         }
>  
> -       double t = coarseSearch(prior, stats);
> +       double t = coarseSearch(prior, stats, range);
>         double r = ctR_.eval(t);
>         double b = ctB_.eval(t);
>         LOG(Awb, Debug)
> @@ -316,14 +303,15 @@ AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux)
>                 << "After fine search: r " << r << " b " << b << " (gains r "
>                 << 1 / r << " b " << 1 / b << ")";
>  
> -       return { { { 1.0 / r, 1.0, 1.0 / b } }, t };
> +       return { { { 1.0 / r, 1.0, 1.0 / b } }, static_cast<unsigned int>(t) };
>  }
>  
> -double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) const
> +double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats,
> +                             Span<double> range) const
>  {
>         std::vector<Pwl::Point> points;
>         size_t bestPoint = 0;
> -       double t = currentMode_->ctLo;
> +       double t = range[0];
>         int spanR = -1;
>         int spanB = -1;
>         LimitsRecorder<double> errorLimits;
> @@ -345,14 +333,14 @@ double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) cons
>                 if (points.back().y() < points[bestPoint].y())
>                         bestPoint = points.size() - 1;
>  
> -               if (t == currentMode_->ctHi)
> +               if (t == range[1])
>                         break;
>  
>                 /*
>                  * Ensure even steps along the r/b curve by scaling them by the
>                  * current t.
>                  */
> -               t = std::min(t + t / 10 * kSearchStep, currentMode_->ctHi);
> +               t = std::min(t + t / 10 * kSearchStep, range[1]);
>         }
>  
>         t = points[bestPoint].x();
> diff --git a/src/ipa/libipa/awb_bayes.h b/src/ipa/libipa/awb_bayes.h
> index 1e3373676bc0..8d4cff076d4a 100644
> --- a/src/ipa/libipa/awb_bayes.h
> +++ b/src/ipa/libipa/awb_bayes.h
> @@ -20,22 +20,24 @@ namespace libcamera {
>  
>  namespace ipa {
>  
> -class AwbBayes : public AwbAlgorithm
> +class AwbBayes : public AwbImplementation
>  {
>  public:
>         AwbBayes() = default;
>  
>         int init(const ValueNode &tuningData) override;
> -       AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) override;
> +       AwbImplementation::Result
> +       calculateAwb(const AwbStats &stats, unsigned int lux,
> +                    std::array<double, 2> range) override;
>         std::optional<RGB<double>> gainsFromColourTemperature(double temperatureK) override;
> -       void handleControls(const ControlList &controls) override;
>  
>  private:
>         int readPriors(const ValueNode &tuningData);
>  
>         void fineSearch(double &t, double &r, double &b, ipa::Pwl const &prior,
>                         const AwbStats &stats) const;
> -       double coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) const;
> +       double coarseSearch(const ipa::Pwl &prior, const AwbStats &stats,
> +                           Span<double> range) const;
>         double interpolateQuadratic(ipa::Pwl::Point const &a,
>                                     ipa::Pwl::Point const &b,
>                                     ipa::Pwl::Point const &c) const;
> @@ -50,8 +52,6 @@ private:
>  
>         double transversePos_;
>         double transverseNeg_;
> -
> -       ModeConfig *currentMode_ = nullptr;
>  };
>  
>  } /* namespace ipa */
> diff --git a/src/ipa/libipa/awb_grey.cpp b/src/ipa/libipa/awb_grey.cpp
> index b14b096810ae..1e4af717f62d 100644
> --- a/src/ipa/libipa/awb_grey.cpp
> +++ b/src/ipa/libipa/awb_grey.cpp
> @@ -60,6 +60,7 @@ int AwbGrey::init(const ValueNode &tuningData)
>   * \brief Calculate AWB data from the given statistics
>   * \param[in] stats The statistics to use for the calculation
>   * \param[in] lux The lux value of the scene
> + * \param[in] range The colour temperature search limits
>   *
>   * The colour temperature is estimated based on the colours::estimateCCT()
>   * function. The gains are calculated purely based on the RGB means provided by
> @@ -70,20 +71,23 @@ int AwbGrey::init(const ValueNode &tuningData)
>   *
>   * \return The AWB result
>   */
> -AwbResult AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned int lux)
> +AwbImplementation::Result
> +AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned int lux,
> +                     [[maybe_unused]] std::array<double, 2> range)
>  {
> -       AwbResult result;
> +       AwbImplementation::Result result;
>         auto means = stats.rgbMeans();
> -       result.colourTemperature = estimateCCT(means);
> +       result.temperatureK = estimateCCT(means);
>  
>         /*
> -        * Estimate the red and blue gains to apply in a grey world. The green
> -        * gain is hardcoded to 1.0. Avoid divisions by zero by clamping the
> -        * divisor to a minimum value of 1.0.
> +        * Calculate the red and blue gains to apply in a grey world by simply
> +        * inverting the red/green and blue/green ratios as reported in
> +        * statistics. The green gain is hardcoded to 1.0.
>          */
>         result.gains.r() = means.g() / std::max(means.r(), 1.0);
>         result.gains.g() = 1.0;
>         result.gains.b() = means.g() / std::max(means.b(), 1.0);
> +
>         return result;
>  }
>  
> @@ -93,15 +97,16 @@ AwbResult AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned
>   *
>   * Compute the white balance gains from a \a colourTemperature. This function
>   * does not take any statistics into account. It simply interpolates the colour
> - * gains configured in the colour temperature curve.
> + * gains curve (if provided in the tuning file) on a new colour temperature.
>   *
> - * \return The colour gains if a colour temperature curve is available,
> - * [1, 1, 1] otherwise.
> + * \return The RGB gain values adjusted to \a temperatureK or std::nullopt if
> + * no gain curve is specified in the tuning data
>   */
>  std::optional<RGB<double>> AwbGrey::gainsFromColourTemperature(double colourTemperature)
>  {
>         if (!colourGainCurve_) {
> -               LOG(Awb, Error) << "No gains defined";
> +               LOG(Awb, Info) << "No gains curve defined, "
> +                              << "unable to interpolate gains to colour temperature";
>                 return std::nullopt;
>         }
>  
> diff --git a/src/ipa/libipa/awb_grey.h b/src/ipa/libipa/awb_grey.h
> index 154a2af97f15..d1b0088f0470 100644
> --- a/src/ipa/libipa/awb_grey.h
> +++ b/src/ipa/libipa/awb_grey.h
> @@ -18,13 +18,15 @@ namespace libcamera {
>  
>  namespace ipa {
>  
> -class AwbGrey : public AwbAlgorithm
> +class AwbGrey : public AwbImplementation
>  {
>  public:
>         AwbGrey() = default;
>  
>         int init(const ValueNode &tuningData) override;
> -       AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) override;
> +       AwbImplementation::Result
> +       calculateAwb(const AwbStats &stats, unsigned int lux,
> +                    std::array<double, 2> range) override;
>         std::optional<RGB<double>> gainsFromColourTemperature(double colourTemperature) override;
>  
>  private:
> diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp
> index 0eb05c2b6e1a..ae9f6a0c243e 100644
> --- a/src/ipa/rkisp1/algorithms/awb.cpp
> +++ b/src/ipa/rkisp1/algorithms/awb.cpp
> @@ -8,17 +8,12 @@
>  #include "awb.h"
>  
>  #include <algorithm>
> -#include <ios>
>  
>  #include <libcamera/base/log.h>
>  
> -#include <libcamera/control_ids.h>
> -
>  #include <libcamera/ipa/core_ipa_interface.h>
>  
> -#include "libipa/awb_bayes.h"
> -#include "libipa/awb_grey.h"
> -#include "libipa/colours.h"
> +#include "libcamera/internal/vector.h"
>  
>  /**
>   * \file awb.h
> @@ -28,26 +23,16 @@ namespace libcamera {
>  
>  namespace ipa::rkisp1::algorithms {
>  
> -/**
> - * \class Awb
> - * \brief Manage the white balance with automatic and manual controls
> - */
> -
>  LOG_DEFINE_CATEGORY(RkISP1Awb)
>  
> -constexpr int32_t kMinColourTemperature = 2500;
> -constexpr int32_t kMaxColourTemperature = 10000;
> -constexpr int32_t kDefaultColourTemperature = 5000;
> -
> -/* Minimum mean value below which AWB can't operate. */
> -constexpr double kMeanMinThreshold = 2.0;
> -
>  class RkISP1AwbStats final : public AwbStats
>  {
>  public:
> -       RkISP1AwbStats(const RGB<double> &rgbMeans)
> -               : rgbMeans_(rgbMeans)
> +       RkISP1AwbStats(const IPAFrameContext &frameContext,
> +                      const rkisp1_stat_buffer *stats)
>         {
> +               calculateRgbMeans(frameContext, stats);
> +
>                 rg_ = rgbMeans_.r() / rgbMeans_.g();
>                 bg_ = rgbMeans_.b() / rgbMeans_.g();
>         }
> @@ -65,17 +50,106 @@ public:
>                 return delta2;
>         }
>  
> -       RGB<double> rgbMeans() const override
> +       bool valid() const override
>         {
> -               return rgbMeans_;
> +               /* Minimum mean value below which AWB can't operate. */
> +               constexpr double minValue = 2.0;

As rgbMeans shall now be in the [0, 1] range, this min value is not
reachable anymore. I seem to miss the normalization code. As the
validity test is no longer platform dependent we might want to remove
the valid() function and move the test into the core algorithm?

> +
> +               return rgbMeans_.r() > minValue || rgbMeans_.g() > minValue ||
> +                      rgbMeans_.b() > minValue;
>         }
>  
>  private:
> -       RGB<double> rgbMeans_;
> -       double rg_;
> -       double bg_;
> +       void calculateRgbMeans(const IPAFrameContext &frameContext,
> +                              const rkisp1_stat_buffer *stats);
> +       bool rgbMode_ = false;

rgbMode_ is now duplicated and lost its connection to Awb::rgbMode_. One
solution might be to pass the measurement mode to the constructor?

>  };
>  
> +void RkISP1AwbStats::calculateRgbMeans(const IPAFrameContext &frameContext,
> +                                      const rkisp1_stat_buffer *stats)
> +{
> +       if (!stats || !(stats->meas_type & RKISP1_CIF_ISP_STAT_AWB)) {
> +               LOG(RkISP1Awb, Error) << "AWB data is missing in statistics";
> +               return;
> +       }
> +
> +       const rkisp1_cif_isp_awb_stat *awb = &stats->params.awb;
> +
> +       if (awb->awb_mean[0].cnt == 0) {
> +               LOG(RkISP1Awb, Debug) << "AWB statistics are empty";
> +               return;

I think the RGB class doesn't zero initialize it's components. So this
early return now leads to possibly valid stats with random data in them
- or did I miss something?

> +       }
> +
> +       if (rgbMode_) {
> +               rgbMeans_ = {{
> +                       static_cast<double>(awb->awb_mean[0].mean_cr_or_r),
> +                       static_cast<double>(awb->awb_mean[0].mean_y_or_g),
> +                       static_cast<double>(awb->awb_mean[0].mean_cb_or_b)
> +               }};
> +       } else {
> +               /* Get the YCbCr mean values */
> +               Vector<double, 3> yuvMeans({
> +                       static_cast<double>(awb->awb_mean[0].mean_y_or_g),
> +                       static_cast<double>(awb->awb_mean[0].mean_cb_or_b),
> +                       static_cast<double>(awb->awb_mean[0].mean_cr_or_r)
> +               });
> +
> +               /*
> +                * Convert from YCbCr to RGB. The hardware uses the following
> +                * formulas:
> +                *
> +                * Y  =  16 + 0.2500 R + 0.5000 G + 0.1094 B
> +                * Cb = 128 - 0.1406 R - 0.2969 G + 0.4375 B
> +                * Cr = 128 + 0.4375 R - 0.3750 G - 0.0625 B
> +                *
> +                * This seems to be based on limited range BT.601 with Q1.6
> +                * precision.
> +                *
> +                * The inverse matrix is:
> +                *
> +                * [[1,1636, -0,0623,  1,6008]
> +                *  [1,1636, -0,4045, -0,7949]
> +                *  [1,1636,  1,9912, -0,0250]]
> +                */
> +               static const Matrix<double, 3, 3> yuv2rgbMatrix({
> +                       1.1636, -0.0623,  1.6008,
> +                       1.1636, -0.4045, -0.7949,
> +                       1.1636,  1.9912, -0.0250
> +               });
> +               static const Vector<double, 3> yuv2rgbOffset({
> +                       16, 128, 128
> +               });
> +
> +               rgbMeans_ = yuv2rgbMatrix * (yuvMeans - yuv2rgbOffset);
> +
> +               /*
> +                * Due to hardware rounding errors in the YCbCr means, the
> +                * calculated RGB means may be negative. This would lead to
> +                * negative gains, messing up calculation. Prevent this by
> +                * clamping the means to positive values.
> +                */
> +               rgbMeans_ = rgbMeans_.max(0.0);
> +       }
> +
> +       /*
> +        * The ISP computes the AWB means after applying the CCM. Apply the
> +        * inverse as we want to get the raw means before the colour gains.
> +        */
> +       rgbMeans_ = frameContext.ccm.ccm.inverse() * rgbMeans_;
> +
> +       /*
> +        * The ISP computes the AWB means after applying the colour gains,
> +        * divide by the gains that were used to get the raw means from the
> +        * sensor. Apply a minimum value to avoid divisions by near-zero.
> +        */
> +       rgbMeans_ /= frameContext.awb.gains.max(0.01);

I think this misses the normalization with awb_mean[0].cnt.

> +}
> +
> +/**
> + * \class Awb
> + * \brief Manage the white balance with automatic and manual controls
> + */
> +
>  Awb::Awb()
>         : rgbMode_(false)
>  {
> @@ -86,40 +160,7 @@ Awb::Awb()
>   */
>  int Awb::init(IPAContext &context, const ValueNode &tuningData)
>  {
> -       auto &cmap = context.ctrlMap;
> -       cmap[&controls::ColourTemperature] = ControlInfo(kMinColourTemperature,
> -                                                        kMaxColourTemperature,
> -                                                        kDefaultColourTemperature);
> -       cmap[&controls::AwbEnable] = ControlInfo(false, true);
> -
> -       cmap[&controls::ColourGains] = ControlInfo(0.0f, 3.996f,
> -                                                  Span<const float, 2>{ { 1.0f, 1.0f } });
> -
> -       if (!tuningData.contains("algorithm"))
> -               LOG(RkISP1Awb, Info) << "No AWB algorithm specified."
> -                                    << " Default to grey world";
> -
> -       auto mode = tuningData["algorithm"].get<std::string>("grey");
> -       if (mode == "grey") {
> -               awbAlgo_ = std::make_unique<AwbGrey>();
> -       } else if (mode == "bayes") {
> -               awbAlgo_ = std::make_unique<AwbBayes>();
> -       } else {
> -               LOG(RkISP1Awb, Error) << "Unknown AWB algorithm: " << mode;
> -               return -EINVAL;
> -       }
> -       LOG(RkISP1Awb, Debug) << "Using AWB algorithm: " << mode;
> -
> -       int ret = awbAlgo_->init(tuningData);
> -       if (ret) {
> -               LOG(RkISP1Awb, Error) << "Failed to init AWB algorithm";
> -               return ret;
> -       }
> -
> -       const auto &src = awbAlgo_->controls();
> -       cmap.insert(src.begin(), src.end());
> -
> -       return 0;
> +       return awbAlgo_.init(tuningData, context.ctrlMap);
>  }
>  
>  /**
> @@ -128,16 +169,7 @@ int Awb::init(IPAContext &context, const ValueNode &tuningData)
>  int Awb::configure(IPAContext &context,
>                    const IPACameraSensorInfo &configInfo)
>  {
> -       context.activeState.awb.manual.gains = RGB<double>{ 1.0 };
> -       auto gains = awbAlgo_->gainsFromColourTemperature(kDefaultColourTemperature);
> -       if (gains)
> -               context.activeState.awb.automatic.gains = *gains;
> -       else
> -               context.activeState.awb.automatic.gains = RGB<double>{ 1.0 };
> -
> -       context.activeState.awb.autoEnabled = true;
> -       context.activeState.awb.manual.temperatureK = kDefaultColourTemperature;
> -       context.activeState.awb.automatic.temperatureK = kDefaultColourTemperature;
> +       awbAlgo_.configure(context.activeState.awb);
>  
>         /*
>          * Define the measurement window for AWB as a centered rectangle
> @@ -156,56 +188,12 @@ int Awb::configure(IPAContext &context,
>  /**
>   * \copydoc libcamera::ipa::Algorithm::queueRequest
>   */
> -void Awb::queueRequest(IPAContext &context,
> -                      [[maybe_unused]] const uint32_t frame,
> +void Awb::queueRequest(IPAContext &context, const uint32_t frame,
>                        IPAFrameContext &frameContext,
>                        const ControlList &controls)
>  {
> -       auto &awb = context.activeState.awb;
> -
> -       const auto &awbEnable = controls.get(controls::AwbEnable);
> -       if (awbEnable && *awbEnable != awb.autoEnabled) {
> -               awb.autoEnabled = *awbEnable;
> -
> -               LOG(RkISP1Awb, Debug)
> -                       << (*awbEnable ? "Enabling" : "Disabling") << " AWB";
> -       }
> -
> -       awbAlgo_->handleControls(controls);
> -
> -       frameContext.awb.autoEnabled = awb.autoEnabled;
> -
> -       if (awb.autoEnabled)
> -               return;
> -
> -       const auto &colourGains = controls.get(controls::ColourGains);
> -       const auto &colourTemperature = controls.get(controls::ColourTemperature);
> -       bool update = false;
> -       if (colourGains) {
> -               awb.manual.gains.r() = (*colourGains)[0];
> -               awb.manual.gains.b() = (*colourGains)[1];
> -               /*
> -                * \todo Colour temperature reported in metadata is now
> -                * incorrect, as we can't deduce the temperature from the gains.
> -                * This will be fixed with the bayes AWB algorithm.
> -                */
> -               update = true;
> -       } else if (colourTemperature) {
> -               awb.manual.temperatureK = *colourTemperature;
> -               const auto &gains = awbAlgo_->gainsFromColourTemperature(*colourTemperature);
> -               if (gains) {
> -                       awb.manual.gains.r() = gains->r();
> -                       awb.manual.gains.b() = gains->b();
> -                       update = true;
> -               }
> -       }
> -
> -       if (update)
> -               LOG(RkISP1Awb, Debug)
> -                       << "Set colour gains to " << awb.manual.gains;
> -
> -       frameContext.awb.gains = awb.manual.gains;
> -       frameContext.awb.temperatureK = awb.manual.temperatureK;
> +       awbAlgo_.queueRequest(context.activeState.awb, frame, frameContext.awb,
> +                             controls);
>  }
>  
>  /**
> @@ -214,15 +202,7 @@ void Awb::queueRequest(IPAContext &context,
>  void Awb::prepare(IPAContext &context, const uint32_t frame,
>                   IPAFrameContext &frameContext, RkISP1Params *params)
>  {
> -       /*
> -        * This is the latest time we can read the active state. This is the
> -        * most up-to-date automatic values we can read.
> -        */
> -       if (frameContext.awb.autoEnabled) {
> -               const auto &awb = context.activeState.awb;
> -               frameContext.awb.gains = awb.automatic.gains;
> -               frameContext.awb.temperatureK = awb.automatic.temperatureK;
> -       }
> +       awbAlgo_.prepare(context.activeState.awb, frameContext.awb);
>  
>         auto gainConfig = params->block<BlockType::AwbGain>();
>         gainConfig.setEnabled(true);
> @@ -291,136 +271,12 @@ void Awb::process(IPAContext &context,
>                   const rkisp1_stat_buffer *stats,
>                   ControlList &metadata)
>  {
> -       IPAActiveState &activeState = context.activeState;
> -
> -       metadata.set(controls::AwbEnable, frameContext.awb.autoEnabled);
> -       metadata.set(controls::ColourGains, {
> -                       static_cast<float>(frameContext.awb.gains.r()),
> -                       static_cast<float>(frameContext.awb.gains.b())
> -               });
> -       metadata.set(controls::ColourTemperature, frameContext.awb.temperatureK);
> -
> -       if (!stats || !(stats->meas_type & RKISP1_CIF_ISP_STAT_AWB)) {
> -               LOG(RkISP1Awb, Error) << "AWB data is missing in statistics";
> -               return;
> -       }
> -
> -       const rkisp1_cif_isp_stat *params = &stats->params;
> -       const rkisp1_cif_isp_awb_stat *awb = &params->awb;
> +       RkISP1AwbStats awbStats(frameContext, stats);
>  
> -       if (awb->awb_mean[0].cnt == 0) {
> -               LOG(RkISP1Awb, Debug) << "AWB statistics are empty";
> -               return;
> -       }
> -
> -       RGB<double> rgbMeans = calculateRgbMeans(frameContext, awb);
> -
> -       /*
> -        * If the means are too small we don't have enough information to
> -        * meaningfully calculate gains. Freeze the algorithm in that case.
> -        */
> -       if (rgbMeans.r() < kMeanMinThreshold && rgbMeans.g() < kMeanMinThreshold &&
> -           rgbMeans.b() < kMeanMinThreshold)
> -               return;
> -
> -       RkISP1AwbStats awbStats{ rgbMeans };
> -       AwbResult awbResult = awbAlgo_->calculateAwb(awbStats, frameContext.lux.lux);
> -
> -       /*
> -        * Clamp the gain values to the hardware, which expresses gains as Q2.8
> -        * unsigned integer values. Set the minimum just above zero to avoid
> -        * divisions by zero when computing the raw means in subsequent
> -        * iterations.
> -        */
> -       awbResult.gains = awbResult.gains.clamp(1.0 / 256, 1023.0 / 256);
> -
> -       /* Filter the values to avoid oscillations. */
> -       double speed = 0.2;
> -       double ct = awbResult.colourTemperature;
> -       ct = ct * speed + activeState.awb.automatic.temperatureK * (1 - speed);
> -       awbResult.gains = awbResult.gains * speed +
> -                         activeState.awb.automatic.gains * (1 - speed);
> -
> -       activeState.awb.automatic.temperatureK = static_cast<unsigned int>(ct);
> -       activeState.awb.automatic.gains = awbResult.gains;
> -
> -       LOG(RkISP1Awb, Debug)
> -               << std::showpoint
> -               << "Means " << rgbMeans << ", gains "
> -               << activeState.awb.automatic.gains << ", temp "
> -               << activeState.awb.automatic.temperatureK << "K";
> +       awbAlgo_.process(context.activeState.awb, frameContext.awb, awbStats,
> +                        frameContext.lux.lux, metadata);
>  }
>  
> -RGB<double> Awb::calculateRgbMeans(const IPAFrameContext &frameContext, const rkisp1_cif_isp_awb_stat *awb) const
> -{
> -       Vector<double, 3> rgbMeans;
> -
> -       if (rgbMode_) {
> -               rgbMeans = {{
> -                       static_cast<double>(awb->awb_mean[0].mean_cr_or_r),
> -                       static_cast<double>(awb->awb_mean[0].mean_y_or_g),
> -                       static_cast<double>(awb->awb_mean[0].mean_cb_or_b)
> -               }};
> -       } else {
> -               /* Get the YCbCr mean values */
> -               Vector<double, 3> yuvMeans({
> -                       static_cast<double>(awb->awb_mean[0].mean_y_or_g),
> -                       static_cast<double>(awb->awb_mean[0].mean_cb_or_b),
> -                       static_cast<double>(awb->awb_mean[0].mean_cr_or_r)
> -               });
> -
> -               /*
> -                * Convert from YCbCr to RGB. The hardware uses the following
> -                * formulas:
> -                *
> -                * Y  =  16 + 0.2500 R + 0.5000 G + 0.1094 B
> -                * Cb = 128 - 0.1406 R - 0.2969 G + 0.4375 B
> -                * Cr = 128 + 0.4375 R - 0.3750 G - 0.0625 B
> -                *
> -                * This seems to be based on limited range BT.601 with Q1.6
> -                * precision.
> -                *
> -                * The inverse matrix is:
> -                *
> -                * [[1,1636, -0,0623,  1,6008]
> -                *  [1,1636, -0,4045, -0,7949]
> -                *  [1,1636,  1,9912, -0,0250]]
> -                */
> -               static const Matrix<double, 3, 3> yuv2rgbMatrix({
> -                       1.1636, -0.0623,  1.6008,
> -                       1.1636, -0.4045, -0.7949,
> -                       1.1636,  1.9912, -0.0250
> -               });
> -               static const Vector<double, 3> yuv2rgbOffset({
> -                       16, 128, 128
> -               });
> -
> -               rgbMeans = yuv2rgbMatrix * (yuvMeans - yuv2rgbOffset);
> -
> -               /*
> -                * Due to hardware rounding errors in the YCbCr means, the
> -                * calculated RGB means may be negative. This would lead to
> -                * negative gains, messing up calculation. Prevent this by
> -                * clamping the means to positive values.
> -                */
> -               rgbMeans = rgbMeans.max(0.0);
> -       }
> -
> -       /*
> -        * The ISP computes the AWB means after applying the CCM. Apply the
> -        * inverse as we want to get the raw means before the colour gains.
> -        */
> -       rgbMeans = frameContext.ccm.ccm.inverse() * rgbMeans;
> -
> -       /*
> -        * The ISP computes the AWB means after applying the colour gains,
> -        * divide by the gains that were used to get the raw means from the
> -        * sensor. Apply a minimum value to avoid divisions by near-zero.
> -        */
> -       rgbMeans /= frameContext.awb.gains.max(0.01);
> -
> -       return rgbMeans;
> -}

Moving this big function would have been an easy cut out into its own
patch?

Best regards,
Stefan

>  
>  REGISTER_IPA_ALGORITHM(Awb, "Awb")
>  
> diff --git a/src/ipa/rkisp1/algorithms/awb.h b/src/ipa/rkisp1/algorithms/awb.h
> index 60d9ef111495..c7cb59a75bde 100644
> --- a/src/ipa/rkisp1/algorithms/awb.h
> +++ b/src/ipa/rkisp1/algorithms/awb.h
> @@ -7,17 +7,25 @@
>  
>  #pragma once
>  
> -#include "libcamera/internal/vector.h"
> +#include <linux/rkisp1-config.h>
> +
> +#include <libcamera/controls.h>
> +
> +#include "libcamera/internal/value_node.h"
>  
>  #include "libipa/awb.h"
> -#include "libipa/interpolator.h"
> +#include "libipa/fixedpoint.h"
>  
>  #include "algorithm.h"
> +#include "ipa_context.h"
> +#include "params.h"
>  
>  namespace libcamera {
>  
>  namespace ipa::rkisp1::algorithms {
>  
> +class RkISP1AwbStats;
> +
>  class Awb : public Algorithm
>  {
>  public:
> @@ -38,10 +46,10 @@ public:
>                      ControlList &metadata) override;
>  
>  private:
> -       RGB<double> calculateRgbMeans(const IPAFrameContext &frameContext,
> -                                     const rkisp1_cif_isp_awb_stat *awb) const;
> +       RkISP1AwbStats calculateRgbMeans(const IPAFrameContext &frameContext,
> +                                        const rkisp1_stat_buffer *stats) const;
>  
> -       std::unique_ptr<AwbAlgorithm> awbAlgo_;
> +       AwbAlgorithm<UQ<2, 8>> awbAlgo_;
>  
>         bool rgbMode_;
>  };
> diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h
> index e5f70d934d67..c36c1f7e0084 100644
> --- a/src/ipa/rkisp1/ipa_context.h
> +++ b/src/ipa/rkisp1/ipa_context.h
> @@ -25,6 +25,7 @@
>  #include "libcamera/internal/vector.h"
>  
>  #include "libipa/agc_mean_luminance.h"
> +#include "libipa/awb.h"
>  #include "libipa/camera_sensor_helper.h"
>  #include "libipa/fc_queue.h"
>  #include "libipa/fixedpoint.h"
> @@ -48,15 +49,17 @@ struct IPAHwSettings {
>         bool compand;
>  };
>  
> +struct RKISP1AwbSession {
> +       struct rkisp1_cif_isp_window measureWindow;
> +       bool enabled;
> +};
> +
>  struct IPASessionConfiguration {
>         struct {
>                 struct rkisp1_cif_isp_window measureWindow;
>         } agc;
>  
> -       struct {
> -               struct rkisp1_cif_isp_window measureWindow;
> -               bool enabled;
> -       } awb;
> +       struct RKISP1AwbSession awb;
>  
>         struct {
>                 bool supported;
> @@ -100,17 +103,7 @@ struct IPAActiveState {
>                 utils::Duration maxFrameDuration;
>         } agc;
>  
> -       struct {
> -               struct AwbState {
> -                       RGB<double> gains;
> -                       unsigned int temperatureK;
> -               };
> -
> -               AwbState manual;
> -               AwbState automatic;
> -
> -               bool autoEnabled;
> -       } awb;
> +       ipa::awb::ActiveState awb;
>  
>         struct {
>                 Matrix<float, 3, 3> manual;
> @@ -176,11 +169,7 @@ struct IPAFrameContext : public FrameContext {
>                 bool autoGainModeChange;
>         } agc;
>  
> -       struct {
> -               RGB<double> gains;
> -               bool autoEnabled;
> -               unsigned int temperatureK;
> -       } awb;
> +       ipa::awb::FrameContext awb;
>  
>         struct {
>                 float actualBrightness;
> 
> -- 
> 2.54.0
>
Jacopo Mondi July 7, 2026, 7:36 a.m. UTC | #2
Hi Stefan

On Tue, Jul 07, 2026 at 08:31:10AM +0200, Stefan Klug wrote:
> Hi Jacopo,
>
> Thank you for the patch.
>
> Quoting Jacopo Mondi (2026-07-06 10:01:21)
> > The current implementation of AwbAlgorithm is the following one:
> >
> > AwbAlgorithm o---- RkISP1Awb
> >     ^
> >     |
> >  |-----|
> >  |     |
> > Bayes Grey
> >
> > The platform-specific Awb implementation instantiate one of
> > the Bayes or Grey Awb helper classes and both inherit from the
> > AwbAlgorithm base class.
> >
> > The handling of libcamera controls is split between the base and the
> > derived (mostly Bayes) class.
> >
> > We want instead the AwbAlgorithm to handle all-things-libcamera and let
> > the Bayes and Grey classes implement the logic. In order to achieve that
> > use composition also in the AwbAlgorithm class and introduce an
> > AwbImplementation base class from which Bayes and Grey derive from.
> >
> > AwbImplementation o---- AwbAlgorithmBase
> >     ^                           ^
> >     |                           |
> >  |-----|                AwbAlgorithm<Q<x,y>> o---- RkISP1Awb
> >  |     |
> > Bayes Grey
> >
> > The AwbAlgorithm<> class is instantiated with a Q<x, y> template
> > argument that represents the platform-specific register format which
> > is used to initialize controls and clamp gains to the hardware limits.
> >
> > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> > Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
> > Tested-by: Daniel Scally <dan.scally@ideasonboard.com>
> > ---
> >  src/ipa/libipa/awb.cpp            | 472 ++++++++++++++++++++++++++++++++------
> >  src/ipa/libipa/awb.h              | 109 +++++++--
> >  src/ipa/libipa/awb_bayes.cpp      |  52 ++---
> >  src/ipa/libipa/awb_bayes.h        |  12 +-
> >  src/ipa/libipa/awb_grey.cpp       |  25 +-
> >  src/ipa/libipa/awb_grey.h         |   6 +-
> >  src/ipa/rkisp1/algorithms/awb.cpp | 360 +++++++++--------------------
> >  src/ipa/rkisp1/algorithms/awb.h   |  18 +-
> >  src/ipa/rkisp1/ipa_context.h      |  29 +--
> >  9 files changed, 672 insertions(+), 411 deletions(-)
> >
> > diff --git a/src/ipa/libipa/awb.cpp b/src/ipa/libipa/awb.cpp
> > index 29a78069c52c..95291840d8fb 100644
> > --- a/src/ipa/libipa/awb.cpp
> > +++ b/src/ipa/libipa/awb.cpp
> > @@ -2,7 +2,7 @@
> >  /*
> >   * Copyright (C) 2024 Ideas on Board Oy
> >   *
> > - * Generic AWB algorithms
> > + * libIPA Awb algorithms
> >   */
> >
> >  #include "awb.h"
> > @@ -11,9 +11,16 @@
> >
> >  #include <libcamera/control_ids.h>
> >
> > +#include "awb_bayes.h"
> > +#include "awb_grey.h"
> > +
> > +constexpr int32_t kMinColourTemperature = 2500;
> > +constexpr int32_t kMaxColourTemperature = 10000;
> > +constexpr int32_t kDefaultColourTemperature = 5000;
> > +
> >  /**
> >   * \file awb.h
> > - * \brief Base classes for AWB algorithms
> > + * \brief libipa awb implementation
> >   */
> >
> >  namespace libcamera {
> > @@ -22,30 +29,60 @@ LOG_DEFINE_CATEGORY(Awb)
> >
> >  namespace ipa {
> >
> > +namespace awb {
> > +
> >  /**
> > - * \class AwbResult
> > - * \brief The result of an AWB calculation
> > + * \struct Context
> > + * \brief Awb gains and colour temperature
> > + *
> > + * \var Context::gains
> > + * \brief The white balance gains
> >   *
> > - * This class holds the result of an auto white balance calculation.
> > + * \var Context::temperatureK
> > + * \brief The colour temperature, in Kelvin
> >   */
> >
> >  /**
> > - * \var AwbResult::gains
> > - * \brief The calculated white balance gains
> > + * \struct ActiveState
> > + * \brief Active awb state shared across frames
> > + *
> > + * \var ActiveState::manual
> > + * \brief The most recent manually requested awb state
> > + *
> > + * \var ActiveState::automatic
> > + * \brief The most recent automatically calculated awb state
> > + *
> > + * \var ActiveState::autoEnabled
> > + * \brief True when automatic awb is selected
> >   */
> >
> >  /**
> > - * \var AwbResult::colourTemperature
> > - * \brief The calculated colour temperature in Kelvin
> > + * \struct FrameContext
> > + * \brief Per-frame awb state
> > + *
> > + * \var FrameContext::autoEnabled
> > + * \brief True when automatic awb is in use
> >   */
> >
> > +} /* namespace awb */
> > +
> >  /**
> >   * \class AwbStats
> > - * \brief An abstraction class wrapping hardware-specific AWB statistics
> > + * \brief An abstraction class wrapping hardware-specific awb statistics
>
> Out of curiosity. I was told that abbreviations like AWB are always
> capitalized in writing. Is that incorrect?

I'm not sure. But we had a mix of "Awb", "AWB" and "awb" which annoyed
me. So I went for a single version (wherever I noticed).

We can decide what's best.

>
> >   *
> > - * IPA modules using an AWB algorithm based on the AwbAlgorithm class need to
> > - * implement this class to give the algorithm access to the hardware-specific
> > - * statistics data.
> > + * IPA modules shall provide a derived implementation of this class to give the
> > + * awb algorithm access to the hardware-specific statistics data in a common
> > + * format.
> > + */
> > +
> > +/**
> > + * \fn AwbStats::AwbStats()
> > + * \brief Construct an empty AwbStat
> > + */
> > +
> > +/**
> > + * AwbStat::~AwbStat
> > + * \brief Virtual class destructor
> >   */
> >
> >  /**
> > @@ -57,7 +94,7 @@ namespace ipa {
> >   * applied. To keep the actual implementations computationally inexpensive,
> >   * the squared colour error shall be returned.
> >   *
> > - * If the AWB statistics provide multiple zones, the average of the individual
> > + * If the awb statistics provide multiple zones, the average of the individual
> >   * squared errors shall be returned. Averaging/normalizing is necessary so that
> >   * the numeric dimensions are the same on all hardware platforms.
> >   *
> > @@ -65,79 +102,328 @@ namespace ipa {
> >   */
> >
> >  /**
> > - * \fn AwbStats::rgbMeans()
> > - * \brief Get RGB means of the statistics
> > + * \fn AwbStats::valid()
> > + * \brief Retrieve if the awb statistics are valid
> >   *
> > - * Fetch the RGB means from the statistics. The values of each channel are
> > - * dimensionless and only the ratios are used for further calculations. This is
> > - * used by the simple grey world model to calculate the gains to apply.
> > + * If the colour mean values are too small, the AwbAlgorithm class doesn't have
> > + * enough information to meaningfully calculate white-balance gains. Freeze the
> > + * algorithm in that case.
> > + *
> > + * \return True if the awb statistics are valid, false otherwise
> > + */
> > +
> > +/**
> > + * \fn AwbStats::rgbMeans
> > + * \brief Retrieve the RGB means values
> > + *
> > + * Fetch the RGB means from the statistics in the [0, 1] range.
> >   *
> >   * \return The RGB means
> >   */
> >
> >  /**
> > - * \class AwbAlgorithm
> > - * \brief A base class for auto white balance algorithms
> > + * \var AwbStats::rgbMeans_
> > + * \brief Mean values of colour channels
> > + */
> > +
> > +/**
> > + * \var AwbStats::rg_
> > + * \brief Red/Green ratio
> > + */
> > +
> > +/**
> > + * \var AwbStats::bg_
> > + * \brief Blue/Green ratio
> > + */
> > +
> > +/**
> > + * \class AwbImplementation
> > + * \brief Pure virtual base class for awb algorithms implementations
> > + *
> > + * The AwbImplementation class defines the interface for the awb algorithm
> > + * implementations.
> >   *
> > - * This class is a base class for auto white balance algorithms. It defines an
> > - * interface for the algorithms to implement, and is used by the IPAs to
> > - * interact with the concrete implementation.
> > + * It is currently implemented by the AwbGrey and AwbBayes classes.
> > + *
> > + * The interface defines an init() function to initialize the algorithm with the
> > + * content of the tuning file and two functions to compute colour gains
> > + * according to the algorithm operating mode (auto or manual) in use.
> > + *
> > + * The calculateAwb() function calculates colour gains given a set of statistics
> > + * provided by the IPA module. It is used when the algorithm operates in auto
> > + * mode and gains are dynamically computed given a new set of statistics from
> > + * the awb engine.
> > + *
> > + * The gainsFromColourTemperature() function instead computes the white balance
> > + * gains from a colour temperature. This function does not take any statistics
> > + * into account. It is used to compute the colour gains when the user manually
> > + * specifies a colour temperature.
> >   */
> >
> >  /**
> > - * \fn AwbAlgorithm::init()
> > - * \brief Initialize the algorithm with the given tuning data
> > - * \param[in] tuningData The tuning data to use for the algorithm
> > + * \struct AwbImplementation::Result
> > + * \brief The result of an awb computation
> >   *
> > - * \return 0 on success, a negative error code otherwise
> > + * \var AwbImplementation::Result::gains
> > + * \brief The computed white balance gains
> > + *
> > + * \var AwbImplementation::Result::temperatureK
> > + * \brief The computed colour temperature, in Kelvin
> >   */
> >
> >  /**
> > - * \fn AwbAlgorithm::calculateAwb()
> > - * \brief Calculate AWB data from the given statistics
> > - * \param[in] stats The statistics to use for the calculation
> > - * \param[in] lux The lux value of the scene
> > + * \fn AwbImplementation::~AwbImplementation
> > + * \brief Virtual class destructor
> > + */
> > +
> > +/**
> > + * \fn AwbImplementation::init()
> > + * \param[in] tuningData
> > + * \brief Initialize the algorithm by parsing \a tuningData
> > + */
> > +
> > +/**
> > + * \fn AwbImplementation::calculateAwb()
> > + * \param[in] stats The awb statistics
> > + * \param[in] lux The estimated lux level
> > + * \param[in] range The colour temperature search limits
> >   *
> > - * Calculate an AwbResult object from the given statistics and lux value. A \a
> > - * lux value of 0 means it is unknown or invalid and the algorithm shall ignore
> > - * it.
> > + * Calculate a new set of colour gains and a colour temperature given a new
> > + * set of statistics \a stats, an estimated luminance \a lux and a range of
> > + * colour temperature to limit the search.
> >   *
> > - * \return The AWB result
> > + * \return A Result computed using the new statistics
> > + */
> > +/**
> > + * \fn AwbImplementation::gainsFromColourTemperature()
> > + * \param[in] temperatureK Colour temperature in Kelvin
> > + *
> > + * Calculate a new set of colour gains for a new manually specified colour
> > + * temperature \a temperatureK.
> > + *
> > + * \return The RGB gain values adjusted to \a temperatureK or std::nullopt
> > + */
> > +
> > +/**
> > + * \class AwbAlgorithmBase
> > + * \brief Base class for AwbAlgorithm for non-templated functions implementation
> > + *
> > + * Base class for AwbAlgorithm where non-templated functions are implemented.
> > + * IPA implementations shall use AwbAlgorithm and not this class.
> >   */
> >
> >  /**
> > - * \fn AwbAlgorithm::gainsFromColourTemperature()
> > - * \brief Compute white balance gains from a colour temperature
> > - * \param[in] colourTemperature The colour temperature in Kelvin
> > + * \brief Initialize the algorithm with the given tuning data
> > + * \param[in] tuningData The tuning data to use for the algorithm
> >   *
> > - * Compute the white balance gains from a \a colourTemperature. This function
> > - * does not take any statistics into account. It is used to compute the colour
> > - * gains when the user manually specifies a colour temperature.
> > + * Parse \a tuningData to initialize the awb algorithm and register controls.
> > + * IPA modules are expected to call this function as part of their
> > + * implementation of Algorithm::init().
> >   *
> > - * \return The colour gains or std::nullopt if the conversion is not possible
> > + * \return 0 on success, a negative error code otherwise
> > + */
> > +int AwbAlgorithmBase::init(const ValueNode &tuningData)
> > +{
> > +       bayes_ = false;
> > +
> > +       if (!tuningData.contains("algorithm"))
> > +               LOG(Awb, Info) << "No Awb algorithm specified, using grey world";
> > +
> > +       auto mode = tuningData["algorithm"].get<std::string>("grey");
> > +       if (mode == "grey") {
> > +               impl_ = std::make_unique<AwbGrey>();
> > +       } else if (mode == "bayes") {
> > +               impl_ = std::make_unique<AwbBayes>();
> > +               bayes_ = true;
> > +       } else {
> > +               LOG(Awb, Error) << "Unknown Awb algorithm: " << mode;
> > +               return -EINVAL;
> > +       }
> > +
> > +       LOG(Awb, Debug) << "Using Awb algorithm: " << mode;
> > +
> > +       int ret = impl_->init(tuningData);
> > +       if (ret)
> > +               return ret;
> > +
> > +       controls_[&controls::ColourTemperature] =
> > +               ControlInfo(kMinColourTemperature, kMaxColourTemperature,
> > +                           kDefaultColourTemperature);
> > +       controls_[&controls::AwbEnable] = ControlInfo(false, true);
> > +
> > +       return parseModeConfigs(tuningData, controls::AwbAuto);
> > +}
> > +
> > +/**
> > + * \brief Configure the awb algorithm
> > + * \param[in] state The awb active state
> > + * \return 0 if successful, an error code otherwise
> >   */
> > +int AwbAlgorithmBase::configure(awb::ActiveState &state)
> > +{
> > +       state.manual.gains = RGB<double>{ 1.0 };
> > +       auto gains = impl_->gainsFromColourTemperature(kDefaultColourTemperature);
> > +       if (gains)
> > +               state.automatic.gains = *gains;
> > +       else
> > +               state.automatic.gains = RGB<double>{ 1.0 };
> > +
> > +       state.autoEnabled = true;
> > +       state.manual.temperatureK = kDefaultColourTemperature;
> > +       state.automatic.temperatureK = kDefaultColourTemperature;
> > +
> > +       return 0;
> > +}
> >
> >  /**
> > - * \fn AwbAlgorithm::controls()
> > - * \brief Get the controls info map for this algorithm
> > + * \brief Queue a Request to the awb algorithm
> > + * \param[in] state The awb active state
> > + * \param[in] frame The frame number
> > + * \param[in] frameContext The awb frame context
> > + * \param[in] controls The list of controls part of the Request
> >   *
> > - * \return The controls info map
> > + * Queue a new Request to the awb algorithm and modify its behaviour according
> > + * to the provided controls.
> > + *
> > + * The currently handled controls are:
> > + * - controls::AwbEnable
> > + * - controls::AwbMode
> > + * - controls::ColourGains
> > + * - controls::ColourTemperature
> >   */
> > +void AwbAlgorithmBase::queueRequest(awb::ActiveState &state,
> > +                                   [[maybe_unused]] const uint32_t frame,
> > +                                   awb::FrameContext &frameContext,
> > +                                   const ControlList &controls)
> > +{
> > +       const auto &awbEnable = controls.get(controls::AwbEnable);
> > +       if (awbEnable && *awbEnable != state.autoEnabled) {
> > +               state.autoEnabled = *awbEnable;
> > +
> > +               LOG(Awb, Debug)
> > +                       << (*awbEnable ? "Enabling" : "Disabling") << " Awb";
> > +       }
> > +
> > +       auto mode = controls.get(controls::AwbMode);
> > +       if (mode) {
> > +               auto it = modes_.find(static_cast<controls::AwbModeEnum>(*mode));
> > +               if (it == modes_.end()) {
> > +                       LOG(Awb, Error) << "Unsupported Awb mode " << *mode;
> > +                       return;
> > +               }
> > +
> > +               currentMode_ = &it->second;
> > +       }
> > +
> > +       frameContext.autoEnabled = state.autoEnabled;
> > +
> > +       if (frameContext.autoEnabled)
> > +               return;
> > +
> > +       const auto &colourGains = controls.get(controls::ColourGains);
> > +       const auto &colourTemperature = controls.get(controls::ColourTemperature);
> > +       bool update = false;
> > +       if (colourGains) {
> > +               state.manual.gains.r() = (*colourGains)[0];
> > +               state.manual.gains.b() = (*colourGains)[1];
> > +               /*
> > +                * \todo Colour temperature reported in metadata is now
> > +                * incorrect, as we can't deduce the temperature from the gains.
> > +                * This will be fixed with the bayes AWB algorithm.
> > +                */
> > +               update = true;
> > +       } else if (colourTemperature) {
> > +               state.manual.temperatureK = *colourTemperature;
> > +               const auto &gains = impl_->gainsFromColourTemperature(*colourTemperature);
> > +               if (gains) {
> > +                       state.manual.gains.r() = gains->r();
> > +                       state.manual.gains.b() = gains->b();
> > +                       update = true;
> > +               }
> > +       }
> > +
> > +       if (update)
> > +               LOG(Awb, Debug)
> > +                       << "Set colour gains to " << state.manual.gains;
> > +
> > +       frameContext.gains = state.manual.gains;
> > +       frameContext.temperatureK = state.manual.temperatureK;
> > +}
> >
> >  /**
> > - * \fn AwbAlgorithm::handleControls()
> > - * \param[in] controls The controls to handle
> > - * \brief Handle the controls supplied in a request
> > + * \brief Set the gains and colour temperature values in \a frameContext
> > + * \param[in] state The awb active state
> > + * \param[in] frameContext The awb frame context
> > + *
> > + * If auto mode is enabled, take the most recently computed gains and use them
> > + * for the current frame. Otherwise, if in manual mode, gains and colour
> > + * temperature for a frame are set at queueRequest() time.
> >   */
> > +void AwbAlgorithmBase::prepare(awb::ActiveState &state,
> > +                              awb::FrameContext &frameContext)
> > +{
> > +       if (frameContext.autoEnabled) {
> > +               frameContext.gains = state.automatic.gains;
> > +               frameContext.temperatureK = state.automatic.temperatureK;
> > +       }
> > +}
> >
> >  /**
> > + * \brief Process awb statistics to calculate gains and populate metadata
> > + * \param[in] state The awb active state
> > + * \param[in] frameContext The awb frame context
> > + * \param[in] stats The awb statistics
> > + * \param[in] lux The lux value as estimated by the IPA module
> > + * \param[out] metadata The metadata list
> > + *
> > + * Process \a stats to calculate new gains and colour temperature and populate
> > + * \a metadata with the results.
> > + */
> > +void AwbAlgorithmBase::process(awb::ActiveState &state,
> > +                              awb::FrameContext &frameContext,
> > +                              const AwbStats &stats, unsigned int lux,
> > +                              ControlList &metadata)
> > +{
> > +       if (!stats.valid())
> > +               return;
> > +
> > +       auto awbResult = impl_->calculateAwb(stats, lux, { currentMode_->ctLo,
> > +                                                          currentMode_->ctHi });
> > +
> > +       /*
> > +        * Clamp the gain values to the hardware, according to the gainMin_
> > +        * and gainMax_ values.
> > +        */
> > +       awbResult.gains = awbResult.gains.clamp(gainMin_, gainMax_);
> > +
> > +       /* Smooth color gains adjustments. */
> > +       double speed = 0.2;
> > +       double ct = awbResult.temperatureK;
> > +       ct = ct * speed + state.automatic.temperatureK * (1 - speed);
> > +
> > +       state.automatic.temperatureK = awbResult.temperatureK;
> > +       state.automatic.gains = awbResult.gains * speed +
> > +                               state.automatic.gains * (1 - speed);
> > +
> > +       /* Populate metadata. */
> > +       metadata.set(controls::AwbEnable, frameContext.autoEnabled);
> > +       metadata.set(controls::ColourGains, { static_cast<float>(frameContext.gains.r()),
> > +                                             static_cast<float>(frameContext.gains.b()) });
> > +       metadata.set(controls::ColourTemperature, frameContext.temperatureK);
> > +
> > +       LOG(Awb, Debug) << std::showpoint << "Means " << stats.rgbMeans()
> > +                       << ", gains " << state.automatic.gains
> > +                       << ", temp " << state.automatic.temperatureK << "K";
> > +}
> > +
> > +/*
> >   * \brief Parse the mode configurations from the tuning data
> >   * \param[in] tuningData the ValueNode representing the tuning data
> >   * \param[in] def The default value for the AwbMode control
> >   *
> >   * Utility function to parse the tuning data for an AwbMode entry and read all
> > - * provided modes. It adds controls::AwbMode to AwbAlgorithm::controls_ and
> > - * populates AwbAlgorithm::modes_. For a list of possible modes see \ref
> > + * provided modes. It adds controls::AwbMode to AwbAlgorithmBase::controls_ and
> > + * populates AwbAlgorithmBase::modes_. For a list of possible modes see \ref
> >   * controls::AwbModeEnum.
> >   *
> >   * Each mode entry must contain a "lo" and "hi" key to specify the lower and
> > @@ -156,15 +442,23 @@ namespace ipa {
> >   *       ...
> >   * \endcode
> >   *
> > - * If \a def is supplied but not contained in the the \a tuningData, -EINVAL is
> > + * If \a def is supplied but not contained in the \a tuningData, -EINVAL is
> >   * returned.
> >   *
> > + * AwbModes are only used by the AwbBayes implementation.
> > + *
> >   * \sa controls::AwbModeEnum
> >   * \return Zero on success, negative error code otherwise
> >   */
> > -int AwbAlgorithm::parseModeConfigs(const ValueNode &tuningData,
> > -                                  const ControlValue &def)
> > +int AwbAlgorithmBase::parseModeConfigs(const ValueNode &tuningData,
> > +                                      const ControlValue &def)
> >  {
> > +       if (!bayes_) {
> > +               /* AwbGrey does not support and does not use modes. */
> > +               currentMode_ = &AwbGreyMode;
> > +               return 0;
> > +       }
> > +
> >         std::vector<ControlValue> availableModes;
> >
> >         const ValueNode &modes = tuningData[controls::AwbMode.name()];
> > @@ -227,37 +521,83 @@ int AwbAlgorithm::parseModeConfigs(const ValueNode &tuningData,
> >         }
> >
> >         controls_[&controls::AwbMode] = ControlInfo(availableModes, def);
> > +       currentMode_ = &modes_[controls::AwbAuto];
> >
> >         return 0;
> >  }
> >
> >  /**
> > - * \class AwbAlgorithm::ModeConfig
> > - * \brief Holds the configuration of a single AWB mode
> > + * \var AwbAlgorithmBase::controls_
> > + * \brief Controls info map for the controls registered by the awb algorithm
> > + */
> > +
> > +/**
> > + * \var AwbAlgorithmBase::gainMin_
> > + * \brief The minimum supported gain value
> > + *
> > + * Minimum gain value used to clamp the awb algorithm calculation results in the
> > + * range supported by the platform awb engine.
> >   *
> > - * AWB modes limit the regulation of the AWB algorithm to a specific range of
> > - * colour temperatures.
> > + * The min and max gain values are initialized by AwbAlgorithm::init().
> >   */
> >
> >  /**
> > - * \var AwbAlgorithm::ModeConfig::ctLo
> > + * \var AwbAlgorithmBase::gainMax_
> > + * \brief The maximum supported gain value
> > + *
> > + * Maximum gain value used to clamp the awb algorithm calculation results in the
> > + * range supported by the platform awb engine.
> > + *
> > + * The min and max gain values are initialized by AwbAlgorithm::init().
> > + */
> > +
> > +/*
> > + * \class AwbAlgorithmBase::ModeConfig
> > + * \brief Holds the configuration of a single awb mode
> > + *
> > + * AWB modes limit the regulation of the awb algorithm to a specific range of
> > + * colour temperatures. Use by AwbBayes only.
> > + */
> > +
> > +/*
> > + * \var AwbAlgorithmBase::ModeConfig::ctLo
> >   * \brief The lowest valid colour temperature of that mode
> >   */
> >
> > -/**
> > - * \var AwbAlgorithm::ModeConfig::ctHi
> > +/*
> > + * \var AwbAlgorithmBase::ModeConfig::ctHi
> >   * \brief The highest valid colour temperature of that mode
> >   */
> >
> > +/*
> > + * \var AwbAlgorithmBase::modes_
> > + * \brief Map of all configured modes
> > + * \sa AwbAlgorithmBase::parseModeConfigs
> > + */
> > +
> >  /**
> > - * \var AwbAlgorithm::controls_
> > - * \brief Controls info map for the controls provided by the algorithm
> > + * \class AwbAlgorithm
> > + * \brief The libipa awb algorithm
> > + * \tparam Q The fixedpoint register representation of gain values
> > + *
> > + * Implement the awb algorithm for libipa.
> > + *
> > + * The AwbAlgorithm class implements an interface similar in spirit to the one
> > + * of the Algorithm class. IPA modules are expected to store an instance of
> > + * AwbAlgorithm as class member, template it with the awb engine gain register
> > + * representation and call its function in their implementations of the
> > + * Algorithm interface.
> > + *
> > + * The AwbAlgorithm instantiates an AwbImplementation implementation (AwbGrey or
> > + * AwbBayes) at init() time by parsing the tuning data and uses it to compute
> > + * the RGB gains and estimate a colour temperature given a set of statistics
> > + * in the form of a AwbStats derived implementation.
> >   */
> >
> >  /**
> > - * \var AwbAlgorithm::modes_
> > - * \brief Map of all configured modes
> > - * \sa AwbAlgorithm::parseModeConfigs
> > + * \fn AwbAlgorithm::init()
> > + * \param[in] controls The info map of the IPA controls
> > + * \copydoc AwbAlgorithmBase::init()
> >   */
> >
> >  } /* namespace ipa */
> > diff --git a/src/ipa/libipa/awb.h b/src/ipa/libipa/awb.h
> > index 09c00e47d604..72f46367a136 100644
> > --- a/src/ipa/libipa/awb.h
> > +++ b/src/ipa/libipa/awb.h
> > @@ -2,11 +2,12 @@
> >  /*
> >   * Copyright (C) 2024 Ideas on Board Oy
> >   *
> > - * Generic AWB algorithms
> > + * libIPA AWB algorithms
> >   */
> >
> >  #pragma once
> >
> > +#include <array>
> >  #include <map>
> >  #include <optional>
> >
> > @@ -20,46 +21,118 @@ namespace libcamera {
> >
> >  namespace ipa {
> >
> > -struct AwbResult {
> > +namespace awb {
> > +
> > +struct Context {
> >         RGB<double> gains;
> > -       double colourTemperature;
> > +       unsigned int temperatureK;
> >  };
> >
> > +struct ActiveState {
> > +       Context manual;
> > +       Context automatic;
> > +
> > +       bool autoEnabled;
> > +};
> > +
> > +struct FrameContext : public Context {
> > +       bool autoEnabled;
> > +};
> > +
> > +} /* namespace awb */
> > +
> >  struct AwbStats {
> > +       AwbStats() = default;
> > +       virtual ~AwbStats() = default;
> > +
> >         virtual double computeColourError(const RGB<double> &gains) const = 0;
> > -       virtual RGB<double> rgbMeans() const = 0;
> > +       virtual bool valid() const = 0;
> > +       RGB<double> rgbMeans() const { return rgbMeans_; }
> >
> >  protected:
> > -       ~AwbStats() = default;
> > +       RGB<double> rgbMeans_;
> > +       double rg_;
> > +       double bg_;
>
> I think these (rg_ and bg_) are not used anymore and can be dropped.

I see they being used by RkISP1AwbStats::computeColourError() which I
haven't modified compared to master.

I can move rg_ and bg_ to RkISP1Stats but if you want them to be
dropped and modify computeColourError() accordingly, it will be done
on top.

> Personally I'd also leave the rgbMeans_ member in the platform
> implementation and keep it as interface only - up to you.

rgbMeans_ is used by AwbGray

>
> >  };
> >
> > -class AwbAlgorithm
> > +class AwbImplementation
> >  {
> >  public:
> > -       virtual ~AwbAlgorithm() = default;
> > +       struct Result {
> > +               RGB<double> gains;
> > +               unsigned int temperatureK;
> > +       };
> >
> > +       virtual ~AwbImplementation() = default;
> >         virtual int init(const ValueNode &tuningData) = 0;
> > -       virtual AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) = 0;
> > -       virtual std::optional<RGB<double>> gainsFromColourTemperature(double colourTemperature) = 0;
> > +       virtual Result calculateAwb(const AwbStats &stats, unsigned int lux,
> > +                                   std::array<double, 2> range) = 0;
> > +       virtual std::optional<RGB<double>>
> > +       gainsFromColourTemperature(double temperatureK) = 0;
> > +};
> >
> > -       const ControlInfoMap::Map &controls() const
> > -       {
> > -               return controls_;
> > -       }
> > +class AwbAlgorithmBase
> > +{
> > +public:
> > +       int init(const ValueNode &tuningData);
> >
> > -       virtual void handleControls([[maybe_unused]] const ControlList &controls) {}
> > +       int configure(awb::ActiveState &state);
> > +
> > +       void queueRequest(awb::ActiveState &state,
> > +                         const uint32_t frame,
> > +                         awb::FrameContext &frameContext,
> > +                         const ControlList &controls);
> > +
> > +       void prepare(awb::ActiveState &state, awb::FrameContext &frameContext);
> > +
> > +       void process(awb::ActiveState &state, awb::FrameContext &frameContext,
> > +                    const AwbStats &stats, unsigned int lux,
> > +                    ControlList &metadata);
> >
> >  protected:
> > -       int parseModeConfigs(const ValueNode &tuningData,
> > -                            const ControlValue &def = {});
> > +       AwbAlgorithmBase() = default;
> > +
> > +       ControlInfoMap::Map controls_;
> > +       float gainMin_;
> > +       float gainMax_;
> >
> > +private:
> >         struct ModeConfig {
> >                 double ctHi;
> >                 double ctLo;
> >         };
> >
> > -       ControlInfoMap::Map controls_;
> > -       std::map<controls::AwbModeEnum, AwbAlgorithm::ModeConfig> modes_;
> > +       /* AwbGrey does not support modes; */
> > +       static constexpr ModeConfig AwbGreyMode = { 0.0, 0.0 };
> > +
> > +       int parseModeConfigs(const ValueNode &tuningData,
> > +                            const ControlValue &def = {});
> > +
> > +       std::map<controls::AwbModeEnum, AwbAlgorithmBase::ModeConfig> modes_;
> > +       const ModeConfig *currentMode_ = nullptr;
> > +       std::unique_ptr<AwbImplementation> impl_;
> > +       bool bayes_ = false;
> > +};
> > +
> > +template<typename Q>
> > +class AwbAlgorithm : public AwbAlgorithmBase
> > +{
> > +public:
> > +       int init(const ValueNode &tuningData, ControlInfoMap::Map &controls)
> > +       {
> > +               AwbAlgorithmBase::init(tuningData);
> > +
> > +               gainMin_ = std::max(Q::TraitsType::min, 1.0f);
> > +               gainMax_ = Q::TraitsType::max;
> > +
> > +               controls_[&controls::ColourGains] =
> > +                       ControlInfo(gainMin_, gainMax_,
> > +                                   Span<const float, 2>{ { 1.0f, 1.0f } });
> > +
> > +               controls.insert(controls_.begin(), controls_.end());
> > +
> > +               return 0;
> > +       }
> >  };
> >
> >  } /* namespace ipa */
> > diff --git a/src/ipa/libipa/awb_bayes.cpp b/src/ipa/libipa/awb_bayes.cpp
> > index 9fd85e5a4505..2c10f9c4c82d 100644
> > --- a/src/ipa/libipa/awb_bayes.cpp
> > +++ b/src/ipa/libipa/awb_bayes.cpp
> > @@ -140,11 +140,6 @@ void Interpolator<Pwl>::interpolate(const Pwl &a, const Pwl &b, Pwl &dest, doubl
> >   * \brief How far to wander off CT curve towards "more green"
> >   */
> >
> > -/**
> > - * \var AwbBayes::currentMode_
> > - * \brief The currently selected mode
> > - */
> > -
> >  int AwbBayes::init(const ValueNode &tuningData)
> >  {
> >         int ret = colourGainCurve_.readYaml(tuningData["colourGains"], "ct", "gains");
> > @@ -172,14 +167,6 @@ int AwbBayes::init(const ValueNode &tuningData)
> >                 return ret;
> >         }
> >
> > -       ret = parseModeConfigs(tuningData, controls::AwbAuto);
> > -       if (ret) {
> > -               LOG(Awb, Error)
> > -                       << "Failed to parse mode parameter from tuning file";
> > -               return ret;
> > -       }
> > -       currentMode_ = &modes_[controls::AwbAuto];
> > -
> >         transversePos_ = tuningData["transversePos"].get<double>(0.01);
> >         transverseNeg_ = tuningData["transverseNeg"].get<double>(0.01);
> >         if (transversePos_ <= 0 || transverseNeg_ <= 0) {
> > @@ -260,18 +247,16 @@ int AwbBayes::readPriors(const ValueNode &tuningData)
> >         return 0;
> >  }
> >
> > -void AwbBayes::handleControls(const ControlList &controls)
> > -{
> > -       auto mode = controls.get(controls::AwbMode);
> > -       if (mode) {
> > -               auto it = modes_.find(static_cast<controls::AwbModeEnum>(*mode));
> > -               if (it != modes_.end())
> > -                       currentMode_ = &it->second;
> > -               else
> > -                       LOG(Awb, Error) << "Unsupported AWB mode " << *mode;
> > -       }
> > -}
> > -
> > +/**
> > + * \brief Compute white balance gains from a colour temperature
> > + * \param[in] colourTemperature The colour temperature in Kelvin
> > + *
> > + * Compute the white balance gains from a \a colourTemperature. This function
> > + * does not take any statistics into account. It simply interpolates the colour
> > + * gains curve on a new colour temperature.
> > + *
> > + * \return The RGB gain values adjusted to \a temperatureK
> > + */
> >  std::optional<RGB<double>> AwbBayes::gainsFromColourTemperature(double colourTemperature)
> >  {
> >         /*
> > @@ -283,7 +268,9 @@ std::optional<RGB<double>> AwbBayes::gainsFromColourTemperature(double colourTem
> >         return RGB<double>{ { gains[0], 1.0, gains[1] } };
> >  }
> >
> > -AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux)
> > +AwbImplementation::Result
> > +AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux,
> > +                      std::array<double, 2> range)
> >  {
> >         ipa::Pwl prior;
> >         if (lux > 0) {
> > @@ -295,7 +282,7 @@ AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux)
> >                 prior.append(0, 1.0);
> >         }
> >
> > -       double t = coarseSearch(prior, stats);
> > +       double t = coarseSearch(prior, stats, range);
> >         double r = ctR_.eval(t);
> >         double b = ctB_.eval(t);
> >         LOG(Awb, Debug)
> > @@ -316,14 +303,15 @@ AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux)
> >                 << "After fine search: r " << r << " b " << b << " (gains r "
> >                 << 1 / r << " b " << 1 / b << ")";
> >
> > -       return { { { 1.0 / r, 1.0, 1.0 / b } }, t };
> > +       return { { { 1.0 / r, 1.0, 1.0 / b } }, static_cast<unsigned int>(t) };
> >  }
> >
> > -double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) const
> > +double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats,
> > +                             Span<double> range) const
> >  {
> >         std::vector<Pwl::Point> points;
> >         size_t bestPoint = 0;
> > -       double t = currentMode_->ctLo;
> > +       double t = range[0];
> >         int spanR = -1;
> >         int spanB = -1;
> >         LimitsRecorder<double> errorLimits;
> > @@ -345,14 +333,14 @@ double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) cons
> >                 if (points.back().y() < points[bestPoint].y())
> >                         bestPoint = points.size() - 1;
> >
> > -               if (t == currentMode_->ctHi)
> > +               if (t == range[1])
> >                         break;
> >
> >                 /*
> >                  * Ensure even steps along the r/b curve by scaling them by the
> >                  * current t.
> >                  */
> > -               t = std::min(t + t / 10 * kSearchStep, currentMode_->ctHi);
> > +               t = std::min(t + t / 10 * kSearchStep, range[1]);
> >         }
> >
> >         t = points[bestPoint].x();
> > diff --git a/src/ipa/libipa/awb_bayes.h b/src/ipa/libipa/awb_bayes.h
> > index 1e3373676bc0..8d4cff076d4a 100644
> > --- a/src/ipa/libipa/awb_bayes.h
> > +++ b/src/ipa/libipa/awb_bayes.h
> > @@ -20,22 +20,24 @@ namespace libcamera {
> >
> >  namespace ipa {
> >
> > -class AwbBayes : public AwbAlgorithm
> > +class AwbBayes : public AwbImplementation
> >  {
> >  public:
> >         AwbBayes() = default;
> >
> >         int init(const ValueNode &tuningData) override;
> > -       AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) override;
> > +       AwbImplementation::Result
> > +       calculateAwb(const AwbStats &stats, unsigned int lux,
> > +                    std::array<double, 2> range) override;
> >         std::optional<RGB<double>> gainsFromColourTemperature(double temperatureK) override;
> > -       void handleControls(const ControlList &controls) override;
> >
> >  private:
> >         int readPriors(const ValueNode &tuningData);
> >
> >         void fineSearch(double &t, double &r, double &b, ipa::Pwl const &prior,
> >                         const AwbStats &stats) const;
> > -       double coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) const;
> > +       double coarseSearch(const ipa::Pwl &prior, const AwbStats &stats,
> > +                           Span<double> range) const;
> >         double interpolateQuadratic(ipa::Pwl::Point const &a,
> >                                     ipa::Pwl::Point const &b,
> >                                     ipa::Pwl::Point const &c) const;
> > @@ -50,8 +52,6 @@ private:
> >
> >         double transversePos_;
> >         double transverseNeg_;
> > -
> > -       ModeConfig *currentMode_ = nullptr;
> >  };
> >
> >  } /* namespace ipa */
> > diff --git a/src/ipa/libipa/awb_grey.cpp b/src/ipa/libipa/awb_grey.cpp
> > index b14b096810ae..1e4af717f62d 100644
> > --- a/src/ipa/libipa/awb_grey.cpp
> > +++ b/src/ipa/libipa/awb_grey.cpp
> > @@ -60,6 +60,7 @@ int AwbGrey::init(const ValueNode &tuningData)
> >   * \brief Calculate AWB data from the given statistics
> >   * \param[in] stats The statistics to use for the calculation
> >   * \param[in] lux The lux value of the scene
> > + * \param[in] range The colour temperature search limits
> >   *
> >   * The colour temperature is estimated based on the colours::estimateCCT()
> >   * function. The gains are calculated purely based on the RGB means provided by
> > @@ -70,20 +71,23 @@ int AwbGrey::init(const ValueNode &tuningData)
> >   *
> >   * \return The AWB result
> >   */
> > -AwbResult AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned int lux)
> > +AwbImplementation::Result
> > +AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned int lux,
> > +                     [[maybe_unused]] std::array<double, 2> range)
> >  {
> > -       AwbResult result;
> > +       AwbImplementation::Result result;
> >         auto means = stats.rgbMeans();
> > -       result.colourTemperature = estimateCCT(means);
> > +       result.temperatureK = estimateCCT(means);
> >
> >         /*
> > -        * Estimate the red and blue gains to apply in a grey world. The green
> > -        * gain is hardcoded to 1.0. Avoid divisions by zero by clamping the
> > -        * divisor to a minimum value of 1.0.
> > +        * Calculate the red and blue gains to apply in a grey world by simply
> > +        * inverting the red/green and blue/green ratios as reported in
> > +        * statistics. The green gain is hardcoded to 1.0.
> >          */
> >         result.gains.r() = means.g() / std::max(means.r(), 1.0);
> >         result.gains.g() = 1.0;
> >         result.gains.b() = means.g() / std::max(means.b(), 1.0);
> > +
> >         return result;
> >  }
> >
> > @@ -93,15 +97,16 @@ AwbResult AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned
> >   *
> >   * Compute the white balance gains from a \a colourTemperature. This function
> >   * does not take any statistics into account. It simply interpolates the colour
> > - * gains configured in the colour temperature curve.
> > + * gains curve (if provided in the tuning file) on a new colour temperature.
> >   *
> > - * \return The colour gains if a colour temperature curve is available,
> > - * [1, 1, 1] otherwise.
> > + * \return The RGB gain values adjusted to \a temperatureK or std::nullopt if
> > + * no gain curve is specified in the tuning data
> >   */
> >  std::optional<RGB<double>> AwbGrey::gainsFromColourTemperature(double colourTemperature)
> >  {
> >         if (!colourGainCurve_) {
> > -               LOG(Awb, Error) << "No gains defined";
> > +               LOG(Awb, Info) << "No gains curve defined, "
> > +                              << "unable to interpolate gains to colour temperature";
> >                 return std::nullopt;
> >         }
> >
> > diff --git a/src/ipa/libipa/awb_grey.h b/src/ipa/libipa/awb_grey.h
> > index 154a2af97f15..d1b0088f0470 100644
> > --- a/src/ipa/libipa/awb_grey.h
> > +++ b/src/ipa/libipa/awb_grey.h
> > @@ -18,13 +18,15 @@ namespace libcamera {
> >
> >  namespace ipa {
> >
> > -class AwbGrey : public AwbAlgorithm
> > +class AwbGrey : public AwbImplementation
> >  {
> >  public:
> >         AwbGrey() = default;
> >
> >         int init(const ValueNode &tuningData) override;
> > -       AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) override;
> > +       AwbImplementation::Result
> > +       calculateAwb(const AwbStats &stats, unsigned int lux,
> > +                    std::array<double, 2> range) override;
> >         std::optional<RGB<double>> gainsFromColourTemperature(double colourTemperature) override;
> >
> >  private:
> > diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp
> > index 0eb05c2b6e1a..ae9f6a0c243e 100644
> > --- a/src/ipa/rkisp1/algorithms/awb.cpp
> > +++ b/src/ipa/rkisp1/algorithms/awb.cpp
> > @@ -8,17 +8,12 @@
> >  #include "awb.h"
> >
> >  #include <algorithm>
> > -#include <ios>
> >
> >  #include <libcamera/base/log.h>
> >
> > -#include <libcamera/control_ids.h>
> > -
> >  #include <libcamera/ipa/core_ipa_interface.h>
> >
> > -#include "libipa/awb_bayes.h"
> > -#include "libipa/awb_grey.h"
> > -#include "libipa/colours.h"
> > +#include "libcamera/internal/vector.h"
> >
> >  /**
> >   * \file awb.h
> > @@ -28,26 +23,16 @@ namespace libcamera {
> >
> >  namespace ipa::rkisp1::algorithms {
> >
> > -/**
> > - * \class Awb
> > - * \brief Manage the white balance with automatic and manual controls
> > - */
> > -
> >  LOG_DEFINE_CATEGORY(RkISP1Awb)
> >
> > -constexpr int32_t kMinColourTemperature = 2500;
> > -constexpr int32_t kMaxColourTemperature = 10000;
> > -constexpr int32_t kDefaultColourTemperature = 5000;
> > -
> > -/* Minimum mean value below which AWB can't operate. */
> > -constexpr double kMeanMinThreshold = 2.0;
> > -
> >  class RkISP1AwbStats final : public AwbStats
> >  {
> >  public:
> > -       RkISP1AwbStats(const RGB<double> &rgbMeans)
> > -               : rgbMeans_(rgbMeans)
> > +       RkISP1AwbStats(const IPAFrameContext &frameContext,
> > +                      const rkisp1_stat_buffer *stats)
> >         {
> > +               calculateRgbMeans(frameContext, stats);
> > +
> >                 rg_ = rgbMeans_.r() / rgbMeans_.g();
> >                 bg_ = rgbMeans_.b() / rgbMeans_.g();
> >         }
> > @@ -65,17 +50,106 @@ public:
> >                 return delta2;
> >         }
> >
> > -       RGB<double> rgbMeans() const override
> > +       bool valid() const override
> >         {
> > -               return rgbMeans_;
> > +               /* Minimum mean value below which AWB can't operate. */
> > +               constexpr double minValue = 2.0;
>
> As rgbMeans shall now be in the [0, 1] range, this min value is not

I might have missed why rgbMeans is now in [0, 1] range.
I mean, I haven't intentionally modified the algorithm logic, so if it
wasn't in that range before this patch, it shouldn't be after this
patch

> reachable anymore. I seem to miss the normalization code. As the

Where was it ?

> validity test is no longer platform dependent we might want to remove
> the valid() function and move the test into the core algorithm?
>
> > +
> > +               return rgbMeans_.r() > minValue || rgbMeans_.g() > minValue ||
> > +                      rgbMeans_.b() > minValue;
> >         }
> >
> >  private:
> > -       RGB<double> rgbMeans_;
> > -       double rg_;
> > -       double bg_;
> > +       void calculateRgbMeans(const IPAFrameContext &frameContext,
> > +                              const rkisp1_stat_buffer *stats);
> > +       bool rgbMode_ = false;
>
> rgbMode_ is now duplicated and lost its connection to Awb::rgbMode_. One
> solution might be to pass the measurement mode to the constructor?
>

Ah yes, we now have two rgbMode_ which is not nice.

To be honest, the whole RGB mode support is there mostly for
documentation purposes as rgbMode_ is hardcoded to false (in RPPX1 I
dropped the RGB part completely).

I can pass the RkISP1 AWB::rgbMode_ to RkISP1Stats() so at least the
two are connected.

> >  };
> >
> > +void RkISP1AwbStats::calculateRgbMeans(const IPAFrameContext &frameContext,
> > +                                      const rkisp1_stat_buffer *stats)
> > +{
> > +       if (!stats || !(stats->meas_type & RKISP1_CIF_ISP_STAT_AWB)) {
> > +               LOG(RkISP1Awb, Error) << "AWB data is missing in statistics";
> > +               return;
> > +       }
> > +
> > +       const rkisp1_cif_isp_awb_stat *awb = &stats->params.awb;
> > +
> > +       if (awb->awb_mean[0].cnt == 0) {
> > +               LOG(RkISP1Awb, Debug) << "AWB statistics are empty";
> > +               return;
>
> I think the RGB class doesn't zero initialize it's components. So this

Should I rbgMeans_ = {} at the beginning of the function ?

> early return now leads to possibly valid stats with random data in them
> - or did I miss something?
>

Possibily.

To be honest I should have pushed back against the request of moving
calculateRgbMeans() inside RkISP1AwbStats as it's largely unrelated to
this series.

> > +       }
> > +
> > +       if (rgbMode_) {
> > +               rgbMeans_ = {{
> > +                       static_cast<double>(awb->awb_mean[0].mean_cr_or_r),
> > +                       static_cast<double>(awb->awb_mean[0].mean_y_or_g),
> > +                       static_cast<double>(awb->awb_mean[0].mean_cb_or_b)
> > +               }};
> > +       } else {
> > +               /* Get the YCbCr mean values */
> > +               Vector<double, 3> yuvMeans({
> > +                       static_cast<double>(awb->awb_mean[0].mean_y_or_g),
> > +                       static_cast<double>(awb->awb_mean[0].mean_cb_or_b),
> > +                       static_cast<double>(awb->awb_mean[0].mean_cr_or_r)
> > +               });
> > +
> > +               /*
> > +                * Convert from YCbCr to RGB. The hardware uses the following
> > +                * formulas:
> > +                *
> > +                * Y  =  16 + 0.2500 R + 0.5000 G + 0.1094 B
> > +                * Cb = 128 - 0.1406 R - 0.2969 G + 0.4375 B
> > +                * Cr = 128 + 0.4375 R - 0.3750 G - 0.0625 B
> > +                *
> > +                * This seems to be based on limited range BT.601 with Q1.6
> > +                * precision.
> > +                *
> > +                * The inverse matrix is:
> > +                *
> > +                * [[1,1636, -0,0623,  1,6008]
> > +                *  [1,1636, -0,4045, -0,7949]
> > +                *  [1,1636,  1,9912, -0,0250]]
> > +                */
> > +               static const Matrix<double, 3, 3> yuv2rgbMatrix({
> > +                       1.1636, -0.0623,  1.6008,
> > +                       1.1636, -0.4045, -0.7949,
> > +                       1.1636,  1.9912, -0.0250
> > +               });
> > +               static const Vector<double, 3> yuv2rgbOffset({
> > +                       16, 128, 128
> > +               });
> > +
> > +               rgbMeans_ = yuv2rgbMatrix * (yuvMeans - yuv2rgbOffset);
> > +
> > +               /*
> > +                * Due to hardware rounding errors in the YCbCr means, the
> > +                * calculated RGB means may be negative. This would lead to
> > +                * negative gains, messing up calculation. Prevent this by
> > +                * clamping the means to positive values.
> > +                */
> > +               rgbMeans_ = rgbMeans_.max(0.0);
> > +       }
> > +
> > +       /*
> > +        * The ISP computes the AWB means after applying the CCM. Apply the
> > +        * inverse as we want to get the raw means before the colour gains.
> > +        */
> > +       rgbMeans_ = frameContext.ccm.ccm.inverse() * rgbMeans_;
> > +
> > +       /*
> > +        * The ISP computes the AWB means after applying the colour gains,
> > +        * divide by the gains that were used to get the raw means from the
> > +        * sensor. Apply a minimum value to avoid divisions by near-zero.
> > +        */
> > +       rgbMeans_ /= frameContext.awb.gains.max(0.01);
>
> I think this misses the normalization with awb_mean[0].cnt.

I think I'm missing where it was in the existing code, as this should
be a copy of the already existing Awb::calculateRgbMeans() function.

>
> > +}
> > +
> > +/**
> > + * \class Awb
> > + * \brief Manage the white balance with automatic and manual controls
> > + */
> > +
> >  Awb::Awb()
> >         : rgbMode_(false)
> >  {
> > @@ -86,40 +160,7 @@ Awb::Awb()
> >   */
> >  int Awb::init(IPAContext &context, const ValueNode &tuningData)
> >  {
> > -       auto &cmap = context.ctrlMap;
> > -       cmap[&controls::ColourTemperature] = ControlInfo(kMinColourTemperature,
> > -                                                        kMaxColourTemperature,
> > -                                                        kDefaultColourTemperature);
> > -       cmap[&controls::AwbEnable] = ControlInfo(false, true);
> > -
> > -       cmap[&controls::ColourGains] = ControlInfo(0.0f, 3.996f,
> > -                                                  Span<const float, 2>{ { 1.0f, 1.0f } });
> > -
> > -       if (!tuningData.contains("algorithm"))
> > -               LOG(RkISP1Awb, Info) << "No AWB algorithm specified."
> > -                                    << " Default to grey world";
> > -
> > -       auto mode = tuningData["algorithm"].get<std::string>("grey");
> > -       if (mode == "grey") {
> > -               awbAlgo_ = std::make_unique<AwbGrey>();
> > -       } else if (mode == "bayes") {
> > -               awbAlgo_ = std::make_unique<AwbBayes>();
> > -       } else {
> > -               LOG(RkISP1Awb, Error) << "Unknown AWB algorithm: " << mode;
> > -               return -EINVAL;
> > -       }
> > -       LOG(RkISP1Awb, Debug) << "Using AWB algorithm: " << mode;
> > -
> > -       int ret = awbAlgo_->init(tuningData);
> > -       if (ret) {
> > -               LOG(RkISP1Awb, Error) << "Failed to init AWB algorithm";
> > -               return ret;
> > -       }
> > -
> > -       const auto &src = awbAlgo_->controls();
> > -       cmap.insert(src.begin(), src.end());
> > -
> > -       return 0;
> > +       return awbAlgo_.init(tuningData, context.ctrlMap);
> >  }
> >
> >  /**
> > @@ -128,16 +169,7 @@ int Awb::init(IPAContext &context, const ValueNode &tuningData)
> >  int Awb::configure(IPAContext &context,
> >                    const IPACameraSensorInfo &configInfo)
> >  {
> > -       context.activeState.awb.manual.gains = RGB<double>{ 1.0 };
> > -       auto gains = awbAlgo_->gainsFromColourTemperature(kDefaultColourTemperature);
> > -       if (gains)
> > -               context.activeState.awb.automatic.gains = *gains;
> > -       else
> > -               context.activeState.awb.automatic.gains = RGB<double>{ 1.0 };
> > -
> > -       context.activeState.awb.autoEnabled = true;
> > -       context.activeState.awb.manual.temperatureK = kDefaultColourTemperature;
> > -       context.activeState.awb.automatic.temperatureK = kDefaultColourTemperature;
> > +       awbAlgo_.configure(context.activeState.awb);
> >
> >         /*
> >          * Define the measurement window for AWB as a centered rectangle
> > @@ -156,56 +188,12 @@ int Awb::configure(IPAContext &context,
> >  /**
> >   * \copydoc libcamera::ipa::Algorithm::queueRequest
> >   */
> > -void Awb::queueRequest(IPAContext &context,
> > -                      [[maybe_unused]] const uint32_t frame,
> > +void Awb::queueRequest(IPAContext &context, const uint32_t frame,
> >                        IPAFrameContext &frameContext,
> >                        const ControlList &controls)
> >  {
> > -       auto &awb = context.activeState.awb;
> > -
> > -       const auto &awbEnable = controls.get(controls::AwbEnable);
> > -       if (awbEnable && *awbEnable != awb.autoEnabled) {
> > -               awb.autoEnabled = *awbEnable;
> > -
> > -               LOG(RkISP1Awb, Debug)
> > -                       << (*awbEnable ? "Enabling" : "Disabling") << " AWB";
> > -       }
> > -
> > -       awbAlgo_->handleControls(controls);
> > -
> > -       frameContext.awb.autoEnabled = awb.autoEnabled;
> > -
> > -       if (awb.autoEnabled)
> > -               return;
> > -
> > -       const auto &colourGains = controls.get(controls::ColourGains);
> > -       const auto &colourTemperature = controls.get(controls::ColourTemperature);
> > -       bool update = false;
> > -       if (colourGains) {
> > -               awb.manual.gains.r() = (*colourGains)[0];
> > -               awb.manual.gains.b() = (*colourGains)[1];
> > -               /*
> > -                * \todo Colour temperature reported in metadata is now
> > -                * incorrect, as we can't deduce the temperature from the gains.
> > -                * This will be fixed with the bayes AWB algorithm.
> > -                */
> > -               update = true;
> > -       } else if (colourTemperature) {
> > -               awb.manual.temperatureK = *colourTemperature;
> > -               const auto &gains = awbAlgo_->gainsFromColourTemperature(*colourTemperature);
> > -               if (gains) {
> > -                       awb.manual.gains.r() = gains->r();
> > -                       awb.manual.gains.b() = gains->b();
> > -                       update = true;
> > -               }
> > -       }
> > -
> > -       if (update)
> > -               LOG(RkISP1Awb, Debug)
> > -                       << "Set colour gains to " << awb.manual.gains;
> > -
> > -       frameContext.awb.gains = awb.manual.gains;
> > -       frameContext.awb.temperatureK = awb.manual.temperatureK;
> > +       awbAlgo_.queueRequest(context.activeState.awb, frame, frameContext.awb,
> > +                             controls);
> >  }
> >
> >  /**
> > @@ -214,15 +202,7 @@ void Awb::queueRequest(IPAContext &context,
> >  void Awb::prepare(IPAContext &context, const uint32_t frame,
> >                   IPAFrameContext &frameContext, RkISP1Params *params)
> >  {
> > -       /*
> > -        * This is the latest time we can read the active state. This is the
> > -        * most up-to-date automatic values we can read.
> > -        */
> > -       if (frameContext.awb.autoEnabled) {
> > -               const auto &awb = context.activeState.awb;
> > -               frameContext.awb.gains = awb.automatic.gains;
> > -               frameContext.awb.temperatureK = awb.automatic.temperatureK;
> > -       }
> > +       awbAlgo_.prepare(context.activeState.awb, frameContext.awb);
> >
> >         auto gainConfig = params->block<BlockType::AwbGain>();
> >         gainConfig.setEnabled(true);
> > @@ -291,136 +271,12 @@ void Awb::process(IPAContext &context,
> >                   const rkisp1_stat_buffer *stats,
> >                   ControlList &metadata)
> >  {
> > -       IPAActiveState &activeState = context.activeState;
> > -
> > -       metadata.set(controls::AwbEnable, frameContext.awb.autoEnabled);
> > -       metadata.set(controls::ColourGains, {
> > -                       static_cast<float>(frameContext.awb.gains.r()),
> > -                       static_cast<float>(frameContext.awb.gains.b())
> > -               });
> > -       metadata.set(controls::ColourTemperature, frameContext.awb.temperatureK);
> > -
> > -       if (!stats || !(stats->meas_type & RKISP1_CIF_ISP_STAT_AWB)) {
> > -               LOG(RkISP1Awb, Error) << "AWB data is missing in statistics";
> > -               return;
> > -       }
> > -
> > -       const rkisp1_cif_isp_stat *params = &stats->params;
> > -       const rkisp1_cif_isp_awb_stat *awb = &params->awb;
> > +       RkISP1AwbStats awbStats(frameContext, stats);
> >
> > -       if (awb->awb_mean[0].cnt == 0) {
> > -               LOG(RkISP1Awb, Debug) << "AWB statistics are empty";
> > -               return;
> > -       }
> > -
> > -       RGB<double> rgbMeans = calculateRgbMeans(frameContext, awb);
> > -
> > -       /*
> > -        * If the means are too small we don't have enough information to
> > -        * meaningfully calculate gains. Freeze the algorithm in that case.
> > -        */
> > -       if (rgbMeans.r() < kMeanMinThreshold && rgbMeans.g() < kMeanMinThreshold &&
> > -           rgbMeans.b() < kMeanMinThreshold)
> > -               return;
> > -
> > -       RkISP1AwbStats awbStats{ rgbMeans };
> > -       AwbResult awbResult = awbAlgo_->calculateAwb(awbStats, frameContext.lux.lux);
> > -
> > -       /*
> > -        * Clamp the gain values to the hardware, which expresses gains as Q2.8
> > -        * unsigned integer values. Set the minimum just above zero to avoid
> > -        * divisions by zero when computing the raw means in subsequent
> > -        * iterations.
> > -        */
> > -       awbResult.gains = awbResult.gains.clamp(1.0 / 256, 1023.0 / 256);
> > -
> > -       /* Filter the values to avoid oscillations. */
> > -       double speed = 0.2;
> > -       double ct = awbResult.colourTemperature;
> > -       ct = ct * speed + activeState.awb.automatic.temperatureK * (1 - speed);
> > -       awbResult.gains = awbResult.gains * speed +
> > -                         activeState.awb.automatic.gains * (1 - speed);
> > -
> > -       activeState.awb.automatic.temperatureK = static_cast<unsigned int>(ct);
> > -       activeState.awb.automatic.gains = awbResult.gains;
> > -
> > -       LOG(RkISP1Awb, Debug)
> > -               << std::showpoint
> > -               << "Means " << rgbMeans << ", gains "
> > -               << activeState.awb.automatic.gains << ", temp "
> > -               << activeState.awb.automatic.temperatureK << "K";
> > +       awbAlgo_.process(context.activeState.awb, frameContext.awb, awbStats,
> > +                        frameContext.lux.lux, metadata);
> >  }
> >
> > -RGB<double> Awb::calculateRgbMeans(const IPAFrameContext &frameContext, const rkisp1_cif_isp_awb_stat *awb) const
> > -{
> > -       Vector<double, 3> rgbMeans;
> > -
> > -       if (rgbMode_) {
> > -               rgbMeans = {{
> > -                       static_cast<double>(awb->awb_mean[0].mean_cr_or_r),
> > -                       static_cast<double>(awb->awb_mean[0].mean_y_or_g),
> > -                       static_cast<double>(awb->awb_mean[0].mean_cb_or_b)
> > -               }};
> > -       } else {
> > -               /* Get the YCbCr mean values */
> > -               Vector<double, 3> yuvMeans({
> > -                       static_cast<double>(awb->awb_mean[0].mean_y_or_g),
> > -                       static_cast<double>(awb->awb_mean[0].mean_cb_or_b),
> > -                       static_cast<double>(awb->awb_mean[0].mean_cr_or_r)
> > -               });
> > -
> > -               /*
> > -                * Convert from YCbCr to RGB. The hardware uses the following
> > -                * formulas:
> > -                *
> > -                * Y  =  16 + 0.2500 R + 0.5000 G + 0.1094 B
> > -                * Cb = 128 - 0.1406 R - 0.2969 G + 0.4375 B
> > -                * Cr = 128 + 0.4375 R - 0.3750 G - 0.0625 B
> > -                *
> > -                * This seems to be based on limited range BT.601 with Q1.6
> > -                * precision.
> > -                *
> > -                * The inverse matrix is:
> > -                *
> > -                * [[1,1636, -0,0623,  1,6008]
> > -                *  [1,1636, -0,4045, -0,7949]
> > -                *  [1,1636,  1,9912, -0,0250]]
> > -                */
> > -               static const Matrix<double, 3, 3> yuv2rgbMatrix({
> > -                       1.1636, -0.0623,  1.6008,
> > -                       1.1636, -0.4045, -0.7949,
> > -                       1.1636,  1.9912, -0.0250
> > -               });
> > -               static const Vector<double, 3> yuv2rgbOffset({
> > -                       16, 128, 128
> > -               });
> > -
> > -               rgbMeans = yuv2rgbMatrix * (yuvMeans - yuv2rgbOffset);
> > -
> > -               /*
> > -                * Due to hardware rounding errors in the YCbCr means, the
> > -                * calculated RGB means may be negative. This would lead to
> > -                * negative gains, messing up calculation. Prevent this by
> > -                * clamping the means to positive values.
> > -                */
> > -               rgbMeans = rgbMeans.max(0.0);
> > -       }
> > -
> > -       /*
> > -        * The ISP computes the AWB means after applying the CCM. Apply the
> > -        * inverse as we want to get the raw means before the colour gains.
> > -        */
> > -       rgbMeans = frameContext.ccm.ccm.inverse() * rgbMeans;
> > -
> > -       /*
> > -        * The ISP computes the AWB means after applying the colour gains,
> > -        * divide by the gains that were used to get the raw means from the
> > -        * sensor. Apply a minimum value to avoid divisions by near-zero.
> > -        */
> > -       rgbMeans /= frameContext.awb.gains.max(0.01);
> > -
> > -       return rgbMeans;
> > -}
>
> Moving this big function would have been an easy cut out into its own
> patch?

You're right. I shouldn't be doing this as part of this series.
I'll move calculateRgbMeans() back to the Awb class.

Thanks
  j

>
> Best regards,
> Stefan
>
> >
> >  REGISTER_IPA_ALGORITHM(Awb, "Awb")
> >
> > diff --git a/src/ipa/rkisp1/algorithms/awb.h b/src/ipa/rkisp1/algorithms/awb.h
> > index 60d9ef111495..c7cb59a75bde 100644
> > --- a/src/ipa/rkisp1/algorithms/awb.h
> > +++ b/src/ipa/rkisp1/algorithms/awb.h
> > @@ -7,17 +7,25 @@
> >
> >  #pragma once
> >
> > -#include "libcamera/internal/vector.h"
> > +#include <linux/rkisp1-config.h>
> > +
> > +#include <libcamera/controls.h>
> > +
> > +#include "libcamera/internal/value_node.h"
> >
> >  #include "libipa/awb.h"
> > -#include "libipa/interpolator.h"
> > +#include "libipa/fixedpoint.h"
> >
> >  #include "algorithm.h"
> > +#include "ipa_context.h"
> > +#include "params.h"
> >
> >  namespace libcamera {
> >
> >  namespace ipa::rkisp1::algorithms {
> >
> > +class RkISP1AwbStats;
> > +
> >  class Awb : public Algorithm
> >  {
> >  public:
> > @@ -38,10 +46,10 @@ public:
> >                      ControlList &metadata) override;
> >
> >  private:
> > -       RGB<double> calculateRgbMeans(const IPAFrameContext &frameContext,
> > -                                     const rkisp1_cif_isp_awb_stat *awb) const;
> > +       RkISP1AwbStats calculateRgbMeans(const IPAFrameContext &frameContext,
> > +                                        const rkisp1_stat_buffer *stats) const;
> >
> > -       std::unique_ptr<AwbAlgorithm> awbAlgo_;
> > +       AwbAlgorithm<UQ<2, 8>> awbAlgo_;
> >
> >         bool rgbMode_;
> >  };
> > diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h
> > index e5f70d934d67..c36c1f7e0084 100644
> > --- a/src/ipa/rkisp1/ipa_context.h
> > +++ b/src/ipa/rkisp1/ipa_context.h
> > @@ -25,6 +25,7 @@
> >  #include "libcamera/internal/vector.h"
> >
> >  #include "libipa/agc_mean_luminance.h"
> > +#include "libipa/awb.h"
> >  #include "libipa/camera_sensor_helper.h"
> >  #include "libipa/fc_queue.h"
> >  #include "libipa/fixedpoint.h"
> > @@ -48,15 +49,17 @@ struct IPAHwSettings {
> >         bool compand;
> >  };
> >
> > +struct RKISP1AwbSession {
> > +       struct rkisp1_cif_isp_window measureWindow;
> > +       bool enabled;
> > +};
> > +
> >  struct IPASessionConfiguration {
> >         struct {
> >                 struct rkisp1_cif_isp_window measureWindow;
> >         } agc;
> >
> > -       struct {
> > -               struct rkisp1_cif_isp_window measureWindow;
> > -               bool enabled;
> > -       } awb;
> > +       struct RKISP1AwbSession awb;
> >
> >         struct {
> >                 bool supported;
> > @@ -100,17 +103,7 @@ struct IPAActiveState {
> >                 utils::Duration maxFrameDuration;
> >         } agc;
> >
> > -       struct {
> > -               struct AwbState {
> > -                       RGB<double> gains;
> > -                       unsigned int temperatureK;
> > -               };
> > -
> > -               AwbState manual;
> > -               AwbState automatic;
> > -
> > -               bool autoEnabled;
> > -       } awb;
> > +       ipa::awb::ActiveState awb;
> >
> >         struct {
> >                 Matrix<float, 3, 3> manual;
> > @@ -176,11 +169,7 @@ struct IPAFrameContext : public FrameContext {
> >                 bool autoGainModeChange;
> >         } agc;
> >
> > -       struct {
> > -               RGB<double> gains;
> > -               bool autoEnabled;
> > -               unsigned int temperatureK;
> > -       } awb;
> > +       ipa::awb::FrameContext awb;
> >
> >         struct {
> >                 float actualBrightness;
> >
> > --
> > 2.54.0
> >
Stefan Klug July 7, 2026, 10:46 a.m. UTC | #3
Hi Jacopo,

Quoting Jacopo Mondi (2026-07-07 09:36:33)
> Hi Stefan
> 
> On Tue, Jul 07, 2026 at 08:31:10AM +0200, Stefan Klug wrote:
> > Hi Jacopo,
> >
> > Thank you for the patch.
> >
> > Quoting Jacopo Mondi (2026-07-06 10:01:21)
> > > The current implementation of AwbAlgorithm is the following one:
> > >
> > > AwbAlgorithm o---- RkISP1Awb
> > >     ^
> > >     |
> > >  |-----|
> > >  |     |
> > > Bayes Grey
> > >
> > > The platform-specific Awb implementation instantiate one of
> > > the Bayes or Grey Awb helper classes and both inherit from the
> > > AwbAlgorithm base class.
> > >
> > > The handling of libcamera controls is split between the base and the
> > > derived (mostly Bayes) class.
> > >
> > > We want instead the AwbAlgorithm to handle all-things-libcamera and let
> > > the Bayes and Grey classes implement the logic. In order to achieve that
> > > use composition also in the AwbAlgorithm class and introduce an
> > > AwbImplementation base class from which Bayes and Grey derive from.
> > >
> > > AwbImplementation o---- AwbAlgorithmBase
> > >     ^                           ^
> > >     |                           |
> > >  |-----|                AwbAlgorithm<Q<x,y>> o---- RkISP1Awb
> > >  |     |
> > > Bayes Grey
> > >
> > > The AwbAlgorithm<> class is instantiated with a Q<x, y> template
> > > argument that represents the platform-specific register format which
> > > is used to initialize controls and clamp gains to the hardware limits.
> > >
> > > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> > > Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > > Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
> > > Tested-by: Daniel Scally <dan.scally@ideasonboard.com>
> > > ---
> > >  src/ipa/libipa/awb.cpp            | 472 ++++++++++++++++++++++++++++++++------
> > >  src/ipa/libipa/awb.h              | 109 +++++++--
> > >  src/ipa/libipa/awb_bayes.cpp      |  52 ++---
> > >  src/ipa/libipa/awb_bayes.h        |  12 +-
> > >  src/ipa/libipa/awb_grey.cpp       |  25 +-
> > >  src/ipa/libipa/awb_grey.h         |   6 +-
> > >  src/ipa/rkisp1/algorithms/awb.cpp | 360 +++++++++--------------------
> > >  src/ipa/rkisp1/algorithms/awb.h   |  18 +-
> > >  src/ipa/rkisp1/ipa_context.h      |  29 +--
> > >  9 files changed, 672 insertions(+), 411 deletions(-)
> > >
> > > diff --git a/src/ipa/libipa/awb.cpp b/src/ipa/libipa/awb.cpp
> > > index 29a78069c52c..95291840d8fb 100644
> > > --- a/src/ipa/libipa/awb.cpp
> > > +++ b/src/ipa/libipa/awb.cpp
> > > @@ -2,7 +2,7 @@
> > >  /*
> > >   * Copyright (C) 2024 Ideas on Board Oy
> > >   *
> > > - * Generic AWB algorithms
> > > + * libIPA Awb algorithms
> > >   */
> > >
> > >  #include "awb.h"
> > > @@ -11,9 +11,16 @@
> > >
> > >  #include <libcamera/control_ids.h>
> > >
> > > +#include "awb_bayes.h"
> > > +#include "awb_grey.h"
> > > +
> > > +constexpr int32_t kMinColourTemperature = 2500;
> > > +constexpr int32_t kMaxColourTemperature = 10000;
> > > +constexpr int32_t kDefaultColourTemperature = 5000;
> > > +
> > >  /**
> > >   * \file awb.h
> > > - * \brief Base classes for AWB algorithms
> > > + * \brief libipa awb implementation
> > >   */
> > >
> > >  namespace libcamera {
> > > @@ -22,30 +29,60 @@ LOG_DEFINE_CATEGORY(Awb)
> > >
> > >  namespace ipa {
> > >
> > > +namespace awb {
> > > +
> > >  /**
> > > - * \class AwbResult
> > > - * \brief The result of an AWB calculation
> > > + * \struct Context
> > > + * \brief Awb gains and colour temperature
> > > + *
> > > + * \var Context::gains
> > > + * \brief The white balance gains
> > >   *
> > > - * This class holds the result of an auto white balance calculation.
> > > + * \var Context::temperatureK
> > > + * \brief The colour temperature, in Kelvin
> > >   */
> > >
> > >  /**
> > > - * \var AwbResult::gains
> > > - * \brief The calculated white balance gains
> > > + * \struct ActiveState
> > > + * \brief Active awb state shared across frames
> > > + *
> > > + * \var ActiveState::manual
> > > + * \brief The most recent manually requested awb state
> > > + *
> > > + * \var ActiveState::automatic
> > > + * \brief The most recent automatically calculated awb state
> > > + *
> > > + * \var ActiveState::autoEnabled
> > > + * \brief True when automatic awb is selected
> > >   */
> > >
> > >  /**
> > > - * \var AwbResult::colourTemperature
> > > - * \brief The calculated colour temperature in Kelvin
> > > + * \struct FrameContext
> > > + * \brief Per-frame awb state
> > > + *
> > > + * \var FrameContext::autoEnabled
> > > + * \brief True when automatic awb is in use
> > >   */
> > >
> > > +} /* namespace awb */
> > > +
> > >  /**
> > >   * \class AwbStats
> > > - * \brief An abstraction class wrapping hardware-specific AWB statistics
> > > + * \brief An abstraction class wrapping hardware-specific awb statistics
> >
> > Out of curiosity. I was told that abbreviations like AWB are always
> > capitalized in writing. Is that incorrect?
> 
> I'm not sure. But we had a mix of "Awb", "AWB" and "awb" which annoyed
> me. So I went for a single version (wherever I noticed).
> 
> We can decide what's best.
> 
> >
> > >   *
> > > - * IPA modules using an AWB algorithm based on the AwbAlgorithm class need to
> > > - * implement this class to give the algorithm access to the hardware-specific
> > > - * statistics data.
> > > + * IPA modules shall provide a derived implementation of this class to give the
> > > + * awb algorithm access to the hardware-specific statistics data in a common
> > > + * format.
> > > + */
> > > +
> > > +/**
> > > + * \fn AwbStats::AwbStats()
> > > + * \brief Construct an empty AwbStat
> > > + */
> > > +
> > > +/**
> > > + * AwbStat::~AwbStat
> > > + * \brief Virtual class destructor
> > >   */
> > >
> > >  /**
> > > @@ -57,7 +94,7 @@ namespace ipa {
> > >   * applied. To keep the actual implementations computationally inexpensive,
> > >   * the squared colour error shall be returned.
> > >   *
> > > - * If the AWB statistics provide multiple zones, the average of the individual
> > > + * If the awb statistics provide multiple zones, the average of the individual
> > >   * squared errors shall be returned. Averaging/normalizing is necessary so that
> > >   * the numeric dimensions are the same on all hardware platforms.
> > >   *
> > > @@ -65,79 +102,328 @@ namespace ipa {
> > >   */
> > >
> > >  /**
> > > - * \fn AwbStats::rgbMeans()
> > > - * \brief Get RGB means of the statistics
> > > + * \fn AwbStats::valid()
> > > + * \brief Retrieve if the awb statistics are valid
> > >   *
> > > - * Fetch the RGB means from the statistics. The values of each channel are
> > > - * dimensionless and only the ratios are used for further calculations. This is
> > > - * used by the simple grey world model to calculate the gains to apply.
> > > + * If the colour mean values are too small, the AwbAlgorithm class doesn't have
> > > + * enough information to meaningfully calculate white-balance gains. Freeze the
> > > + * algorithm in that case.
> > > + *
> > > + * \return True if the awb statistics are valid, false otherwise
> > > + */
> > > +
> > > +/**
> > > + * \fn AwbStats::rgbMeans
> > > + * \brief Retrieve the RGB means values
> > > + *
> > > + * Fetch the RGB means from the statistics in the [0, 1] range.
> > >   *
> > >   * \return The RGB means
> > >   */
> > >
> > >  /**
> > > - * \class AwbAlgorithm
> > > - * \brief A base class for auto white balance algorithms
> > > + * \var AwbStats::rgbMeans_
> > > + * \brief Mean values of colour channels
> > > + */
> > > +
> > > +/**
> > > + * \var AwbStats::rg_
> > > + * \brief Red/Green ratio
> > > + */
> > > +
> > > +/**
> > > + * \var AwbStats::bg_
> > > + * \brief Blue/Green ratio
> > > + */
> > > +
> > > +/**
> > > + * \class AwbImplementation
> > > + * \brief Pure virtual base class for awb algorithms implementations
> > > + *
> > > + * The AwbImplementation class defines the interface for the awb algorithm
> > > + * implementations.
> > >   *
> > > - * This class is a base class for auto white balance algorithms. It defines an
> > > - * interface for the algorithms to implement, and is used by the IPAs to
> > > - * interact with the concrete implementation.
> > > + * It is currently implemented by the AwbGrey and AwbBayes classes.
> > > + *
> > > + * The interface defines an init() function to initialize the algorithm with the
> > > + * content of the tuning file and two functions to compute colour gains
> > > + * according to the algorithm operating mode (auto or manual) in use.
> > > + *
> > > + * The calculateAwb() function calculates colour gains given a set of statistics
> > > + * provided by the IPA module. It is used when the algorithm operates in auto
> > > + * mode and gains are dynamically computed given a new set of statistics from
> > > + * the awb engine.
> > > + *
> > > + * The gainsFromColourTemperature() function instead computes the white balance
> > > + * gains from a colour temperature. This function does not take any statistics
> > > + * into account. It is used to compute the colour gains when the user manually
> > > + * specifies a colour temperature.
> > >   */
> > >
> > >  /**
> > > - * \fn AwbAlgorithm::init()
> > > - * \brief Initialize the algorithm with the given tuning data
> > > - * \param[in] tuningData The tuning data to use for the algorithm
> > > + * \struct AwbImplementation::Result
> > > + * \brief The result of an awb computation
> > >   *
> > > - * \return 0 on success, a negative error code otherwise
> > > + * \var AwbImplementation::Result::gains
> > > + * \brief The computed white balance gains
> > > + *
> > > + * \var AwbImplementation::Result::temperatureK
> > > + * \brief The computed colour temperature, in Kelvin
> > >   */
> > >
> > >  /**
> > > - * \fn AwbAlgorithm::calculateAwb()
> > > - * \brief Calculate AWB data from the given statistics
> > > - * \param[in] stats The statistics to use for the calculation
> > > - * \param[in] lux The lux value of the scene
> > > + * \fn AwbImplementation::~AwbImplementation
> > > + * \brief Virtual class destructor
> > > + */
> > > +
> > > +/**
> > > + * \fn AwbImplementation::init()
> > > + * \param[in] tuningData
> > > + * \brief Initialize the algorithm by parsing \a tuningData
> > > + */
> > > +
> > > +/**
> > > + * \fn AwbImplementation::calculateAwb()
> > > + * \param[in] stats The awb statistics
> > > + * \param[in] lux The estimated lux level
> > > + * \param[in] range The colour temperature search limits
> > >   *
> > > - * Calculate an AwbResult object from the given statistics and lux value. A \a
> > > - * lux value of 0 means it is unknown or invalid and the algorithm shall ignore
> > > - * it.
> > > + * Calculate a new set of colour gains and a colour temperature given a new
> > > + * set of statistics \a stats, an estimated luminance \a lux and a range of
> > > + * colour temperature to limit the search.
> > >   *
> > > - * \return The AWB result
> > > + * \return A Result computed using the new statistics
> > > + */
> > > +/**
> > > + * \fn AwbImplementation::gainsFromColourTemperature()
> > > + * \param[in] temperatureK Colour temperature in Kelvin
> > > + *
> > > + * Calculate a new set of colour gains for a new manually specified colour
> > > + * temperature \a temperatureK.
> > > + *
> > > + * \return The RGB gain values adjusted to \a temperatureK or std::nullopt
> > > + */
> > > +
> > > +/**
> > > + * \class AwbAlgorithmBase
> > > + * \brief Base class for AwbAlgorithm for non-templated functions implementation
> > > + *
> > > + * Base class for AwbAlgorithm where non-templated functions are implemented.
> > > + * IPA implementations shall use AwbAlgorithm and not this class.
> > >   */
> > >
> > >  /**
> > > - * \fn AwbAlgorithm::gainsFromColourTemperature()
> > > - * \brief Compute white balance gains from a colour temperature
> > > - * \param[in] colourTemperature The colour temperature in Kelvin
> > > + * \brief Initialize the algorithm with the given tuning data
> > > + * \param[in] tuningData The tuning data to use for the algorithm
> > >   *
> > > - * Compute the white balance gains from a \a colourTemperature. This function
> > > - * does not take any statistics into account. It is used to compute the colour
> > > - * gains when the user manually specifies a colour temperature.
> > > + * Parse \a tuningData to initialize the awb algorithm and register controls.
> > > + * IPA modules are expected to call this function as part of their
> > > + * implementation of Algorithm::init().
> > >   *
> > > - * \return The colour gains or std::nullopt if the conversion is not possible
> > > + * \return 0 on success, a negative error code otherwise
> > > + */
> > > +int AwbAlgorithmBase::init(const ValueNode &tuningData)
> > > +{
> > > +       bayes_ = false;
> > > +
> > > +       if (!tuningData.contains("algorithm"))
> > > +               LOG(Awb, Info) << "No Awb algorithm specified, using grey world";
> > > +
> > > +       auto mode = tuningData["algorithm"].get<std::string>("grey");
> > > +       if (mode == "grey") {
> > > +               impl_ = std::make_unique<AwbGrey>();
> > > +       } else if (mode == "bayes") {
> > > +               impl_ = std::make_unique<AwbBayes>();
> > > +               bayes_ = true;
> > > +       } else {
> > > +               LOG(Awb, Error) << "Unknown Awb algorithm: " << mode;
> > > +               return -EINVAL;
> > > +       }
> > > +
> > > +       LOG(Awb, Debug) << "Using Awb algorithm: " << mode;
> > > +
> > > +       int ret = impl_->init(tuningData);
> > > +       if (ret)
> > > +               return ret;
> > > +
> > > +       controls_[&controls::ColourTemperature] =
> > > +               ControlInfo(kMinColourTemperature, kMaxColourTemperature,
> > > +                           kDefaultColourTemperature);
> > > +       controls_[&controls::AwbEnable] = ControlInfo(false, true);
> > > +
> > > +       return parseModeConfigs(tuningData, controls::AwbAuto);
> > > +}
> > > +
> > > +/**
> > > + * \brief Configure the awb algorithm
> > > + * \param[in] state The awb active state
> > > + * \return 0 if successful, an error code otherwise
> > >   */
> > > +int AwbAlgorithmBase::configure(awb::ActiveState &state)
> > > +{
> > > +       state.manual.gains = RGB<double>{ 1.0 };
> > > +       auto gains = impl_->gainsFromColourTemperature(kDefaultColourTemperature);
> > > +       if (gains)
> > > +               state.automatic.gains = *gains;
> > > +       else
> > > +               state.automatic.gains = RGB<double>{ 1.0 };
> > > +
> > > +       state.autoEnabled = true;
> > > +       state.manual.temperatureK = kDefaultColourTemperature;
> > > +       state.automatic.temperatureK = kDefaultColourTemperature;
> > > +
> > > +       return 0;
> > > +}
> > >
> > >  /**
> > > - * \fn AwbAlgorithm::controls()
> > > - * \brief Get the controls info map for this algorithm
> > > + * \brief Queue a Request to the awb algorithm
> > > + * \param[in] state The awb active state
> > > + * \param[in] frame The frame number
> > > + * \param[in] frameContext The awb frame context
> > > + * \param[in] controls The list of controls part of the Request
> > >   *
> > > - * \return The controls info map
> > > + * Queue a new Request to the awb algorithm and modify its behaviour according
> > > + * to the provided controls.
> > > + *
> > > + * The currently handled controls are:
> > > + * - controls::AwbEnable
> > > + * - controls::AwbMode
> > > + * - controls::ColourGains
> > > + * - controls::ColourTemperature
> > >   */
> > > +void AwbAlgorithmBase::queueRequest(awb::ActiveState &state,
> > > +                                   [[maybe_unused]] const uint32_t frame,
> > > +                                   awb::FrameContext &frameContext,
> > > +                                   const ControlList &controls)
> > > +{
> > > +       const auto &awbEnable = controls.get(controls::AwbEnable);
> > > +       if (awbEnable && *awbEnable != state.autoEnabled) {
> > > +               state.autoEnabled = *awbEnable;
> > > +
> > > +               LOG(Awb, Debug)
> > > +                       << (*awbEnable ? "Enabling" : "Disabling") << " Awb";
> > > +       }
> > > +
> > > +       auto mode = controls.get(controls::AwbMode);
> > > +       if (mode) {
> > > +               auto it = modes_.find(static_cast<controls::AwbModeEnum>(*mode));
> > > +               if (it == modes_.end()) {
> > > +                       LOG(Awb, Error) << "Unsupported Awb mode " << *mode;
> > > +                       return;
> > > +               }
> > > +
> > > +               currentMode_ = &it->second;
> > > +       }
> > > +
> > > +       frameContext.autoEnabled = state.autoEnabled;
> > > +
> > > +       if (frameContext.autoEnabled)
> > > +               return;
> > > +
> > > +       const auto &colourGains = controls.get(controls::ColourGains);
> > > +       const auto &colourTemperature = controls.get(controls::ColourTemperature);
> > > +       bool update = false;
> > > +       if (colourGains) {
> > > +               state.manual.gains.r() = (*colourGains)[0];
> > > +               state.manual.gains.b() = (*colourGains)[1];
> > > +               /*
> > > +                * \todo Colour temperature reported in metadata is now
> > > +                * incorrect, as we can't deduce the temperature from the gains.
> > > +                * This will be fixed with the bayes AWB algorithm.
> > > +                */
> > > +               update = true;
> > > +       } else if (colourTemperature) {
> > > +               state.manual.temperatureK = *colourTemperature;
> > > +               const auto &gains = impl_->gainsFromColourTemperature(*colourTemperature);
> > > +               if (gains) {
> > > +                       state.manual.gains.r() = gains->r();
> > > +                       state.manual.gains.b() = gains->b();
> > > +                       update = true;
> > > +               }
> > > +       }
> > > +
> > > +       if (update)
> > > +               LOG(Awb, Debug)
> > > +                       << "Set colour gains to " << state.manual.gains;
> > > +
> > > +       frameContext.gains = state.manual.gains;
> > > +       frameContext.temperatureK = state.manual.temperatureK;
> > > +}
> > >
> > >  /**
> > > - * \fn AwbAlgorithm::handleControls()
> > > - * \param[in] controls The controls to handle
> > > - * \brief Handle the controls supplied in a request
> > > + * \brief Set the gains and colour temperature values in \a frameContext
> > > + * \param[in] state The awb active state
> > > + * \param[in] frameContext The awb frame context
> > > + *
> > > + * If auto mode is enabled, take the most recently computed gains and use them
> > > + * for the current frame. Otherwise, if in manual mode, gains and colour
> > > + * temperature for a frame are set at queueRequest() time.
> > >   */
> > > +void AwbAlgorithmBase::prepare(awb::ActiveState &state,
> > > +                              awb::FrameContext &frameContext)
> > > +{
> > > +       if (frameContext.autoEnabled) {
> > > +               frameContext.gains = state.automatic.gains;
> > > +               frameContext.temperatureK = state.automatic.temperatureK;
> > > +       }
> > > +}
> > >
> > >  /**
> > > + * \brief Process awb statistics to calculate gains and populate metadata
> > > + * \param[in] state The awb active state
> > > + * \param[in] frameContext The awb frame context
> > > + * \param[in] stats The awb statistics
> > > + * \param[in] lux The lux value as estimated by the IPA module
> > > + * \param[out] metadata The metadata list
> > > + *
> > > + * Process \a stats to calculate new gains and colour temperature and populate
> > > + * \a metadata with the results.
> > > + */
> > > +void AwbAlgorithmBase::process(awb::ActiveState &state,
> > > +                              awb::FrameContext &frameContext,
> > > +                              const AwbStats &stats, unsigned int lux,
> > > +                              ControlList &metadata)
> > > +{
> > > +       if (!stats.valid())
> > > +               return;
> > > +
> > > +       auto awbResult = impl_->calculateAwb(stats, lux, { currentMode_->ctLo,
> > > +                                                          currentMode_->ctHi });
> > > +
> > > +       /*
> > > +        * Clamp the gain values to the hardware, according to the gainMin_
> > > +        * and gainMax_ values.
> > > +        */
> > > +       awbResult.gains = awbResult.gains.clamp(gainMin_, gainMax_);
> > > +
> > > +       /* Smooth color gains adjustments. */
> > > +       double speed = 0.2;
> > > +       double ct = awbResult.temperatureK;
> > > +       ct = ct * speed + state.automatic.temperatureK * (1 - speed);
> > > +
> > > +       state.automatic.temperatureK = awbResult.temperatureK;
> > > +       state.automatic.gains = awbResult.gains * speed +
> > > +                               state.automatic.gains * (1 - speed);
> > > +
> > > +       /* Populate metadata. */
> > > +       metadata.set(controls::AwbEnable, frameContext.autoEnabled);
> > > +       metadata.set(controls::ColourGains, { static_cast<float>(frameContext.gains.r()),
> > > +                                             static_cast<float>(frameContext.gains.b()) });
> > > +       metadata.set(controls::ColourTemperature, frameContext.temperatureK);
> > > +
> > > +       LOG(Awb, Debug) << std::showpoint << "Means " << stats.rgbMeans()
> > > +                       << ", gains " << state.automatic.gains
> > > +                       << ", temp " << state.automatic.temperatureK << "K";
> > > +}
> > > +
> > > +/*
> > >   * \brief Parse the mode configurations from the tuning data
> > >   * \param[in] tuningData the ValueNode representing the tuning data
> > >   * \param[in] def The default value for the AwbMode control
> > >   *
> > >   * Utility function to parse the tuning data for an AwbMode entry and read all
> > > - * provided modes. It adds controls::AwbMode to AwbAlgorithm::controls_ and
> > > - * populates AwbAlgorithm::modes_. For a list of possible modes see \ref
> > > + * provided modes. It adds controls::AwbMode to AwbAlgorithmBase::controls_ and
> > > + * populates AwbAlgorithmBase::modes_. For a list of possible modes see \ref
> > >   * controls::AwbModeEnum.
> > >   *
> > >   * Each mode entry must contain a "lo" and "hi" key to specify the lower and
> > > @@ -156,15 +442,23 @@ namespace ipa {
> > >   *       ...
> > >   * \endcode
> > >   *
> > > - * If \a def is supplied but not contained in the the \a tuningData, -EINVAL is
> > > + * If \a def is supplied but not contained in the \a tuningData, -EINVAL is
> > >   * returned.
> > >   *
> > > + * AwbModes are only used by the AwbBayes implementation.
> > > + *
> > >   * \sa controls::AwbModeEnum
> > >   * \return Zero on success, negative error code otherwise
> > >   */
> > > -int AwbAlgorithm::parseModeConfigs(const ValueNode &tuningData,
> > > -                                  const ControlValue &def)
> > > +int AwbAlgorithmBase::parseModeConfigs(const ValueNode &tuningData,
> > > +                                      const ControlValue &def)
> > >  {
> > > +       if (!bayes_) {
> > > +               /* AwbGrey does not support and does not use modes. */
> > > +               currentMode_ = &AwbGreyMode;
> > > +               return 0;
> > > +       }
> > > +
> > >         std::vector<ControlValue> availableModes;
> > >
> > >         const ValueNode &modes = tuningData[controls::AwbMode.name()];
> > > @@ -227,37 +521,83 @@ int AwbAlgorithm::parseModeConfigs(const ValueNode &tuningData,
> > >         }
> > >
> > >         controls_[&controls::AwbMode] = ControlInfo(availableModes, def);
> > > +       currentMode_ = &modes_[controls::AwbAuto];
> > >
> > >         return 0;
> > >  }
> > >
> > >  /**
> > > - * \class AwbAlgorithm::ModeConfig
> > > - * \brief Holds the configuration of a single AWB mode
> > > + * \var AwbAlgorithmBase::controls_
> > > + * \brief Controls info map for the controls registered by the awb algorithm
> > > + */
> > > +
> > > +/**
> > > + * \var AwbAlgorithmBase::gainMin_
> > > + * \brief The minimum supported gain value
> > > + *
> > > + * Minimum gain value used to clamp the awb algorithm calculation results in the
> > > + * range supported by the platform awb engine.
> > >   *
> > > - * AWB modes limit the regulation of the AWB algorithm to a specific range of
> > > - * colour temperatures.
> > > + * The min and max gain values are initialized by AwbAlgorithm::init().
> > >   */
> > >
> > >  /**
> > > - * \var AwbAlgorithm::ModeConfig::ctLo
> > > + * \var AwbAlgorithmBase::gainMax_
> > > + * \brief The maximum supported gain value
> > > + *
> > > + * Maximum gain value used to clamp the awb algorithm calculation results in the
> > > + * range supported by the platform awb engine.
> > > + *
> > > + * The min and max gain values are initialized by AwbAlgorithm::init().
> > > + */
> > > +
> > > +/*
> > > + * \class AwbAlgorithmBase::ModeConfig
> > > + * \brief Holds the configuration of a single awb mode
> > > + *
> > > + * AWB modes limit the regulation of the awb algorithm to a specific range of
> > > + * colour temperatures. Use by AwbBayes only.
> > > + */
> > > +
> > > +/*
> > > + * \var AwbAlgorithmBase::ModeConfig::ctLo
> > >   * \brief The lowest valid colour temperature of that mode
> > >   */
> > >
> > > -/**
> > > - * \var AwbAlgorithm::ModeConfig::ctHi
> > > +/*
> > > + * \var AwbAlgorithmBase::ModeConfig::ctHi
> > >   * \brief The highest valid colour temperature of that mode
> > >   */
> > >
> > > +/*
> > > + * \var AwbAlgorithmBase::modes_
> > > + * \brief Map of all configured modes
> > > + * \sa AwbAlgorithmBase::parseModeConfigs
> > > + */
> > > +
> > >  /**
> > > - * \var AwbAlgorithm::controls_
> > > - * \brief Controls info map for the controls provided by the algorithm
> > > + * \class AwbAlgorithm
> > > + * \brief The libipa awb algorithm
> > > + * \tparam Q The fixedpoint register representation of gain values
> > > + *
> > > + * Implement the awb algorithm for libipa.
> > > + *
> > > + * The AwbAlgorithm class implements an interface similar in spirit to the one
> > > + * of the Algorithm class. IPA modules are expected to store an instance of
> > > + * AwbAlgorithm as class member, template it with the awb engine gain register
> > > + * representation and call its function in their implementations of the
> > > + * Algorithm interface.
> > > + *
> > > + * The AwbAlgorithm instantiates an AwbImplementation implementation (AwbGrey or
> > > + * AwbBayes) at init() time by parsing the tuning data and uses it to compute
> > > + * the RGB gains and estimate a colour temperature given a set of statistics
> > > + * in the form of a AwbStats derived implementation.
> > >   */
> > >
> > >  /**
> > > - * \var AwbAlgorithm::modes_
> > > - * \brief Map of all configured modes
> > > - * \sa AwbAlgorithm::parseModeConfigs
> > > + * \fn AwbAlgorithm::init()
> > > + * \param[in] controls The info map of the IPA controls
> > > + * \copydoc AwbAlgorithmBase::init()
> > >   */
> > >
> > >  } /* namespace ipa */
> > > diff --git a/src/ipa/libipa/awb.h b/src/ipa/libipa/awb.h
> > > index 09c00e47d604..72f46367a136 100644
> > > --- a/src/ipa/libipa/awb.h
> > > +++ b/src/ipa/libipa/awb.h
> > > @@ -2,11 +2,12 @@
> > >  /*
> > >   * Copyright (C) 2024 Ideas on Board Oy
> > >   *
> > > - * Generic AWB algorithms
> > > + * libIPA AWB algorithms
> > >   */
> > >
> > >  #pragma once
> > >
> > > +#include <array>
> > >  #include <map>
> > >  #include <optional>
> > >
> > > @@ -20,46 +21,118 @@ namespace libcamera {
> > >
> > >  namespace ipa {
> > >
> > > -struct AwbResult {
> > > +namespace awb {
> > > +
> > > +struct Context {
> > >         RGB<double> gains;
> > > -       double colourTemperature;
> > > +       unsigned int temperatureK;
> > >  };
> > >
> > > +struct ActiveState {
> > > +       Context manual;
> > > +       Context automatic;
> > > +
> > > +       bool autoEnabled;
> > > +};
> > > +
> > > +struct FrameContext : public Context {
> > > +       bool autoEnabled;
> > > +};
> > > +
> > > +} /* namespace awb */
> > > +
> > >  struct AwbStats {
> > > +       AwbStats() = default;
> > > +       virtual ~AwbStats() = default;
> > > +
> > >         virtual double computeColourError(const RGB<double> &gains) const = 0;
> > > -       virtual RGB<double> rgbMeans() const = 0;
> > > +       virtual bool valid() const = 0;
> > > +       RGB<double> rgbMeans() const { return rgbMeans_; }
> > >
> > >  protected:
> > > -       ~AwbStats() = default;
> > > +       RGB<double> rgbMeans_;
> > > +       double rg_;
> > > +       double bg_;
> >
> > I think these (rg_ and bg_) are not used anymore and can be dropped.
> 
> I see they being used by RkISP1AwbStats::computeColourError() which I
> haven't modified compared to master.
> 
> I can move rg_ and bg_ to RkISP1Stats but if you want them to be
> dropped and modify computeColourError() accordingly, it will be done
> on top.

Ahh now, I understand where that comes from. Yes that is in RkISP1Stats
and is only there to not calculate the ratios over and over again. The
reason is that rkisp1 has only one measurement window. For multi window
hardware this function needs to iterate over the measurements of the
individual windows.

So yes, these should be moved to the platform implementation.

> 
> > Personally I'd also leave the rgbMeans_ member in the platform
> > implementation and keep it as interface only - up to you.
> 
> rgbMeans_ is used by AwbGray

It uses rgbMeans() doesn't it?

> 
> >
> > >  };
> > >
> > > -class AwbAlgorithm
> > > +class AwbImplementation
> > >  {
> > >  public:
> > > -       virtual ~AwbAlgorithm() = default;
> > > +       struct Result {
> > > +               RGB<double> gains;
> > > +               unsigned int temperatureK;
> > > +       };
> > >
> > > +       virtual ~AwbImplementation() = default;
> > >         virtual int init(const ValueNode &tuningData) = 0;
> > > -       virtual AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) = 0;
> > > -       virtual std::optional<RGB<double>> gainsFromColourTemperature(double colourTemperature) = 0;
> > > +       virtual Result calculateAwb(const AwbStats &stats, unsigned int lux,
> > > +                                   std::array<double, 2> range) = 0;
> > > +       virtual std::optional<RGB<double>>
> > > +       gainsFromColourTemperature(double temperatureK) = 0;
> > > +};
> > >
> > > -       const ControlInfoMap::Map &controls() const
> > > -       {
> > > -               return controls_;
> > > -       }
> > > +class AwbAlgorithmBase
> > > +{
> > > +public:
> > > +       int init(const ValueNode &tuningData);
> > >
> > > -       virtual void handleControls([[maybe_unused]] const ControlList &controls) {}
> > > +       int configure(awb::ActiveState &state);
> > > +
> > > +       void queueRequest(awb::ActiveState &state,
> > > +                         const uint32_t frame,
> > > +                         awb::FrameContext &frameContext,
> > > +                         const ControlList &controls);
> > > +
> > > +       void prepare(awb::ActiveState &state, awb::FrameContext &frameContext);
> > > +
> > > +       void process(awb::ActiveState &state, awb::FrameContext &frameContext,
> > > +                    const AwbStats &stats, unsigned int lux,
> > > +                    ControlList &metadata);
> > >
> > >  protected:
> > > -       int parseModeConfigs(const ValueNode &tuningData,
> > > -                            const ControlValue &def = {});
> > > +       AwbAlgorithmBase() = default;
> > > +
> > > +       ControlInfoMap::Map controls_;
> > > +       float gainMin_;
> > > +       float gainMax_;
> > >
> > > +private:
> > >         struct ModeConfig {
> > >                 double ctHi;
> > >                 double ctLo;
> > >         };
> > >
> > > -       ControlInfoMap::Map controls_;
> > > -       std::map<controls::AwbModeEnum, AwbAlgorithm::ModeConfig> modes_;
> > > +       /* AwbGrey does not support modes; */
> > > +       static constexpr ModeConfig AwbGreyMode = { 0.0, 0.0 };
> > > +
> > > +       int parseModeConfigs(const ValueNode &tuningData,
> > > +                            const ControlValue &def = {});
> > > +
> > > +       std::map<controls::AwbModeEnum, AwbAlgorithmBase::ModeConfig> modes_;
> > > +       const ModeConfig *currentMode_ = nullptr;
> > > +       std::unique_ptr<AwbImplementation> impl_;
> > > +       bool bayes_ = false;
> > > +};
> > > +
> > > +template<typename Q>
> > > +class AwbAlgorithm : public AwbAlgorithmBase
> > > +{
> > > +public:
> > > +       int init(const ValueNode &tuningData, ControlInfoMap::Map &controls)
> > > +       {
> > > +               AwbAlgorithmBase::init(tuningData);
> > > +
> > > +               gainMin_ = std::max(Q::TraitsType::min, 1.0f);
> > > +               gainMax_ = Q::TraitsType::max;
> > > +
> > > +               controls_[&controls::ColourGains] =
> > > +                       ControlInfo(gainMin_, gainMax_,
> > > +                                   Span<const float, 2>{ { 1.0f, 1.0f } });
> > > +
> > > +               controls.insert(controls_.begin(), controls_.end());
> > > +
> > > +               return 0;
> > > +       }
> > >  };
> > >
> > >  } /* namespace ipa */
> > > diff --git a/src/ipa/libipa/awb_bayes.cpp b/src/ipa/libipa/awb_bayes.cpp
> > > index 9fd85e5a4505..2c10f9c4c82d 100644
> > > --- a/src/ipa/libipa/awb_bayes.cpp
> > > +++ b/src/ipa/libipa/awb_bayes.cpp
> > > @@ -140,11 +140,6 @@ void Interpolator<Pwl>::interpolate(const Pwl &a, const Pwl &b, Pwl &dest, doubl
> > >   * \brief How far to wander off CT curve towards "more green"
> > >   */
> > >
> > > -/**
> > > - * \var AwbBayes::currentMode_
> > > - * \brief The currently selected mode
> > > - */
> > > -
> > >  int AwbBayes::init(const ValueNode &tuningData)
> > >  {
> > >         int ret = colourGainCurve_.readYaml(tuningData["colourGains"], "ct", "gains");
> > > @@ -172,14 +167,6 @@ int AwbBayes::init(const ValueNode &tuningData)
> > >                 return ret;
> > >         }
> > >
> > > -       ret = parseModeConfigs(tuningData, controls::AwbAuto);
> > > -       if (ret) {
> > > -               LOG(Awb, Error)
> > > -                       << "Failed to parse mode parameter from tuning file";
> > > -               return ret;
> > > -       }
> > > -       currentMode_ = &modes_[controls::AwbAuto];
> > > -
> > >         transversePos_ = tuningData["transversePos"].get<double>(0.01);
> > >         transverseNeg_ = tuningData["transverseNeg"].get<double>(0.01);
> > >         if (transversePos_ <= 0 || transverseNeg_ <= 0) {
> > > @@ -260,18 +247,16 @@ int AwbBayes::readPriors(const ValueNode &tuningData)
> > >         return 0;
> > >  }
> > >
> > > -void AwbBayes::handleControls(const ControlList &controls)
> > > -{
> > > -       auto mode = controls.get(controls::AwbMode);
> > > -       if (mode) {
> > > -               auto it = modes_.find(static_cast<controls::AwbModeEnum>(*mode));
> > > -               if (it != modes_.end())
> > > -                       currentMode_ = &it->second;
> > > -               else
> > > -                       LOG(Awb, Error) << "Unsupported AWB mode " << *mode;
> > > -       }
> > > -}
> > > -
> > > +/**
> > > + * \brief Compute white balance gains from a colour temperature
> > > + * \param[in] colourTemperature The colour temperature in Kelvin
> > > + *
> > > + * Compute the white balance gains from a \a colourTemperature. This function
> > > + * does not take any statistics into account. It simply interpolates the colour
> > > + * gains curve on a new colour temperature.
> > > + *
> > > + * \return The RGB gain values adjusted to \a temperatureK
> > > + */
> > >  std::optional<RGB<double>> AwbBayes::gainsFromColourTemperature(double colourTemperature)
> > >  {
> > >         /*
> > > @@ -283,7 +268,9 @@ std::optional<RGB<double>> AwbBayes::gainsFromColourTemperature(double colourTem
> > >         return RGB<double>{ { gains[0], 1.0, gains[1] } };
> > >  }
> > >
> > > -AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux)
> > > +AwbImplementation::Result
> > > +AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux,
> > > +                      std::array<double, 2> range)
> > >  {
> > >         ipa::Pwl prior;
> > >         if (lux > 0) {
> > > @@ -295,7 +282,7 @@ AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux)
> > >                 prior.append(0, 1.0);
> > >         }
> > >
> > > -       double t = coarseSearch(prior, stats);
> > > +       double t = coarseSearch(prior, stats, range);
> > >         double r = ctR_.eval(t);
> > >         double b = ctB_.eval(t);
> > >         LOG(Awb, Debug)
> > > @@ -316,14 +303,15 @@ AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux)
> > >                 << "After fine search: r " << r << " b " << b << " (gains r "
> > >                 << 1 / r << " b " << 1 / b << ")";
> > >
> > > -       return { { { 1.0 / r, 1.0, 1.0 / b } }, t };
> > > +       return { { { 1.0 / r, 1.0, 1.0 / b } }, static_cast<unsigned int>(t) };
> > >  }
> > >
> > > -double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) const
> > > +double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats,
> > > +                             Span<double> range) const
> > >  {
> > >         std::vector<Pwl::Point> points;
> > >         size_t bestPoint = 0;
> > > -       double t = currentMode_->ctLo;
> > > +       double t = range[0];
> > >         int spanR = -1;
> > >         int spanB = -1;
> > >         LimitsRecorder<double> errorLimits;
> > > @@ -345,14 +333,14 @@ double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) cons
> > >                 if (points.back().y() < points[bestPoint].y())
> > >                         bestPoint = points.size() - 1;
> > >
> > > -               if (t == currentMode_->ctHi)
> > > +               if (t == range[1])
> > >                         break;
> > >
> > >                 /*
> > >                  * Ensure even steps along the r/b curve by scaling them by the
> > >                  * current t.
> > >                  */
> > > -               t = std::min(t + t / 10 * kSearchStep, currentMode_->ctHi);
> > > +               t = std::min(t + t / 10 * kSearchStep, range[1]);
> > >         }
> > >
> > >         t = points[bestPoint].x();
> > > diff --git a/src/ipa/libipa/awb_bayes.h b/src/ipa/libipa/awb_bayes.h
> > > index 1e3373676bc0..8d4cff076d4a 100644
> > > --- a/src/ipa/libipa/awb_bayes.h
> > > +++ b/src/ipa/libipa/awb_bayes.h
> > > @@ -20,22 +20,24 @@ namespace libcamera {
> > >
> > >  namespace ipa {
> > >
> > > -class AwbBayes : public AwbAlgorithm
> > > +class AwbBayes : public AwbImplementation
> > >  {
> > >  public:
> > >         AwbBayes() = default;
> > >
> > >         int init(const ValueNode &tuningData) override;
> > > -       AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) override;
> > > +       AwbImplementation::Result
> > > +       calculateAwb(const AwbStats &stats, unsigned int lux,
> > > +                    std::array<double, 2> range) override;
> > >         std::optional<RGB<double>> gainsFromColourTemperature(double temperatureK) override;
> > > -       void handleControls(const ControlList &controls) override;
> > >
> > >  private:
> > >         int readPriors(const ValueNode &tuningData);
> > >
> > >         void fineSearch(double &t, double &r, double &b, ipa::Pwl const &prior,
> > >                         const AwbStats &stats) const;
> > > -       double coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) const;
> > > +       double coarseSearch(const ipa::Pwl &prior, const AwbStats &stats,
> > > +                           Span<double> range) const;
> > >         double interpolateQuadratic(ipa::Pwl::Point const &a,
> > >                                     ipa::Pwl::Point const &b,
> > >                                     ipa::Pwl::Point const &c) const;
> > > @@ -50,8 +52,6 @@ private:
> > >
> > >         double transversePos_;
> > >         double transverseNeg_;
> > > -
> > > -       ModeConfig *currentMode_ = nullptr;
> > >  };
> > >
> > >  } /* namespace ipa */
> > > diff --git a/src/ipa/libipa/awb_grey.cpp b/src/ipa/libipa/awb_grey.cpp
> > > index b14b096810ae..1e4af717f62d 100644
> > > --- a/src/ipa/libipa/awb_grey.cpp
> > > +++ b/src/ipa/libipa/awb_grey.cpp
> > > @@ -60,6 +60,7 @@ int AwbGrey::init(const ValueNode &tuningData)
> > >   * \brief Calculate AWB data from the given statistics
> > >   * \param[in] stats The statistics to use for the calculation
> > >   * \param[in] lux The lux value of the scene
> > > + * \param[in] range The colour temperature search limits
> > >   *
> > >   * The colour temperature is estimated based on the colours::estimateCCT()
> > >   * function. The gains are calculated purely based on the RGB means provided by
> > > @@ -70,20 +71,23 @@ int AwbGrey::init(const ValueNode &tuningData)
> > >   *
> > >   * \return The AWB result
> > >   */
> > > -AwbResult AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned int lux)
> > > +AwbImplementation::Result
> > > +AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned int lux,
> > > +                     [[maybe_unused]] std::array<double, 2> range)
> > >  {
> > > -       AwbResult result;
> > > +       AwbImplementation::Result result;
> > >         auto means = stats.rgbMeans();
> > > -       result.colourTemperature = estimateCCT(means);
> > > +       result.temperatureK = estimateCCT(means);
> > >
> > >         /*
> > > -        * Estimate the red and blue gains to apply in a grey world. The green
> > > -        * gain is hardcoded to 1.0. Avoid divisions by zero by clamping the
> > > -        * divisor to a minimum value of 1.0.
> > > +        * Calculate the red and blue gains to apply in a grey world by simply
> > > +        * inverting the red/green and blue/green ratios as reported in
> > > +        * statistics. The green gain is hardcoded to 1.0.
> > >          */
> > >         result.gains.r() = means.g() / std::max(means.r(), 1.0);
> > >         result.gains.g() = 1.0;
> > >         result.gains.b() = means.g() / std::max(means.b(), 1.0);
> > > +
> > >         return result;
> > >  }
> > >
> > > @@ -93,15 +97,16 @@ AwbResult AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned
> > >   *
> > >   * Compute the white balance gains from a \a colourTemperature. This function
> > >   * does not take any statistics into account. It simply interpolates the colour
> > > - * gains configured in the colour temperature curve.
> > > + * gains curve (if provided in the tuning file) on a new colour temperature.
> > >   *
> > > - * \return The colour gains if a colour temperature curve is available,
> > > - * [1, 1, 1] otherwise.
> > > + * \return The RGB gain values adjusted to \a temperatureK or std::nullopt if
> > > + * no gain curve is specified in the tuning data
> > >   */
> > >  std::optional<RGB<double>> AwbGrey::gainsFromColourTemperature(double colourTemperature)
> > >  {
> > >         if (!colourGainCurve_) {
> > > -               LOG(Awb, Error) << "No gains defined";
> > > +               LOG(Awb, Info) << "No gains curve defined, "
> > > +                              << "unable to interpolate gains to colour temperature";
> > >                 return std::nullopt;
> > >         }
> > >
> > > diff --git a/src/ipa/libipa/awb_grey.h b/src/ipa/libipa/awb_grey.h
> > > index 154a2af97f15..d1b0088f0470 100644
> > > --- a/src/ipa/libipa/awb_grey.h
> > > +++ b/src/ipa/libipa/awb_grey.h
> > > @@ -18,13 +18,15 @@ namespace libcamera {
> > >
> > >  namespace ipa {
> > >
> > > -class AwbGrey : public AwbAlgorithm
> > > +class AwbGrey : public AwbImplementation
> > >  {
> > >  public:
> > >         AwbGrey() = default;
> > >
> > >         int init(const ValueNode &tuningData) override;
> > > -       AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) override;
> > > +       AwbImplementation::Result
> > > +       calculateAwb(const AwbStats &stats, unsigned int lux,
> > > +                    std::array<double, 2> range) override;
> > >         std::optional<RGB<double>> gainsFromColourTemperature(double colourTemperature) override;
> > >
> > >  private:
> > > diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp
> > > index 0eb05c2b6e1a..ae9f6a0c243e 100644
> > > --- a/src/ipa/rkisp1/algorithms/awb.cpp
> > > +++ b/src/ipa/rkisp1/algorithms/awb.cpp
> > > @@ -8,17 +8,12 @@
> > >  #include "awb.h"
> > >
> > >  #include <algorithm>
> > > -#include <ios>
> > >
> > >  #include <libcamera/base/log.h>
> > >
> > > -#include <libcamera/control_ids.h>
> > > -
> > >  #include <libcamera/ipa/core_ipa_interface.h>
> > >
> > > -#include "libipa/awb_bayes.h"
> > > -#include "libipa/awb_grey.h"
> > > -#include "libipa/colours.h"
> > > +#include "libcamera/internal/vector.h"
> > >
> > >  /**
> > >   * \file awb.h
> > > @@ -28,26 +23,16 @@ namespace libcamera {
> > >
> > >  namespace ipa::rkisp1::algorithms {
> > >
> > > -/**
> > > - * \class Awb
> > > - * \brief Manage the white balance with automatic and manual controls
> > > - */
> > > -
> > >  LOG_DEFINE_CATEGORY(RkISP1Awb)
> > >
> > > -constexpr int32_t kMinColourTemperature = 2500;
> > > -constexpr int32_t kMaxColourTemperature = 10000;
> > > -constexpr int32_t kDefaultColourTemperature = 5000;
> > > -
> > > -/* Minimum mean value below which AWB can't operate. */
> > > -constexpr double kMeanMinThreshold = 2.0;
> > > -
> > >  class RkISP1AwbStats final : public AwbStats
> > >  {
> > >  public:
> > > -       RkISP1AwbStats(const RGB<double> &rgbMeans)
> > > -               : rgbMeans_(rgbMeans)
> > > +       RkISP1AwbStats(const IPAFrameContext &frameContext,
> > > +                      const rkisp1_stat_buffer *stats)
> > >         {
> > > +               calculateRgbMeans(frameContext, stats);
> > > +
> > >                 rg_ = rgbMeans_.r() / rgbMeans_.g();
> > >                 bg_ = rgbMeans_.b() / rgbMeans_.g();
> > >         }
> > > @@ -65,17 +50,106 @@ public:
> > >                 return delta2;
> > >         }
> > >
> > > -       RGB<double> rgbMeans() const override
> > > +       bool valid() const override
> > >         {
> > > -               return rgbMeans_;
> > > +               /* Minimum mean value below which AWB can't operate. */
> > > +               constexpr double minValue = 2.0;
> >
> > As rgbMeans shall now be in the [0, 1] range, this min value is not
> 
> I might have missed why rgbMeans is now in [0, 1] range.
> I mean, I haven't intentionally modified the algorithm logic, so if it
> wasn't in that range before this patch, it shouldn't be after this
> patch

You added it after our last discussion to get rid of the magic 2.0 in
the validity check. But the current code doesn't normalize rgbMeans. 

> 
> > reachable anymore. I seem to miss the normalization code. As the
> 
> Where was it ?

It was never there. It needs to be added.

> 
> > validity test is no longer platform dependent we might want to remove
> > the valid() function and move the test into the core algorithm?
> >
> > > +
> > > +               return rgbMeans_.r() > minValue || rgbMeans_.g() > minValue ||
> > > +                      rgbMeans_.b() > minValue;
> > >         }
> > >
> > >  private:
> > > -       RGB<double> rgbMeans_;
> > > -       double rg_;
> > > -       double bg_;
> > > +       void calculateRgbMeans(const IPAFrameContext &frameContext,
> > > +                              const rkisp1_stat_buffer *stats);
> > > +       bool rgbMode_ = false;
> >
> > rgbMode_ is now duplicated and lost its connection to Awb::rgbMode_. One
> > solution might be to pass the measurement mode to the constructor?
> >
> 
> Ah yes, we now have two rgbMode_ which is not nice.
> 
> To be honest, the whole RGB mode support is there mostly for
> documentation purposes as rgbMode_ is hardcoded to false (in RPPX1 I
> dropped the RGB part completely).can

It still puzzles me, that we can't use it. So I'd like to keep the
single switch to play around with both options. (This will become more
interesting when we add Awb64 support)

> 
> I can pass the RkISP1 AWB::rgbMode_ to RkISP1Stats() so at least the
> two are connected.

That would be great.

> 
> > >  };
> > >
> > > +void RkISP1AwbStats::calculateRgbMeans(const IPAFrameContext &frameContext,
> > > +                                      const rkisp1_stat_buffer *stats)
> > > +{
> > > +       if (!stats || !(stats->meas_type & RKISP1_CIF_ISP_STAT_AWB)) {
> > > +               LOG(RkISP1Awb, Error) << "AWB data is missing in statistics";
> > > +               return;
> > > +       }
> > > +
> > > +       const rkisp1_cif_isp_awb_stat *awb = &stats->params.awb;
> > > +
> > > +       if (awb->awb_mean[0].cnt == 0) {
> > > +               LOG(RkISP1Awb, Debug) << "AWB statistics are empty";
> > > +               return;
> >
> > I think the RGB class doesn't zero initialize it's components. So this
> 
> Should I rbgMeans_ = {} at the beginning of the function ?

Yes, or in the constructor.

> 
> > early return now leads to possibly valid stats with random data in them
> > - or did I miss something?
> >
> 
> Possibily.
> 
> To be honest I should have pushed back against the request of moving
> calculateRgbMeans() inside RkISP1AwbStats as it's largely unrelated to
> this series.

Yes, we could still do that on top. But for centralizing the validity
check, it would at least be nice to normalize rgbMeans.

> 
> > > +       }
> > > +
> > > +       if (rgbMode_) {
> > > +               rgbMeans_ = {{
> > > +                       static_cast<double>(awb->awb_mean[0].mean_cr_or_r),
> > > +                       static_cast<double>(awb->awb_mean[0].mean_y_or_g),
> > > +                       static_cast<double>(awb->awb_mean[0].mean_cb_or_b)
> > > +               }};
> > > +       } else {
> > > +               /* Get the YCbCr mean values */
> > > +               Vector<double, 3> yuvMeans({
> > > +                       static_cast<double>(awb->awb_mean[0].mean_y_or_g),
> > > +                       static_cast<double>(awb->awb_mean[0].mean_cb_or_b),
> > > +                       static_cast<double>(awb->awb_mean[0].mean_cr_or_r)
> > > +               });
> > > +
> > > +               /*
> > > +                * Convert from YCbCr to RGB. The hardware uses the following
> > > +                * formulas:
> > > +                *
> > > +                * Y  =  16 + 0.2500 R + 0.5000 G + 0.1094 B
> > > +                * Cb = 128 - 0.1406 R - 0.2969 G + 0.4375 B
> > > +                * Cr = 128 + 0.4375 R - 0.3750 G - 0.0625 B
> > > +                *
> > > +                * This seems to be based on limited range BT.601 with Q1.6
> > > +                * precision.
> > > +                *
> > > +                * The inverse matrix is:
> > > +                *
> > > +                * [[1,1636, -0,0623,  1,6008]
> > > +                *  [1,1636, -0,4045, -0,7949]
> > > +                *  [1,1636,  1,9912, -0,0250]]
> > > +                */
> > > +               static const Matrix<double, 3, 3> yuv2rgbMatrix({
> > > +                       1.1636, -0.0623,  1.6008,
> > > +                       1.1636, -0.4045, -0.7949,
> > > +                       1.1636,  1.9912, -0.0250
> > > +               });
> > > +               static const Vector<double, 3> yuv2rgbOffset({
> > > +                       16, 128, 128
> > > +               });
> > > +
> > > +               rgbMeans_ = yuv2rgbMatrix * (yuvMeans - yuv2rgbOffset);
> > > +
> > > +               /*
> > > +                * Due to hardware rounding errors in the YCbCr means, the
> > > +                * calculated RGB means may be negative. This would lead to
> > > +                * negative gains, messing up calculation. Prevent this by
> > > +                * clamping the means to positive values.
> > > +                */
> > > +               rgbMeans_ = rgbMeans_.max(0.0);
> > > +       }
> > > +
> > > +       /*
> > > +        * The ISP computes the AWB means after applying the CCM. Apply the
> > > +        * inverse as we want to get the raw means before the colour gains.
> > > +        */
> > > +       rgbMeans_ = frameContext.ccm.ccm.inverse() * rgbMeans_;
> > > +
> > > +       /*
> > > +        * The ISP computes the AWB means after applying the colour gains,
> > > +        * divide by the gains that were used to get the raw means from the
> > > +        * sensor. Apply a minimum value to avoid divisions by near-zero.
> > > +        */
> > > +       rgbMeans_ /= frameContext.awb.gains.max(0.01);
> >
> > I think this misses the normalization with awb_mean[0].cnt.
> 
> I think I'm missing where it was in the existing code, as this should
> be a copy of the already existing Awb::calculateRgbMeans() function.

It wasn't there.

> 
> >
> > > +}
> > > +
> > > +/**
> > > + * \class Awb
> > > + * \brief Manage the white balance with automatic and manual controls
> > > + */
> > > +
> > >  Awb::Awb()
> > >         : rgbMode_(false)
> > >  {
> > > @@ -86,40 +160,7 @@ Awb::Awb()
> > >   */
> > >  int Awb::init(IPAContext &context, const ValueNode &tuningData)
> > >  {
> > > -       auto &cmap = context.ctrlMap;
> > > -       cmap[&controls::ColourTemperature] = ControlInfo(kMinColourTemperature,
> > > -                                                        kMaxColourTemperature,
> > > -                                                        kDefaultColourTemperature);
> > > -       cmap[&controls::AwbEnable] = ControlInfo(false, true);
> > > -
> > > -       cmap[&controls::ColourGains] = ControlInfo(0.0f, 3.996f,
> > > -                                                  Span<const float, 2>{ { 1.0f, 1.0f } });
> > > -
> > > -       if (!tuningData.contains("algorithm"))
> > > -               LOG(RkISP1Awb, Info) << "No AWB algorithm specified."
> > > -                                    << " Default to grey world";
> > > -
> > > -       auto mode = tuningData["algorithm"].get<std::string>("grey");
> > > -       if (mode == "grey") {
> > > -               awbAlgo_ = std::make_unique<AwbGrey>();
> > > -       } else if (mode == "bayes") {
> > > -               awbAlgo_ = std::make_unique<AwbBayes>();
> > > -       } else {
> > > -               LOG(RkISP1Awb, Error) << "Unknown AWB algorithm: " << mode;
> > > -               return -EINVAL;
> > > -       }
> > > -       LOG(RkISP1Awb, Debug) << "Using AWB algorithm: " << mode;
> > > -
> > > -       int ret = awbAlgo_->init(tuningData);
> > > -       if (ret) {
> > > -               LOG(RkISP1Awb, Error) << "Failed to init AWB algorithm";
> > > -               return ret;
> > > -       }
> > > -
> > > -       const auto &src = awbAlgo_->controls();
> > > -       cmap.insert(src.begin(), src.end());
> > > -
> > > -       return 0;
> > > +       return awbAlgo_.init(tuningData, context.ctrlMap);
> > >  }
> > >
> > >  /**
> > > @@ -128,16 +169,7 @@ int Awb::init(IPAContext &context, const ValueNode &tuningData)
> > >  int Awb::configure(IPAContext &context,
> > >                    const IPACameraSensorInfo &configInfo)
> > >  {
> > > -       context.activeState.awb.manual.gains = RGB<double>{ 1.0 };
> > > -       auto gains = awbAlgo_->gainsFromColourTemperature(kDefaultColourTemperature);
> > > -       if (gains)
> > > -               context.activeState.awb.automatic.gains = *gains;
> > > -       else
> > > -               context.activeState.awb.automatic.gains = RGB<double>{ 1.0 };
> > > -
> > > -       context.activeState.awb.autoEnabled = true;
> > > -       context.activeState.awb.manual.temperatureK = kDefaultColourTemperature;
> > > -       context.activeState.awb.automatic.temperatureK = kDefaultColourTemperature;
> > > +       awbAlgo_.configure(context.activeState.awb);
> > >
> > >         /*
> > >          * Define the measurement window for AWB as a centered rectangle
> > > @@ -156,56 +188,12 @@ int Awb::configure(IPAContext &context,
> > >  /**
> > >   * \copydoc libcamera::ipa::Algorithm::queueRequest
> > >   */
> > > -void Awb::queueRequest(IPAContext &context,
> > > -                      [[maybe_unused]] const uint32_t frame,
> > > +void Awb::queueRequest(IPAContext &context, const uint32_t frame,
> > >                        IPAFrameContext &frameContext,
> > >                        const ControlList &controls)
> > >  {
> > > -       auto &awb = context.activeState.awb;
> > > -
> > > -       const auto &awbEnable = controls.get(controls::AwbEnable);
> > > -       if (awbEnable && *awbEnable != awb.autoEnabled) {
> > > -               awb.autoEnabled = *awbEnable;
> > > -
> > > -               LOG(RkISP1Awb, Debug)
> > > -                       << (*awbEnable ? "Enabling" : "Disabling") << " AWB";
> > > -       }
> > > -
> > > -       awbAlgo_->handleControls(controls);
> > > -
> > > -       frameContext.awb.autoEnabled = awb.autoEnabled;
> > > -
> > > -       if (awb.autoEnabled)
> > > -               return;
> > > -
> > > -       const auto &colourGains = controls.get(controls::ColourGains);
> > > -       const auto &colourTemperature = controls.get(controls::ColourTemperature);
> > > -       bool update = false;
> > > -       if (colourGains) {
> > > -               awb.manual.gains.r() = (*colourGains)[0];
> > > -               awb.manual.gains.b() = (*colourGains)[1];
> > > -               /*
> > > -                * \todo Colour temperature reported in metadata is now
> > > -                * incorrect, as we can't deduce the temperature from the gains.
> > > -                * This will be fixed with the bayes AWB algorithm.
> > > -                */
> > > -               update = true;
> > > -       } else if (colourTemperature) {
> > > -               awb.manual.temperatureK = *colourTemperature;
> > > -               const auto &gains = awbAlgo_->gainsFromColourTemperature(*colourTemperature);
> > > -               if (gains) {
> > > -                       awb.manual.gains.r() = gains->r();
> > > -                       awb.manual.gains.b() = gains->b();
> > > -                       update = true;
> > > -               }
> > > -       }
> > > -
> > > -       if (update)
> > > -               LOG(RkISP1Awb, Debug)
> > > -                       << "Set colour gains to " << awb.manual.gains;
> > > -
> > > -       frameContext.awb.gains = awb.manual.gains;
> > > -       frameContext.awb.temperatureK = awb.manual.temperatureK;
> > > +       awbAlgo_.queueRequest(context.activeState.awb, frame, frameContext.awb,
> > > +                             controls);
> > >  }
> > >
> > >  /**
> > > @@ -214,15 +202,7 @@ void Awb::queueRequest(IPAContext &context,
> > >  void Awb::prepare(IPAContext &context, const uint32_t frame,
> > >                   IPAFrameContext &frameContext, RkISP1Params *params)
> > >  {
> > > -       /*
> > > -        * This is the latest time we can read the active state. This is the
> > > -        * most up-to-date automatic values we can read.
> > > -        */
> > > -       if (frameContext.awb.autoEnabled) {
> > > -               const auto &awb = context.activeState.awb;
> > > -               frameContext.awb.gains = awb.automatic.gains;
> > > -               frameContext.awb.temperatureK = awb.automatic.temperatureK;
> > > -       }
> > > +       awbAlgo_.prepare(context.activeState.awb, frameContext.awb);
> > >
> > >         auto gainConfig = params->block<BlockType::AwbGain>();
> > >         gainConfig.setEnabled(true);
> > > @@ -291,136 +271,12 @@ void Awb::process(IPAContext &context,
> > >                   const rkisp1_stat_buffer *stats,
> > >                   ControlList &metadata)
> > >  {
> > > -       IPAActiveState &activeState = context.activeState;
> > > -
> > > -       metadata.set(controls::AwbEnable, frameContext.awb.autoEnabled);
> > > -       metadata.set(controls::ColourGains, {
> > > -                       static_cast<float>(frameContext.awb.gains.r()),
> > > -                       static_cast<float>(frameContext.awb.gains.b())
> > > -               });
> > > -       metadata.set(controls::ColourTemperature, frameContext.awb.temperatureK);
> > > -
> > > -       if (!stats || !(stats->meas_type & RKISP1_CIF_ISP_STAT_AWB)) {
> > > -               LOG(RkISP1Awb, Error) << "AWB data is missing in statistics";
> > > -               return;
> > > -       }
> > > -
> > > -       const rkisp1_cif_isp_stat *params = &stats->params;
> > > -       const rkisp1_cif_isp_awb_stat *awb = &params->awb;
> > > +       RkISP1AwbStats awbStats(frameContext, stats);
> > >
> > > -       if (awb->awb_mean[0].cnt == 0) {
> > > -               LOG(RkISP1Awb, Debug) << "AWB statistics are empty";
> > > -               return;
> > > -       }
> > > -
> > > -       RGB<double> rgbMeans = calculateRgbMeans(frameContext, awb);
> > > -
> > > -       /*
> > > -        * If the means are too small we don't have enough information to
> > > -        * meaningfully calculate gains. Freeze the algorithm in that case.
> > > -        */
> > > -       if (rgbMeans.r() < kMeanMinThreshold && rgbMeans.g() < kMeanMinThreshold &&
> > > -           rgbMeans.b() < kMeanMinThreshold)
> > > -               return;
> > > -
> > > -       RkISP1AwbStats awbStats{ rgbMeans };
> > > -       AwbResult awbResult = awbAlgo_->calculateAwb(awbStats, frameContext.lux.lux);
> > > -
> > > -       /*
> > > -        * Clamp the gain values to the hardware, which expresses gains as Q2.8
> > > -        * unsigned integer values. Set the minimum just above zero to avoid
> > > -        * divisions by zero when computing the raw means in subsequent
> > > -        * iterations.
> > > -        */
> > > -       awbResult.gains = awbResult.gains.clamp(1.0 / 256, 1023.0 / 256);
> > > -
> > > -       /* Filter the values to avoid oscillations. */
> > > -       double speed = 0.2;
> > > -       double ct = awbResult.colourTemperature;
> > > -       ct = ct * speed + activeState.awb.automatic.temperatureK * (1 - speed);
> > > -       awbResult.gains = awbResult.gains * speed +
> > > -                         activeState.awb.automatic.gains * (1 - speed);
> > > -
> > > -       activeState.awb.automatic.temperatureK = static_cast<unsigned int>(ct);
> > > -       activeState.awb.automatic.gains = awbResult.gains;
> > > -
> > > -       LOG(RkISP1Awb, Debug)
> > > -               << std::showpoint
> > > -               << "Means " << rgbMeans << ", gains "
> > > -               << activeState.awb.automatic.gains << ", temp "
> > > -               << activeState.awb.automatic.temperatureK << "K";
> > > +       awbAlgo_.process(context.activeState.awb, frameContext.awb, awbStats,
> > > +                        frameContext.lux.lux, metadata);
> > >  }
> > >
> > > -RGB<double> Awb::calculateRgbMeans(const IPAFrameContext &frameContext, const rkisp1_cif_isp_awb_stat *awb) const
> > > -{
> > > -       Vector<double, 3> rgbMeans;
> > > -
> > > -       if (rgbMode_) {
> > > -               rgbMeans = {{
> > > -                       static_cast<double>(awb->awb_mean[0].mean_cr_or_r),
> > > -                       static_cast<double>(awb->awb_mean[0].mean_y_or_g),
> > > -                       static_cast<double>(awb->awb_mean[0].mean_cb_or_b)
> > > -               }};
> > > -       } else {
> > > -               /* Get the YCbCr mean values */
> > > -               Vector<double, 3> yuvMeans({
> > > -                       static_cast<double>(awb->awb_mean[0].mean_y_or_g),
> > > -                       static_cast<double>(awb->awb_mean[0].mean_cb_or_b),
> > > -                       static_cast<double>(awb->awb_mean[0].mean_cr_or_r)
> > > -               });
> > > -
> > > -               /*
> > > -                * Convert from YCbCr to RGB. The hardware uses the following
> > > -                * formulas:
> > > -                *
> > > -                * Y  =  16 + 0.2500 R + 0.5000 G + 0.1094 B
> > > -                * Cb = 128 - 0.1406 R - 0.2969 G + 0.4375 B
> > > -                * Cr = 128 + 0.4375 R - 0.3750 G - 0.0625 B
> > > -                *
> > > -                * This seems to be based on limited range BT.601 with Q1.6
> > > -                * precision.
> > > -                *
> > > -                * The inverse matrix is:
> > > -                *
> > > -                * [[1,1636, -0,0623,  1,6008]
> > > -                *  [1,1636, -0,4045, -0,7949]
> > > -                *  [1,1636,  1,9912, -0,0250]]
> > > -                */
> > > -               static const Matrix<double, 3, 3> yuv2rgbMatrix({
> > > -                       1.1636, -0.0623,  1.6008,
> > > -                       1.1636, -0.4045, -0.7949,
> > > -                       1.1636,  1.9912, -0.0250
> > > -               });
> > > -               static const Vector<double, 3> yuv2rgbOffset({
> > > -                       16, 128, 128
> > > -               });
> > > -
> > > -               rgbMeans = yuv2rgbMatrix * (yuvMeans - yuv2rgbOffset);
> > > -
> > > -               /*
> > > -                * Due to hardware rounding errors in the YCbCr means, the
> > > -                * calculated RGB means may be negative. This would lead to
> > > -                * negative gains, messing up calculation. Prevent this by
> > > -                * clamping the means to positive values.
> > > -                */
> > > -               rgbMeans = rgbMeans.max(0.0);
> > > -       }
> > > -
> > > -       /*
> > > -        * The ISP computes the AWB means after applying the CCM. Apply the
> > > -        * inverse as we want to get the raw means before the colour gains.
> > > -        */
> > > -       rgbMeans = frameContext.ccm.ccm.inverse() * rgbMeans;
> > > -
> > > -       /*
> > > -        * The ISP computes the AWB means after applying the colour gains,
> > > -        * divide by the gains that were used to get the raw means from the
> > > -        * sensor. Apply a minimum value to avoid divisions by near-zero.
> > > -        */
> > > -       rgbMeans /= frameContext.awb.gains.max(0.01);
> > > -
> > > -       return rgbMeans;
> > > -}
> >
> > Moving this big function would have been an easy cut out into its own
> > patch?
> 
> You're right. I shouldn't be doing this as part of this series.
> I'll move calculateRgbMeans() back to the Awb class.

Fine with me. The reason I suggested this was that it lead to a bad
pattern in the mali implementation.

Best regards,
Stefan

> 
> Thanks
>   j
> 
> >
> > Best regards,
> > Stefan
> >
> > >
> > >  REGISTER_IPA_ALGORITHM(Awb, "Awb")
> > >
> > > diff --git a/src/ipa/rkisp1/algorithms/awb.h b/src/ipa/rkisp1/algorithms/awb.h
> > > index 60d9ef111495..c7cb59a75bde 100644
> > > --- a/src/ipa/rkisp1/algorithms/awb.h
> > > +++ b/src/ipa/rkisp1/algorithms/awb.h
> > > @@ -7,17 +7,25 @@
> > >
> > >  #pragma once
> > >
> > > -#include "libcamera/internal/vector.h"
> > > +#include <linux/rkisp1-config.h>
> > > +
> > > +#include <libcamera/controls.h>
> > > +
> > > +#include "libcamera/internal/value_node.h"
> > >
> > >  #include "libipa/awb.h"
> > > -#include "libipa/interpolator.h"
> > > +#include "libipa/fixedpoint.h"
> > >
> > >  #include "algorithm.h"
> > > +#include "ipa_context.h"
> > > +#include "params.h"
> > >
> > >  namespace libcamera {
> > >
> > >  namespace ipa::rkisp1::algorithms {
> > >
> > > +class RkISP1AwbStats;
> > > +
> > >  class Awb : public Algorithm
> > >  {
> > >  public:
> > > @@ -38,10 +46,10 @@ public:
> > >                      ControlList &metadata) override;
> > >
> > >  private:
> > > -       RGB<double> calculateRgbMeans(const IPAFrameContext &frameContext,
> > > -                                     const rkisp1_cif_isp_awb_stat *awb) const;
> > > +       RkISP1AwbStats calculateRgbMeans(const IPAFrameContext &frameContext,
> > > +                                        const rkisp1_stat_buffer *stats) const;
> > >
> > > -       std::unique_ptr<AwbAlgorithm> awbAlgo_;
> > > +       AwbAlgorithm<UQ<2, 8>> awbAlgo_;
> > >
> > >         bool rgbMode_;
> > >  };
> > > diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h
> > > index e5f70d934d67..c36c1f7e0084 100644
> > > --- a/src/ipa/rkisp1/ipa_context.h
> > > +++ b/src/ipa/rkisp1/ipa_context.h
> > > @@ -25,6 +25,7 @@
> > >  #include "libcamera/internal/vector.h"
> > >
> > >  #include "libipa/agc_mean_luminance.h"
> > > +#include "libipa/awb.h"
> > >  #include "libipa/camera_sensor_helper.h"
> > >  #include "libipa/fc_queue.h"
> > >  #include "libipa/fixedpoint.h"
> > > @@ -48,15 +49,17 @@ struct IPAHwSettings {
> > >         bool compand;
> > >  };
> > >
> > > +struct RKISP1AwbSession {
> > > +       struct rkisp1_cif_isp_window measureWindow;
> > > +       bool enabled;
> > > +};
> > > +
> > >  struct IPASessionConfiguration {
> > >         struct {
> > >                 struct rkisp1_cif_isp_window measureWindow;
> > >         } agc;
> > >
> > > -       struct {
> > > -               struct rkisp1_cif_isp_window measureWindow;
> > > -               bool enabled;
> > > -       } awb;
> > > +       struct RKISP1AwbSession awb;
> > >
> > >         struct {
> > >                 bool supported;
> > > @@ -100,17 +103,7 @@ struct IPAActiveState {
> > >                 utils::Duration maxFrameDuration;
> > >         } agc;
> > >
> > > -       struct {
> > > -               struct AwbState {
> > > -                       RGB<double> gains;
> > > -                       unsigned int temperatureK;
> > > -               };
> > > -
> > > -               AwbState manual;
> > > -               AwbState automatic;
> > > -
> > > -               bool autoEnabled;
> > > -       } awb;
> > > +       ipa::awb::ActiveState awb;
> > >
> > >         struct {
> > >                 Matrix<float, 3, 3> manual;
> > > @@ -176,11 +169,7 @@ struct IPAFrameContext : public FrameContext {
> > >                 bool autoGainModeChange;
> > >         } agc;
> > >
> > > -       struct {
> > > -               RGB<double> gains;
> > > -               bool autoEnabled;
> > > -               unsigned int temperatureK;
> > > -       } awb;
> > > +       ipa::awb::FrameContext awb;
> > >
> > >         struct {
> > >                 float actualBrightness;
> > >
> > > --
> > > 2.54.0
> > >
Robert Mader July 7, 2026, 11:09 a.m. UTC | #4
Hi, thanks for the new revision!

On 06.07.26 10:01, Jacopo Mondi wrote:
> The current implementation of AwbAlgorithm is the following one:
>
> AwbAlgorithm o---- RkISP1Awb
>      ^
>      |
>   |-----|
>   |     |
> Bayes Grey
>
> The platform-specific Awb implementation instantiate one of
> the Bayes or Grey Awb helper classes and both inherit from the
> AwbAlgorithm base class.
>
> The handling of libcamera controls is split between the base and the
> derived (mostly Bayes) class.
>
> We want instead the AwbAlgorithm to handle all-things-libcamera and let
> the Bayes and Grey classes implement the logic. In order to achieve that
> use composition also in the AwbAlgorithm class and introduce an
> AwbImplementation base class from which Bayes and Grey derive from.
>
> AwbImplementation o---- AwbAlgorithmBase
>      ^                           ^
>      |                           |
>   |-----|                AwbAlgorithm<Q<x,y>> o---- RkISP1Awb
>   |     |
> Bayes Grey
>
> The AwbAlgorithm<> class is instantiated with a Q<x, y> template
> argument that represents the platform-specific register format which
> is used to initialize controls and clamp gains to the hardware limits.
>
> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
> Tested-by: Daniel Scally <dan.scally@ideasonboard.com>
Tested-by: Robert Mader <robert.mader@collabora.com>
> ---
>   src/ipa/libipa/awb.cpp            | 472 ++++++++++++++++++++++++++++++++------
>   src/ipa/libipa/awb.h              | 109 +++++++--
>   src/ipa/libipa/awb_bayes.cpp      |  52 ++---
>   src/ipa/libipa/awb_bayes.h        |  12 +-
>   src/ipa/libipa/awb_grey.cpp       |  25 +-
>   src/ipa/libipa/awb_grey.h         |   6 +-
>   src/ipa/rkisp1/algorithms/awb.cpp | 360 +++++++++--------------------
>   src/ipa/rkisp1/algorithms/awb.h   |  18 +-
>   src/ipa/rkisp1/ipa_context.h      |  29 +--
>   9 files changed, 672 insertions(+), 411 deletions(-)
>
> diff --git a/src/ipa/libipa/awb.cpp b/src/ipa/libipa/awb.cpp
> index 29a78069c52c..95291840d8fb 100644
> --- a/src/ipa/libipa/awb.cpp
> +++ b/src/ipa/libipa/awb.cpp
> @@ -2,7 +2,7 @@
>   /*
>    * Copyright (C) 2024 Ideas on Board Oy
>    *
> - * Generic AWB algorithms
> + * libIPA Awb algorithms
>    */
>   
>   #include "awb.h"
> @@ -11,9 +11,16 @@
>   
>   #include <libcamera/control_ids.h>
>   
> +#include "awb_bayes.h"
> +#include "awb_grey.h"
> +
> +constexpr int32_t kMinColourTemperature = 2500;
> +constexpr int32_t kMaxColourTemperature = 10000;
> +constexpr int32_t kDefaultColourTemperature = 5000;
> +
>   /**
>    * \file awb.h
> - * \brief Base classes for AWB algorithms
> + * \brief libipa awb implementation
>    */
>   
>   namespace libcamera {
> @@ -22,30 +29,60 @@ LOG_DEFINE_CATEGORY(Awb)
>   
>   namespace ipa {
>   
> +namespace awb {
> +
>   /**
> - * \class AwbResult
> - * \brief The result of an AWB calculation
> + * \struct Context
> + * \brief Awb gains and colour temperature
> + *
> + * \var Context::gains
> + * \brief The white balance gains
>    *
> - * This class holds the result of an auto white balance calculation.
> + * \var Context::temperatureK
> + * \brief The colour temperature, in Kelvin
>    */
>   
>   /**
> - * \var AwbResult::gains
> - * \brief The calculated white balance gains
> + * \struct ActiveState
> + * \brief Active awb state shared across frames
> + *
> + * \var ActiveState::manual
> + * \brief The most recent manually requested awb state
> + *
> + * \var ActiveState::automatic
> + * \brief The most recent automatically calculated awb state
> + *
> + * \var ActiveState::autoEnabled
> + * \brief True when automatic awb is selected
>    */
>   
>   /**
> - * \var AwbResult::colourTemperature
> - * \brief The calculated colour temperature in Kelvin
> + * \struct FrameContext
> + * \brief Per-frame awb state
> + *
> + * \var FrameContext::autoEnabled
> + * \brief True when automatic awb is in use
>    */
>   
> +} /* namespace awb */
> +
>   /**
>    * \class AwbStats
> - * \brief An abstraction class wrapping hardware-specific AWB statistics
> + * \brief An abstraction class wrapping hardware-specific awb statistics
>    *
> - * IPA modules using an AWB algorithm based on the AwbAlgorithm class need to
> - * implement this class to give the algorithm access to the hardware-specific
> - * statistics data.
> + * IPA modules shall provide a derived implementation of this class to give the
> + * awb algorithm access to the hardware-specific statistics data in a common
> + * format.
> + */
> +
> +/**
> + * \fn AwbStats::AwbStats()
> + * \brief Construct an empty AwbStat
> + */
> +
> +/**
> + * AwbStat::~AwbStat
> + * \brief Virtual class destructor
>    */
>   
>   /**
> @@ -57,7 +94,7 @@ namespace ipa {
>    * applied. To keep the actual implementations computationally inexpensive,
>    * the squared colour error shall be returned.
>    *
> - * If the AWB statistics provide multiple zones, the average of the individual
> + * If the awb statistics provide multiple zones, the average of the individual
>    * squared errors shall be returned. Averaging/normalizing is necessary so that
>    * the numeric dimensions are the same on all hardware platforms.
>    *
> @@ -65,79 +102,328 @@ namespace ipa {
>    */
>   
>   /**
> - * \fn AwbStats::rgbMeans()
> - * \brief Get RGB means of the statistics
> + * \fn AwbStats::valid()
> + * \brief Retrieve if the awb statistics are valid
>    *
> - * Fetch the RGB means from the statistics. The values of each channel are
> - * dimensionless and only the ratios are used for further calculations. This is
> - * used by the simple grey world model to calculate the gains to apply.
> + * If the colour mean values are too small, the AwbAlgorithm class doesn't have
> + * enough information to meaningfully calculate white-balance gains. Freeze the
> + * algorithm in that case.
> + *
> + * \return True if the awb statistics are valid, false otherwise
> + */
> +
> +/**
> + * \fn AwbStats::rgbMeans
> + * \brief Retrieve the RGB means values
> + *
> + * Fetch the RGB means from the statistics in the [0, 1] range.
>    *
>    * \return The RGB means
>    */
>   
>   /**
> - * \class AwbAlgorithm
> - * \brief A base class for auto white balance algorithms
> + * \var AwbStats::rgbMeans_
> + * \brief Mean values of colour channels
> + */
> +
> +/**
> + * \var AwbStats::rg_
> + * \brief Red/Green ratio
> + */
> +
> +/**
> + * \var AwbStats::bg_
> + * \brief Blue/Green ratio
> + */
> +
> +/**
> + * \class AwbImplementation
> + * \brief Pure virtual base class for awb algorithms implementations
> + *
> + * The AwbImplementation class defines the interface for the awb algorithm
> + * implementations.
>    *
> - * This class is a base class for auto white balance algorithms. It defines an
> - * interface for the algorithms to implement, and is used by the IPAs to
> - * interact with the concrete implementation.
> + * It is currently implemented by the AwbGrey and AwbBayes classes.
> + *
> + * The interface defines an init() function to initialize the algorithm with the
> + * content of the tuning file and two functions to compute colour gains
> + * according to the algorithm operating mode (auto or manual) in use.
> + *
> + * The calculateAwb() function calculates colour gains given a set of statistics
> + * provided by the IPA module. It is used when the algorithm operates in auto
> + * mode and gains are dynamically computed given a new set of statistics from
> + * the awb engine.
> + *
> + * The gainsFromColourTemperature() function instead computes the white balance
> + * gains from a colour temperature. This function does not take any statistics
> + * into account. It is used to compute the colour gains when the user manually
> + * specifies a colour temperature.
>    */
>   
>   /**
> - * \fn AwbAlgorithm::init()
> - * \brief Initialize the algorithm with the given tuning data
> - * \param[in] tuningData The tuning data to use for the algorithm
> + * \struct AwbImplementation::Result
> + * \brief The result of an awb computation
>    *
> - * \return 0 on success, a negative error code otherwise
> + * \var AwbImplementation::Result::gains
> + * \brief The computed white balance gains
> + *
> + * \var AwbImplementation::Result::temperatureK
> + * \brief The computed colour temperature, in Kelvin
>    */
>   
>   /**
> - * \fn AwbAlgorithm::calculateAwb()
> - * \brief Calculate AWB data from the given statistics
> - * \param[in] stats The statistics to use for the calculation
> - * \param[in] lux The lux value of the scene
> + * \fn AwbImplementation::~AwbImplementation
> + * \brief Virtual class destructor
> + */
> +
> +/**
> + * \fn AwbImplementation::init()
> + * \param[in] tuningData
> + * \brief Initialize the algorithm by parsing \a tuningData
> + */
> +
> +/**
> + * \fn AwbImplementation::calculateAwb()
> + * \param[in] stats The awb statistics
> + * \param[in] lux The estimated lux level
> + * \param[in] range The colour temperature search limits
>    *
> - * Calculate an AwbResult object from the given statistics and lux value. A \a
> - * lux value of 0 means it is unknown or invalid and the algorithm shall ignore
> - * it.
> + * Calculate a new set of colour gains and a colour temperature given a new
> + * set of statistics \a stats, an estimated luminance \a lux and a range of
> + * colour temperature to limit the search.
>    *
> - * \return The AWB result
> + * \return A Result computed using the new statistics
> + */
> +/**
> + * \fn AwbImplementation::gainsFromColourTemperature()
> + * \param[in] temperatureK Colour temperature in Kelvin
> + *
> + * Calculate a new set of colour gains for a new manually specified colour
> + * temperature \a temperatureK.
> + *
> + * \return The RGB gain values adjusted to \a temperatureK or std::nullopt
> + */
> +
> +/**
> + * \class AwbAlgorithmBase
> + * \brief Base class for AwbAlgorithm for non-templated functions implementation
> + *
> + * Base class for AwbAlgorithm where non-templated functions are implemented.
> + * IPA implementations shall use AwbAlgorithm and not this class.
>    */
>   
>   /**
> - * \fn AwbAlgorithm::gainsFromColourTemperature()
> - * \brief Compute white balance gains from a colour temperature
> - * \param[in] colourTemperature The colour temperature in Kelvin
> + * \brief Initialize the algorithm with the given tuning data
> + * \param[in] tuningData The tuning data to use for the algorithm
>    *
> - * Compute the white balance gains from a \a colourTemperature. This function
> - * does not take any statistics into account. It is used to compute the colour
> - * gains when the user manually specifies a colour temperature.
> + * Parse \a tuningData to initialize the awb algorithm and register controls.
> + * IPA modules are expected to call this function as part of their
> + * implementation of Algorithm::init().
>    *
> - * \return The colour gains or std::nullopt if the conversion is not possible
> + * \return 0 on success, a negative error code otherwise
> + */
> +int AwbAlgorithmBase::init(const ValueNode &tuningData)
> +{
> +	bayes_ = false;
> +
> +	if (!tuningData.contains("algorithm"))
> +		LOG(Awb, Info) << "No Awb algorithm specified, using grey world";
> +
> +	auto mode = tuningData["algorithm"].get<std::string>("grey");
> +	if (mode == "grey") {
> +		impl_ = std::make_unique<AwbGrey>();
> +	} else if (mode == "bayes") {
> +		impl_ = std::make_unique<AwbBayes>();
> +		bayes_ = true;
> +	} else {
> +		LOG(Awb, Error) << "Unknown Awb algorithm: " << mode;
> +		return -EINVAL;
> +	}
> +
> +	LOG(Awb, Debug) << "Using Awb algorithm: " << mode;
> +
> +	int ret = impl_->init(tuningData);
> +	if (ret)
> +		return ret;
> +
> +	controls_[&controls::ColourTemperature] =
> +		ControlInfo(kMinColourTemperature, kMaxColourTemperature,
> +			    kDefaultColourTemperature);
> +	controls_[&controls::AwbEnable] = ControlInfo(false, true);
> +
> +	return parseModeConfigs(tuningData, controls::AwbAuto);
> +}
> +
> +/**
> + * \brief Configure the awb algorithm
> + * \param[in] state The awb active state
> + * \return 0 if successful, an error code otherwise
>    */
> +int AwbAlgorithmBase::configure(awb::ActiveState &state)
> +{
> +	state.manual.gains = RGB<double>{ 1.0 };
> +	auto gains = impl_->gainsFromColourTemperature(kDefaultColourTemperature);
> +	if (gains)
> +		state.automatic.gains = *gains;
> +	else
> +		state.automatic.gains = RGB<double>{ 1.0 };
> +
> +	state.autoEnabled = true;
> +	state.manual.temperatureK = kDefaultColourTemperature;
> +	state.automatic.temperatureK = kDefaultColourTemperature;
> +
> +	return 0;
> +}
>   
>   /**
> - * \fn AwbAlgorithm::controls()
> - * \brief Get the controls info map for this algorithm
> + * \brief Queue a Request to the awb algorithm
> + * \param[in] state The awb active state
> + * \param[in] frame The frame number
> + * \param[in] frameContext The awb frame context
> + * \param[in] controls The list of controls part of the Request
>    *
> - * \return The controls info map
> + * Queue a new Request to the awb algorithm and modify its behaviour according
> + * to the provided controls.
> + *
> + * The currently handled controls are:
> + * - controls::AwbEnable
> + * - controls::AwbMode
> + * - controls::ColourGains
> + * - controls::ColourTemperature
>    */
> +void AwbAlgorithmBase::queueRequest(awb::ActiveState &state,
> +				    [[maybe_unused]] const uint32_t frame,
> +				    awb::FrameContext &frameContext,
> +				    const ControlList &controls)
> +{
> +	const auto &awbEnable = controls.get(controls::AwbEnable);
> +	if (awbEnable && *awbEnable != state.autoEnabled) {
> +		state.autoEnabled = *awbEnable;
> +
> +		LOG(Awb, Debug)
> +			<< (*awbEnable ? "Enabling" : "Disabling") << " Awb";
> +	}
> +
> +	auto mode = controls.get(controls::AwbMode);
> +	if (mode) {
> +		auto it = modes_.find(static_cast<controls::AwbModeEnum>(*mode));
> +		if (it == modes_.end()) {
> +			LOG(Awb, Error) << "Unsupported Awb mode " << *mode;
> +			return;
> +		}
> +
> +		currentMode_ = &it->second;
> +	}
> +
> +	frameContext.autoEnabled = state.autoEnabled;
> +
> +	if (frameContext.autoEnabled)
> +		return;
> +
> +	const auto &colourGains = controls.get(controls::ColourGains);
> +	const auto &colourTemperature = controls.get(controls::ColourTemperature);
> +	bool update = false;
> +	if (colourGains) {
> +		state.manual.gains.r() = (*colourGains)[0];
> +		state.manual.gains.b() = (*colourGains)[1];
> +		/*
> +		 * \todo Colour temperature reported in metadata is now
> +		 * incorrect, as we can't deduce the temperature from the gains.
> +		 * This will be fixed with the bayes AWB algorithm.
> +		 */
> +		update = true;
> +	} else if (colourTemperature) {
> +		state.manual.temperatureK = *colourTemperature;
> +		const auto &gains = impl_->gainsFromColourTemperature(*colourTemperature);
> +		if (gains) {
> +			state.manual.gains.r() = gains->r();
> +			state.manual.gains.b() = gains->b();
> +			update = true;
> +		}
> +	}
> +
> +	if (update)
> +		LOG(Awb, Debug)
> +			<< "Set colour gains to " << state.manual.gains;
> +
> +	frameContext.gains = state.manual.gains;
> +	frameContext.temperatureK = state.manual.temperatureK;
> +}
>   
>   /**
> - * \fn AwbAlgorithm::handleControls()
> - * \param[in] controls The controls to handle
> - * \brief Handle the controls supplied in a request
> + * \brief Set the gains and colour temperature values in \a frameContext
> + * \param[in] state The awb active state
> + * \param[in] frameContext The awb frame context
> + *
> + * If auto mode is enabled, take the most recently computed gains and use them
> + * for the current frame. Otherwise, if in manual mode, gains and colour
> + * temperature for a frame are set at queueRequest() time.
>    */
> +void AwbAlgorithmBase::prepare(awb::ActiveState &state,
> +			       awb::FrameContext &frameContext)
> +{
> +	if (frameContext.autoEnabled) {
> +		frameContext.gains = state.automatic.gains;
> +		frameContext.temperatureK = state.automatic.temperatureK;
> +	}
> +}
>   
>   /**
> + * \brief Process awb statistics to calculate gains and populate metadata
> + * \param[in] state The awb active state
> + * \param[in] frameContext The awb frame context
> + * \param[in] stats The awb statistics
> + * \param[in] lux The lux value as estimated by the IPA module
> + * \param[out] metadata The metadata list
> + *
> + * Process \a stats to calculate new gains and colour temperature and populate
> + * \a metadata with the results.
> + */
> +void AwbAlgorithmBase::process(awb::ActiveState &state,
> +			       awb::FrameContext &frameContext,
> +			       const AwbStats &stats, unsigned int lux,
> +			       ControlList &metadata)
> +{
> +	if (!stats.valid())
> +		return;
> +
> +	auto awbResult = impl_->calculateAwb(stats, lux, { currentMode_->ctLo,
> +							   currentMode_->ctHi });
> +
> +	/*
> +	 * Clamp the gain values to the hardware, according to the gainMin_
> +	 * and gainMax_ values.
> +	 */
> +	awbResult.gains = awbResult.gains.clamp(gainMin_, gainMax_);
> +
> +	/* Smooth color gains adjustments. */
> +	double speed = 0.2;
> +	double ct = awbResult.temperatureK;
> +	ct = ct * speed + state.automatic.temperatureK * (1 - speed);
> +
> +	state.automatic.temperatureK = awbResult.temperatureK;
> +	state.automatic.gains = awbResult.gains * speed +
> +				state.automatic.gains * (1 - speed);
> +
> +	/* Populate metadata. */
> +	metadata.set(controls::AwbEnable, frameContext.autoEnabled);
> +	metadata.set(controls::ColourGains, { static_cast<float>(frameContext.gains.r()),
> +					      static_cast<float>(frameContext.gains.b()) });
> +	metadata.set(controls::ColourTemperature, frameContext.temperatureK);
> +
> +	LOG(Awb, Debug) << std::showpoint << "Means " << stats.rgbMeans()
> +			<< ", gains " << state.automatic.gains
> +			<< ", temp " << state.automatic.temperatureK << "K";
> +}
> +
> +/*
>    * \brief Parse the mode configurations from the tuning data
>    * \param[in] tuningData the ValueNode representing the tuning data
>    * \param[in] def The default value for the AwbMode control
>    *
>    * Utility function to parse the tuning data for an AwbMode entry and read all
> - * provided modes. It adds controls::AwbMode to AwbAlgorithm::controls_ and
> - * populates AwbAlgorithm::modes_. For a list of possible modes see \ref
> + * provided modes. It adds controls::AwbMode to AwbAlgorithmBase::controls_ and
> + * populates AwbAlgorithmBase::modes_. For a list of possible modes see \ref
>    * controls::AwbModeEnum.
>    *
>    * Each mode entry must contain a "lo" and "hi" key to specify the lower and
> @@ -156,15 +442,23 @@ namespace ipa {
>    *       ...
>    * \endcode
>    *
> - * If \a def is supplied but not contained in the the \a tuningData, -EINVAL is
> + * If \a def is supplied but not contained in the \a tuningData, -EINVAL is
>    * returned.
>    *
> + * AwbModes are only used by the AwbBayes implementation.
> + *
>    * \sa controls::AwbModeEnum
>    * \return Zero on success, negative error code otherwise
>    */
> -int AwbAlgorithm::parseModeConfigs(const ValueNode &tuningData,
> -				   const ControlValue &def)
> +int AwbAlgorithmBase::parseModeConfigs(const ValueNode &tuningData,
> +				       const ControlValue &def)
>   {
> +	if (!bayes_) {
> +		/* AwbGrey does not support and does not use modes. */
> +		currentMode_ = &AwbGreyMode;
> +		return 0;
> +	}
> +
>   	std::vector<ControlValue> availableModes;
>   
>   	const ValueNode &modes = tuningData[controls::AwbMode.name()];
> @@ -227,37 +521,83 @@ int AwbAlgorithm::parseModeConfigs(const ValueNode &tuningData,
>   	}
>   
>   	controls_[&controls::AwbMode] = ControlInfo(availableModes, def);
> +	currentMode_ = &modes_[controls::AwbAuto];
>   
>   	return 0;
>   }
>   
>   /**
> - * \class AwbAlgorithm::ModeConfig
> - * \brief Holds the configuration of a single AWB mode
> + * \var AwbAlgorithmBase::controls_
> + * \brief Controls info map for the controls registered by the awb algorithm
> + */
> +
> +/**
> + * \var AwbAlgorithmBase::gainMin_
> + * \brief The minimum supported gain value
> + *
> + * Minimum gain value used to clamp the awb algorithm calculation results in the
> + * range supported by the platform awb engine.
>    *
> - * AWB modes limit the regulation of the AWB algorithm to a specific range of
> - * colour temperatures.
> + * The min and max gain values are initialized by AwbAlgorithm::init().
>    */
>   
>   /**
> - * \var AwbAlgorithm::ModeConfig::ctLo
> + * \var AwbAlgorithmBase::gainMax_
> + * \brief The maximum supported gain value
> + *
> + * Maximum gain value used to clamp the awb algorithm calculation results in the
> + * range supported by the platform awb engine.
> + *
> + * The min and max gain values are initialized by AwbAlgorithm::init().
> + */
> +
> +/*
> + * \class AwbAlgorithmBase::ModeConfig
> + * \brief Holds the configuration of a single awb mode
> + *
> + * AWB modes limit the regulation of the awb algorithm to a specific range of
> + * colour temperatures. Use by AwbBayes only.
> + */
> +
> +/*
> + * \var AwbAlgorithmBase::ModeConfig::ctLo
>    * \brief The lowest valid colour temperature of that mode
>    */
>   
> -/**
> - * \var AwbAlgorithm::ModeConfig::ctHi
> +/*
> + * \var AwbAlgorithmBase::ModeConfig::ctHi
>    * \brief The highest valid colour temperature of that mode
>    */
>   
> +/*
> + * \var AwbAlgorithmBase::modes_
> + * \brief Map of all configured modes
> + * \sa AwbAlgorithmBase::parseModeConfigs
> + */
> +
>   /**
> - * \var AwbAlgorithm::controls_
> - * \brief Controls info map for the controls provided by the algorithm
> + * \class AwbAlgorithm
> + * \brief The libipa awb algorithm
> + * \tparam Q The fixedpoint register representation of gain values
> + *
> + * Implement the awb algorithm for libipa.
> + *
> + * The AwbAlgorithm class implements an interface similar in spirit to the one
> + * of the Algorithm class. IPA modules are expected to store an instance of
> + * AwbAlgorithm as class member, template it with the awb engine gain register
> + * representation and call its function in their implementations of the
> + * Algorithm interface.
> + *
> + * The AwbAlgorithm instantiates an AwbImplementation implementation (AwbGrey or
> + * AwbBayes) at init() time by parsing the tuning data and uses it to compute
> + * the RGB gains and estimate a colour temperature given a set of statistics
> + * in the form of a AwbStats derived implementation.
>    */
>   
>   /**
> - * \var AwbAlgorithm::modes_
> - * \brief Map of all configured modes
> - * \sa AwbAlgorithm::parseModeConfigs
> + * \fn AwbAlgorithm::init()
> + * \param[in] controls The info map of the IPA controls
> + * \copydoc AwbAlgorithmBase::init()
>    */
>   
>   } /* namespace ipa */
> diff --git a/src/ipa/libipa/awb.h b/src/ipa/libipa/awb.h
> index 09c00e47d604..72f46367a136 100644
> --- a/src/ipa/libipa/awb.h
> +++ b/src/ipa/libipa/awb.h
> @@ -2,11 +2,12 @@
>   /*
>    * Copyright (C) 2024 Ideas on Board Oy
>    *
> - * Generic AWB algorithms
> + * libIPA AWB algorithms
>    */
>   
>   #pragma once
>   
> +#include <array>
>   #include <map>
>   #include <optional>
>   
> @@ -20,46 +21,118 @@ namespace libcamera {
>   
>   namespace ipa {
>   
> -struct AwbResult {
> +namespace awb {
> +
> +struct Context {
>   	RGB<double> gains;
> -	double colourTemperature;
> +	unsigned int temperatureK;
>   };
>   
> +struct ActiveState {
> +	Context manual;
> +	Context automatic;
> +
> +	bool autoEnabled;
> +};
> +
> +struct FrameContext : public Context {
> +	bool autoEnabled;
> +};
> +
> +} /* namespace awb */
> +
>   struct AwbStats {
> +	AwbStats() = default;
> +	virtual ~AwbStats() = default;
> +
>   	virtual double computeColourError(const RGB<double> &gains) const = 0;
> -	virtual RGB<double> rgbMeans() const = 0;
> +	virtual bool valid() const = 0;
> +	RGB<double> rgbMeans() const { return rgbMeans_; }
>   
>   protected:
> -	~AwbStats() = default;
> +	RGB<double> rgbMeans_;
> +	double rg_;
> +	double bg_;
>   };
>   
> -class AwbAlgorithm
> +class AwbImplementation
>   {
>   public:
> -	virtual ~AwbAlgorithm() = default;
> +	struct Result {
> +		RGB<double> gains;
> +		unsigned int temperatureK;
> +	};
>   
> +	virtual ~AwbImplementation() = default;
>   	virtual int init(const ValueNode &tuningData) = 0;
> -	virtual AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) = 0;
> -	virtual std::optional<RGB<double>> gainsFromColourTemperature(double colourTemperature) = 0;
> +	virtual Result calculateAwb(const AwbStats &stats, unsigned int lux,
> +				    std::array<double, 2> range) = 0;
> +	virtual std::optional<RGB<double>>
> +	gainsFromColourTemperature(double temperatureK) = 0;
> +};
>   
> -	const ControlInfoMap::Map &controls() const
> -	{
> -		return controls_;
> -	}
> +class AwbAlgorithmBase
> +{
> +public:
> +	int init(const ValueNode &tuningData);
>   
> -	virtual void handleControls([[maybe_unused]] const ControlList &controls) {}
> +	int configure(awb::ActiveState &state);
> +
> +	void queueRequest(awb::ActiveState &state,
> +			  const uint32_t frame,
> +			  awb::FrameContext &frameContext,
> +			  const ControlList &controls);
> +
> +	void prepare(awb::ActiveState &state, awb::FrameContext &frameContext);
> +
> +	void process(awb::ActiveState &state, awb::FrameContext &frameContext,
> +		     const AwbStats &stats, unsigned int lux,
> +		     ControlList &metadata);
>   
>   protected:
> -	int parseModeConfigs(const ValueNode &tuningData,
> -			     const ControlValue &def = {});
> +	AwbAlgorithmBase() = default;
> +
> +	ControlInfoMap::Map controls_;
> +	float gainMin_;
> +	float gainMax_;
>   
> +private:
>   	struct ModeConfig {
>   		double ctHi;
>   		double ctLo;
>   	};
>   
> -	ControlInfoMap::Map controls_;
> -	std::map<controls::AwbModeEnum, AwbAlgorithm::ModeConfig> modes_;
> +	/* AwbGrey does not support modes; */
> +	static constexpr ModeConfig AwbGreyMode = { 0.0, 0.0 };
> +
> +	int parseModeConfigs(const ValueNode &tuningData,
> +			     const ControlValue &def = {});
> +
> +	std::map<controls::AwbModeEnum, AwbAlgorithmBase::ModeConfig> modes_;
> +	const ModeConfig *currentMode_ = nullptr;
> +	std::unique_ptr<AwbImplementation> impl_;
> +	bool bayes_ = false;
> +};
> +
> +template<typename Q>
> +class AwbAlgorithm : public AwbAlgorithmBase
> +{
> +public:
> +	int init(const ValueNode &tuningData, ControlInfoMap::Map &controls)
> +	{
> +		AwbAlgorithmBase::init(tuningData);
> +
> +		gainMin_ = std::max(Q::TraitsType::min, 1.0f);
> +		gainMax_ = Q::TraitsType::max;
> +
> +		controls_[&controls::ColourGains] =
> +			ControlInfo(gainMin_, gainMax_,
> +				    Span<const float, 2>{ { 1.0f, 1.0f } });
> +
> +		controls.insert(controls_.begin(), controls_.end());
> +
> +		return 0;
> +	}
>   };
>   
>   } /* namespace ipa */
> diff --git a/src/ipa/libipa/awb_bayes.cpp b/src/ipa/libipa/awb_bayes.cpp
> index 9fd85e5a4505..2c10f9c4c82d 100644
> --- a/src/ipa/libipa/awb_bayes.cpp
> +++ b/src/ipa/libipa/awb_bayes.cpp
> @@ -140,11 +140,6 @@ void Interpolator<Pwl>::interpolate(const Pwl &a, const Pwl &b, Pwl &dest, doubl
>    * \brief How far to wander off CT curve towards "more green"
>    */
>   
> -/**
> - * \var AwbBayes::currentMode_
> - * \brief The currently selected mode
> - */
> -
>   int AwbBayes::init(const ValueNode &tuningData)
>   {
>   	int ret = colourGainCurve_.readYaml(tuningData["colourGains"], "ct", "gains");
> @@ -172,14 +167,6 @@ int AwbBayes::init(const ValueNode &tuningData)
>   		return ret;
>   	}
>   
> -	ret = parseModeConfigs(tuningData, controls::AwbAuto);
> -	if (ret) {
> -		LOG(Awb, Error)
> -			<< "Failed to parse mode parameter from tuning file";
> -		return ret;
> -	}
> -	currentMode_ = &modes_[controls::AwbAuto];
> -
>   	transversePos_ = tuningData["transversePos"].get<double>(0.01);
>   	transverseNeg_ = tuningData["transverseNeg"].get<double>(0.01);
>   	if (transversePos_ <= 0 || transverseNeg_ <= 0) {
> @@ -260,18 +247,16 @@ int AwbBayes::readPriors(const ValueNode &tuningData)
>   	return 0;
>   }
>   
> -void AwbBayes::handleControls(const ControlList &controls)
> -{
> -	auto mode = controls.get(controls::AwbMode);
> -	if (mode) {
> -		auto it = modes_.find(static_cast<controls::AwbModeEnum>(*mode));
> -		if (it != modes_.end())
> -			currentMode_ = &it->second;
> -		else
> -			LOG(Awb, Error) << "Unsupported AWB mode " << *mode;
> -	}
> -}
> -
> +/**
> + * \brief Compute white balance gains from a colour temperature
> + * \param[in] colourTemperature The colour temperature in Kelvin
> + *
> + * Compute the white balance gains from a \a colourTemperature. This function
> + * does not take any statistics into account. It simply interpolates the colour
> + * gains curve on a new colour temperature.
> + *
> + * \return The RGB gain values adjusted to \a temperatureK
> + */
>   std::optional<RGB<double>> AwbBayes::gainsFromColourTemperature(double colourTemperature)
>   {
>   	/*
> @@ -283,7 +268,9 @@ std::optional<RGB<double>> AwbBayes::gainsFromColourTemperature(double colourTem
>   	return RGB<double>{ { gains[0], 1.0, gains[1] } };
>   }
>   
> -AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux)
> +AwbImplementation::Result
> +AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux,
> +		       std::array<double, 2> range)
>   {
>   	ipa::Pwl prior;
>   	if (lux > 0) {
> @@ -295,7 +282,7 @@ AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux)
>   		prior.append(0, 1.0);
>   	}
>   
> -	double t = coarseSearch(prior, stats);
> +	double t = coarseSearch(prior, stats, range);
>   	double r = ctR_.eval(t);
>   	double b = ctB_.eval(t);
>   	LOG(Awb, Debug)
> @@ -316,14 +303,15 @@ AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux)
>   		<< "After fine search: r " << r << " b " << b << " (gains r "
>   		<< 1 / r << " b " << 1 / b << ")";
>   
> -	return { { { 1.0 / r, 1.0, 1.0 / b } }, t };
> +	return { { { 1.0 / r, 1.0, 1.0 / b } }, static_cast<unsigned int>(t) };
>   }
>   
> -double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) const
> +double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats,
> +			      Span<double> range) const
>   {
>   	std::vector<Pwl::Point> points;
>   	size_t bestPoint = 0;
> -	double t = currentMode_->ctLo;
> +	double t = range[0];
>   	int spanR = -1;
>   	int spanB = -1;
>   	LimitsRecorder<double> errorLimits;
> @@ -345,14 +333,14 @@ double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) cons
>   		if (points.back().y() < points[bestPoint].y())
>   			bestPoint = points.size() - 1;
>   
> -		if (t == currentMode_->ctHi)
> +		if (t == range[1])
>   			break;
>   
>   		/*
>   		 * Ensure even steps along the r/b curve by scaling them by the
>   		 * current t.
>   		 */
> -		t = std::min(t + t / 10 * kSearchStep, currentMode_->ctHi);
> +		t = std::min(t + t / 10 * kSearchStep, range[1]);
>   	}
>   
>   	t = points[bestPoint].x();
> diff --git a/src/ipa/libipa/awb_bayes.h b/src/ipa/libipa/awb_bayes.h
> index 1e3373676bc0..8d4cff076d4a 100644
> --- a/src/ipa/libipa/awb_bayes.h
> +++ b/src/ipa/libipa/awb_bayes.h
> @@ -20,22 +20,24 @@ namespace libcamera {
>   
>   namespace ipa {
>   
> -class AwbBayes : public AwbAlgorithm
> +class AwbBayes : public AwbImplementation
>   {
>   public:
>   	AwbBayes() = default;
>   
>   	int init(const ValueNode &tuningData) override;
> -	AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) override;
> +	AwbImplementation::Result
> +	calculateAwb(const AwbStats &stats, unsigned int lux,
> +		     std::array<double, 2> range) override;
>   	std::optional<RGB<double>> gainsFromColourTemperature(double temperatureK) override;
> -	void handleControls(const ControlList &controls) override;
>   
>   private:
>   	int readPriors(const ValueNode &tuningData);
>   
>   	void fineSearch(double &t, double &r, double &b, ipa::Pwl const &prior,
>   			const AwbStats &stats) const;
> -	double coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) const;
> +	double coarseSearch(const ipa::Pwl &prior, const AwbStats &stats,
> +			    Span<double> range) const;
>   	double interpolateQuadratic(ipa::Pwl::Point const &a,
>   				    ipa::Pwl::Point const &b,
>   				    ipa::Pwl::Point const &c) const;
> @@ -50,8 +52,6 @@ private:
>   
>   	double transversePos_;
>   	double transverseNeg_;
> -
> -	ModeConfig *currentMode_ = nullptr;
>   };
>   
>   } /* namespace ipa */
> diff --git a/src/ipa/libipa/awb_grey.cpp b/src/ipa/libipa/awb_grey.cpp
> index b14b096810ae..1e4af717f62d 100644
> --- a/src/ipa/libipa/awb_grey.cpp
> +++ b/src/ipa/libipa/awb_grey.cpp
> @@ -60,6 +60,7 @@ int AwbGrey::init(const ValueNode &tuningData)
>    * \brief Calculate AWB data from the given statistics
>    * \param[in] stats The statistics to use for the calculation
>    * \param[in] lux The lux value of the scene
> + * \param[in] range The colour temperature search limits
>    *
>    * The colour temperature is estimated based on the colours::estimateCCT()
>    * function. The gains are calculated purely based on the RGB means provided by
> @@ -70,20 +71,23 @@ int AwbGrey::init(const ValueNode &tuningData)
>    *
>    * \return The AWB result
>    */
> -AwbResult AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned int lux)
> +AwbImplementation::Result
> +AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned int lux,
> +		      [[maybe_unused]] std::array<double, 2> range)
>   {
> -	AwbResult result;
> +	AwbImplementation::Result result;
>   	auto means = stats.rgbMeans();
> -	result.colourTemperature = estimateCCT(means);
> +	result.temperatureK = estimateCCT(means);
>   
>   	/*
> -	 * Estimate the red and blue gains to apply in a grey world. The green
> -	 * gain is hardcoded to 1.0. Avoid divisions by zero by clamping the
> -	 * divisor to a minimum value of 1.0.
> +	 * Calculate the red and blue gains to apply in a grey world by simply
> +	 * inverting the red/green and blue/green ratios as reported in
> +	 * statistics. The green gain is hardcoded to 1.0.
>   	 */
>   	result.gains.r() = means.g() / std::max(means.r(), 1.0);
>   	result.gains.g() = 1.0;
>   	result.gains.b() = means.g() / std::max(means.b(), 1.0);
> +
>   	return result;
>   }
>   
> @@ -93,15 +97,16 @@ AwbResult AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned
>    *
>    * Compute the white balance gains from a \a colourTemperature. This function
>    * does not take any statistics into account. It simply interpolates the colour
> - * gains configured in the colour temperature curve.
> + * gains curve (if provided in the tuning file) on a new colour temperature.
>    *
> - * \return The colour gains if a colour temperature curve is available,
> - * [1, 1, 1] otherwise.
> + * \return The RGB gain values adjusted to \a temperatureK or std::nullopt if
> + * no gain curve is specified in the tuning data
>    */
>   std::optional<RGB<double>> AwbGrey::gainsFromColourTemperature(double colourTemperature)
>   {
>   	if (!colourGainCurve_) {
> -		LOG(Awb, Error) << "No gains defined";
> +		LOG(Awb, Info) << "No gains curve defined, "
> +			       << "unable to interpolate gains to colour temperature";
>   		return std::nullopt;
>   	}
>   
> diff --git a/src/ipa/libipa/awb_grey.h b/src/ipa/libipa/awb_grey.h
> index 154a2af97f15..d1b0088f0470 100644
> --- a/src/ipa/libipa/awb_grey.h
> +++ b/src/ipa/libipa/awb_grey.h
> @@ -18,13 +18,15 @@ namespace libcamera {
>   
>   namespace ipa {
>   
> -class AwbGrey : public AwbAlgorithm
> +class AwbGrey : public AwbImplementation
>   {
>   public:
>   	AwbGrey() = default;
>   
>   	int init(const ValueNode &tuningData) override;
> -	AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) override;
> +	AwbImplementation::Result
> +	calculateAwb(const AwbStats &stats, unsigned int lux,
> +		     std::array<double, 2> range) override;
>   	std::optional<RGB<double>> gainsFromColourTemperature(double colourTemperature) override;
>   
>   private:
> diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp
> index 0eb05c2b6e1a..ae9f6a0c243e 100644
> --- a/src/ipa/rkisp1/algorithms/awb.cpp
> +++ b/src/ipa/rkisp1/algorithms/awb.cpp
> @@ -8,17 +8,12 @@
>   #include "awb.h"
>   
>   #include <algorithm>
> -#include <ios>
>   
>   #include <libcamera/base/log.h>
>   
> -#include <libcamera/control_ids.h>
> -
>   #include <libcamera/ipa/core_ipa_interface.h>
>   
> -#include "libipa/awb_bayes.h"
> -#include "libipa/awb_grey.h"
> -#include "libipa/colours.h"
> +#include "libcamera/internal/vector.h"
>   
>   /**
>    * \file awb.h
> @@ -28,26 +23,16 @@ namespace libcamera {
>   
>   namespace ipa::rkisp1::algorithms {
>   
> -/**
> - * \class Awb
> - * \brief Manage the white balance with automatic and manual controls
> - */
> -
>   LOG_DEFINE_CATEGORY(RkISP1Awb)
>   
> -constexpr int32_t kMinColourTemperature = 2500;
> -constexpr int32_t kMaxColourTemperature = 10000;
> -constexpr int32_t kDefaultColourTemperature = 5000;
> -
> -/* Minimum mean value below which AWB can't operate. */
> -constexpr double kMeanMinThreshold = 2.0;
> -
>   class RkISP1AwbStats final : public AwbStats
>   {
>   public:
> -	RkISP1AwbStats(const RGB<double> &rgbMeans)
> -		: rgbMeans_(rgbMeans)
> +	RkISP1AwbStats(const IPAFrameContext &frameContext,
> +		       const rkisp1_stat_buffer *stats)
>   	{
> +		calculateRgbMeans(frameContext, stats);
> +
>   		rg_ = rgbMeans_.r() / rgbMeans_.g();
>   		bg_ = rgbMeans_.b() / rgbMeans_.g();
>   	}
> @@ -65,17 +50,106 @@ public:
>   		return delta2;
>   	}
>   
> -	RGB<double> rgbMeans() const override
> +	bool valid() const override
>   	{
> -		return rgbMeans_;
> +		/* Minimum mean value below which AWB can't operate. */
> +		constexpr double minValue = 2.0;
> +
> +		return rgbMeans_.r() > minValue || rgbMeans_.g() > minValue ||
> +		       rgbMeans_.b() > minValue;
>   	}
>   
>   private:
> -	RGB<double> rgbMeans_;
> -	double rg_;
> -	double bg_;
> +	void calculateRgbMeans(const IPAFrameContext &frameContext,
> +			       const rkisp1_stat_buffer *stats);
> +	bool rgbMode_ = false;
>   };
>   
> +void RkISP1AwbStats::calculateRgbMeans(const IPAFrameContext &frameContext,
> +				       const rkisp1_stat_buffer *stats)
> +{
> +	if (!stats || !(stats->meas_type & RKISP1_CIF_ISP_STAT_AWB)) {
> +		LOG(RkISP1Awb, Error) << "AWB data is missing in statistics";
> +		return;
> +	}
> +
> +	const rkisp1_cif_isp_awb_stat *awb = &stats->params.awb;
> +
> +	if (awb->awb_mean[0].cnt == 0) {
> +		LOG(RkISP1Awb, Debug) << "AWB statistics are empty";
> +		return;
> +	}
> +
> +	if (rgbMode_) {
> +		rgbMeans_ = {{
> +			static_cast<double>(awb->awb_mean[0].mean_cr_or_r),
> +			static_cast<double>(awb->awb_mean[0].mean_y_or_g),
> +			static_cast<double>(awb->awb_mean[0].mean_cb_or_b)
> +		}};
> +	} else {
> +		/* Get the YCbCr mean values */
> +		Vector<double, 3> yuvMeans({
> +			static_cast<double>(awb->awb_mean[0].mean_y_or_g),
> +			static_cast<double>(awb->awb_mean[0].mean_cb_or_b),
> +			static_cast<double>(awb->awb_mean[0].mean_cr_or_r)
> +		});
> +
> +		/*
> +		 * Convert from YCbCr to RGB. The hardware uses the following
> +		 * formulas:
> +		 *
> +		 * Y  =  16 + 0.2500 R + 0.5000 G + 0.1094 B
> +		 * Cb = 128 - 0.1406 R - 0.2969 G + 0.4375 B
> +		 * Cr = 128 + 0.4375 R - 0.3750 G - 0.0625 B
> +		 *
> +		 * This seems to be based on limited range BT.601 with Q1.6
> +		 * precision.
> +		 *
> +		 * The inverse matrix is:
> +		 *
> +		 * [[1,1636, -0,0623,  1,6008]
> +		 *  [1,1636, -0,4045, -0,7949]
> +		 *  [1,1636,  1,9912, -0,0250]]
> +		 */
> +		static const Matrix<double, 3, 3> yuv2rgbMatrix({
> +			1.1636, -0.0623,  1.6008,
> +			1.1636, -0.4045, -0.7949,
> +			1.1636,  1.9912, -0.0250
> +		});
> +		static const Vector<double, 3> yuv2rgbOffset({
> +			16, 128, 128
> +		});
> +
> +		rgbMeans_ = yuv2rgbMatrix * (yuvMeans - yuv2rgbOffset);
> +
> +		/*
> +		 * Due to hardware rounding errors in the YCbCr means, the
> +		 * calculated RGB means may be negative. This would lead to
> +		 * negative gains, messing up calculation. Prevent this by
> +		 * clamping the means to positive values.
> +		 */
> +		rgbMeans_ = rgbMeans_.max(0.0);
> +	}
> +
> +	/*
> +	 * The ISP computes the AWB means after applying the CCM. Apply the
> +	 * inverse as we want to get the raw means before the colour gains.
> +	 */
> +	rgbMeans_ = frameContext.ccm.ccm.inverse() * rgbMeans_;
> +
> +	/*
> +	 * The ISP computes the AWB means after applying the colour gains,
> +	 * divide by the gains that were used to get the raw means from the
> +	 * sensor. Apply a minimum value to avoid divisions by near-zero.
> +	 */
> +	rgbMeans_ /= frameContext.awb.gains.max(0.01);
> +}
> +
> +/**
> + * \class Awb
> + * \brief Manage the white balance with automatic and manual controls
> + */
> +
>   Awb::Awb()
>   	: rgbMode_(false)
>   {
> @@ -86,40 +160,7 @@ Awb::Awb()
>    */
>   int Awb::init(IPAContext &context, const ValueNode &tuningData)
>   {
> -	auto &cmap = context.ctrlMap;
> -	cmap[&controls::ColourTemperature] = ControlInfo(kMinColourTemperature,
> -							 kMaxColourTemperature,
> -							 kDefaultColourTemperature);
> -	cmap[&controls::AwbEnable] = ControlInfo(false, true);
> -
> -	cmap[&controls::ColourGains] = ControlInfo(0.0f, 3.996f,
> -						   Span<const float, 2>{ { 1.0f, 1.0f } });
> -
> -	if (!tuningData.contains("algorithm"))
> -		LOG(RkISP1Awb, Info) << "No AWB algorithm specified."
> -				     << " Default to grey world";
> -
> -	auto mode = tuningData["algorithm"].get<std::string>("grey");
> -	if (mode == "grey") {
> -		awbAlgo_ = std::make_unique<AwbGrey>();
> -	} else if (mode == "bayes") {
> -		awbAlgo_ = std::make_unique<AwbBayes>();
> -	} else {
> -		LOG(RkISP1Awb, Error) << "Unknown AWB algorithm: " << mode;
> -		return -EINVAL;
> -	}
> -	LOG(RkISP1Awb, Debug) << "Using AWB algorithm: " << mode;
> -
> -	int ret = awbAlgo_->init(tuningData);
> -	if (ret) {
> -		LOG(RkISP1Awb, Error) << "Failed to init AWB algorithm";
> -		return ret;
> -	}
> -
> -	const auto &src = awbAlgo_->controls();
> -	cmap.insert(src.begin(), src.end());
> -
> -	return 0;
> +	return awbAlgo_.init(tuningData, context.ctrlMap);
>   }
>   
>   /**
> @@ -128,16 +169,7 @@ int Awb::init(IPAContext &context, const ValueNode &tuningData)
>   int Awb::configure(IPAContext &context,
>   		   const IPACameraSensorInfo &configInfo)
>   {
> -	context.activeState.awb.manual.gains = RGB<double>{ 1.0 };
> -	auto gains = awbAlgo_->gainsFromColourTemperature(kDefaultColourTemperature);
> -	if (gains)
> -		context.activeState.awb.automatic.gains = *gains;
> -	else
> -		context.activeState.awb.automatic.gains = RGB<double>{ 1.0 };
> -
> -	context.activeState.awb.autoEnabled = true;
> -	context.activeState.awb.manual.temperatureK = kDefaultColourTemperature;
> -	context.activeState.awb.automatic.temperatureK = kDefaultColourTemperature;
> +	awbAlgo_.configure(context.activeState.awb);
>   
>   	/*
>   	 * Define the measurement window for AWB as a centered rectangle
> @@ -156,56 +188,12 @@ int Awb::configure(IPAContext &context,
>   /**
>    * \copydoc libcamera::ipa::Algorithm::queueRequest
>    */
> -void Awb::queueRequest(IPAContext &context,
> -		       [[maybe_unused]] const uint32_t frame,
> +void Awb::queueRequest(IPAContext &context, const uint32_t frame,
>   		       IPAFrameContext &frameContext,
>   		       const ControlList &controls)
>   {
> -	auto &awb = context.activeState.awb;
> -
> -	const auto &awbEnable = controls.get(controls::AwbEnable);
> -	if (awbEnable && *awbEnable != awb.autoEnabled) {
> -		awb.autoEnabled = *awbEnable;
> -
> -		LOG(RkISP1Awb, Debug)
> -			<< (*awbEnable ? "Enabling" : "Disabling") << " AWB";
> -	}
> -
> -	awbAlgo_->handleControls(controls);
> -
> -	frameContext.awb.autoEnabled = awb.autoEnabled;
> -
> -	if (awb.autoEnabled)
> -		return;
> -
> -	const auto &colourGains = controls.get(controls::ColourGains);
> -	const auto &colourTemperature = controls.get(controls::ColourTemperature);
> -	bool update = false;
> -	if (colourGains) {
> -		awb.manual.gains.r() = (*colourGains)[0];
> -		awb.manual.gains.b() = (*colourGains)[1];
> -		/*
> -		 * \todo Colour temperature reported in metadata is now
> -		 * incorrect, as we can't deduce the temperature from the gains.
> -		 * This will be fixed with the bayes AWB algorithm.
> -		 */
> -		update = true;
> -	} else if (colourTemperature) {
> -		awb.manual.temperatureK = *colourTemperature;
> -		const auto &gains = awbAlgo_->gainsFromColourTemperature(*colourTemperature);
> -		if (gains) {
> -			awb.manual.gains.r() = gains->r();
> -			awb.manual.gains.b() = gains->b();
> -			update = true;
> -		}
> -	}
> -
> -	if (update)
> -		LOG(RkISP1Awb, Debug)
> -			<< "Set colour gains to " << awb.manual.gains;
> -
> -	frameContext.awb.gains = awb.manual.gains;
> -	frameContext.awb.temperatureK = awb.manual.temperatureK;
> +	awbAlgo_.queueRequest(context.activeState.awb, frame, frameContext.awb,
> +			      controls);
>   }
>   
>   /**
> @@ -214,15 +202,7 @@ void Awb::queueRequest(IPAContext &context,
>   void Awb::prepare(IPAContext &context, const uint32_t frame,
>   		  IPAFrameContext &frameContext, RkISP1Params *params)
>   {
> -	/*
> -	 * This is the latest time we can read the active state. This is the
> -	 * most up-to-date automatic values we can read.
> -	 */
> -	if (frameContext.awb.autoEnabled) {
> -		const auto &awb = context.activeState.awb;
> -		frameContext.awb.gains = awb.automatic.gains;
> -		frameContext.awb.temperatureK = awb.automatic.temperatureK;
> -	}
> +	awbAlgo_.prepare(context.activeState.awb, frameContext.awb);
>   
>   	auto gainConfig = params->block<BlockType::AwbGain>();
>   	gainConfig.setEnabled(true);
> @@ -291,136 +271,12 @@ void Awb::process(IPAContext &context,
>   		  const rkisp1_stat_buffer *stats,
>   		  ControlList &metadata)
>   {
> -	IPAActiveState &activeState = context.activeState;
> -
> -	metadata.set(controls::AwbEnable, frameContext.awb.autoEnabled);
> -	metadata.set(controls::ColourGains, {
> -			static_cast<float>(frameContext.awb.gains.r()),
> -			static_cast<float>(frameContext.awb.gains.b())
> -		});
> -	metadata.set(controls::ColourTemperature, frameContext.awb.temperatureK);
> -
> -	if (!stats || !(stats->meas_type & RKISP1_CIF_ISP_STAT_AWB)) {
> -		LOG(RkISP1Awb, Error) << "AWB data is missing in statistics";
> -		return;
> -	}
> -
> -	const rkisp1_cif_isp_stat *params = &stats->params;
> -	const rkisp1_cif_isp_awb_stat *awb = &params->awb;
> +	RkISP1AwbStats awbStats(frameContext, stats);
>   
> -	if (awb->awb_mean[0].cnt == 0) {
> -		LOG(RkISP1Awb, Debug) << "AWB statistics are empty";
> -		return;
> -	}
> -
> -	RGB<double> rgbMeans = calculateRgbMeans(frameContext, awb);
> -
> -	/*
> -	 * If the means are too small we don't have enough information to
> -	 * meaningfully calculate gains. Freeze the algorithm in that case.
> -	 */
> -	if (rgbMeans.r() < kMeanMinThreshold && rgbMeans.g() < kMeanMinThreshold &&
> -	    rgbMeans.b() < kMeanMinThreshold)
> -		return;
> -
> -	RkISP1AwbStats awbStats{ rgbMeans };
> -	AwbResult awbResult = awbAlgo_->calculateAwb(awbStats, frameContext.lux.lux);
> -
> -	/*
> -	 * Clamp the gain values to the hardware, which expresses gains as Q2.8
> -	 * unsigned integer values. Set the minimum just above zero to avoid
> -	 * divisions by zero when computing the raw means in subsequent
> -	 * iterations.
> -	 */
> -	awbResult.gains = awbResult.gains.clamp(1.0 / 256, 1023.0 / 256);
> -
> -	/* Filter the values to avoid oscillations. */
> -	double speed = 0.2;
> -	double ct = awbResult.colourTemperature;
> -	ct = ct * speed + activeState.awb.automatic.temperatureK * (1 - speed);
> -	awbResult.gains = awbResult.gains * speed +
> -			  activeState.awb.automatic.gains * (1 - speed);
> -
> -	activeState.awb.automatic.temperatureK = static_cast<unsigned int>(ct);
> -	activeState.awb.automatic.gains = awbResult.gains;
> -
> -	LOG(RkISP1Awb, Debug)
> -		<< std::showpoint
> -		<< "Means " << rgbMeans << ", gains "
> -		<< activeState.awb.automatic.gains << ", temp "
> -		<< activeState.awb.automatic.temperatureK << "K";
> +	awbAlgo_.process(context.activeState.awb, frameContext.awb, awbStats,
> +			 frameContext.lux.lux, metadata);
>   }
>   
> -RGB<double> Awb::calculateRgbMeans(const IPAFrameContext &frameContext, const rkisp1_cif_isp_awb_stat *awb) const
> -{
> -	Vector<double, 3> rgbMeans;
> -
> -	if (rgbMode_) {
> -		rgbMeans = {{
> -			static_cast<double>(awb->awb_mean[0].mean_cr_or_r),
> -			static_cast<double>(awb->awb_mean[0].mean_y_or_g),
> -			static_cast<double>(awb->awb_mean[0].mean_cb_or_b)
> -		}};
> -	} else {
> -		/* Get the YCbCr mean values */
> -		Vector<double, 3> yuvMeans({
> -			static_cast<double>(awb->awb_mean[0].mean_y_or_g),
> -			static_cast<double>(awb->awb_mean[0].mean_cb_or_b),
> -			static_cast<double>(awb->awb_mean[0].mean_cr_or_r)
> -		});
> -
> -		/*
> -		 * Convert from YCbCr to RGB. The hardware uses the following
> -		 * formulas:
> -		 *
> -		 * Y  =  16 + 0.2500 R + 0.5000 G + 0.1094 B
> -		 * Cb = 128 - 0.1406 R - 0.2969 G + 0.4375 B
> -		 * Cr = 128 + 0.4375 R - 0.3750 G - 0.0625 B
> -		 *
> -		 * This seems to be based on limited range BT.601 with Q1.6
> -		 * precision.
> -		 *
> -		 * The inverse matrix is:
> -		 *
> -		 * [[1,1636, -0,0623,  1,6008]
> -		 *  [1,1636, -0,4045, -0,7949]
> -		 *  [1,1636,  1,9912, -0,0250]]
> -		 */
> -		static const Matrix<double, 3, 3> yuv2rgbMatrix({
> -			1.1636, -0.0623,  1.6008,
> -			1.1636, -0.4045, -0.7949,
> -			1.1636,  1.9912, -0.0250
> -		});
> -		static const Vector<double, 3> yuv2rgbOffset({
> -			16, 128, 128
> -		});
> -
> -		rgbMeans = yuv2rgbMatrix * (yuvMeans - yuv2rgbOffset);
> -
> -		/*
> -		 * Due to hardware rounding errors in the YCbCr means, the
> -		 * calculated RGB means may be negative. This would lead to
> -		 * negative gains, messing up calculation. Prevent this by
> -		 * clamping the means to positive values.
> -		 */
> -		rgbMeans = rgbMeans.max(0.0);
> -	}
> -
> -	/*
> -	 * The ISP computes the AWB means after applying the CCM. Apply the
> -	 * inverse as we want to get the raw means before the colour gains.
> -	 */
> -	rgbMeans = frameContext.ccm.ccm.inverse() * rgbMeans;
> -
> -	/*
> -	 * The ISP computes the AWB means after applying the colour gains,
> -	 * divide by the gains that were used to get the raw means from the
> -	 * sensor. Apply a minimum value to avoid divisions by near-zero.
> -	 */
> -	rgbMeans /= frameContext.awb.gains.max(0.01);
> -
> -	return rgbMeans;
> -}
>   
>   REGISTER_IPA_ALGORITHM(Awb, "Awb")
>   
> diff --git a/src/ipa/rkisp1/algorithms/awb.h b/src/ipa/rkisp1/algorithms/awb.h
> index 60d9ef111495..c7cb59a75bde 100644
> --- a/src/ipa/rkisp1/algorithms/awb.h
> +++ b/src/ipa/rkisp1/algorithms/awb.h
> @@ -7,17 +7,25 @@
>   
>   #pragma once
>   
> -#include "libcamera/internal/vector.h"
> +#include <linux/rkisp1-config.h>
> +
> +#include <libcamera/controls.h>
> +
> +#include "libcamera/internal/value_node.h"
>   
>   #include "libipa/awb.h"
> -#include "libipa/interpolator.h"
> +#include "libipa/fixedpoint.h"
>   
>   #include "algorithm.h"
> +#include "ipa_context.h"
> +#include "params.h"
>   
>   namespace libcamera {
>   
>   namespace ipa::rkisp1::algorithms {
>   
> +class RkISP1AwbStats;
> +
>   class Awb : public Algorithm
>   {
>   public:
> @@ -38,10 +46,10 @@ public:
>   		     ControlList &metadata) override;
>   
>   private:
> -	RGB<double> calculateRgbMeans(const IPAFrameContext &frameContext,
> -				      const rkisp1_cif_isp_awb_stat *awb) const;
> +	RkISP1AwbStats calculateRgbMeans(const IPAFrameContext &frameContext,
> +					 const rkisp1_stat_buffer *stats) const;
>   
> -	std::unique_ptr<AwbAlgorithm> awbAlgo_;
> +	AwbAlgorithm<UQ<2, 8>> awbAlgo_;
>   
>   	bool rgbMode_;
>   };
> diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h
> index e5f70d934d67..c36c1f7e0084 100644
> --- a/src/ipa/rkisp1/ipa_context.h
> +++ b/src/ipa/rkisp1/ipa_context.h
> @@ -25,6 +25,7 @@
>   #include "libcamera/internal/vector.h"
>   
>   #include "libipa/agc_mean_luminance.h"
> +#include "libipa/awb.h"
>   #include "libipa/camera_sensor_helper.h"
>   #include "libipa/fc_queue.h"
>   #include "libipa/fixedpoint.h"
> @@ -48,15 +49,17 @@ struct IPAHwSettings {
>   	bool compand;
>   };
>   
> +struct RKISP1AwbSession {
> +	struct rkisp1_cif_isp_window measureWindow;
> +	bool enabled;
> +};
> +
>   struct IPASessionConfiguration {
>   	struct {
>   		struct rkisp1_cif_isp_window measureWindow;
>   	} agc;
>   
> -	struct {
> -		struct rkisp1_cif_isp_window measureWindow;
> -		bool enabled;
> -	} awb;
> +	struct RKISP1AwbSession awb;
>   
>   	struct {
>   		bool supported;
> @@ -100,17 +103,7 @@ struct IPAActiveState {
>   		utils::Duration maxFrameDuration;
>   	} agc;
>   
> -	struct {
> -		struct AwbState {
> -			RGB<double> gains;
> -			unsigned int temperatureK;
> -		};
> -
> -		AwbState manual;
> -		AwbState automatic;
> -
> -		bool autoEnabled;
> -	} awb;
> +	ipa::awb::ActiveState awb;
>   
>   	struct {
>   		Matrix<float, 3, 3> manual;
> @@ -176,11 +169,7 @@ struct IPAFrameContext : public FrameContext {
>   		bool autoGainModeChange;
>   	} agc;
>   
> -	struct {
> -		RGB<double> gains;
> -		bool autoEnabled;
> -		unsigned int temperatureK;
> -	} awb;
> +	ipa::awb::FrameContext awb;
>   
>   	struct {
>   		float actualBrightness;
>
Jacopo Mondi July 7, 2026, 11:32 a.m. UTC | #5
Hi Stefan

On Tue, Jul 07, 2026 at 12:46:37PM +0200, Stefan Klug wrote:
> Hi Jacopo,
>
> Quoting Jacopo Mondi (2026-07-07 09:36:33)
> > Hi Stefan
> >
> > On Tue, Jul 07, 2026 at 08:31:10AM +0200, Stefan Klug wrote:
> > > Hi Jacopo,
> > >
> > > Thank you for the patch.
> > >
> > > Quoting Jacopo Mondi (2026-07-06 10:01:21)
> > > > The current implementation of AwbAlgorithm is the following one:
> > > >
> > > > AwbAlgorithm o---- RkISP1Awb
> > > >     ^
> > > >     |
> > > >  |-----|
> > > >  |     |
> > > > Bayes Grey
> > > >
> > > > The platform-specific Awb implementation instantiate one of
> > > > the Bayes or Grey Awb helper classes and both inherit from the
> > > > AwbAlgorithm base class.
> > > >
> > > > The handling of libcamera controls is split between the base and the
> > > > derived (mostly Bayes) class.
> > > >
> > > > We want instead the AwbAlgorithm to handle all-things-libcamera and let
> > > > the Bayes and Grey classes implement the logic. In order to achieve that
> > > > use composition also in the AwbAlgorithm class and introduce an
> > > > AwbImplementation base class from which Bayes and Grey derive from.
> > > >
> > > > AwbImplementation o---- AwbAlgorithmBase
> > > >     ^                           ^
> > > >     |                           |
> > > >  |-----|                AwbAlgorithm<Q<x,y>> o---- RkISP1Awb
> > > >  |     |
> > > > Bayes Grey
> > > >
> > > > The AwbAlgorithm<> class is instantiated with a Q<x, y> template
> > > > argument that represents the platform-specific register format which
> > > > is used to initialize controls and clamp gains to the hardware limits.
> > > >
> > > > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> > > > Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > > > Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
> > > > Tested-by: Daniel Scally <dan.scally@ideasonboard.com>
> > > > ---
> > > >  src/ipa/libipa/awb.cpp            | 472 ++++++++++++++++++++++++++++++++------
> > > >  src/ipa/libipa/awb.h              | 109 +++++++--
> > > >  src/ipa/libipa/awb_bayes.cpp      |  52 ++---
> > > >  src/ipa/libipa/awb_bayes.h        |  12 +-
> > > >  src/ipa/libipa/awb_grey.cpp       |  25 +-
> > > >  src/ipa/libipa/awb_grey.h         |   6 +-
> > > >  src/ipa/rkisp1/algorithms/awb.cpp | 360 +++++++++--------------------
> > > >  src/ipa/rkisp1/algorithms/awb.h   |  18 +-
> > > >  src/ipa/rkisp1/ipa_context.h      |  29 +--
> > > >  9 files changed, 672 insertions(+), 411 deletions(-)
> > > >
> > > > diff --git a/src/ipa/libipa/awb.cpp b/src/ipa/libipa/awb.cpp
> > > > index 29a78069c52c..95291840d8fb 100644
> > > > --- a/src/ipa/libipa/awb.cpp
> > > > +++ b/src/ipa/libipa/awb.cpp
> > > > @@ -2,7 +2,7 @@
> > > >  /*
> > > >   * Copyright (C) 2024 Ideas on Board Oy
> > > >   *
> > > > - * Generic AWB algorithms
> > > > + * libIPA Awb algorithms
> > > >   */
> > > >
> > > >  #include "awb.h"
> > > > @@ -11,9 +11,16 @@
> > > >
> > > >  #include <libcamera/control_ids.h>
> > > >
> > > > +#include "awb_bayes.h"
> > > > +#include "awb_grey.h"
> > > > +
> > > > +constexpr int32_t kMinColourTemperature = 2500;
> > > > +constexpr int32_t kMaxColourTemperature = 10000;
> > > > +constexpr int32_t kDefaultColourTemperature = 5000;
> > > > +
> > > >  /**
> > > >   * \file awb.h
> > > > - * \brief Base classes for AWB algorithms
> > > > + * \brief libipa awb implementation
> > > >   */
> > > >
> > > >  namespace libcamera {
> > > > @@ -22,30 +29,60 @@ LOG_DEFINE_CATEGORY(Awb)
> > > >
> > > >  namespace ipa {
> > > >
> > > > +namespace awb {
> > > > +
> > > >  /**
> > > > - * \class AwbResult
> > > > - * \brief The result of an AWB calculation
> > > > + * \struct Context
> > > > + * \brief Awb gains and colour temperature
> > > > + *
> > > > + * \var Context::gains
> > > > + * \brief The white balance gains
> > > >   *
> > > > - * This class holds the result of an auto white balance calculation.
> > > > + * \var Context::temperatureK
> > > > + * \brief The colour temperature, in Kelvin
> > > >   */
> > > >
> > > >  /**
> > > > - * \var AwbResult::gains
> > > > - * \brief The calculated white balance gains
> > > > + * \struct ActiveState
> > > > + * \brief Active awb state shared across frames
> > > > + *
> > > > + * \var ActiveState::manual
> > > > + * \brief The most recent manually requested awb state
> > > > + *
> > > > + * \var ActiveState::automatic
> > > > + * \brief The most recent automatically calculated awb state
> > > > + *
> > > > + * \var ActiveState::autoEnabled
> > > > + * \brief True when automatic awb is selected
> > > >   */
> > > >
> > > >  /**
> > > > - * \var AwbResult::colourTemperature
> > > > - * \brief The calculated colour temperature in Kelvin
> > > > + * \struct FrameContext
> > > > + * \brief Per-frame awb state
> > > > + *
> > > > + * \var FrameContext::autoEnabled
> > > > + * \brief True when automatic awb is in use
> > > >   */
> > > >
> > > > +} /* namespace awb */
> > > > +
> > > >  /**
> > > >   * \class AwbStats
> > > > - * \brief An abstraction class wrapping hardware-specific AWB statistics
> > > > + * \brief An abstraction class wrapping hardware-specific awb statistics
> > >
> > > Out of curiosity. I was told that abbreviations like AWB are always
> > > capitalized in writing. Is that incorrect?
> >
> > I'm not sure. But we had a mix of "Awb", "AWB" and "awb" which annoyed
> > me. So I went for a single version (wherever I noticed).
> >
> > We can decide what's best.
> >
> > >
> > > >   *
> > > > - * IPA modules using an AWB algorithm based on the AwbAlgorithm class need to
> > > > - * implement this class to give the algorithm access to the hardware-specific
> > > > - * statistics data.
> > > > + * IPA modules shall provide a derived implementation of this class to give the
> > > > + * awb algorithm access to the hardware-specific statistics data in a common
> > > > + * format.
> > > > + */
> > > > +
> > > > +/**
> > > > + * \fn AwbStats::AwbStats()
> > > > + * \brief Construct an empty AwbStat
> > > > + */
> > > > +
> > > > +/**
> > > > + * AwbStat::~AwbStat
> > > > + * \brief Virtual class destructor
> > > >   */
> > > >
> > > >  /**
> > > > @@ -57,7 +94,7 @@ namespace ipa {
> > > >   * applied. To keep the actual implementations computationally inexpensive,
> > > >   * the squared colour error shall be returned.
> > > >   *
> > > > - * If the AWB statistics provide multiple zones, the average of the individual
> > > > + * If the awb statistics provide multiple zones, the average of the individual
> > > >   * squared errors shall be returned. Averaging/normalizing is necessary so that
> > > >   * the numeric dimensions are the same on all hardware platforms.
> > > >   *
> > > > @@ -65,79 +102,328 @@ namespace ipa {
> > > >   */
> > > >
> > > >  /**
> > > > - * \fn AwbStats::rgbMeans()
> > > > - * \brief Get RGB means of the statistics
> > > > + * \fn AwbStats::valid()
> > > > + * \brief Retrieve if the awb statistics are valid
> > > >   *
> > > > - * Fetch the RGB means from the statistics. The values of each channel are
> > > > - * dimensionless and only the ratios are used for further calculations. This is
> > > > - * used by the simple grey world model to calculate the gains to apply.
> > > > + * If the colour mean values are too small, the AwbAlgorithm class doesn't have
> > > > + * enough information to meaningfully calculate white-balance gains. Freeze the
> > > > + * algorithm in that case.
> > > > + *
> > > > + * \return True if the awb statistics are valid, false otherwise
> > > > + */
> > > > +
> > > > +/**
> > > > + * \fn AwbStats::rgbMeans
> > > > + * \brief Retrieve the RGB means values
> > > > + *
> > > > + * Fetch the RGB means from the statistics in the [0, 1] range.
> > > >   *
> > > >   * \return The RGB means
> > > >   */
> > > >
> > > >  /**
> > > > - * \class AwbAlgorithm
> > > > - * \brief A base class for auto white balance algorithms
> > > > + * \var AwbStats::rgbMeans_
> > > > + * \brief Mean values of colour channels
> > > > + */
> > > > +
> > > > +/**
> > > > + * \var AwbStats::rg_
> > > > + * \brief Red/Green ratio
> > > > + */
> > > > +
> > > > +/**
> > > > + * \var AwbStats::bg_
> > > > + * \brief Blue/Green ratio
> > > > + */
> > > > +
> > > > +/**
> > > > + * \class AwbImplementation
> > > > + * \brief Pure virtual base class for awb algorithms implementations
> > > > + *
> > > > + * The AwbImplementation class defines the interface for the awb algorithm
> > > > + * implementations.
> > > >   *
> > > > - * This class is a base class for auto white balance algorithms. It defines an
> > > > - * interface for the algorithms to implement, and is used by the IPAs to
> > > > - * interact with the concrete implementation.
> > > > + * It is currently implemented by the AwbGrey and AwbBayes classes.
> > > > + *
> > > > + * The interface defines an init() function to initialize the algorithm with the
> > > > + * content of the tuning file and two functions to compute colour gains
> > > > + * according to the algorithm operating mode (auto or manual) in use.
> > > > + *
> > > > + * The calculateAwb() function calculates colour gains given a set of statistics
> > > > + * provided by the IPA module. It is used when the algorithm operates in auto
> > > > + * mode and gains are dynamically computed given a new set of statistics from
> > > > + * the awb engine.
> > > > + *
> > > > + * The gainsFromColourTemperature() function instead computes the white balance
> > > > + * gains from a colour temperature. This function does not take any statistics
> > > > + * into account. It is used to compute the colour gains when the user manually
> > > > + * specifies a colour temperature.
> > > >   */
> > > >
> > > >  /**
> > > > - * \fn AwbAlgorithm::init()
> > > > - * \brief Initialize the algorithm with the given tuning data
> > > > - * \param[in] tuningData The tuning data to use for the algorithm
> > > > + * \struct AwbImplementation::Result
> > > > + * \brief The result of an awb computation
> > > >   *
> > > > - * \return 0 on success, a negative error code otherwise
> > > > + * \var AwbImplementation::Result::gains
> > > > + * \brief The computed white balance gains
> > > > + *
> > > > + * \var AwbImplementation::Result::temperatureK
> > > > + * \brief The computed colour temperature, in Kelvin
> > > >   */
> > > >
> > > >  /**
> > > > - * \fn AwbAlgorithm::calculateAwb()
> > > > - * \brief Calculate AWB data from the given statistics
> > > > - * \param[in] stats The statistics to use for the calculation
> > > > - * \param[in] lux The lux value of the scene
> > > > + * \fn AwbImplementation::~AwbImplementation
> > > > + * \brief Virtual class destructor
> > > > + */
> > > > +
> > > > +/**
> > > > + * \fn AwbImplementation::init()
> > > > + * \param[in] tuningData
> > > > + * \brief Initialize the algorithm by parsing \a tuningData
> > > > + */
> > > > +
> > > > +/**
> > > > + * \fn AwbImplementation::calculateAwb()
> > > > + * \param[in] stats The awb statistics
> > > > + * \param[in] lux The estimated lux level
> > > > + * \param[in] range The colour temperature search limits
> > > >   *
> > > > - * Calculate an AwbResult object from the given statistics and lux value. A \a
> > > > - * lux value of 0 means it is unknown or invalid and the algorithm shall ignore
> > > > - * it.
> > > > + * Calculate a new set of colour gains and a colour temperature given a new
> > > > + * set of statistics \a stats, an estimated luminance \a lux and a range of
> > > > + * colour temperature to limit the search.
> > > >   *
> > > > - * \return The AWB result
> > > > + * \return A Result computed using the new statistics
> > > > + */
> > > > +/**
> > > > + * \fn AwbImplementation::gainsFromColourTemperature()
> > > > + * \param[in] temperatureK Colour temperature in Kelvin
> > > > + *
> > > > + * Calculate a new set of colour gains for a new manually specified colour
> > > > + * temperature \a temperatureK.
> > > > + *
> > > > + * \return The RGB gain values adjusted to \a temperatureK or std::nullopt
> > > > + */
> > > > +
> > > > +/**
> > > > + * \class AwbAlgorithmBase
> > > > + * \brief Base class for AwbAlgorithm for non-templated functions implementation
> > > > + *
> > > > + * Base class for AwbAlgorithm where non-templated functions are implemented.
> > > > + * IPA implementations shall use AwbAlgorithm and not this class.
> > > >   */
> > > >
> > > >  /**
> > > > - * \fn AwbAlgorithm::gainsFromColourTemperature()
> > > > - * \brief Compute white balance gains from a colour temperature
> > > > - * \param[in] colourTemperature The colour temperature in Kelvin
> > > > + * \brief Initialize the algorithm with the given tuning data
> > > > + * \param[in] tuningData The tuning data to use for the algorithm
> > > >   *
> > > > - * Compute the white balance gains from a \a colourTemperature. This function
> > > > - * does not take any statistics into account. It is used to compute the colour
> > > > - * gains when the user manually specifies a colour temperature.
> > > > + * Parse \a tuningData to initialize the awb algorithm and register controls.
> > > > + * IPA modules are expected to call this function as part of their
> > > > + * implementation of Algorithm::init().
> > > >   *
> > > > - * \return The colour gains or std::nullopt if the conversion is not possible
> > > > + * \return 0 on success, a negative error code otherwise
> > > > + */
> > > > +int AwbAlgorithmBase::init(const ValueNode &tuningData)
> > > > +{
> > > > +       bayes_ = false;
> > > > +
> > > > +       if (!tuningData.contains("algorithm"))
> > > > +               LOG(Awb, Info) << "No Awb algorithm specified, using grey world";
> > > > +
> > > > +       auto mode = tuningData["algorithm"].get<std::string>("grey");
> > > > +       if (mode == "grey") {
> > > > +               impl_ = std::make_unique<AwbGrey>();
> > > > +       } else if (mode == "bayes") {
> > > > +               impl_ = std::make_unique<AwbBayes>();
> > > > +               bayes_ = true;
> > > > +       } else {
> > > > +               LOG(Awb, Error) << "Unknown Awb algorithm: " << mode;
> > > > +               return -EINVAL;
> > > > +       }
> > > > +
> > > > +       LOG(Awb, Debug) << "Using Awb algorithm: " << mode;
> > > > +
> > > > +       int ret = impl_->init(tuningData);
> > > > +       if (ret)
> > > > +               return ret;
> > > > +
> > > > +       controls_[&controls::ColourTemperature] =
> > > > +               ControlInfo(kMinColourTemperature, kMaxColourTemperature,
> > > > +                           kDefaultColourTemperature);
> > > > +       controls_[&controls::AwbEnable] = ControlInfo(false, true);
> > > > +
> > > > +       return parseModeConfigs(tuningData, controls::AwbAuto);
> > > > +}
> > > > +
> > > > +/**
> > > > + * \brief Configure the awb algorithm
> > > > + * \param[in] state The awb active state
> > > > + * \return 0 if successful, an error code otherwise
> > > >   */
> > > > +int AwbAlgorithmBase::configure(awb::ActiveState &state)
> > > > +{
> > > > +       state.manual.gains = RGB<double>{ 1.0 };
> > > > +       auto gains = impl_->gainsFromColourTemperature(kDefaultColourTemperature);
> > > > +       if (gains)
> > > > +               state.automatic.gains = *gains;
> > > > +       else
> > > > +               state.automatic.gains = RGB<double>{ 1.0 };
> > > > +
> > > > +       state.autoEnabled = true;
> > > > +       state.manual.temperatureK = kDefaultColourTemperature;
> > > > +       state.automatic.temperatureK = kDefaultColourTemperature;
> > > > +
> > > > +       return 0;
> > > > +}
> > > >
> > > >  /**
> > > > - * \fn AwbAlgorithm::controls()
> > > > - * \brief Get the controls info map for this algorithm
> > > > + * \brief Queue a Request to the awb algorithm
> > > > + * \param[in] state The awb active state
> > > > + * \param[in] frame The frame number
> > > > + * \param[in] frameContext The awb frame context
> > > > + * \param[in] controls The list of controls part of the Request
> > > >   *
> > > > - * \return The controls info map
> > > > + * Queue a new Request to the awb algorithm and modify its behaviour according
> > > > + * to the provided controls.
> > > > + *
> > > > + * The currently handled controls are:
> > > > + * - controls::AwbEnable
> > > > + * - controls::AwbMode
> > > > + * - controls::ColourGains
> > > > + * - controls::ColourTemperature
> > > >   */
> > > > +void AwbAlgorithmBase::queueRequest(awb::ActiveState &state,
> > > > +                                   [[maybe_unused]] const uint32_t frame,
> > > > +                                   awb::FrameContext &frameContext,
> > > > +                                   const ControlList &controls)
> > > > +{
> > > > +       const auto &awbEnable = controls.get(controls::AwbEnable);
> > > > +       if (awbEnable && *awbEnable != state.autoEnabled) {
> > > > +               state.autoEnabled = *awbEnable;
> > > > +
> > > > +               LOG(Awb, Debug)
> > > > +                       << (*awbEnable ? "Enabling" : "Disabling") << " Awb";
> > > > +       }
> > > > +
> > > > +       auto mode = controls.get(controls::AwbMode);
> > > > +       if (mode) {
> > > > +               auto it = modes_.find(static_cast<controls::AwbModeEnum>(*mode));
> > > > +               if (it == modes_.end()) {
> > > > +                       LOG(Awb, Error) << "Unsupported Awb mode " << *mode;
> > > > +                       return;
> > > > +               }
> > > > +
> > > > +               currentMode_ = &it->second;
> > > > +       }
> > > > +
> > > > +       frameContext.autoEnabled = state.autoEnabled;
> > > > +
> > > > +       if (frameContext.autoEnabled)
> > > > +               return;
> > > > +
> > > > +       const auto &colourGains = controls.get(controls::ColourGains);
> > > > +       const auto &colourTemperature = controls.get(controls::ColourTemperature);
> > > > +       bool update = false;
> > > > +       if (colourGains) {
> > > > +               state.manual.gains.r() = (*colourGains)[0];
> > > > +               state.manual.gains.b() = (*colourGains)[1];
> > > > +               /*
> > > > +                * \todo Colour temperature reported in metadata is now
> > > > +                * incorrect, as we can't deduce the temperature from the gains.
> > > > +                * This will be fixed with the bayes AWB algorithm.
> > > > +                */
> > > > +               update = true;
> > > > +       } else if (colourTemperature) {
> > > > +               state.manual.temperatureK = *colourTemperature;
> > > > +               const auto &gains = impl_->gainsFromColourTemperature(*colourTemperature);
> > > > +               if (gains) {
> > > > +                       state.manual.gains.r() = gains->r();
> > > > +                       state.manual.gains.b() = gains->b();
> > > > +                       update = true;
> > > > +               }
> > > > +       }
> > > > +
> > > > +       if (update)
> > > > +               LOG(Awb, Debug)
> > > > +                       << "Set colour gains to " << state.manual.gains;
> > > > +
> > > > +       frameContext.gains = state.manual.gains;
> > > > +       frameContext.temperatureK = state.manual.temperatureK;
> > > > +}
> > > >
> > > >  /**
> > > > - * \fn AwbAlgorithm::handleControls()
> > > > - * \param[in] controls The controls to handle
> > > > - * \brief Handle the controls supplied in a request
> > > > + * \brief Set the gains and colour temperature values in \a frameContext
> > > > + * \param[in] state The awb active state
> > > > + * \param[in] frameContext The awb frame context
> > > > + *
> > > > + * If auto mode is enabled, take the most recently computed gains and use them
> > > > + * for the current frame. Otherwise, if in manual mode, gains and colour
> > > > + * temperature for a frame are set at queueRequest() time.
> > > >   */
> > > > +void AwbAlgorithmBase::prepare(awb::ActiveState &state,
> > > > +                              awb::FrameContext &frameContext)
> > > > +{
> > > > +       if (frameContext.autoEnabled) {
> > > > +               frameContext.gains = state.automatic.gains;
> > > > +               frameContext.temperatureK = state.automatic.temperatureK;
> > > > +       }
> > > > +}
> > > >
> > > >  /**
> > > > + * \brief Process awb statistics to calculate gains and populate metadata
> > > > + * \param[in] state The awb active state
> > > > + * \param[in] frameContext The awb frame context
> > > > + * \param[in] stats The awb statistics
> > > > + * \param[in] lux The lux value as estimated by the IPA module
> > > > + * \param[out] metadata The metadata list
> > > > + *
> > > > + * Process \a stats to calculate new gains and colour temperature and populate
> > > > + * \a metadata with the results.
> > > > + */
> > > > +void AwbAlgorithmBase::process(awb::ActiveState &state,
> > > > +                              awb::FrameContext &frameContext,
> > > > +                              const AwbStats &stats, unsigned int lux,
> > > > +                              ControlList &metadata)
> > > > +{
> > > > +       if (!stats.valid())
> > > > +               return;
> > > > +
> > > > +       auto awbResult = impl_->calculateAwb(stats, lux, { currentMode_->ctLo,
> > > > +                                                          currentMode_->ctHi });
> > > > +
> > > > +       /*
> > > > +        * Clamp the gain values to the hardware, according to the gainMin_
> > > > +        * and gainMax_ values.
> > > > +        */
> > > > +       awbResult.gains = awbResult.gains.clamp(gainMin_, gainMax_);
> > > > +
> > > > +       /* Smooth color gains adjustments. */
> > > > +       double speed = 0.2;
> > > > +       double ct = awbResult.temperatureK;
> > > > +       ct = ct * speed + state.automatic.temperatureK * (1 - speed);
> > > > +
> > > > +       state.automatic.temperatureK = awbResult.temperatureK;
> > > > +       state.automatic.gains = awbResult.gains * speed +
> > > > +                               state.automatic.gains * (1 - speed);
> > > > +
> > > > +       /* Populate metadata. */
> > > > +       metadata.set(controls::AwbEnable, frameContext.autoEnabled);
> > > > +       metadata.set(controls::ColourGains, { static_cast<float>(frameContext.gains.r()),
> > > > +                                             static_cast<float>(frameContext.gains.b()) });
> > > > +       metadata.set(controls::ColourTemperature, frameContext.temperatureK);
> > > > +
> > > > +       LOG(Awb, Debug) << std::showpoint << "Means " << stats.rgbMeans()
> > > > +                       << ", gains " << state.automatic.gains
> > > > +                       << ", temp " << state.automatic.temperatureK << "K";
> > > > +}
> > > > +
> > > > +/*
> > > >   * \brief Parse the mode configurations from the tuning data
> > > >   * \param[in] tuningData the ValueNode representing the tuning data
> > > >   * \param[in] def The default value for the AwbMode control
> > > >   *
> > > >   * Utility function to parse the tuning data for an AwbMode entry and read all
> > > > - * provided modes. It adds controls::AwbMode to AwbAlgorithm::controls_ and
> > > > - * populates AwbAlgorithm::modes_. For a list of possible modes see \ref
> > > > + * provided modes. It adds controls::AwbMode to AwbAlgorithmBase::controls_ and
> > > > + * populates AwbAlgorithmBase::modes_. For a list of possible modes see \ref
> > > >   * controls::AwbModeEnum.
> > > >   *
> > > >   * Each mode entry must contain a "lo" and "hi" key to specify the lower and
> > > > @@ -156,15 +442,23 @@ namespace ipa {
> > > >   *       ...
> > > >   * \endcode
> > > >   *
> > > > - * If \a def is supplied but not contained in the the \a tuningData, -EINVAL is
> > > > + * If \a def is supplied but not contained in the \a tuningData, -EINVAL is
> > > >   * returned.
> > > >   *
> > > > + * AwbModes are only used by the AwbBayes implementation.
> > > > + *
> > > >   * \sa controls::AwbModeEnum
> > > >   * \return Zero on success, negative error code otherwise
> > > >   */
> > > > -int AwbAlgorithm::parseModeConfigs(const ValueNode &tuningData,
> > > > -                                  const ControlValue &def)
> > > > +int AwbAlgorithmBase::parseModeConfigs(const ValueNode &tuningData,
> > > > +                                      const ControlValue &def)
> > > >  {
> > > > +       if (!bayes_) {
> > > > +               /* AwbGrey does not support and does not use modes. */
> > > > +               currentMode_ = &AwbGreyMode;
> > > > +               return 0;
> > > > +       }
> > > > +
> > > >         std::vector<ControlValue> availableModes;
> > > >
> > > >         const ValueNode &modes = tuningData[controls::AwbMode.name()];
> > > > @@ -227,37 +521,83 @@ int AwbAlgorithm::parseModeConfigs(const ValueNode &tuningData,
> > > >         }
> > > >
> > > >         controls_[&controls::AwbMode] = ControlInfo(availableModes, def);
> > > > +       currentMode_ = &modes_[controls::AwbAuto];
> > > >
> > > >         return 0;
> > > >  }
> > > >
> > > >  /**
> > > > - * \class AwbAlgorithm::ModeConfig
> > > > - * \brief Holds the configuration of a single AWB mode
> > > > + * \var AwbAlgorithmBase::controls_
> > > > + * \brief Controls info map for the controls registered by the awb algorithm
> > > > + */
> > > > +
> > > > +/**
> > > > + * \var AwbAlgorithmBase::gainMin_
> > > > + * \brief The minimum supported gain value
> > > > + *
> > > > + * Minimum gain value used to clamp the awb algorithm calculation results in the
> > > > + * range supported by the platform awb engine.
> > > >   *
> > > > - * AWB modes limit the regulation of the AWB algorithm to a specific range of
> > > > - * colour temperatures.
> > > > + * The min and max gain values are initialized by AwbAlgorithm::init().
> > > >   */
> > > >
> > > >  /**
> > > > - * \var AwbAlgorithm::ModeConfig::ctLo
> > > > + * \var AwbAlgorithmBase::gainMax_
> > > > + * \brief The maximum supported gain value
> > > > + *
> > > > + * Maximum gain value used to clamp the awb algorithm calculation results in the
> > > > + * range supported by the platform awb engine.
> > > > + *
> > > > + * The min and max gain values are initialized by AwbAlgorithm::init().
> > > > + */
> > > > +
> > > > +/*
> > > > + * \class AwbAlgorithmBase::ModeConfig
> > > > + * \brief Holds the configuration of a single awb mode
> > > > + *
> > > > + * AWB modes limit the regulation of the awb algorithm to a specific range of
> > > > + * colour temperatures. Use by AwbBayes only.
> > > > + */
> > > > +
> > > > +/*
> > > > + * \var AwbAlgorithmBase::ModeConfig::ctLo
> > > >   * \brief The lowest valid colour temperature of that mode
> > > >   */
> > > >
> > > > -/**
> > > > - * \var AwbAlgorithm::ModeConfig::ctHi
> > > > +/*
> > > > + * \var AwbAlgorithmBase::ModeConfig::ctHi
> > > >   * \brief The highest valid colour temperature of that mode
> > > >   */
> > > >
> > > > +/*
> > > > + * \var AwbAlgorithmBase::modes_
> > > > + * \brief Map of all configured modes
> > > > + * \sa AwbAlgorithmBase::parseModeConfigs
> > > > + */
> > > > +
> > > >  /**
> > > > - * \var AwbAlgorithm::controls_
> > > > - * \brief Controls info map for the controls provided by the algorithm
> > > > + * \class AwbAlgorithm
> > > > + * \brief The libipa awb algorithm
> > > > + * \tparam Q The fixedpoint register representation of gain values
> > > > + *
> > > > + * Implement the awb algorithm for libipa.
> > > > + *
> > > > + * The AwbAlgorithm class implements an interface similar in spirit to the one
> > > > + * of the Algorithm class. IPA modules are expected to store an instance of
> > > > + * AwbAlgorithm as class member, template it with the awb engine gain register
> > > > + * representation and call its function in their implementations of the
> > > > + * Algorithm interface.
> > > > + *
> > > > + * The AwbAlgorithm instantiates an AwbImplementation implementation (AwbGrey or
> > > > + * AwbBayes) at init() time by parsing the tuning data and uses it to compute
> > > > + * the RGB gains and estimate a colour temperature given a set of statistics
> > > > + * in the form of a AwbStats derived implementation.
> > > >   */
> > > >
> > > >  /**
> > > > - * \var AwbAlgorithm::modes_
> > > > - * \brief Map of all configured modes
> > > > - * \sa AwbAlgorithm::parseModeConfigs
> > > > + * \fn AwbAlgorithm::init()
> > > > + * \param[in] controls The info map of the IPA controls
> > > > + * \copydoc AwbAlgorithmBase::init()
> > > >   */
> > > >
> > > >  } /* namespace ipa */
> > > > diff --git a/src/ipa/libipa/awb.h b/src/ipa/libipa/awb.h
> > > > index 09c00e47d604..72f46367a136 100644
> > > > --- a/src/ipa/libipa/awb.h
> > > > +++ b/src/ipa/libipa/awb.h
> > > > @@ -2,11 +2,12 @@
> > > >  /*
> > > >   * Copyright (C) 2024 Ideas on Board Oy
> > > >   *
> > > > - * Generic AWB algorithms
> > > > + * libIPA AWB algorithms
> > > >   */
> > > >
> > > >  #pragma once
> > > >
> > > > +#include <array>
> > > >  #include <map>
> > > >  #include <optional>
> > > >
> > > > @@ -20,46 +21,118 @@ namespace libcamera {
> > > >
> > > >  namespace ipa {
> > > >
> > > > -struct AwbResult {
> > > > +namespace awb {
> > > > +
> > > > +struct Context {
> > > >         RGB<double> gains;
> > > > -       double colourTemperature;
> > > > +       unsigned int temperatureK;
> > > >  };
> > > >
> > > > +struct ActiveState {
> > > > +       Context manual;
> > > > +       Context automatic;
> > > > +
> > > > +       bool autoEnabled;
> > > > +};
> > > > +
> > > > +struct FrameContext : public Context {
> > > > +       bool autoEnabled;
> > > > +};
> > > > +
> > > > +} /* namespace awb */
> > > > +
> > > >  struct AwbStats {
> > > > +       AwbStats() = default;
> > > > +       virtual ~AwbStats() = default;
> > > > +
> > > >         virtual double computeColourError(const RGB<double> &gains) const = 0;
> > > > -       virtual RGB<double> rgbMeans() const = 0;
> > > > +       virtual bool valid() const = 0;
> > > > +       RGB<double> rgbMeans() const { return rgbMeans_; }
> > > >
> > > >  protected:
> > > > -       ~AwbStats() = default;
> > > > +       RGB<double> rgbMeans_;
> > > > +       double rg_;
> > > > +       double bg_;
> > >
> > > I think these (rg_ and bg_) are not used anymore and can be dropped.
> >
> > I see they being used by RkISP1AwbStats::computeColourError() which I
> > haven't modified compared to master.
> >
> > I can move rg_ and bg_ to RkISP1Stats but if you want them to be
> > dropped and modify computeColourError() accordingly, it will be done
> > on top.
>
> Ahh now, I understand where that comes from. Yes that is in RkISP1Stats
> and is only there to not calculate the ratios over and over again. The
> reason is that rkisp1 has only one measurement window. For multi window
> hardware this function needs to iterate over the measurements of the
> individual windows.
>
> So yes, these should be moved to the platform implementation.
>
> >
> > > Personally I'd also leave the rgbMeans_ member in the platform
> > > implementation and keep it as interface only - up to you.
> >
> > rgbMeans_ is used by AwbGray
>
> It uses rgbMeans() doesn't it?
>

Which returns rgbMeans_ :)

Maybe now I get what you mean, for RkISP1 rgbMeans() returns
rgbMeans_, but in other implementation it might do something different?

> >
> > >
> > > >  };
> > > >
> > > > -class AwbAlgorithm
> > > > +class AwbImplementation
> > > >  {
> > > >  public:
> > > > -       virtual ~AwbAlgorithm() = default;
> > > > +       struct Result {
> > > > +               RGB<double> gains;
> > > > +               unsigned int temperatureK;
> > > > +       };
> > > >
> > > > +       virtual ~AwbImplementation() = default;
> > > >         virtual int init(const ValueNode &tuningData) = 0;
> > > > -       virtual AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) = 0;
> > > > -       virtual std::optional<RGB<double>> gainsFromColourTemperature(double colourTemperature) = 0;
> > > > +       virtual Result calculateAwb(const AwbStats &stats, unsigned int lux,
> > > > +                                   std::array<double, 2> range) = 0;
> > > > +       virtual std::optional<RGB<double>>
> > > > +       gainsFromColourTemperature(double temperatureK) = 0;
> > > > +};
> > > >
> > > > -       const ControlInfoMap::Map &controls() const
> > > > -       {
> > > > -               return controls_;
> > > > -       }
> > > > +class AwbAlgorithmBase
> > > > +{
> > > > +public:
> > > > +       int init(const ValueNode &tuningData);
> > > >
> > > > -       virtual void handleControls([[maybe_unused]] const ControlList &controls) {}
> > > > +       int configure(awb::ActiveState &state);
> > > > +
> > > > +       void queueRequest(awb::ActiveState &state,
> > > > +                         const uint32_t frame,
> > > > +                         awb::FrameContext &frameContext,
> > > > +                         const ControlList &controls);
> > > > +
> > > > +       void prepare(awb::ActiveState &state, awb::FrameContext &frameContext);
> > > > +
> > > > +       void process(awb::ActiveState &state, awb::FrameContext &frameContext,
> > > > +                    const AwbStats &stats, unsigned int lux,
> > > > +                    ControlList &metadata);
> > > >
> > > >  protected:
> > > > -       int parseModeConfigs(const ValueNode &tuningData,
> > > > -                            const ControlValue &def = {});
> > > > +       AwbAlgorithmBase() = default;
> > > > +
> > > > +       ControlInfoMap::Map controls_;
> > > > +       float gainMin_;
> > > > +       float gainMax_;
> > > >
> > > > +private:
> > > >         struct ModeConfig {
> > > >                 double ctHi;
> > > >                 double ctLo;
> > > >         };
> > > >
> > > > -       ControlInfoMap::Map controls_;
> > > > -       std::map<controls::AwbModeEnum, AwbAlgorithm::ModeConfig> modes_;
> > > > +       /* AwbGrey does not support modes; */
> > > > +       static constexpr ModeConfig AwbGreyMode = { 0.0, 0.0 };
> > > > +
> > > > +       int parseModeConfigs(const ValueNode &tuningData,
> > > > +                            const ControlValue &def = {});
> > > > +
> > > > +       std::map<controls::AwbModeEnum, AwbAlgorithmBase::ModeConfig> modes_;
> > > > +       const ModeConfig *currentMode_ = nullptr;
> > > > +       std::unique_ptr<AwbImplementation> impl_;
> > > > +       bool bayes_ = false;
> > > > +};
> > > > +
> > > > +template<typename Q>
> > > > +class AwbAlgorithm : public AwbAlgorithmBase
> > > > +{
> > > > +public:
> > > > +       int init(const ValueNode &tuningData, ControlInfoMap::Map &controls)
> > > > +       {
> > > > +               AwbAlgorithmBase::init(tuningData);
> > > > +
> > > > +               gainMin_ = std::max(Q::TraitsType::min, 1.0f);
> > > > +               gainMax_ = Q::TraitsType::max;
> > > > +
> > > > +               controls_[&controls::ColourGains] =
> > > > +                       ControlInfo(gainMin_, gainMax_,
> > > > +                                   Span<const float, 2>{ { 1.0f, 1.0f } });
> > > > +
> > > > +               controls.insert(controls_.begin(), controls_.end());
> > > > +
> > > > +               return 0;
> > > > +       }
> > > >  };
> > > >
> > > >  } /* namespace ipa */
> > > > diff --git a/src/ipa/libipa/awb_bayes.cpp b/src/ipa/libipa/awb_bayes.cpp
> > > > index 9fd85e5a4505..2c10f9c4c82d 100644
> > > > --- a/src/ipa/libipa/awb_bayes.cpp
> > > > +++ b/src/ipa/libipa/awb_bayes.cpp
> > > > @@ -140,11 +140,6 @@ void Interpolator<Pwl>::interpolate(const Pwl &a, const Pwl &b, Pwl &dest, doubl
> > > >   * \brief How far to wander off CT curve towards "more green"
> > > >   */
> > > >
> > > > -/**
> > > > - * \var AwbBayes::currentMode_
> > > > - * \brief The currently selected mode
> > > > - */
> > > > -
> > > >  int AwbBayes::init(const ValueNode &tuningData)
> > > >  {
> > > >         int ret = colourGainCurve_.readYaml(tuningData["colourGains"], "ct", "gains");
> > > > @@ -172,14 +167,6 @@ int AwbBayes::init(const ValueNode &tuningData)
> > > >                 return ret;
> > > >         }
> > > >
> > > > -       ret = parseModeConfigs(tuningData, controls::AwbAuto);
> > > > -       if (ret) {
> > > > -               LOG(Awb, Error)
> > > > -                       << "Failed to parse mode parameter from tuning file";
> > > > -               return ret;
> > > > -       }
> > > > -       currentMode_ = &modes_[controls::AwbAuto];
> > > > -
> > > >         transversePos_ = tuningData["transversePos"].get<double>(0.01);
> > > >         transverseNeg_ = tuningData["transverseNeg"].get<double>(0.01);
> > > >         if (transversePos_ <= 0 || transverseNeg_ <= 0) {
> > > > @@ -260,18 +247,16 @@ int AwbBayes::readPriors(const ValueNode &tuningData)
> > > >         return 0;
> > > >  }
> > > >
> > > > -void AwbBayes::handleControls(const ControlList &controls)
> > > > -{
> > > > -       auto mode = controls.get(controls::AwbMode);
> > > > -       if (mode) {
> > > > -               auto it = modes_.find(static_cast<controls::AwbModeEnum>(*mode));
> > > > -               if (it != modes_.end())
> > > > -                       currentMode_ = &it->second;
> > > > -               else
> > > > -                       LOG(Awb, Error) << "Unsupported AWB mode " << *mode;
> > > > -       }
> > > > -}
> > > > -
> > > > +/**
> > > > + * \brief Compute white balance gains from a colour temperature
> > > > + * \param[in] colourTemperature The colour temperature in Kelvin
> > > > + *
> > > > + * Compute the white balance gains from a \a colourTemperature. This function
> > > > + * does not take any statistics into account. It simply interpolates the colour
> > > > + * gains curve on a new colour temperature.
> > > > + *
> > > > + * \return The RGB gain values adjusted to \a temperatureK
> > > > + */
> > > >  std::optional<RGB<double>> AwbBayes::gainsFromColourTemperature(double colourTemperature)
> > > >  {
> > > >         /*
> > > > @@ -283,7 +268,9 @@ std::optional<RGB<double>> AwbBayes::gainsFromColourTemperature(double colourTem
> > > >         return RGB<double>{ { gains[0], 1.0, gains[1] } };
> > > >  }
> > > >
> > > > -AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux)
> > > > +AwbImplementation::Result
> > > > +AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux,
> > > > +                      std::array<double, 2> range)
> > > >  {
> > > >         ipa::Pwl prior;
> > > >         if (lux > 0) {
> > > > @@ -295,7 +282,7 @@ AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux)
> > > >                 prior.append(0, 1.0);
> > > >         }
> > > >
> > > > -       double t = coarseSearch(prior, stats);
> > > > +       double t = coarseSearch(prior, stats, range);
> > > >         double r = ctR_.eval(t);
> > > >         double b = ctB_.eval(t);
> > > >         LOG(Awb, Debug)
> > > > @@ -316,14 +303,15 @@ AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux)
> > > >                 << "After fine search: r " << r << " b " << b << " (gains r "
> > > >                 << 1 / r << " b " << 1 / b << ")";
> > > >
> > > > -       return { { { 1.0 / r, 1.0, 1.0 / b } }, t };
> > > > +       return { { { 1.0 / r, 1.0, 1.0 / b } }, static_cast<unsigned int>(t) };
> > > >  }
> > > >
> > > > -double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) const
> > > > +double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats,
> > > > +                             Span<double> range) const
> > > >  {
> > > >         std::vector<Pwl::Point> points;
> > > >         size_t bestPoint = 0;
> > > > -       double t = currentMode_->ctLo;
> > > > +       double t = range[0];
> > > >         int spanR = -1;
> > > >         int spanB = -1;
> > > >         LimitsRecorder<double> errorLimits;
> > > > @@ -345,14 +333,14 @@ double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) cons
> > > >                 if (points.back().y() < points[bestPoint].y())
> > > >                         bestPoint = points.size() - 1;
> > > >
> > > > -               if (t == currentMode_->ctHi)
> > > > +               if (t == range[1])
> > > >                         break;
> > > >
> > > >                 /*
> > > >                  * Ensure even steps along the r/b curve by scaling them by the
> > > >                  * current t.
> > > >                  */
> > > > -               t = std::min(t + t / 10 * kSearchStep, currentMode_->ctHi);
> > > > +               t = std::min(t + t / 10 * kSearchStep, range[1]);
> > > >         }
> > > >
> > > >         t = points[bestPoint].x();
> > > > diff --git a/src/ipa/libipa/awb_bayes.h b/src/ipa/libipa/awb_bayes.h
> > > > index 1e3373676bc0..8d4cff076d4a 100644
> > > > --- a/src/ipa/libipa/awb_bayes.h
> > > > +++ b/src/ipa/libipa/awb_bayes.h
> > > > @@ -20,22 +20,24 @@ namespace libcamera {
> > > >
> > > >  namespace ipa {
> > > >
> > > > -class AwbBayes : public AwbAlgorithm
> > > > +class AwbBayes : public AwbImplementation
> > > >  {
> > > >  public:
> > > >         AwbBayes() = default;
> > > >
> > > >         int init(const ValueNode &tuningData) override;
> > > > -       AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) override;
> > > > +       AwbImplementation::Result
> > > > +       calculateAwb(const AwbStats &stats, unsigned int lux,
> > > > +                    std::array<double, 2> range) override;
> > > >         std::optional<RGB<double>> gainsFromColourTemperature(double temperatureK) override;
> > > > -       void handleControls(const ControlList &controls) override;
> > > >
> > > >  private:
> > > >         int readPriors(const ValueNode &tuningData);
> > > >
> > > >         void fineSearch(double &t, double &r, double &b, ipa::Pwl const &prior,
> > > >                         const AwbStats &stats) const;
> > > > -       double coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) const;
> > > > +       double coarseSearch(const ipa::Pwl &prior, const AwbStats &stats,
> > > > +                           Span<double> range) const;
> > > >         double interpolateQuadratic(ipa::Pwl::Point const &a,
> > > >                                     ipa::Pwl::Point const &b,
> > > >                                     ipa::Pwl::Point const &c) const;
> > > > @@ -50,8 +52,6 @@ private:
> > > >
> > > >         double transversePos_;
> > > >         double transverseNeg_;
> > > > -
> > > > -       ModeConfig *currentMode_ = nullptr;
> > > >  };
> > > >
> > > >  } /* namespace ipa */
> > > > diff --git a/src/ipa/libipa/awb_grey.cpp b/src/ipa/libipa/awb_grey.cpp
> > > > index b14b096810ae..1e4af717f62d 100644
> > > > --- a/src/ipa/libipa/awb_grey.cpp
> > > > +++ b/src/ipa/libipa/awb_grey.cpp
> > > > @@ -60,6 +60,7 @@ int AwbGrey::init(const ValueNode &tuningData)
> > > >   * \brief Calculate AWB data from the given statistics
> > > >   * \param[in] stats The statistics to use for the calculation
> > > >   * \param[in] lux The lux value of the scene
> > > > + * \param[in] range The colour temperature search limits
> > > >   *
> > > >   * The colour temperature is estimated based on the colours::estimateCCT()
> > > >   * function. The gains are calculated purely based on the RGB means provided by
> > > > @@ -70,20 +71,23 @@ int AwbGrey::init(const ValueNode &tuningData)
> > > >   *
> > > >   * \return The AWB result
> > > >   */
> > > > -AwbResult AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned int lux)
> > > > +AwbImplementation::Result
> > > > +AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned int lux,
> > > > +                     [[maybe_unused]] std::array<double, 2> range)
> > > >  {
> > > > -       AwbResult result;
> > > > +       AwbImplementation::Result result;
> > > >         auto means = stats.rgbMeans();
> > > > -       result.colourTemperature = estimateCCT(means);
> > > > +       result.temperatureK = estimateCCT(means);
> > > >
> > > >         /*
> > > > -        * Estimate the red and blue gains to apply in a grey world. The green
> > > > -        * gain is hardcoded to 1.0. Avoid divisions by zero by clamping the
> > > > -        * divisor to a minimum value of 1.0.
> > > > +        * Calculate the red and blue gains to apply in a grey world by simply
> > > > +        * inverting the red/green and blue/green ratios as reported in
> > > > +        * statistics. The green gain is hardcoded to 1.0.
> > > >          */
> > > >         result.gains.r() = means.g() / std::max(means.r(), 1.0);
> > > >         result.gains.g() = 1.0;
> > > >         result.gains.b() = means.g() / std::max(means.b(), 1.0);
> > > > +
> > > >         return result;
> > > >  }
> > > >
> > > > @@ -93,15 +97,16 @@ AwbResult AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned
> > > >   *
> > > >   * Compute the white balance gains from a \a colourTemperature. This function
> > > >   * does not take any statistics into account. It simply interpolates the colour
> > > > - * gains configured in the colour temperature curve.
> > > > + * gains curve (if provided in the tuning file) on a new colour temperature.
> > > >   *
> > > > - * \return The colour gains if a colour temperature curve is available,
> > > > - * [1, 1, 1] otherwise.
> > > > + * \return The RGB gain values adjusted to \a temperatureK or std::nullopt if
> > > > + * no gain curve is specified in the tuning data
> > > >   */
> > > >  std::optional<RGB<double>> AwbGrey::gainsFromColourTemperature(double colourTemperature)
> > > >  {
> > > >         if (!colourGainCurve_) {
> > > > -               LOG(Awb, Error) << "No gains defined";
> > > > +               LOG(Awb, Info) << "No gains curve defined, "
> > > > +                              << "unable to interpolate gains to colour temperature";
> > > >                 return std::nullopt;
> > > >         }
> > > >
> > > > diff --git a/src/ipa/libipa/awb_grey.h b/src/ipa/libipa/awb_grey.h
> > > > index 154a2af97f15..d1b0088f0470 100644
> > > > --- a/src/ipa/libipa/awb_grey.h
> > > > +++ b/src/ipa/libipa/awb_grey.h
> > > > @@ -18,13 +18,15 @@ namespace libcamera {
> > > >
> > > >  namespace ipa {
> > > >
> > > > -class AwbGrey : public AwbAlgorithm
> > > > +class AwbGrey : public AwbImplementation
> > > >  {
> > > >  public:
> > > >         AwbGrey() = default;
> > > >
> > > >         int init(const ValueNode &tuningData) override;
> > > > -       AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) override;
> > > > +       AwbImplementation::Result
> > > > +       calculateAwb(const AwbStats &stats, unsigned int lux,
> > > > +                    std::array<double, 2> range) override;
> > > >         std::optional<RGB<double>> gainsFromColourTemperature(double colourTemperature) override;
> > > >
> > > >  private:
> > > > diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp
> > > > index 0eb05c2b6e1a..ae9f6a0c243e 100644
> > > > --- a/src/ipa/rkisp1/algorithms/awb.cpp
> > > > +++ b/src/ipa/rkisp1/algorithms/awb.cpp
> > > > @@ -8,17 +8,12 @@
> > > >  #include "awb.h"
> > > >
> > > >  #include <algorithm>
> > > > -#include <ios>
> > > >
> > > >  #include <libcamera/base/log.h>
> > > >
> > > > -#include <libcamera/control_ids.h>
> > > > -
> > > >  #include <libcamera/ipa/core_ipa_interface.h>
> > > >
> > > > -#include "libipa/awb_bayes.h"
> > > > -#include "libipa/awb_grey.h"
> > > > -#include "libipa/colours.h"
> > > > +#include "libcamera/internal/vector.h"
> > > >
> > > >  /**
> > > >   * \file awb.h
> > > > @@ -28,26 +23,16 @@ namespace libcamera {
> > > >
> > > >  namespace ipa::rkisp1::algorithms {
> > > >
> > > > -/**
> > > > - * \class Awb
> > > > - * \brief Manage the white balance with automatic and manual controls
> > > > - */
> > > > -
> > > >  LOG_DEFINE_CATEGORY(RkISP1Awb)
> > > >
> > > > -constexpr int32_t kMinColourTemperature = 2500;
> > > > -constexpr int32_t kMaxColourTemperature = 10000;
> > > > -constexpr int32_t kDefaultColourTemperature = 5000;
> > > > -
> > > > -/* Minimum mean value below which AWB can't operate. */
> > > > -constexpr double kMeanMinThreshold = 2.0;
> > > > -
> > > >  class RkISP1AwbStats final : public AwbStats
> > > >  {
> > > >  public:
> > > > -       RkISP1AwbStats(const RGB<double> &rgbMeans)
> > > > -               : rgbMeans_(rgbMeans)
> > > > +       RkISP1AwbStats(const IPAFrameContext &frameContext,
> > > > +                      const rkisp1_stat_buffer *stats)
> > > >         {
> > > > +               calculateRgbMeans(frameContext, stats);
> > > > +
> > > >                 rg_ = rgbMeans_.r() / rgbMeans_.g();
> > > >                 bg_ = rgbMeans_.b() / rgbMeans_.g();
> > > >         }
> > > > @@ -65,17 +50,106 @@ public:
> > > >                 return delta2;
> > > >         }
> > > >
> > > > -       RGB<double> rgbMeans() const override
> > > > +       bool valid() const override
> > > >         {
> > > > -               return rgbMeans_;
> > > > +               /* Minimum mean value below which AWB can't operate. */
> > > > +               constexpr double minValue = 2.0;
> > >
> > > As rgbMeans shall now be in the [0, 1] range, this min value is not
> >
> > I might have missed why rgbMeans is now in [0, 1] range.
> > I mean, I haven't intentionally modified the algorithm logic, so if it
> > wasn't in that range before this patch, it shouldn't be after this
> > patch
>
> You added it after our last discussion to get rid of the magic 2.0 in
> the validity check. But the current code doesn't normalize rgbMeans.
>

Did I ?

> >
> > > reachable anymore. I seem to miss the normalization code. As the
> >
> > Where was it ?
>
> It was never there. It needs to be added.
>

Or I didn't ?

> >
> > > validity test is no longer platform dependent we might want to remove
> > > the valid() function and move the test into the core algorithm?
> > >
> > > > +
> > > > +               return rgbMeans_.r() > minValue || rgbMeans_.g() > minValue ||
> > > > +                      rgbMeans_.b() > minValue;
> > > >         }
> > > >
> > > >  private:
> > > > -       RGB<double> rgbMeans_;
> > > > -       double rg_;
> > > > -       double bg_;
> > > > +       void calculateRgbMeans(const IPAFrameContext &frameContext,
> > > > +                              const rkisp1_stat_buffer *stats);
> > > > +       bool rgbMode_ = false;
> > >
> > > rgbMode_ is now duplicated and lost its connection to Awb::rgbMode_. One
> > > solution might be to pass the measurement mode to the constructor?
> > >
> >
> > Ah yes, we now have two rgbMode_ which is not nice.
> >
> > To be honest, the whole RGB mode support is there mostly for
> > documentation purposes as rgbMode_ is hardcoded to false (in RPPX1 I
> > dropped the RGB part completely).can
>
> It still puzzles me, that we can't use it. So I'd like to keep the
> single switch to play around with both options. (This will become more
> interesting when we add Awb64 support)
>
> >
> > I can pass the RkISP1 AWB::rgbMode_ to RkISP1Stats() so at least the
> > two are connected.
>
> That would be great.
>

I'll do that

> >
> > > >  };
> > > >
> > > > +void RkISP1AwbStats::calculateRgbMeans(const IPAFrameContext &frameContext,
> > > > +                                      const rkisp1_stat_buffer *stats)
> > > > +{
> > > > +       if (!stats || !(stats->meas_type & RKISP1_CIF_ISP_STAT_AWB)) {
> > > > +               LOG(RkISP1Awb, Error) << "AWB data is missing in statistics";
> > > > +               return;
> > > > +       }
> > > > +
> > > > +       const rkisp1_cif_isp_awb_stat *awb = &stats->params.awb;
> > > > +
> > > > +       if (awb->awb_mean[0].cnt == 0) {
> > > > +               LOG(RkISP1Awb, Debug) << "AWB statistics are empty";
> > > > +               return;
> > >
> > > I think the RGB class doesn't zero initialize it's components. So this
> >
> > Should I rbgMeans_ = {} at the beginning of the function ?
>
> Yes, or in the constructor.
>
> >
> > > early return now leads to possibly valid stats with random data in them
> > > - or did I miss something?
> > >
> >
> > Possibily.
> >
> > To be honest I should have pushed back against the request of moving
> > calculateRgbMeans() inside RkISP1AwbStats as it's largely unrelated to
> > this series.
>
> Yes, we could still do that on top. But for centralizing the validity
> check, it would at least be nice to normalize rgbMeans.
>

I see.

To be honest I feel it's best to decide if we should
1) Normalize and implement valid() in the base class
2) Not normalize and defer valid() to defer valid() to the derived
  class

on top ?

> >
> > > > +       }
> > > > +
> > > > +       if (rgbMode_) {
> > > > +               rgbMeans_ = {{
> > > > +                       static_cast<double>(awb->awb_mean[0].mean_cr_or_r),
> > > > +                       static_cast<double>(awb->awb_mean[0].mean_y_or_g),
> > > > +                       static_cast<double>(awb->awb_mean[0].mean_cb_or_b)
> > > > +               }};
> > > > +       } else {
> > > > +               /* Get the YCbCr mean values */
> > > > +               Vector<double, 3> yuvMeans({
> > > > +                       static_cast<double>(awb->awb_mean[0].mean_y_or_g),
> > > > +                       static_cast<double>(awb->awb_mean[0].mean_cb_or_b),
> > > > +                       static_cast<double>(awb->awb_mean[0].mean_cr_or_r)
> > > > +               });
> > > > +
> > > > +               /*
> > > > +                * Convert from YCbCr to RGB. The hardware uses the following
> > > > +                * formulas:
> > > > +                *
> > > > +                * Y  =  16 + 0.2500 R + 0.5000 G + 0.1094 B
> > > > +                * Cb = 128 - 0.1406 R - 0.2969 G + 0.4375 B
> > > > +                * Cr = 128 + 0.4375 R - 0.3750 G - 0.0625 B
> > > > +                *
> > > > +                * This seems to be based on limited range BT.601 with Q1.6
> > > > +                * precision.
> > > > +                *
> > > > +                * The inverse matrix is:
> > > > +                *
> > > > +                * [[1,1636, -0,0623,  1,6008]
> > > > +                *  [1,1636, -0,4045, -0,7949]
> > > > +                *  [1,1636,  1,9912, -0,0250]]
> > > > +                */
> > > > +               static const Matrix<double, 3, 3> yuv2rgbMatrix({
> > > > +                       1.1636, -0.0623,  1.6008,
> > > > +                       1.1636, -0.4045, -0.7949,
> > > > +                       1.1636,  1.9912, -0.0250
> > > > +               });
> > > > +               static const Vector<double, 3> yuv2rgbOffset({
> > > > +                       16, 128, 128
> > > > +               });
> > > > +
> > > > +               rgbMeans_ = yuv2rgbMatrix * (yuvMeans - yuv2rgbOffset);
> > > > +
> > > > +               /*
> > > > +                * Due to hardware rounding errors in the YCbCr means, the
> > > > +                * calculated RGB means may be negative. This would lead to
> > > > +                * negative gains, messing up calculation. Prevent this by
> > > > +                * clamping the means to positive values.
> > > > +                */
> > > > +               rgbMeans_ = rgbMeans_.max(0.0);
> > > > +       }
> > > > +
> > > > +       /*
> > > > +        * The ISP computes the AWB means after applying the CCM. Apply the
> > > > +        * inverse as we want to get the raw means before the colour gains.
> > > > +        */
> > > > +       rgbMeans_ = frameContext.ccm.ccm.inverse() * rgbMeans_;
> > > > +
> > > > +       /*
> > > > +        * The ISP computes the AWB means after applying the colour gains,
> > > > +        * divide by the gains that were used to get the raw means from the
> > > > +        * sensor. Apply a minimum value to avoid divisions by near-zero.
> > > > +        */
> > > > +       rgbMeans_ /= frameContext.awb.gains.max(0.01);
> > >
> > > I think this misses the normalization with awb_mean[0].cnt.
> >
> > I think I'm missing where it was in the existing code, as this should
> > be a copy of the already existing Awb::calculateRgbMeans() function.
>
> It wasn't there.
>

So let's add it on top

> >
> > >
> > > > +}
> > > > +
> > > > +/**
> > > > + * \class Awb
> > > > + * \brief Manage the white balance with automatic and manual controls
> > > > + */
> > > > +
> > > >  Awb::Awb()
> > > >         : rgbMode_(false)
> > > >  {
> > > > @@ -86,40 +160,7 @@ Awb::Awb()
> > > >   */
> > > >  int Awb::init(IPAContext &context, const ValueNode &tuningData)
> > > >  {
> > > > -       auto &cmap = context.ctrlMap;
> > > > -       cmap[&controls::ColourTemperature] = ControlInfo(kMinColourTemperature,
> > > > -                                                        kMaxColourTemperature,
> > > > -                                                        kDefaultColourTemperature);
> > > > -       cmap[&controls::AwbEnable] = ControlInfo(false, true);
> > > > -
> > > > -       cmap[&controls::ColourGains] = ControlInfo(0.0f, 3.996f,
> > > > -                                                  Span<const float, 2>{ { 1.0f, 1.0f } });
> > > > -
> > > > -       if (!tuningData.contains("algorithm"))
> > > > -               LOG(RkISP1Awb, Info) << "No AWB algorithm specified."
> > > > -                                    << " Default to grey world";
> > > > -
> > > > -       auto mode = tuningData["algorithm"].get<std::string>("grey");
> > > > -       if (mode == "grey") {
> > > > -               awbAlgo_ = std::make_unique<AwbGrey>();
> > > > -       } else if (mode == "bayes") {
> > > > -               awbAlgo_ = std::make_unique<AwbBayes>();
> > > > -       } else {
> > > > -               LOG(RkISP1Awb, Error) << "Unknown AWB algorithm: " << mode;
> > > > -               return -EINVAL;
> > > > -       }
> > > > -       LOG(RkISP1Awb, Debug) << "Using AWB algorithm: " << mode;
> > > > -
> > > > -       int ret = awbAlgo_->init(tuningData);
> > > > -       if (ret) {
> > > > -               LOG(RkISP1Awb, Error) << "Failed to init AWB algorithm";
> > > > -               return ret;
> > > > -       }
> > > > -
> > > > -       const auto &src = awbAlgo_->controls();
> > > > -       cmap.insert(src.begin(), src.end());
> > > > -
> > > > -       return 0;
> > > > +       return awbAlgo_.init(tuningData, context.ctrlMap);
> > > >  }
> > > >
> > > >  /**
> > > > @@ -128,16 +169,7 @@ int Awb::init(IPAContext &context, const ValueNode &tuningData)
> > > >  int Awb::configure(IPAContext &context,
> > > >                    const IPACameraSensorInfo &configInfo)
> > > >  {
> > > > -       context.activeState.awb.manual.gains = RGB<double>{ 1.0 };
> > > > -       auto gains = awbAlgo_->gainsFromColourTemperature(kDefaultColourTemperature);
> > > > -       if (gains)
> > > > -               context.activeState.awb.automatic.gains = *gains;
> > > > -       else
> > > > -               context.activeState.awb.automatic.gains = RGB<double>{ 1.0 };
> > > > -
> > > > -       context.activeState.awb.autoEnabled = true;
> > > > -       context.activeState.awb.manual.temperatureK = kDefaultColourTemperature;
> > > > -       context.activeState.awb.automatic.temperatureK = kDefaultColourTemperature;
> > > > +       awbAlgo_.configure(context.activeState.awb);
> > > >
> > > >         /*
> > > >          * Define the measurement window for AWB as a centered rectangle
> > > > @@ -156,56 +188,12 @@ int Awb::configure(IPAContext &context,
> > > >  /**
> > > >   * \copydoc libcamera::ipa::Algorithm::queueRequest
> > > >   */
> > > > -void Awb::queueRequest(IPAContext &context,
> > > > -                      [[maybe_unused]] const uint32_t frame,
> > > > +void Awb::queueRequest(IPAContext &context, const uint32_t frame,
> > > >                        IPAFrameContext &frameContext,
> > > >                        const ControlList &controls)
> > > >  {
> > > > -       auto &awb = context.activeState.awb;
> > > > -
> > > > -       const auto &awbEnable = controls.get(controls::AwbEnable);
> > > > -       if (awbEnable && *awbEnable != awb.autoEnabled) {
> > > > -               awb.autoEnabled = *awbEnable;
> > > > -
> > > > -               LOG(RkISP1Awb, Debug)
> > > > -                       << (*awbEnable ? "Enabling" : "Disabling") << " AWB";
> > > > -       }
> > > > -
> > > > -       awbAlgo_->handleControls(controls);
> > > > -
> > > > -       frameContext.awb.autoEnabled = awb.autoEnabled;
> > > > -
> > > > -       if (awb.autoEnabled)
> > > > -               return;
> > > > -
> > > > -       const auto &colourGains = controls.get(controls::ColourGains);
> > > > -       const auto &colourTemperature = controls.get(controls::ColourTemperature);
> > > > -       bool update = false;
> > > > -       if (colourGains) {
> > > > -               awb.manual.gains.r() = (*colourGains)[0];
> > > > -               awb.manual.gains.b() = (*colourGains)[1];
> > > > -               /*
> > > > -                * \todo Colour temperature reported in metadata is now
> > > > -                * incorrect, as we can't deduce the temperature from the gains.
> > > > -                * This will be fixed with the bayes AWB algorithm.
> > > > -                */
> > > > -               update = true;
> > > > -       } else if (colourTemperature) {
> > > > -               awb.manual.temperatureK = *colourTemperature;
> > > > -               const auto &gains = awbAlgo_->gainsFromColourTemperature(*colourTemperature);
> > > > -               if (gains) {
> > > > -                       awb.manual.gains.r() = gains->r();
> > > > -                       awb.manual.gains.b() = gains->b();
> > > > -                       update = true;
> > > > -               }
> > > > -       }
> > > > -
> > > > -       if (update)
> > > > -               LOG(RkISP1Awb, Debug)
> > > > -                       << "Set colour gains to " << awb.manual.gains;
> > > > -
> > > > -       frameContext.awb.gains = awb.manual.gains;
> > > > -       frameContext.awb.temperatureK = awb.manual.temperatureK;
> > > > +       awbAlgo_.queueRequest(context.activeState.awb, frame, frameContext.awb,
> > > > +                             controls);
> > > >  }
> > > >
> > > >  /**
> > > > @@ -214,15 +202,7 @@ void Awb::queueRequest(IPAContext &context,
> > > >  void Awb::prepare(IPAContext &context, const uint32_t frame,
> > > >                   IPAFrameContext &frameContext, RkISP1Params *params)
> > > >  {
> > > > -       /*
> > > > -        * This is the latest time we can read the active state. This is the
> > > > -        * most up-to-date automatic values we can read.
> > > > -        */
> > > > -       if (frameContext.awb.autoEnabled) {
> > > > -               const auto &awb = context.activeState.awb;
> > > > -               frameContext.awb.gains = awb.automatic.gains;
> > > > -               frameContext.awb.temperatureK = awb.automatic.temperatureK;
> > > > -       }
> > > > +       awbAlgo_.prepare(context.activeState.awb, frameContext.awb);
> > > >
> > > >         auto gainConfig = params->block<BlockType::AwbGain>();
> > > >         gainConfig.setEnabled(true);
> > > > @@ -291,136 +271,12 @@ void Awb::process(IPAContext &context,
> > > >                   const rkisp1_stat_buffer *stats,
> > > >                   ControlList &metadata)
> > > >  {
> > > > -       IPAActiveState &activeState = context.activeState;
> > > > -
> > > > -       metadata.set(controls::AwbEnable, frameContext.awb.autoEnabled);
> > > > -       metadata.set(controls::ColourGains, {
> > > > -                       static_cast<float>(frameContext.awb.gains.r()),
> > > > -                       static_cast<float>(frameContext.awb.gains.b())
> > > > -               });
> > > > -       metadata.set(controls::ColourTemperature, frameContext.awb.temperatureK);
> > > > -
> > > > -       if (!stats || !(stats->meas_type & RKISP1_CIF_ISP_STAT_AWB)) {
> > > > -               LOG(RkISP1Awb, Error) << "AWB data is missing in statistics";
> > > > -               return;
> > > > -       }
> > > > -
> > > > -       const rkisp1_cif_isp_stat *params = &stats->params;
> > > > -       const rkisp1_cif_isp_awb_stat *awb = &params->awb;
> > > > +       RkISP1AwbStats awbStats(frameContext, stats);
> > > >
> > > > -       if (awb->awb_mean[0].cnt == 0) {
> > > > -               LOG(RkISP1Awb, Debug) << "AWB statistics are empty";
> > > > -               return;
> > > > -       }
> > > > -
> > > > -       RGB<double> rgbMeans = calculateRgbMeans(frameContext, awb);
> > > > -
> > > > -       /*
> > > > -        * If the means are too small we don't have enough information to
> > > > -        * meaningfully calculate gains. Freeze the algorithm in that case.
> > > > -        */
> > > > -       if (rgbMeans.r() < kMeanMinThreshold && rgbMeans.g() < kMeanMinThreshold &&
> > > > -           rgbMeans.b() < kMeanMinThreshold)
> > > > -               return;
> > > > -
> > > > -       RkISP1AwbStats awbStats{ rgbMeans };
> > > > -       AwbResult awbResult = awbAlgo_->calculateAwb(awbStats, frameContext.lux.lux);
> > > > -
> > > > -       /*
> > > > -        * Clamp the gain values to the hardware, which expresses gains as Q2.8
> > > > -        * unsigned integer values. Set the minimum just above zero to avoid
> > > > -        * divisions by zero when computing the raw means in subsequent
> > > > -        * iterations.
> > > > -        */
> > > > -       awbResult.gains = awbResult.gains.clamp(1.0 / 256, 1023.0 / 256);
> > > > -
> > > > -       /* Filter the values to avoid oscillations. */
> > > > -       double speed = 0.2;
> > > > -       double ct = awbResult.colourTemperature;
> > > > -       ct = ct * speed + activeState.awb.automatic.temperatureK * (1 - speed);
> > > > -       awbResult.gains = awbResult.gains * speed +
> > > > -                         activeState.awb.automatic.gains * (1 - speed);
> > > > -
> > > > -       activeState.awb.automatic.temperatureK = static_cast<unsigned int>(ct);
> > > > -       activeState.awb.automatic.gains = awbResult.gains;
> > > > -
> > > > -       LOG(RkISP1Awb, Debug)
> > > > -               << std::showpoint
> > > > -               << "Means " << rgbMeans << ", gains "
> > > > -               << activeState.awb.automatic.gains << ", temp "
> > > > -               << activeState.awb.automatic.temperatureK << "K";
> > > > +       awbAlgo_.process(context.activeState.awb, frameContext.awb, awbStats,
> > > > +                        frameContext.lux.lux, metadata);
> > > >  }
> > > >
> > > > -RGB<double> Awb::calculateRgbMeans(const IPAFrameContext &frameContext, const rkisp1_cif_isp_awb_stat *awb) const
> > > > -{
> > > > -       Vector<double, 3> rgbMeans;
> > > > -
> > > > -       if (rgbMode_) {
> > > > -               rgbMeans = {{
> > > > -                       static_cast<double>(awb->awb_mean[0].mean_cr_or_r),
> > > > -                       static_cast<double>(awb->awb_mean[0].mean_y_or_g),
> > > > -                       static_cast<double>(awb->awb_mean[0].mean_cb_or_b)
> > > > -               }};
> > > > -       } else {
> > > > -               /* Get the YCbCr mean values */
> > > > -               Vector<double, 3> yuvMeans({
> > > > -                       static_cast<double>(awb->awb_mean[0].mean_y_or_g),
> > > > -                       static_cast<double>(awb->awb_mean[0].mean_cb_or_b),
> > > > -                       static_cast<double>(awb->awb_mean[0].mean_cr_or_r)
> > > > -               });
> > > > -
> > > > -               /*
> > > > -                * Convert from YCbCr to RGB. The hardware uses the following
> > > > -                * formulas:
> > > > -                *
> > > > -                * Y  =  16 + 0.2500 R + 0.5000 G + 0.1094 B
> > > > -                * Cb = 128 - 0.1406 R - 0.2969 G + 0.4375 B
> > > > -                * Cr = 128 + 0.4375 R - 0.3750 G - 0.0625 B
> > > > -                *
> > > > -                * This seems to be based on limited range BT.601 with Q1.6
> > > > -                * precision.
> > > > -                *
> > > > -                * The inverse matrix is:
> > > > -                *
> > > > -                * [[1,1636, -0,0623,  1,6008]
> > > > -                *  [1,1636, -0,4045, -0,7949]
> > > > -                *  [1,1636,  1,9912, -0,0250]]
> > > > -                */
> > > > -               static const Matrix<double, 3, 3> yuv2rgbMatrix({
> > > > -                       1.1636, -0.0623,  1.6008,
> > > > -                       1.1636, -0.4045, -0.7949,
> > > > -                       1.1636,  1.9912, -0.0250
> > > > -               });
> > > > -               static const Vector<double, 3> yuv2rgbOffset({
> > > > -                       16, 128, 128
> > > > -               });
> > > > -
> > > > -               rgbMeans = yuv2rgbMatrix * (yuvMeans - yuv2rgbOffset);
> > > > -
> > > > -               /*
> > > > -                * Due to hardware rounding errors in the YCbCr means, the
> > > > -                * calculated RGB means may be negative. This would lead to
> > > > -                * negative gains, messing up calculation. Prevent this by
> > > > -                * clamping the means to positive values.
> > > > -                */
> > > > -               rgbMeans = rgbMeans.max(0.0);
> > > > -       }
> > > > -
> > > > -       /*
> > > > -        * The ISP computes the AWB means after applying the CCM. Apply the
> > > > -        * inverse as we want to get the raw means before the colour gains.
> > > > -        */
> > > > -       rgbMeans = frameContext.ccm.ccm.inverse() * rgbMeans;
> > > > -
> > > > -       /*
> > > > -        * The ISP computes the AWB means after applying the colour gains,
> > > > -        * divide by the gains that were used to get the raw means from the
> > > > -        * sensor. Apply a minimum value to avoid divisions by near-zero.
> > > > -        */
> > > > -       rgbMeans /= frameContext.awb.gains.max(0.01);
> > > > -
> > > > -       return rgbMeans;
> > > > -}
> > >
> > > Moving this big function would have been an easy cut out into its own
> > > patch?
> >
> > You're right. I shouldn't be doing this as part of this series.
> > I'll move calculateRgbMeans() back to the Awb class.
>
> Fine with me. The reason I suggested this was that it lead to a bad
> pattern in the mali implementation.
>

I'm still trying to fully understand why..

> Best regards,
> Stefan
>
> >
> > Thanks
> >   j
> >
> > >
> > > Best regards,
> > > Stefan
> > >
> > > >
> > > >  REGISTER_IPA_ALGORITHM(Awb, "Awb")
> > > >
> > > > diff --git a/src/ipa/rkisp1/algorithms/awb.h b/src/ipa/rkisp1/algorithms/awb.h
> > > > index 60d9ef111495..c7cb59a75bde 100644
> > > > --- a/src/ipa/rkisp1/algorithms/awb.h
> > > > +++ b/src/ipa/rkisp1/algorithms/awb.h
> > > > @@ -7,17 +7,25 @@
> > > >
> > > >  #pragma once
> > > >
> > > > -#include "libcamera/internal/vector.h"
> > > > +#include <linux/rkisp1-config.h>
> > > > +
> > > > +#include <libcamera/controls.h>
> > > > +
> > > > +#include "libcamera/internal/value_node.h"
> > > >
> > > >  #include "libipa/awb.h"
> > > > -#include "libipa/interpolator.h"
> > > > +#include "libipa/fixedpoint.h"
> > > >
> > > >  #include "algorithm.h"
> > > > +#include "ipa_context.h"
> > > > +#include "params.h"
> > > >
> > > >  namespace libcamera {
> > > >
> > > >  namespace ipa::rkisp1::algorithms {
> > > >
> > > > +class RkISP1AwbStats;
> > > > +
> > > >  class Awb : public Algorithm
> > > >  {
> > > >  public:
> > > > @@ -38,10 +46,10 @@ public:
> > > >                      ControlList &metadata) override;
> > > >
> > > >  private:
> > > > -       RGB<double> calculateRgbMeans(const IPAFrameContext &frameContext,
> > > > -                                     const rkisp1_cif_isp_awb_stat *awb) const;
> > > > +       RkISP1AwbStats calculateRgbMeans(const IPAFrameContext &frameContext,
> > > > +                                        const rkisp1_stat_buffer *stats) const;
> > > >
> > > > -       std::unique_ptr<AwbAlgorithm> awbAlgo_;
> > > > +       AwbAlgorithm<UQ<2, 8>> awbAlgo_;
> > > >
> > > >         bool rgbMode_;
> > > >  };
> > > > diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h
> > > > index e5f70d934d67..c36c1f7e0084 100644
> > > > --- a/src/ipa/rkisp1/ipa_context.h
> > > > +++ b/src/ipa/rkisp1/ipa_context.h
> > > > @@ -25,6 +25,7 @@
> > > >  #include "libcamera/internal/vector.h"
> > > >
> > > >  #include "libipa/agc_mean_luminance.h"
> > > > +#include "libipa/awb.h"
> > > >  #include "libipa/camera_sensor_helper.h"
> > > >  #include "libipa/fc_queue.h"
> > > >  #include "libipa/fixedpoint.h"
> > > > @@ -48,15 +49,17 @@ struct IPAHwSettings {
> > > >         bool compand;
> > > >  };
> > > >
> > > > +struct RKISP1AwbSession {
> > > > +       struct rkisp1_cif_isp_window measureWindow;
> > > > +       bool enabled;
> > > > +};
> > > > +
> > > >  struct IPASessionConfiguration {
> > > >         struct {
> > > >                 struct rkisp1_cif_isp_window measureWindow;
> > > >         } agc;
> > > >
> > > > -       struct {
> > > > -               struct rkisp1_cif_isp_window measureWindow;
> > > > -               bool enabled;
> > > > -       } awb;
> > > > +       struct RKISP1AwbSession awb;
> > > >
> > > >         struct {
> > > >                 bool supported;
> > > > @@ -100,17 +103,7 @@ struct IPAActiveState {
> > > >                 utils::Duration maxFrameDuration;
> > > >         } agc;
> > > >
> > > > -       struct {
> > > > -               struct AwbState {
> > > > -                       RGB<double> gains;
> > > > -                       unsigned int temperatureK;
> > > > -               };
> > > > -
> > > > -               AwbState manual;
> > > > -               AwbState automatic;
> > > > -
> > > > -               bool autoEnabled;
> > > > -       } awb;
> > > > +       ipa::awb::ActiveState awb;
> > > >
> > > >         struct {
> > > >                 Matrix<float, 3, 3> manual;
> > > > @@ -176,11 +169,7 @@ struct IPAFrameContext : public FrameContext {
> > > >                 bool autoGainModeChange;
> > > >         } agc;
> > > >
> > > > -       struct {
> > > > -               RGB<double> gains;
> > > > -               bool autoEnabled;
> > > > -               unsigned int temperatureK;
> > > > -       } awb;
> > > > +       ipa::awb::FrameContext awb;
> > > >
> > > >         struct {
> > > >                 float actualBrightness;
> > > >
> > > > --
> > > > 2.54.0
> > > >

Patch
diff mbox series

diff --git a/src/ipa/libipa/awb.cpp b/src/ipa/libipa/awb.cpp
index 29a78069c52c..95291840d8fb 100644
--- a/src/ipa/libipa/awb.cpp
+++ b/src/ipa/libipa/awb.cpp
@@ -2,7 +2,7 @@ 
 /*
  * Copyright (C) 2024 Ideas on Board Oy
  *
- * Generic AWB algorithms
+ * libIPA Awb algorithms
  */
 
 #include "awb.h"
@@ -11,9 +11,16 @@ 
 
 #include <libcamera/control_ids.h>
 
+#include "awb_bayes.h"
+#include "awb_grey.h"
+
+constexpr int32_t kMinColourTemperature = 2500;
+constexpr int32_t kMaxColourTemperature = 10000;
+constexpr int32_t kDefaultColourTemperature = 5000;
+
 /**
  * \file awb.h
- * \brief Base classes for AWB algorithms
+ * \brief libipa awb implementation
  */
 
 namespace libcamera {
@@ -22,30 +29,60 @@  LOG_DEFINE_CATEGORY(Awb)
 
 namespace ipa {
 
+namespace awb {
+
 /**
- * \class AwbResult
- * \brief The result of an AWB calculation
+ * \struct Context
+ * \brief Awb gains and colour temperature
+ *
+ * \var Context::gains
+ * \brief The white balance gains
  *
- * This class holds the result of an auto white balance calculation.
+ * \var Context::temperatureK
+ * \brief The colour temperature, in Kelvin
  */
 
 /**
- * \var AwbResult::gains
- * \brief The calculated white balance gains
+ * \struct ActiveState
+ * \brief Active awb state shared across frames
+ *
+ * \var ActiveState::manual
+ * \brief The most recent manually requested awb state
+ *
+ * \var ActiveState::automatic
+ * \brief The most recent automatically calculated awb state
+ *
+ * \var ActiveState::autoEnabled
+ * \brief True when automatic awb is selected
  */
 
 /**
- * \var AwbResult::colourTemperature
- * \brief The calculated colour temperature in Kelvin
+ * \struct FrameContext
+ * \brief Per-frame awb state
+ *
+ * \var FrameContext::autoEnabled
+ * \brief True when automatic awb is in use
  */
 
+} /* namespace awb */
+
 /**
  * \class AwbStats
- * \brief An abstraction class wrapping hardware-specific AWB statistics
+ * \brief An abstraction class wrapping hardware-specific awb statistics
  *
- * IPA modules using an AWB algorithm based on the AwbAlgorithm class need to
- * implement this class to give the algorithm access to the hardware-specific
- * statistics data.
+ * IPA modules shall provide a derived implementation of this class to give the
+ * awb algorithm access to the hardware-specific statistics data in a common
+ * format.
+ */
+
+/**
+ * \fn AwbStats::AwbStats()
+ * \brief Construct an empty AwbStat
+ */
+
+/**
+ * AwbStat::~AwbStat
+ * \brief Virtual class destructor
  */
 
 /**
@@ -57,7 +94,7 @@  namespace ipa {
  * applied. To keep the actual implementations computationally inexpensive,
  * the squared colour error shall be returned.
  *
- * If the AWB statistics provide multiple zones, the average of the individual
+ * If the awb statistics provide multiple zones, the average of the individual
  * squared errors shall be returned. Averaging/normalizing is necessary so that
  * the numeric dimensions are the same on all hardware platforms.
  *
@@ -65,79 +102,328 @@  namespace ipa {
  */
 
 /**
- * \fn AwbStats::rgbMeans()
- * \brief Get RGB means of the statistics
+ * \fn AwbStats::valid()
+ * \brief Retrieve if the awb statistics are valid
  *
- * Fetch the RGB means from the statistics. The values of each channel are
- * dimensionless and only the ratios are used for further calculations. This is
- * used by the simple grey world model to calculate the gains to apply.
+ * If the colour mean values are too small, the AwbAlgorithm class doesn't have
+ * enough information to meaningfully calculate white-balance gains. Freeze the
+ * algorithm in that case.
+ *
+ * \return True if the awb statistics are valid, false otherwise
+ */
+
+/**
+ * \fn AwbStats::rgbMeans
+ * \brief Retrieve the RGB means values
+ *
+ * Fetch the RGB means from the statistics in the [0, 1] range.
  *
  * \return The RGB means
  */
 
 /**
- * \class AwbAlgorithm
- * \brief A base class for auto white balance algorithms
+ * \var AwbStats::rgbMeans_
+ * \brief Mean values of colour channels
+ */
+
+/**
+ * \var AwbStats::rg_
+ * \brief Red/Green ratio
+ */
+
+/**
+ * \var AwbStats::bg_
+ * \brief Blue/Green ratio
+ */
+
+/**
+ * \class AwbImplementation
+ * \brief Pure virtual base class for awb algorithms implementations
+ *
+ * The AwbImplementation class defines the interface for the awb algorithm
+ * implementations.
  *
- * This class is a base class for auto white balance algorithms. It defines an
- * interface for the algorithms to implement, and is used by the IPAs to
- * interact with the concrete implementation.
+ * It is currently implemented by the AwbGrey and AwbBayes classes.
+ *
+ * The interface defines an init() function to initialize the algorithm with the
+ * content of the tuning file and two functions to compute colour gains
+ * according to the algorithm operating mode (auto or manual) in use.
+ *
+ * The calculateAwb() function calculates colour gains given a set of statistics
+ * provided by the IPA module. It is used when the algorithm operates in auto
+ * mode and gains are dynamically computed given a new set of statistics from
+ * the awb engine.
+ *
+ * The gainsFromColourTemperature() function instead computes the white balance
+ * gains from a colour temperature. This function does not take any statistics
+ * into account. It is used to compute the colour gains when the user manually
+ * specifies a colour temperature.
  */
 
 /**
- * \fn AwbAlgorithm::init()
- * \brief Initialize the algorithm with the given tuning data
- * \param[in] tuningData The tuning data to use for the algorithm
+ * \struct AwbImplementation::Result
+ * \brief The result of an awb computation
  *
- * \return 0 on success, a negative error code otherwise
+ * \var AwbImplementation::Result::gains
+ * \brief The computed white balance gains
+ *
+ * \var AwbImplementation::Result::temperatureK
+ * \brief The computed colour temperature, in Kelvin
  */
 
 /**
- * \fn AwbAlgorithm::calculateAwb()
- * \brief Calculate AWB data from the given statistics
- * \param[in] stats The statistics to use for the calculation
- * \param[in] lux The lux value of the scene
+ * \fn AwbImplementation::~AwbImplementation
+ * \brief Virtual class destructor
+ */
+
+/**
+ * \fn AwbImplementation::init()
+ * \param[in] tuningData
+ * \brief Initialize the algorithm by parsing \a tuningData
+ */
+
+/**
+ * \fn AwbImplementation::calculateAwb()
+ * \param[in] stats The awb statistics
+ * \param[in] lux The estimated lux level
+ * \param[in] range The colour temperature search limits
  *
- * Calculate an AwbResult object from the given statistics and lux value. A \a
- * lux value of 0 means it is unknown or invalid and the algorithm shall ignore
- * it.
+ * Calculate a new set of colour gains and a colour temperature given a new
+ * set of statistics \a stats, an estimated luminance \a lux and a range of
+ * colour temperature to limit the search.
  *
- * \return The AWB result
+ * \return A Result computed using the new statistics
+ */
+/**
+ * \fn AwbImplementation::gainsFromColourTemperature()
+ * \param[in] temperatureK Colour temperature in Kelvin
+ *
+ * Calculate a new set of colour gains for a new manually specified colour
+ * temperature \a temperatureK.
+ *
+ * \return The RGB gain values adjusted to \a temperatureK or std::nullopt
+ */
+
+/**
+ * \class AwbAlgorithmBase
+ * \brief Base class for AwbAlgorithm for non-templated functions implementation
+ *
+ * Base class for AwbAlgorithm where non-templated functions are implemented.
+ * IPA implementations shall use AwbAlgorithm and not this class.
  */
 
 /**
- * \fn AwbAlgorithm::gainsFromColourTemperature()
- * \brief Compute white balance gains from a colour temperature
- * \param[in] colourTemperature The colour temperature in Kelvin
+ * \brief Initialize the algorithm with the given tuning data
+ * \param[in] tuningData The tuning data to use for the algorithm
  *
- * Compute the white balance gains from a \a colourTemperature. This function
- * does not take any statistics into account. It is used to compute the colour
- * gains when the user manually specifies a colour temperature.
+ * Parse \a tuningData to initialize the awb algorithm and register controls.
+ * IPA modules are expected to call this function as part of their
+ * implementation of Algorithm::init().
  *
- * \return The colour gains or std::nullopt if the conversion is not possible
+ * \return 0 on success, a negative error code otherwise
+ */
+int AwbAlgorithmBase::init(const ValueNode &tuningData)
+{
+	bayes_ = false;
+
+	if (!tuningData.contains("algorithm"))
+		LOG(Awb, Info) << "No Awb algorithm specified, using grey world";
+
+	auto mode = tuningData["algorithm"].get<std::string>("grey");
+	if (mode == "grey") {
+		impl_ = std::make_unique<AwbGrey>();
+	} else if (mode == "bayes") {
+		impl_ = std::make_unique<AwbBayes>();
+		bayes_ = true;
+	} else {
+		LOG(Awb, Error) << "Unknown Awb algorithm: " << mode;
+		return -EINVAL;
+	}
+
+	LOG(Awb, Debug) << "Using Awb algorithm: " << mode;
+
+	int ret = impl_->init(tuningData);
+	if (ret)
+		return ret;
+
+	controls_[&controls::ColourTemperature] =
+		ControlInfo(kMinColourTemperature, kMaxColourTemperature,
+			    kDefaultColourTemperature);
+	controls_[&controls::AwbEnable] = ControlInfo(false, true);
+
+	return parseModeConfigs(tuningData, controls::AwbAuto);
+}
+
+/**
+ * \brief Configure the awb algorithm
+ * \param[in] state The awb active state
+ * \return 0 if successful, an error code otherwise
  */
+int AwbAlgorithmBase::configure(awb::ActiveState &state)
+{
+	state.manual.gains = RGB<double>{ 1.0 };
+	auto gains = impl_->gainsFromColourTemperature(kDefaultColourTemperature);
+	if (gains)
+		state.automatic.gains = *gains;
+	else
+		state.automatic.gains = RGB<double>{ 1.0 };
+
+	state.autoEnabled = true;
+	state.manual.temperatureK = kDefaultColourTemperature;
+	state.automatic.temperatureK = kDefaultColourTemperature;
+
+	return 0;
+}
 
 /**
- * \fn AwbAlgorithm::controls()
- * \brief Get the controls info map for this algorithm
+ * \brief Queue a Request to the awb algorithm
+ * \param[in] state The awb active state
+ * \param[in] frame The frame number
+ * \param[in] frameContext The awb frame context
+ * \param[in] controls The list of controls part of the Request
  *
- * \return The controls info map
+ * Queue a new Request to the awb algorithm and modify its behaviour according
+ * to the provided controls.
+ *
+ * The currently handled controls are:
+ * - controls::AwbEnable
+ * - controls::AwbMode
+ * - controls::ColourGains
+ * - controls::ColourTemperature
  */
+void AwbAlgorithmBase::queueRequest(awb::ActiveState &state,
+				    [[maybe_unused]] const uint32_t frame,
+				    awb::FrameContext &frameContext,
+				    const ControlList &controls)
+{
+	const auto &awbEnable = controls.get(controls::AwbEnable);
+	if (awbEnable && *awbEnable != state.autoEnabled) {
+		state.autoEnabled = *awbEnable;
+
+		LOG(Awb, Debug)
+			<< (*awbEnable ? "Enabling" : "Disabling") << " Awb";
+	}
+
+	auto mode = controls.get(controls::AwbMode);
+	if (mode) {
+		auto it = modes_.find(static_cast<controls::AwbModeEnum>(*mode));
+		if (it == modes_.end()) {
+			LOG(Awb, Error) << "Unsupported Awb mode " << *mode;
+			return;
+		}
+
+		currentMode_ = &it->second;
+	}
+
+	frameContext.autoEnabled = state.autoEnabled;
+
+	if (frameContext.autoEnabled)
+		return;
+
+	const auto &colourGains = controls.get(controls::ColourGains);
+	const auto &colourTemperature = controls.get(controls::ColourTemperature);
+	bool update = false;
+	if (colourGains) {
+		state.manual.gains.r() = (*colourGains)[0];
+		state.manual.gains.b() = (*colourGains)[1];
+		/*
+		 * \todo Colour temperature reported in metadata is now
+		 * incorrect, as we can't deduce the temperature from the gains.
+		 * This will be fixed with the bayes AWB algorithm.
+		 */
+		update = true;
+	} else if (colourTemperature) {
+		state.manual.temperatureK = *colourTemperature;
+		const auto &gains = impl_->gainsFromColourTemperature(*colourTemperature);
+		if (gains) {
+			state.manual.gains.r() = gains->r();
+			state.manual.gains.b() = gains->b();
+			update = true;
+		}
+	}
+
+	if (update)
+		LOG(Awb, Debug)
+			<< "Set colour gains to " << state.manual.gains;
+
+	frameContext.gains = state.manual.gains;
+	frameContext.temperatureK = state.manual.temperatureK;
+}
 
 /**
- * \fn AwbAlgorithm::handleControls()
- * \param[in] controls The controls to handle
- * \brief Handle the controls supplied in a request
+ * \brief Set the gains and colour temperature values in \a frameContext
+ * \param[in] state The awb active state
+ * \param[in] frameContext The awb frame context
+ *
+ * If auto mode is enabled, take the most recently computed gains and use them
+ * for the current frame. Otherwise, if in manual mode, gains and colour
+ * temperature for a frame are set at queueRequest() time.
  */
+void AwbAlgorithmBase::prepare(awb::ActiveState &state,
+			       awb::FrameContext &frameContext)
+{
+	if (frameContext.autoEnabled) {
+		frameContext.gains = state.automatic.gains;
+		frameContext.temperatureK = state.automatic.temperatureK;
+	}
+}
 
 /**
+ * \brief Process awb statistics to calculate gains and populate metadata
+ * \param[in] state The awb active state
+ * \param[in] frameContext The awb frame context
+ * \param[in] stats The awb statistics
+ * \param[in] lux The lux value as estimated by the IPA module
+ * \param[out] metadata The metadata list
+ *
+ * Process \a stats to calculate new gains and colour temperature and populate
+ * \a metadata with the results.
+ */
+void AwbAlgorithmBase::process(awb::ActiveState &state,
+			       awb::FrameContext &frameContext,
+			       const AwbStats &stats, unsigned int lux,
+			       ControlList &metadata)
+{
+	if (!stats.valid())
+		return;
+
+	auto awbResult = impl_->calculateAwb(stats, lux, { currentMode_->ctLo,
+							   currentMode_->ctHi });
+
+	/*
+	 * Clamp the gain values to the hardware, according to the gainMin_
+	 * and gainMax_ values.
+	 */
+	awbResult.gains = awbResult.gains.clamp(gainMin_, gainMax_);
+
+	/* Smooth color gains adjustments. */
+	double speed = 0.2;
+	double ct = awbResult.temperatureK;
+	ct = ct * speed + state.automatic.temperatureK * (1 - speed);
+
+	state.automatic.temperatureK = awbResult.temperatureK;
+	state.automatic.gains = awbResult.gains * speed +
+				state.automatic.gains * (1 - speed);
+
+	/* Populate metadata. */
+	metadata.set(controls::AwbEnable, frameContext.autoEnabled);
+	metadata.set(controls::ColourGains, { static_cast<float>(frameContext.gains.r()),
+					      static_cast<float>(frameContext.gains.b()) });
+	metadata.set(controls::ColourTemperature, frameContext.temperatureK);
+
+	LOG(Awb, Debug) << std::showpoint << "Means " << stats.rgbMeans()
+			<< ", gains " << state.automatic.gains
+			<< ", temp " << state.automatic.temperatureK << "K";
+}
+
+/*
  * \brief Parse the mode configurations from the tuning data
  * \param[in] tuningData the ValueNode representing the tuning data
  * \param[in] def The default value for the AwbMode control
  *
  * Utility function to parse the tuning data for an AwbMode entry and read all
- * provided modes. It adds controls::AwbMode to AwbAlgorithm::controls_ and
- * populates AwbAlgorithm::modes_. For a list of possible modes see \ref
+ * provided modes. It adds controls::AwbMode to AwbAlgorithmBase::controls_ and
+ * populates AwbAlgorithmBase::modes_. For a list of possible modes see \ref
  * controls::AwbModeEnum.
  *
  * Each mode entry must contain a "lo" and "hi" key to specify the lower and
@@ -156,15 +442,23 @@  namespace ipa {
  *       ...
  * \endcode
  *
- * If \a def is supplied but not contained in the the \a tuningData, -EINVAL is
+ * If \a def is supplied but not contained in the \a tuningData, -EINVAL is
  * returned.
  *
+ * AwbModes are only used by the AwbBayes implementation.
+ *
  * \sa controls::AwbModeEnum
  * \return Zero on success, negative error code otherwise
  */
-int AwbAlgorithm::parseModeConfigs(const ValueNode &tuningData,
-				   const ControlValue &def)
+int AwbAlgorithmBase::parseModeConfigs(const ValueNode &tuningData,
+				       const ControlValue &def)
 {
+	if (!bayes_) {
+		/* AwbGrey does not support and does not use modes. */
+		currentMode_ = &AwbGreyMode;
+		return 0;
+	}
+
 	std::vector<ControlValue> availableModes;
 
 	const ValueNode &modes = tuningData[controls::AwbMode.name()];
@@ -227,37 +521,83 @@  int AwbAlgorithm::parseModeConfigs(const ValueNode &tuningData,
 	}
 
 	controls_[&controls::AwbMode] = ControlInfo(availableModes, def);
+	currentMode_ = &modes_[controls::AwbAuto];
 
 	return 0;
 }
 
 /**
- * \class AwbAlgorithm::ModeConfig
- * \brief Holds the configuration of a single AWB mode
+ * \var AwbAlgorithmBase::controls_
+ * \brief Controls info map for the controls registered by the awb algorithm
+ */
+
+/**
+ * \var AwbAlgorithmBase::gainMin_
+ * \brief The minimum supported gain value
+ *
+ * Minimum gain value used to clamp the awb algorithm calculation results in the
+ * range supported by the platform awb engine.
  *
- * AWB modes limit the regulation of the AWB algorithm to a specific range of
- * colour temperatures.
+ * The min and max gain values are initialized by AwbAlgorithm::init().
  */
 
 /**
- * \var AwbAlgorithm::ModeConfig::ctLo
+ * \var AwbAlgorithmBase::gainMax_
+ * \brief The maximum supported gain value
+ *
+ * Maximum gain value used to clamp the awb algorithm calculation results in the
+ * range supported by the platform awb engine.
+ *
+ * The min and max gain values are initialized by AwbAlgorithm::init().
+ */
+
+/*
+ * \class AwbAlgorithmBase::ModeConfig
+ * \brief Holds the configuration of a single awb mode
+ *
+ * AWB modes limit the regulation of the awb algorithm to a specific range of
+ * colour temperatures. Use by AwbBayes only.
+ */
+
+/*
+ * \var AwbAlgorithmBase::ModeConfig::ctLo
  * \brief The lowest valid colour temperature of that mode
  */
 
-/**
- * \var AwbAlgorithm::ModeConfig::ctHi
+/*
+ * \var AwbAlgorithmBase::ModeConfig::ctHi
  * \brief The highest valid colour temperature of that mode
  */
 
+/*
+ * \var AwbAlgorithmBase::modes_
+ * \brief Map of all configured modes
+ * \sa AwbAlgorithmBase::parseModeConfigs
+ */
+
 /**
- * \var AwbAlgorithm::controls_
- * \brief Controls info map for the controls provided by the algorithm
+ * \class AwbAlgorithm
+ * \brief The libipa awb algorithm
+ * \tparam Q The fixedpoint register representation of gain values
+ *
+ * Implement the awb algorithm for libipa.
+ *
+ * The AwbAlgorithm class implements an interface similar in spirit to the one
+ * of the Algorithm class. IPA modules are expected to store an instance of
+ * AwbAlgorithm as class member, template it with the awb engine gain register
+ * representation and call its function in their implementations of the
+ * Algorithm interface.
+ *
+ * The AwbAlgorithm instantiates an AwbImplementation implementation (AwbGrey or
+ * AwbBayes) at init() time by parsing the tuning data and uses it to compute
+ * the RGB gains and estimate a colour temperature given a set of statistics
+ * in the form of a AwbStats derived implementation.
  */
 
 /**
- * \var AwbAlgorithm::modes_
- * \brief Map of all configured modes
- * \sa AwbAlgorithm::parseModeConfigs
+ * \fn AwbAlgorithm::init()
+ * \param[in] controls The info map of the IPA controls
+ * \copydoc AwbAlgorithmBase::init()
  */
 
 } /* namespace ipa */
diff --git a/src/ipa/libipa/awb.h b/src/ipa/libipa/awb.h
index 09c00e47d604..72f46367a136 100644
--- a/src/ipa/libipa/awb.h
+++ b/src/ipa/libipa/awb.h
@@ -2,11 +2,12 @@ 
 /*
  * Copyright (C) 2024 Ideas on Board Oy
  *
- * Generic AWB algorithms
+ * libIPA AWB algorithms
  */
 
 #pragma once
 
+#include <array>
 #include <map>
 #include <optional>
 
@@ -20,46 +21,118 @@  namespace libcamera {
 
 namespace ipa {
 
-struct AwbResult {
+namespace awb {
+
+struct Context {
 	RGB<double> gains;
-	double colourTemperature;
+	unsigned int temperatureK;
 };
 
+struct ActiveState {
+	Context manual;
+	Context automatic;
+
+	bool autoEnabled;
+};
+
+struct FrameContext : public Context {
+	bool autoEnabled;
+};
+
+} /* namespace awb */
+
 struct AwbStats {
+	AwbStats() = default;
+	virtual ~AwbStats() = default;
+
 	virtual double computeColourError(const RGB<double> &gains) const = 0;
-	virtual RGB<double> rgbMeans() const = 0;
+	virtual bool valid() const = 0;
+	RGB<double> rgbMeans() const { return rgbMeans_; }
 
 protected:
-	~AwbStats() = default;
+	RGB<double> rgbMeans_;
+	double rg_;
+	double bg_;
 };
 
-class AwbAlgorithm
+class AwbImplementation
 {
 public:
-	virtual ~AwbAlgorithm() = default;
+	struct Result {
+		RGB<double> gains;
+		unsigned int temperatureK;
+	};
 
+	virtual ~AwbImplementation() = default;
 	virtual int init(const ValueNode &tuningData) = 0;
-	virtual AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) = 0;
-	virtual std::optional<RGB<double>> gainsFromColourTemperature(double colourTemperature) = 0;
+	virtual Result calculateAwb(const AwbStats &stats, unsigned int lux,
+				    std::array<double, 2> range) = 0;
+	virtual std::optional<RGB<double>>
+	gainsFromColourTemperature(double temperatureK) = 0;
+};
 
-	const ControlInfoMap::Map &controls() const
-	{
-		return controls_;
-	}
+class AwbAlgorithmBase
+{
+public:
+	int init(const ValueNode &tuningData);
 
-	virtual void handleControls([[maybe_unused]] const ControlList &controls) {}
+	int configure(awb::ActiveState &state);
+
+	void queueRequest(awb::ActiveState &state,
+			  const uint32_t frame,
+			  awb::FrameContext &frameContext,
+			  const ControlList &controls);
+
+	void prepare(awb::ActiveState &state, awb::FrameContext &frameContext);
+
+	void process(awb::ActiveState &state, awb::FrameContext &frameContext,
+		     const AwbStats &stats, unsigned int lux,
+		     ControlList &metadata);
 
 protected:
-	int parseModeConfigs(const ValueNode &tuningData,
-			     const ControlValue &def = {});
+	AwbAlgorithmBase() = default;
+
+	ControlInfoMap::Map controls_;
+	float gainMin_;
+	float gainMax_;
 
+private:
 	struct ModeConfig {
 		double ctHi;
 		double ctLo;
 	};
 
-	ControlInfoMap::Map controls_;
-	std::map<controls::AwbModeEnum, AwbAlgorithm::ModeConfig> modes_;
+	/* AwbGrey does not support modes; */
+	static constexpr ModeConfig AwbGreyMode = { 0.0, 0.0 };
+
+	int parseModeConfigs(const ValueNode &tuningData,
+			     const ControlValue &def = {});
+
+	std::map<controls::AwbModeEnum, AwbAlgorithmBase::ModeConfig> modes_;
+	const ModeConfig *currentMode_ = nullptr;
+	std::unique_ptr<AwbImplementation> impl_;
+	bool bayes_ = false;
+};
+
+template<typename Q>
+class AwbAlgorithm : public AwbAlgorithmBase
+{
+public:
+	int init(const ValueNode &tuningData, ControlInfoMap::Map &controls)
+	{
+		AwbAlgorithmBase::init(tuningData);
+
+		gainMin_ = std::max(Q::TraitsType::min, 1.0f);
+		gainMax_ = Q::TraitsType::max;
+
+		controls_[&controls::ColourGains] =
+			ControlInfo(gainMin_, gainMax_,
+				    Span<const float, 2>{ { 1.0f, 1.0f } });
+
+		controls.insert(controls_.begin(), controls_.end());
+
+		return 0;
+	}
 };
 
 } /* namespace ipa */
diff --git a/src/ipa/libipa/awb_bayes.cpp b/src/ipa/libipa/awb_bayes.cpp
index 9fd85e5a4505..2c10f9c4c82d 100644
--- a/src/ipa/libipa/awb_bayes.cpp
+++ b/src/ipa/libipa/awb_bayes.cpp
@@ -140,11 +140,6 @@  void Interpolator<Pwl>::interpolate(const Pwl &a, const Pwl &b, Pwl &dest, doubl
  * \brief How far to wander off CT curve towards "more green"
  */
 
-/**
- * \var AwbBayes::currentMode_
- * \brief The currently selected mode
- */
-
 int AwbBayes::init(const ValueNode &tuningData)
 {
 	int ret = colourGainCurve_.readYaml(tuningData["colourGains"], "ct", "gains");
@@ -172,14 +167,6 @@  int AwbBayes::init(const ValueNode &tuningData)
 		return ret;
 	}
 
-	ret = parseModeConfigs(tuningData, controls::AwbAuto);
-	if (ret) {
-		LOG(Awb, Error)
-			<< "Failed to parse mode parameter from tuning file";
-		return ret;
-	}
-	currentMode_ = &modes_[controls::AwbAuto];
-
 	transversePos_ = tuningData["transversePos"].get<double>(0.01);
 	transverseNeg_ = tuningData["transverseNeg"].get<double>(0.01);
 	if (transversePos_ <= 0 || transverseNeg_ <= 0) {
@@ -260,18 +247,16 @@  int AwbBayes::readPriors(const ValueNode &tuningData)
 	return 0;
 }
 
-void AwbBayes::handleControls(const ControlList &controls)
-{
-	auto mode = controls.get(controls::AwbMode);
-	if (mode) {
-		auto it = modes_.find(static_cast<controls::AwbModeEnum>(*mode));
-		if (it != modes_.end())
-			currentMode_ = &it->second;
-		else
-			LOG(Awb, Error) << "Unsupported AWB mode " << *mode;
-	}
-}
-
+/**
+ * \brief Compute white balance gains from a colour temperature
+ * \param[in] colourTemperature The colour temperature in Kelvin
+ *
+ * Compute the white balance gains from a \a colourTemperature. This function
+ * does not take any statistics into account. It simply interpolates the colour
+ * gains curve on a new colour temperature.
+ *
+ * \return The RGB gain values adjusted to \a temperatureK
+ */
 std::optional<RGB<double>> AwbBayes::gainsFromColourTemperature(double colourTemperature)
 {
 	/*
@@ -283,7 +268,9 @@  std::optional<RGB<double>> AwbBayes::gainsFromColourTemperature(double colourTem
 	return RGB<double>{ { gains[0], 1.0, gains[1] } };
 }
 
-AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux)
+AwbImplementation::Result
+AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux,
+		       std::array<double, 2> range)
 {
 	ipa::Pwl prior;
 	if (lux > 0) {
@@ -295,7 +282,7 @@  AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux)
 		prior.append(0, 1.0);
 	}
 
-	double t = coarseSearch(prior, stats);
+	double t = coarseSearch(prior, stats, range);
 	double r = ctR_.eval(t);
 	double b = ctB_.eval(t);
 	LOG(Awb, Debug)
@@ -316,14 +303,15 @@  AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux)
 		<< "After fine search: r " << r << " b " << b << " (gains r "
 		<< 1 / r << " b " << 1 / b << ")";
 
-	return { { { 1.0 / r, 1.0, 1.0 / b } }, t };
+	return { { { 1.0 / r, 1.0, 1.0 / b } }, static_cast<unsigned int>(t) };
 }
 
-double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) const
+double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats,
+			      Span<double> range) const
 {
 	std::vector<Pwl::Point> points;
 	size_t bestPoint = 0;
-	double t = currentMode_->ctLo;
+	double t = range[0];
 	int spanR = -1;
 	int spanB = -1;
 	LimitsRecorder<double> errorLimits;
@@ -345,14 +333,14 @@  double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) cons
 		if (points.back().y() < points[bestPoint].y())
 			bestPoint = points.size() - 1;
 
-		if (t == currentMode_->ctHi)
+		if (t == range[1])
 			break;
 
 		/*
 		 * Ensure even steps along the r/b curve by scaling them by the
 		 * current t.
 		 */
-		t = std::min(t + t / 10 * kSearchStep, currentMode_->ctHi);
+		t = std::min(t + t / 10 * kSearchStep, range[1]);
 	}
 
 	t = points[bestPoint].x();
diff --git a/src/ipa/libipa/awb_bayes.h b/src/ipa/libipa/awb_bayes.h
index 1e3373676bc0..8d4cff076d4a 100644
--- a/src/ipa/libipa/awb_bayes.h
+++ b/src/ipa/libipa/awb_bayes.h
@@ -20,22 +20,24 @@  namespace libcamera {
 
 namespace ipa {
 
-class AwbBayes : public AwbAlgorithm
+class AwbBayes : public AwbImplementation
 {
 public:
 	AwbBayes() = default;
 
 	int init(const ValueNode &tuningData) override;
-	AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) override;
+	AwbImplementation::Result
+	calculateAwb(const AwbStats &stats, unsigned int lux,
+		     std::array<double, 2> range) override;
 	std::optional<RGB<double>> gainsFromColourTemperature(double temperatureK) override;
-	void handleControls(const ControlList &controls) override;
 
 private:
 	int readPriors(const ValueNode &tuningData);
 
 	void fineSearch(double &t, double &r, double &b, ipa::Pwl const &prior,
 			const AwbStats &stats) const;
-	double coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) const;
+	double coarseSearch(const ipa::Pwl &prior, const AwbStats &stats,
+			    Span<double> range) const;
 	double interpolateQuadratic(ipa::Pwl::Point const &a,
 				    ipa::Pwl::Point const &b,
 				    ipa::Pwl::Point const &c) const;
@@ -50,8 +52,6 @@  private:
 
 	double transversePos_;
 	double transverseNeg_;
-
-	ModeConfig *currentMode_ = nullptr;
 };
 
 } /* namespace ipa */
diff --git a/src/ipa/libipa/awb_grey.cpp b/src/ipa/libipa/awb_grey.cpp
index b14b096810ae..1e4af717f62d 100644
--- a/src/ipa/libipa/awb_grey.cpp
+++ b/src/ipa/libipa/awb_grey.cpp
@@ -60,6 +60,7 @@  int AwbGrey::init(const ValueNode &tuningData)
  * \brief Calculate AWB data from the given statistics
  * \param[in] stats The statistics to use for the calculation
  * \param[in] lux The lux value of the scene
+ * \param[in] range The colour temperature search limits
  *
  * The colour temperature is estimated based on the colours::estimateCCT()
  * function. The gains are calculated purely based on the RGB means provided by
@@ -70,20 +71,23 @@  int AwbGrey::init(const ValueNode &tuningData)
  *
  * \return The AWB result
  */
-AwbResult AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned int lux)
+AwbImplementation::Result
+AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned int lux,
+		      [[maybe_unused]] std::array<double, 2> range)
 {
-	AwbResult result;
+	AwbImplementation::Result result;
 	auto means = stats.rgbMeans();
-	result.colourTemperature = estimateCCT(means);
+	result.temperatureK = estimateCCT(means);
 
 	/*
-	 * Estimate the red and blue gains to apply in a grey world. The green
-	 * gain is hardcoded to 1.0. Avoid divisions by zero by clamping the
-	 * divisor to a minimum value of 1.0.
+	 * Calculate the red and blue gains to apply in a grey world by simply
+	 * inverting the red/green and blue/green ratios as reported in
+	 * statistics. The green gain is hardcoded to 1.0.
 	 */
 	result.gains.r() = means.g() / std::max(means.r(), 1.0);
 	result.gains.g() = 1.0;
 	result.gains.b() = means.g() / std::max(means.b(), 1.0);
+
 	return result;
 }
 
@@ -93,15 +97,16 @@  AwbResult AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned
  *
  * Compute the white balance gains from a \a colourTemperature. This function
  * does not take any statistics into account. It simply interpolates the colour
- * gains configured in the colour temperature curve.
+ * gains curve (if provided in the tuning file) on a new colour temperature.
  *
- * \return The colour gains if a colour temperature curve is available,
- * [1, 1, 1] otherwise.
+ * \return The RGB gain values adjusted to \a temperatureK or std::nullopt if
+ * no gain curve is specified in the tuning data
  */
 std::optional<RGB<double>> AwbGrey::gainsFromColourTemperature(double colourTemperature)
 {
 	if (!colourGainCurve_) {
-		LOG(Awb, Error) << "No gains defined";
+		LOG(Awb, Info) << "No gains curve defined, "
+			       << "unable to interpolate gains to colour temperature";
 		return std::nullopt;
 	}
 
diff --git a/src/ipa/libipa/awb_grey.h b/src/ipa/libipa/awb_grey.h
index 154a2af97f15..d1b0088f0470 100644
--- a/src/ipa/libipa/awb_grey.h
+++ b/src/ipa/libipa/awb_grey.h
@@ -18,13 +18,15 @@  namespace libcamera {
 
 namespace ipa {
 
-class AwbGrey : public AwbAlgorithm
+class AwbGrey : public AwbImplementation
 {
 public:
 	AwbGrey() = default;
 
 	int init(const ValueNode &tuningData) override;
-	AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) override;
+	AwbImplementation::Result
+	calculateAwb(const AwbStats &stats, unsigned int lux,
+		     std::array<double, 2> range) override;
 	std::optional<RGB<double>> gainsFromColourTemperature(double colourTemperature) override;
 
 private:
diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp
index 0eb05c2b6e1a..ae9f6a0c243e 100644
--- a/src/ipa/rkisp1/algorithms/awb.cpp
+++ b/src/ipa/rkisp1/algorithms/awb.cpp
@@ -8,17 +8,12 @@ 
 #include "awb.h"
 
 #include <algorithm>
-#include <ios>
 
 #include <libcamera/base/log.h>
 
-#include <libcamera/control_ids.h>
-
 #include <libcamera/ipa/core_ipa_interface.h>
 
-#include "libipa/awb_bayes.h"
-#include "libipa/awb_grey.h"
-#include "libipa/colours.h"
+#include "libcamera/internal/vector.h"
 
 /**
  * \file awb.h
@@ -28,26 +23,16 @@  namespace libcamera {
 
 namespace ipa::rkisp1::algorithms {
 
-/**
- * \class Awb
- * \brief Manage the white balance with automatic and manual controls
- */
-
 LOG_DEFINE_CATEGORY(RkISP1Awb)
 
-constexpr int32_t kMinColourTemperature = 2500;
-constexpr int32_t kMaxColourTemperature = 10000;
-constexpr int32_t kDefaultColourTemperature = 5000;
-
-/* Minimum mean value below which AWB can't operate. */
-constexpr double kMeanMinThreshold = 2.0;
-
 class RkISP1AwbStats final : public AwbStats
 {
 public:
-	RkISP1AwbStats(const RGB<double> &rgbMeans)
-		: rgbMeans_(rgbMeans)
+	RkISP1AwbStats(const IPAFrameContext &frameContext,
+		       const rkisp1_stat_buffer *stats)
 	{
+		calculateRgbMeans(frameContext, stats);
+
 		rg_ = rgbMeans_.r() / rgbMeans_.g();
 		bg_ = rgbMeans_.b() / rgbMeans_.g();
 	}
@@ -65,17 +50,106 @@  public:
 		return delta2;
 	}
 
-	RGB<double> rgbMeans() const override
+	bool valid() const override
 	{
-		return rgbMeans_;
+		/* Minimum mean value below which AWB can't operate. */
+		constexpr double minValue = 2.0;
+
+		return rgbMeans_.r() > minValue || rgbMeans_.g() > minValue ||
+		       rgbMeans_.b() > minValue;
 	}
 
 private:
-	RGB<double> rgbMeans_;
-	double rg_;
-	double bg_;
+	void calculateRgbMeans(const IPAFrameContext &frameContext,
+			       const rkisp1_stat_buffer *stats);
+	bool rgbMode_ = false;
 };
 
+void RkISP1AwbStats::calculateRgbMeans(const IPAFrameContext &frameContext,
+				       const rkisp1_stat_buffer *stats)
+{
+	if (!stats || !(stats->meas_type & RKISP1_CIF_ISP_STAT_AWB)) {
+		LOG(RkISP1Awb, Error) << "AWB data is missing in statistics";
+		return;
+	}
+
+	const rkisp1_cif_isp_awb_stat *awb = &stats->params.awb;
+
+	if (awb->awb_mean[0].cnt == 0) {
+		LOG(RkISP1Awb, Debug) << "AWB statistics are empty";
+		return;
+	}
+
+	if (rgbMode_) {
+		rgbMeans_ = {{
+			static_cast<double>(awb->awb_mean[0].mean_cr_or_r),
+			static_cast<double>(awb->awb_mean[0].mean_y_or_g),
+			static_cast<double>(awb->awb_mean[0].mean_cb_or_b)
+		}};
+	} else {
+		/* Get the YCbCr mean values */
+		Vector<double, 3> yuvMeans({
+			static_cast<double>(awb->awb_mean[0].mean_y_or_g),
+			static_cast<double>(awb->awb_mean[0].mean_cb_or_b),
+			static_cast<double>(awb->awb_mean[0].mean_cr_or_r)
+		});
+
+		/*
+		 * Convert from YCbCr to RGB. The hardware uses the following
+		 * formulas:
+		 *
+		 * Y  =  16 + 0.2500 R + 0.5000 G + 0.1094 B
+		 * Cb = 128 - 0.1406 R - 0.2969 G + 0.4375 B
+		 * Cr = 128 + 0.4375 R - 0.3750 G - 0.0625 B
+		 *
+		 * This seems to be based on limited range BT.601 with Q1.6
+		 * precision.
+		 *
+		 * The inverse matrix is:
+		 *
+		 * [[1,1636, -0,0623,  1,6008]
+		 *  [1,1636, -0,4045, -0,7949]
+		 *  [1,1636,  1,9912, -0,0250]]
+		 */
+		static const Matrix<double, 3, 3> yuv2rgbMatrix({
+			1.1636, -0.0623,  1.6008,
+			1.1636, -0.4045, -0.7949,
+			1.1636,  1.9912, -0.0250
+		});
+		static const Vector<double, 3> yuv2rgbOffset({
+			16, 128, 128
+		});
+
+		rgbMeans_ = yuv2rgbMatrix * (yuvMeans - yuv2rgbOffset);
+
+		/*
+		 * Due to hardware rounding errors in the YCbCr means, the
+		 * calculated RGB means may be negative. This would lead to
+		 * negative gains, messing up calculation. Prevent this by
+		 * clamping the means to positive values.
+		 */
+		rgbMeans_ = rgbMeans_.max(0.0);
+	}
+
+	/*
+	 * The ISP computes the AWB means after applying the CCM. Apply the
+	 * inverse as we want to get the raw means before the colour gains.
+	 */
+	rgbMeans_ = frameContext.ccm.ccm.inverse() * rgbMeans_;
+
+	/*
+	 * The ISP computes the AWB means after applying the colour gains,
+	 * divide by the gains that were used to get the raw means from the
+	 * sensor. Apply a minimum value to avoid divisions by near-zero.
+	 */
+	rgbMeans_ /= frameContext.awb.gains.max(0.01);
+}
+
+/**
+ * \class Awb
+ * \brief Manage the white balance with automatic and manual controls
+ */
+
 Awb::Awb()
 	: rgbMode_(false)
 {
@@ -86,40 +160,7 @@  Awb::Awb()
  */
 int Awb::init(IPAContext &context, const ValueNode &tuningData)
 {
-	auto &cmap = context.ctrlMap;
-	cmap[&controls::ColourTemperature] = ControlInfo(kMinColourTemperature,
-							 kMaxColourTemperature,
-							 kDefaultColourTemperature);
-	cmap[&controls::AwbEnable] = ControlInfo(false, true);
-
-	cmap[&controls::ColourGains] = ControlInfo(0.0f, 3.996f,
-						   Span<const float, 2>{ { 1.0f, 1.0f } });
-
-	if (!tuningData.contains("algorithm"))
-		LOG(RkISP1Awb, Info) << "No AWB algorithm specified."
-				     << " Default to grey world";
-
-	auto mode = tuningData["algorithm"].get<std::string>("grey");
-	if (mode == "grey") {
-		awbAlgo_ = std::make_unique<AwbGrey>();
-	} else if (mode == "bayes") {
-		awbAlgo_ = std::make_unique<AwbBayes>();
-	} else {
-		LOG(RkISP1Awb, Error) << "Unknown AWB algorithm: " << mode;
-		return -EINVAL;
-	}
-	LOG(RkISP1Awb, Debug) << "Using AWB algorithm: " << mode;
-
-	int ret = awbAlgo_->init(tuningData);
-	if (ret) {
-		LOG(RkISP1Awb, Error) << "Failed to init AWB algorithm";
-		return ret;
-	}
-
-	const auto &src = awbAlgo_->controls();
-	cmap.insert(src.begin(), src.end());
-
-	return 0;
+	return awbAlgo_.init(tuningData, context.ctrlMap);
 }
 
 /**
@@ -128,16 +169,7 @@  int Awb::init(IPAContext &context, const ValueNode &tuningData)
 int Awb::configure(IPAContext &context,
 		   const IPACameraSensorInfo &configInfo)
 {
-	context.activeState.awb.manual.gains = RGB<double>{ 1.0 };
-	auto gains = awbAlgo_->gainsFromColourTemperature(kDefaultColourTemperature);
-	if (gains)
-		context.activeState.awb.automatic.gains = *gains;
-	else
-		context.activeState.awb.automatic.gains = RGB<double>{ 1.0 };
-
-	context.activeState.awb.autoEnabled = true;
-	context.activeState.awb.manual.temperatureK = kDefaultColourTemperature;
-	context.activeState.awb.automatic.temperatureK = kDefaultColourTemperature;
+	awbAlgo_.configure(context.activeState.awb);
 
 	/*
 	 * Define the measurement window for AWB as a centered rectangle
@@ -156,56 +188,12 @@  int Awb::configure(IPAContext &context,
 /**
  * \copydoc libcamera::ipa::Algorithm::queueRequest
  */
-void Awb::queueRequest(IPAContext &context,
-		       [[maybe_unused]] const uint32_t frame,
+void Awb::queueRequest(IPAContext &context, const uint32_t frame,
 		       IPAFrameContext &frameContext,
 		       const ControlList &controls)
 {
-	auto &awb = context.activeState.awb;
-
-	const auto &awbEnable = controls.get(controls::AwbEnable);
-	if (awbEnable && *awbEnable != awb.autoEnabled) {
-		awb.autoEnabled = *awbEnable;
-
-		LOG(RkISP1Awb, Debug)
-			<< (*awbEnable ? "Enabling" : "Disabling") << " AWB";
-	}
-
-	awbAlgo_->handleControls(controls);
-
-	frameContext.awb.autoEnabled = awb.autoEnabled;
-
-	if (awb.autoEnabled)
-		return;
-
-	const auto &colourGains = controls.get(controls::ColourGains);
-	const auto &colourTemperature = controls.get(controls::ColourTemperature);
-	bool update = false;
-	if (colourGains) {
-		awb.manual.gains.r() = (*colourGains)[0];
-		awb.manual.gains.b() = (*colourGains)[1];
-		/*
-		 * \todo Colour temperature reported in metadata is now
-		 * incorrect, as we can't deduce the temperature from the gains.
-		 * This will be fixed with the bayes AWB algorithm.
-		 */
-		update = true;
-	} else if (colourTemperature) {
-		awb.manual.temperatureK = *colourTemperature;
-		const auto &gains = awbAlgo_->gainsFromColourTemperature(*colourTemperature);
-		if (gains) {
-			awb.manual.gains.r() = gains->r();
-			awb.manual.gains.b() = gains->b();
-			update = true;
-		}
-	}
-
-	if (update)
-		LOG(RkISP1Awb, Debug)
-			<< "Set colour gains to " << awb.manual.gains;
-
-	frameContext.awb.gains = awb.manual.gains;
-	frameContext.awb.temperatureK = awb.manual.temperatureK;
+	awbAlgo_.queueRequest(context.activeState.awb, frame, frameContext.awb,
+			      controls);
 }
 
 /**
@@ -214,15 +202,7 @@  void Awb::queueRequest(IPAContext &context,
 void Awb::prepare(IPAContext &context, const uint32_t frame,
 		  IPAFrameContext &frameContext, RkISP1Params *params)
 {
-	/*
-	 * This is the latest time we can read the active state. This is the
-	 * most up-to-date automatic values we can read.
-	 */
-	if (frameContext.awb.autoEnabled) {
-		const auto &awb = context.activeState.awb;
-		frameContext.awb.gains = awb.automatic.gains;
-		frameContext.awb.temperatureK = awb.automatic.temperatureK;
-	}
+	awbAlgo_.prepare(context.activeState.awb, frameContext.awb);
 
 	auto gainConfig = params->block<BlockType::AwbGain>();
 	gainConfig.setEnabled(true);
@@ -291,136 +271,12 @@  void Awb::process(IPAContext &context,
 		  const rkisp1_stat_buffer *stats,
 		  ControlList &metadata)
 {
-	IPAActiveState &activeState = context.activeState;
-
-	metadata.set(controls::AwbEnable, frameContext.awb.autoEnabled);
-	metadata.set(controls::ColourGains, {
-			static_cast<float>(frameContext.awb.gains.r()),
-			static_cast<float>(frameContext.awb.gains.b())
-		});
-	metadata.set(controls::ColourTemperature, frameContext.awb.temperatureK);
-
-	if (!stats || !(stats->meas_type & RKISP1_CIF_ISP_STAT_AWB)) {
-		LOG(RkISP1Awb, Error) << "AWB data is missing in statistics";
-		return;
-	}
-
-	const rkisp1_cif_isp_stat *params = &stats->params;
-	const rkisp1_cif_isp_awb_stat *awb = &params->awb;
+	RkISP1AwbStats awbStats(frameContext, stats);
 
-	if (awb->awb_mean[0].cnt == 0) {
-		LOG(RkISP1Awb, Debug) << "AWB statistics are empty";
-		return;
-	}
-
-	RGB<double> rgbMeans = calculateRgbMeans(frameContext, awb);
-
-	/*
-	 * If the means are too small we don't have enough information to
-	 * meaningfully calculate gains. Freeze the algorithm in that case.
-	 */
-	if (rgbMeans.r() < kMeanMinThreshold && rgbMeans.g() < kMeanMinThreshold &&
-	    rgbMeans.b() < kMeanMinThreshold)
-		return;
-
-	RkISP1AwbStats awbStats{ rgbMeans };
-	AwbResult awbResult = awbAlgo_->calculateAwb(awbStats, frameContext.lux.lux);
-
-	/*
-	 * Clamp the gain values to the hardware, which expresses gains as Q2.8
-	 * unsigned integer values. Set the minimum just above zero to avoid
-	 * divisions by zero when computing the raw means in subsequent
-	 * iterations.
-	 */
-	awbResult.gains = awbResult.gains.clamp(1.0 / 256, 1023.0 / 256);
-
-	/* Filter the values to avoid oscillations. */
-	double speed = 0.2;
-	double ct = awbResult.colourTemperature;
-	ct = ct * speed + activeState.awb.automatic.temperatureK * (1 - speed);
-	awbResult.gains = awbResult.gains * speed +
-			  activeState.awb.automatic.gains * (1 - speed);
-
-	activeState.awb.automatic.temperatureK = static_cast<unsigned int>(ct);
-	activeState.awb.automatic.gains = awbResult.gains;
-
-	LOG(RkISP1Awb, Debug)
-		<< std::showpoint
-		<< "Means " << rgbMeans << ", gains "
-		<< activeState.awb.automatic.gains << ", temp "
-		<< activeState.awb.automatic.temperatureK << "K";
+	awbAlgo_.process(context.activeState.awb, frameContext.awb, awbStats,
+			 frameContext.lux.lux, metadata);
 }
 
-RGB<double> Awb::calculateRgbMeans(const IPAFrameContext &frameContext, const rkisp1_cif_isp_awb_stat *awb) const
-{
-	Vector<double, 3> rgbMeans;
-
-	if (rgbMode_) {
-		rgbMeans = {{
-			static_cast<double>(awb->awb_mean[0].mean_cr_or_r),
-			static_cast<double>(awb->awb_mean[0].mean_y_or_g),
-			static_cast<double>(awb->awb_mean[0].mean_cb_or_b)
-		}};
-	} else {
-		/* Get the YCbCr mean values */
-		Vector<double, 3> yuvMeans({
-			static_cast<double>(awb->awb_mean[0].mean_y_or_g),
-			static_cast<double>(awb->awb_mean[0].mean_cb_or_b),
-			static_cast<double>(awb->awb_mean[0].mean_cr_or_r)
-		});
-
-		/*
-		 * Convert from YCbCr to RGB. The hardware uses the following
-		 * formulas:
-		 *
-		 * Y  =  16 + 0.2500 R + 0.5000 G + 0.1094 B
-		 * Cb = 128 - 0.1406 R - 0.2969 G + 0.4375 B
-		 * Cr = 128 + 0.4375 R - 0.3750 G - 0.0625 B
-		 *
-		 * This seems to be based on limited range BT.601 with Q1.6
-		 * precision.
-		 *
-		 * The inverse matrix is:
-		 *
-		 * [[1,1636, -0,0623,  1,6008]
-		 *  [1,1636, -0,4045, -0,7949]
-		 *  [1,1636,  1,9912, -0,0250]]
-		 */
-		static const Matrix<double, 3, 3> yuv2rgbMatrix({
-			1.1636, -0.0623,  1.6008,
-			1.1636, -0.4045, -0.7949,
-			1.1636,  1.9912, -0.0250
-		});
-		static const Vector<double, 3> yuv2rgbOffset({
-			16, 128, 128
-		});
-
-		rgbMeans = yuv2rgbMatrix * (yuvMeans - yuv2rgbOffset);
-
-		/*
-		 * Due to hardware rounding errors in the YCbCr means, the
-		 * calculated RGB means may be negative. This would lead to
-		 * negative gains, messing up calculation. Prevent this by
-		 * clamping the means to positive values.
-		 */
-		rgbMeans = rgbMeans.max(0.0);
-	}
-
-	/*
-	 * The ISP computes the AWB means after applying the CCM. Apply the
-	 * inverse as we want to get the raw means before the colour gains.
-	 */
-	rgbMeans = frameContext.ccm.ccm.inverse() * rgbMeans;
-
-	/*
-	 * The ISP computes the AWB means after applying the colour gains,
-	 * divide by the gains that were used to get the raw means from the
-	 * sensor. Apply a minimum value to avoid divisions by near-zero.
-	 */
-	rgbMeans /= frameContext.awb.gains.max(0.01);
-
-	return rgbMeans;
-}
 
 REGISTER_IPA_ALGORITHM(Awb, "Awb")
 
diff --git a/src/ipa/rkisp1/algorithms/awb.h b/src/ipa/rkisp1/algorithms/awb.h
index 60d9ef111495..c7cb59a75bde 100644
--- a/src/ipa/rkisp1/algorithms/awb.h
+++ b/src/ipa/rkisp1/algorithms/awb.h
@@ -7,17 +7,25 @@ 
 
 #pragma once
 
-#include "libcamera/internal/vector.h"
+#include <linux/rkisp1-config.h>
+
+#include <libcamera/controls.h>
+
+#include "libcamera/internal/value_node.h"
 
 #include "libipa/awb.h"
-#include "libipa/interpolator.h"
+#include "libipa/fixedpoint.h"
 
 #include "algorithm.h"
+#include "ipa_context.h"
+#include "params.h"
 
 namespace libcamera {
 
 namespace ipa::rkisp1::algorithms {
 
+class RkISP1AwbStats;
+
 class Awb : public Algorithm
 {
 public:
@@ -38,10 +46,10 @@  public:
 		     ControlList &metadata) override;
 
 private:
-	RGB<double> calculateRgbMeans(const IPAFrameContext &frameContext,
-				      const rkisp1_cif_isp_awb_stat *awb) const;
+	RkISP1AwbStats calculateRgbMeans(const IPAFrameContext &frameContext,
+					 const rkisp1_stat_buffer *stats) const;
 
-	std::unique_ptr<AwbAlgorithm> awbAlgo_;
+	AwbAlgorithm<UQ<2, 8>> awbAlgo_;
 
 	bool rgbMode_;
 };
diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h
index e5f70d934d67..c36c1f7e0084 100644
--- a/src/ipa/rkisp1/ipa_context.h
+++ b/src/ipa/rkisp1/ipa_context.h
@@ -25,6 +25,7 @@ 
 #include "libcamera/internal/vector.h"
 
 #include "libipa/agc_mean_luminance.h"
+#include "libipa/awb.h"
 #include "libipa/camera_sensor_helper.h"
 #include "libipa/fc_queue.h"
 #include "libipa/fixedpoint.h"
@@ -48,15 +49,17 @@  struct IPAHwSettings {
 	bool compand;
 };
 
+struct RKISP1AwbSession {
+	struct rkisp1_cif_isp_window measureWindow;
+	bool enabled;
+};
+
 struct IPASessionConfiguration {
 	struct {
 		struct rkisp1_cif_isp_window measureWindow;
 	} agc;
 
-	struct {
-		struct rkisp1_cif_isp_window measureWindow;
-		bool enabled;
-	} awb;
+	struct RKISP1AwbSession awb;
 
 	struct {
 		bool supported;
@@ -100,17 +103,7 @@  struct IPAActiveState {
 		utils::Duration maxFrameDuration;
 	} agc;
 
-	struct {
-		struct AwbState {
-			RGB<double> gains;
-			unsigned int temperatureK;
-		};
-
-		AwbState manual;
-		AwbState automatic;
-
-		bool autoEnabled;
-	} awb;
+	ipa::awb::ActiveState awb;
 
 	struct {
 		Matrix<float, 3, 3> manual;
@@ -176,11 +169,7 @@  struct IPAFrameContext : public FrameContext {
 		bool autoGainModeChange;
 	} agc;
 
-	struct {
-		RGB<double> gains;
-		bool autoEnabled;
-		unsigned int temperatureK;
-	} awb;
+	ipa::awb::FrameContext awb;
 
 	struct {
 		float actualBrightness;