[{"id":39090,"web_url":"https://patchwork.libcamera.org/comment/39090/","msgid":"<855x3j1usj.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","date":"2026-06-15T19:13:32","subject":"Re: [PATCH 01/11] ipa: libipa: awb: Reimplement AwbAlgorithm","submitter":{"id":177,"url":"https://patchwork.libcamera.org/api/people/177/","name":"Milan Zamazal","email":"mzamazal@redhat.com"},"content":"Hi Jacopo,\n\nthank you for taking care of this series.\n\nJacopo Mondi <jacopo.mondi@ideasonboard.com> writes:\n\n> The current implementation of AwbAlgorithm is the following one:\n>\n> AwbAlgorithm o---- RkISP1Awb\n>     ^\n>     |\n>  |-----|\n>  |     |\n> Bayes Grey\n>\n> The platform-specific Awb implementation instantiate one of\n> the Bayes or Grey Awb helper classes and both inherit from the\n> AwbAlgorithm base class.\n>\n> The handling of libcamera controls is split between the base and the\n> derived (mostly Bayes) class.\n>\n> We what instead the AwbAlgorithm to handle all-things-libcamera and let\n     ^^^^\n\nwant?\n\n> the Bayes and Grey classes implement the logic. In order to achieve that\n> use composition also in the AwbAlgorithm class and introduce an\n> AwbImplementation base class from which Bayes and Grey derive from.\n>\n> AwbImplementation o---- AwbAlgorithmBase\n>     ^                           ^\n>     |                           |\n>  |-----|                AwbAlgorithm<Q<x,y>> o---- RkISP1Awb\n>  |     |\n> Bayes Grey\n>\n> The AwbAlgorithm<> class is instantiated with a Q<x, y> template\n> argument that represents the platform-specific register format which\n> is used to initialize controls and clamp gains to the hardware limits.\n>\n> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n> ---\n>  src/ipa/libipa/awb.cpp            | 562 +++++++++++++++++++++++++++++++++-----\n>  src/ipa/libipa/awb.h              | 123 +++++++--\n>  src/ipa/libipa/awb_bayes.cpp      |  40 +--\n>  src/ipa/libipa/awb_bayes.h        |  11 +-\n>  src/ipa/libipa/awb_grey.cpp       |  16 +-\n>  src/ipa/libipa/awb_grey.h         |   5 +-\n>  src/ipa/rkisp1/algorithms/awb.cpp | 225 ++-------------\n>  src/ipa/rkisp1/algorithms/awb.h   |  18 +-\n>  src/ipa/rkisp1/ipa_context.h      |  28 +-\n>  9 files changed, 679 insertions(+), 349 deletions(-)\n\nHmm, quite a patch, not easy to review reliably.\n\n>\n> diff --git a/src/ipa/libipa/awb.cpp b/src/ipa/libipa/awb.cpp\n> index af966f1e007c..b9f7d4004cb1 100644\n> --- a/src/ipa/libipa/awb.cpp\n> +++ b/src/ipa/libipa/awb.cpp\n> @@ -2,18 +2,24 @@\n>  /*\n>   * Copyright (C) 2024 Ideas on Board Oy\n>   *\n> - * Generic AWB algorithms\n> + * libIPA Awb algorithms\n>   */\n>  \n>  #include \"awb.h\"\n> +#include \"awb_bayes.h\"\n> +#include \"awb_grey.h\"\n>  \n>  #include <libcamera/base/log.h>\n>  \n>  #include <libcamera/control_ids.h>\n>  \n> +constexpr int32_t kMinColourTemperature = 2500;\n> +constexpr int32_t kMaxColourTemperature = 10000;\n> +constexpr int32_t kDefaultColourTemperature = 5000;\n> +\n>  /**\n>   * \\file awb.h\n> - * \\brief Base classes for AWB algorithms\n> + * \\brief libipa awb implementation\n>   */\n>  \n>  namespace libcamera {\n> @@ -22,34 +28,88 @@ LOG_DEFINE_CATEGORY(Awb)\n>  \n>  namespace ipa {\n>  \n> +namespace awb {\n> +\n>  /**\n> - * \\class AwbResult\n> - * \\brief The result of an AWB calculation\n> + * \\struct Session\n> + * \\brief Session-wide awb configuration\n>   *\n> - * This class holds the result of an auto white balance calculation.\n> + * \\var awb::Session::enabled\n> + * \\brief True when awb processing is enabled for the session\n>   */\n\nWhat is this good for?  It doesn't seem to be used anywhere.  Should it\nbe a mechanism to enable/disable the algorithm independently of the\ntuning file?  If it is the case, the flag should be used somewhere.\n\n>  /**\n> - * \\var AwbResult::gains\n> - * \\brief The calculated white balance gains\n> + * \\struct ActiveState\n> + * \\brief Active awb state shared across frames\n> + *\n> + * \\var ActiveState::manual\n> + * \\brief The most recent manually requested awb state\n> + *\n> + * \\var ActiveState::automatic\n> + * \\brief The most recent automatically calculated awb state\n> + *\n> + * \\var ActiveState::autoEnabled\n> + * \\brief True when automatic awb is selected\n>   */\n>  \n>  /**\n> - * \\var AwbResult::colourTemperature\n> - * \\brief The calculated colour temperature in Kelvin\n> + * \\struct ActiveState::AwbState\n> + * \\brief Awb gains and colour temperature\n> + *\n> + * \\var ActiveState::AwbState::gains\n> + * \\brief The white balance gains\n> + *\n> + * \\var ActiveState::AwbState::temperatureK\n> + * \\brief The colour temperature, in Kelvin\n>   */\n>  \n> +/**\n> + * \\struct FrameContext\n> + * \\brief Per-frame awb state\n> + *\n> + * \\var FrameContext::gains\n> + * \\brief The white balance gains\n> + *\n> + * \\var FrameContext::autoEnabled\n> + * \\brief True when automatic awb is in use\n> + *\n> + * \\var FrameContext::temperatureK\n> + * \\brief The colour temperature, in Kelvin\n> + */\n> +\n> +} /* namespace awb */\n> +\n>  /**\n>   * \\class AwbStats\n> - * \\brief An abstraction class wrapping hardware-specific AWB statistics\n> + * \\brief An abstraction class wrapping hardware-specific awb statistics\n>   *\n> - * IPA modules using an AWB algorithm based on the AwbAlgorithm class need to\n> - * implement this class to give the algorithm access to the hardware-specific\n> - * statistics data.\n> + * IPA modules shall provide a derived implementation of this class to give the\n> + * awb algorithm access to the hardware-specific statistics data in a common\n> + * format.\n> + */\n> +\n> +/**\n> + * \\fn AwbStats::AwbStats()\n> + * \\brief Construt and empty AwbStat\n\ns/Construt/Construct/\ns/and/an/\n\n> + */\n> +\n> +/**\n> + * \\brief Construct Awb statistics from RGB mean values\n> + * \\param[in] rgbMeans The RGB mean values\n> + */\n> +AwbStats::AwbStats(const RGB<double> &rgbMeans)\n> +\t: rgbMeans_(rgbMeans)\n> +{\n> +\trg_ = rgbMeans_.r() / rgbMeans_.g();\n> +\tbg_ = rgbMeans_.b() / rgbMeans_.g();\n> +}\n> +\n> +/**\n> + * AwbStat::~AwbStat\n> + * \\brief Virtual class destructor\n>   */\n>  \n>  /**\n> - * \\fn AwbStats::computeColourError()\n>   * \\brief Compute an error value for when the given gains would be applied\n>   * \\param[in] gains The gains to apply\n>   *\n> @@ -57,87 +117,407 @@ namespace ipa {\n>   * applied. To keep the actual implementations computationally inexpensive,\n>   * the squared colour error shall be returned.\n>   *\n> - * If the AWB statistics provide multiple zones, the average of the individual\n> + * If the awb statistics provide multiple zones, the average of the individual\n>   * squared errors shall be returned. Averaging/normalizing is necessary so that\n>   * the numeric dimensions are the same on all hardware platforms.\n>   *\n>   * \\return The computed error value\n>   */\n> +double AwbStats::computeColourError(const RGB<double> &gains) const\n> +{\n> +\t/*\n> +\t * Compute the sum of the squared colour error (non-greyness) as\n> +\t * it appears in the log likelihood equation.\n> +\t */\n> +\tdouble deltaR = gains.r() * rg_ - 1.0;\n> +\tdouble deltaB = gains.b() * bg_ - 1.0;\n> +\tdouble delta2 = deltaR * deltaR + deltaB * deltaB;\n> +\n> +\treturn delta2;\n> +}\n> +\n> +/**\n> + * \\brief Retrieve if the awb statistics are valid\n> + *\n> + * If the colour mean values are too small, the AwbAlgorithm class doesn't have\n> + * enough information to meaningfully calculate white-balance gains. Freeze the\n> + * algorithm in that case.\n> + *\n> + * \\return True if the awb statistics are valid, false otherwise\n> + */\n> +bool AwbStats::valid() const\n> +{\n> +\tdouble minValue = minColourValue();\n> +\n> +\tif (rgbMeans_.r() < minValue && rgbMeans_.g() < minValue &&\n> +\t    rgbMeans_.b() < minValue)\n> +\t\treturn false;\n\nOr a bit simpler:\n\n  return rgbMeans_.r() >= minValue ||\n         rgbMeans_.g() >= minValue ||\n         rgbMeans_.b() >= minValue;\n\n> +\n> +\treturn true;\n> +}\n> +\n> +/**\n> + * \\fn AwbStats::rgRatio()\n> + * \\brief Retriev the Red/Green ratio\n\ns/Retriev/Retrieve/\n\n> + * \\return The Red/Green ratio\n> + */\n>  \n>  /**\n> - * \\fn AwbStats::rgbMeans()\n> - * \\brief Get RGB means of the statistics\n> + * \\fn AwbStats::bgRatio()\n> + * \\brief Retrieve the Blue/Green ratio\n> + * \\return The Blue/Green ratio\n> + */\n> +\n> +/**\n> + * \\brief Retrieve the RGB means values\n>   *\n>   * Fetch the RGB means from the statistics. The values of each channel are\n>   * dimensionless and only the ratios are used for further calculations. This is\n> - * used by the simple grey world model to calculate the gains to apply.\n> + * used by the AwbGrey to calculate the gains to apply.\n>   *\n>   * \\return The RGB means\n>   */\n> +RGB<double> AwbStats::rgbMeans() const\n> +{\n> +\treturn rgbMeans_;\n> +}\n>  \n>  /**\n> - * \\class AwbAlgorithm\n> - * \\brief A base class for auto white balance algorithms\n> + * \\fn AwbStats::minColourValue\n> + * \\brief Retrieve the threshold below which a colour information is not valid\n>   *\n> - * This class is a base class for auto white balance algorithms. It defines an\n> - * interface for the algorithms to implement, and is used by the IPAs to\n> - * interact with the concrete implementation.\n> + * Mean colour values as reported by the ISP through the white balance\n> + * statistics are considered valid for colour gain calculation purposes when\n> + * they are above a certain value. The awb algorithm needs to know what is the\n> + * minimum value below which it should ignore the colour mean values.\n> + *\n> + * As different ISP platforms report gains in different formats, the threshold\n> + * is then platform specific, and all IPA implementations that use the\n> + * AwbAlgorithm class should provide that information by implementing this\n> + * function.\n> + *\n> + * The reported minimum value applies to all three colour channels.\n> + *\n> + * This function is used by AwbStats::valid().\n> + *\n> + * \\return The minimum valid colour value\n>   */\n>  \n>  /**\n> - * \\fn AwbAlgorithm::init()\n> - * \\brief Initialize the algorithm with the given tuning data\n> - * \\param[in] tuningData The tuning data to use for the algorithm\n> + * \\var AwbStats::rgbMeans_\n> + * \\brief Mean values of colour channels\n> + */\n> +\n> +/**\n> + * \\var AwbStats::rg_\n> + * \\brief Red/Green ratio\n> + */\n> +\n> +/**\n> + * \\var AwbStats::bg_\n> + * \\brief Blue/Green ratio\n> + */\n> +\n> +/**\n> + * \\class AwbImplementation\n> + * \\brief Pure virtual base class for awb algorithms implementations\n>   *\n> - * \\return 0 on success, a negative error code otherwise\n> + * The AwbImplementation class defines the interface for the awb algorithm\n> + * implementations.\n> + *\n> + * It is currently implemented by the AwbGrey and AwbBayes classes.\n> + *\n> + * The interface defines an init() function to initialize the algorithm with the\n> + * content of the tuning file and two function to compute colour gains according\n\ns/function/functions/\n\n> + * to the algorithm operating mode (auto or manual) in use.\n> + *\n> + * The calculateAwb() function calculates colour gains given a set of statistics\n> + * provided by the IPA module. It is used when the algorithm operates in auto\n> + * mode and gains are dynamically computed given a new set of statistics from\n> + * the awb engine.\n> + *\n> + * The gainsFromColourTemperature() function instead interpolates a gain curve\n> + * if specified in the tuning file with the supplied colour temperature. This\n> + * function is used in manual mode where it is expected the application to\n> + * supply a colour temperature and the algorithm to adjust the white balance\n> + * gains to it.\n> + */\n> +\n> +/**\n> + * \\class AwbImplementation::AwbResult\n> + * \\brief The result of an Awb calculation\n> + *\n> + * This class holds the result of an auto white balance calculation.\n> + */\n> +\n> +/**\n> + * \\var AwbImplementation::AwbResult::gains\n> + * \\brief The calculated white balance gains\n> + */\n> +\n> +/**\n> + * \\var AwbImplementation::AwbResult::colourTemperature\n> + * \\brief The calculated colour temperature in Kelvin\n> + */\n> +\n> +/**\n> + * \\fn AwbImplementation::~AwbImplementation\n> + * \\brief Virtual class destructor\n> + */\n> +\n> +/**\n> + * \\fn AwbImplementation::init()\n> + * \\param[in] tuningData\n> + * \\brief Initialize the algorithm by parsing \\a tuningData\n>   */\n>  \n>  /**\n> - * \\fn AwbAlgorithm::calculateAwb()\n> - * \\brief Calculate AWB data from the given statistics\n> - * \\param[in] stats The statistics to use for the calculation\n> - * \\param[in] lux The lux value of the scene\n> + * \\fn AwbImplementation::calculateAwb()\n> + * \\param[in] stats The awb statistics\n> + * \\param[in] lux The estimated lux level\n> + * \\param[in] ranges The colour temperature search limits (AwbBayes only)\n>   *\n> - * Calculate an AwbResult object from the given statistics and lux value. A \\a\n> - * lux value of 0 means it is unknown or invalid and the algorithm shall ignore\n> - * it.\n> + * Calculate a new set of colour gains and a colour temperature given a new\n> + * set of statistics \\a stats, an estimated luminance \\a lux and a range of\n> + * colour temperature to limit the search (for AwbBayes only).\n>   *\n> - * \\return The AWB result\n> + * \\return An AwbResult computed using the new statistics\n> + */\n> +/**\n> + * \\fn AwbImplementation::gainsFromColourTemperature()\n> + * \\param[in] temperatureK Colour temperature in Kelvin\n> + *\n> + * Calculate a new set of colour gains by interpolating a gain curve (if\n> + * provided in the tuning file) with a new colour temperature \\a temperatureK.\n> + *\n> + * \\return The RGB gain values adjusted to \\a temperatureK\n\nAnd no value if the gain curve is not provided?\n\n>   */\n>  \n>  /**\n> - * \\fn AwbAlgorithm::gainsFromColourTemperature()\n> - * \\brief Compute white balance gains from a colour temperature\n> - * \\param[in] colourTemperature The colour temperature in Kelvin\n> + * \\class AwbAlgorithmBase\n> + * \\brief Base class for AwbAlgorithm for non-templated functions implementation\n>   *\n> - * Compute the white balance gains from a \\a colourTemperature. This function\n> - * does not take any statistics into account. It is used to compute the colour\n> - * gains when the user manually specifies a colour temperature.\n> + * Base class for AwbAlgorithm where non-templated functions are implemented.\n> + * IPA implementations shall use AwbAlgorithm and not this class.\n> + */\n> +\n> +/**\n> + * \\brief Initialize the algorithm with the given tuning data\n> + * \\param[in] tuningData The tuning data to use for the algorithm\n> + *\n> + * Parse \\a tuningData to initialize the awb algorithm and register controls.\n> + * IPA modules are expected to call this function as part of their\n> + * implementation of Algorithm::init().\n>   *\n> - * \\return The colour gains or std::nullopt if the conversion is not possible\n> + * \\return 0 on success, a negative error code otherwise\n> + */\n> +int AwbAlgorithmBase::init(const ValueNode &tuningData)\n> +{\n> +\tbayes_ = false;\n> +\n> +\tif (!tuningData.contains(\"algorithm\"))\n> +\t\tLOG(Awb, Info) << \"No Awb algorithm specified.\"\n> +\t\t\t       << \" Default to grey world\";\n\nThe message punctuation is weird, how about \"No Awb algorithm specified,\nusing grey world\"?\n\n> +\n> +\tauto mode = tuningData[\"algorithm\"].get<std::string>(\"grey\");\n> +\tif (mode == \"grey\") {\n> +\t\timpl_ = std::make_unique<AwbGrey>();\n> +\t} else if (mode == \"bayes\") {\n> +\t\timpl_ = std::make_unique<AwbBayes>();\n> +\t\tbayes_ = true;\n> +\t} else {\n> +\t\tLOG(Awb, Error) << \"Unknown Awb algorithm: \" << mode;\n> +\t\treturn -EINVAL;\n> +\t}\n> +\n> +\tLOG(Awb, Debug) << \"Using Awb algorithm: \" << mode;\n> +\n> +\tint ret = impl_->init(tuningData);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tcontrols_[&controls::ColourTemperature] =\n> +\t\tControlInfo(kMinColourTemperature, kMaxColourTemperature,\n> +\t\t\t    kDefaultColourTemperature);\n> +\tcontrols_[&controls::AwbEnable] = ControlInfo(false, true);\n> +\n> +\treturn parseModeConfigs(tuningData, controls::AwbAuto);\n> +}\n> +\n> +/**\n> + * \\brief Configure the awb algorithm\n> + * \\param[in] state The awb active state\n> + * \\param[in] session The awb session configuration\n> + * \\return 0 if successful, an error code otherwise\n>   */\n> +int AwbAlgorithmBase::configure(awb::ActiveState &state, awb::Session &session)\n> +{\n> +\tstate.manual.gains = RGB<double>{ 1.0 };\n> +\tauto gains = impl_->gainsFromColourTemperature(kDefaultColourTemperature);\n> +\tif (gains)\n> +\t\tstate.automatic.gains = *gains;\n> +\telse\n> +\t\tstate.automatic.gains = RGB<double>{ 1.0 };\n> +\n> +\tstate.autoEnabled = true;\n> +\tstate.manual.temperatureK = kDefaultColourTemperature;\n> +\tstate.automatic.temperatureK = kDefaultColourTemperature;\n> +\n> +\tsession.enabled = true;\n> +\n> +\treturn 0;\n> +}\n>  \n>  /**\n> - * \\fn AwbAlgorithm::controls()\n> - * \\brief Get the controls info map for this algorithm\n> + * \\brief Queue a Request to the awb algorithm\n> + * \\param[in] state The awb active state\n> + * \\param[in] frame The frame number\n> + * \\param[in] frameContext The awb frame context\n> + * \\param[in] controls The list of controls part of the Request\n>   *\n> - * \\return The controls info map\n> + * Queue a new Request to the awb algorithm and modify its behaviour according\n> + * to the provided controls.\n> + *\n> + * The currently handled controls are:\n> + * - controls::AwbEnable\n> + * - controls::AwbMode\n> + * - controls::ColourGains\n> + * - controls::ColourTemperature\n>   */\n> +void AwbAlgorithmBase::queueRequest(awb::ActiveState &state,\n> +\t\t\t\t    [[maybe_unused]] const uint32_t frame,\n> +\t\t\t\t    awb::FrameContext &frameContext,\n> +\t\t\t\t    const ControlList &controls)\n> +{\n> +\tconst auto &awbEnable = controls.get(controls::AwbEnable);\n> +\tif (awbEnable && *awbEnable != state.autoEnabled) {\n> +\t\tstate.autoEnabled = *awbEnable;\n> +\n> +\t\tLOG(Awb, Debug)\n> +\t\t\t<< (*awbEnable ? \"Enabling\" : \"Disabling\") << \" Awb\";\n> +\t}\n> +\n> +\t/* Only AwbBayes registers the AwbMode control. */\n\nIs it true?  It's registered in AwbAlgorithmBase.\n\n> +\tauto mode = controls.get(controls::AwbMode);\n> +\tif (mode) {\n> +\t\tauto it = modes_.find(static_cast<controls::AwbModeEnum>(*mode));\n> +\t\tif (it == modes_.end()) {\n> +\t\t\tLOG(Awb, Error) << \"Unsupported Awb mode \" << *mode;\n> +\t\t\treturn;\n> +\t\t}\n> +\n> +\t\tcurrentMode_ = &it->second;\n> +\t}\n> +\n> +\tframeContext.autoEnabled = state.autoEnabled;\n> +\n> +\tif (frameContext.autoEnabled)\n> +\t\treturn;\n> +\n> +\tconst auto &colourGains = controls.get(controls::ColourGains);\n> +\tconst auto &colourTemperature = controls.get(controls::ColourTemperature);\n> +\tbool update = false;\n> +\tif (colourGains) {\n> +\t\tstate.manual.gains.r() = (*colourGains)[0];\n> +\t\tstate.manual.gains.b() = (*colourGains)[1];\n> +\t\t/*\n> +\t\t * \\todo Colour temperature reported in metadata is now\n> +\t\t * incorrect, as we can't deduce the temperature from the gains.\n> +\t\t * This will be fixed with the bayes AWB algorithm.\n> +\t\t */\n> +\t\tupdate = true;\n> +\t} else if (colourTemperature) {\n> +\t\tstate.manual.temperatureK = *colourTemperature;\n> +\t\tconst auto &gains = impl_->gainsFromColourTemperature(*colourTemperature);\n> +\t\tif (gains) {\n> +\t\t\tstate.manual.gains.r() = gains->r();\n> +\t\t\tstate.manual.gains.b() = gains->b();\n> +\t\t\tupdate = true;\n> +\t\t}\n> +\t}\n> +\n> +\tif (update)\n> +\t\tLOG(Awb, Debug)\n> +\t\t\t<< \"Set colour gains to \" << state.manual.gains;\n> +\n> +\tframeContext.gains = state.manual.gains;\n> +\tframeContext.temperatureK = state.manual.temperatureK;\n> +}\n>  \n>  /**\n> - * \\fn AwbAlgorithm::handleControls()\n> - * \\param[in] controls The controls to handle\n> - * \\brief Handle the controls supplied in a request\n> + * \\brief Set the gains and colour temperature values in \\a frameContext\n> + * \\param[in] state The awb active state\n> + * \\param[in] frameContext The awb frame context\n> + *\n> + * If auto mode is enabled, take the most recently computed gains and use them\n> + * for the current frame. Otherwise, if in manual mode, gains and colour\n> + * temperature for a frame are set at queueRequest() time.\n>   */\n> +void AwbAlgorithmBase::prepare(awb::ActiveState &state,\n> +\t\t\t       awb::FrameContext &frameContext)\n> +{\n> +\tif (frameContext.autoEnabled) {\n> +\t\tframeContext.gains = state.automatic.gains;\n> +\t\tframeContext.temperatureK = state.automatic.temperatureK;\n> +\t}\n> +}\n>  \n>  /**\n> + * \\brief Process awb statistics to calculate gains and populate metadata\n> + * \\param[in] state The awb active state\n> + * \\param[in] frameContext The awb frame context\n> + * \\param[in] stats The awb statistics\n> + * \\param[in] lux The lux value as estimated by the IPA module\n> + * \\param[out] metadata The metadata list\n> + *\n> + * Process \\a stats to calculate new gains and colour temperature and populate\n> + * \\a metadata with the results.\n> + */\n> +void AwbAlgorithmBase::process(awb::ActiveState &state,\n> +\t\t\t       awb::FrameContext &frameContext,\n> +\t\t\t       const AwbStats &stats, unsigned int lux,\n> +\t\t\t       ControlList &metadata)\n> +{\n> +\tif (!stats.valid())\n> +\t\treturn;\n> +\n> +\tauto awbResult = impl_->calculateAwb(stats, lux, { currentMode_->ctLo,\n> +\t\t\t\t\t\t\t   currentMode_->ctHi} );\n> +\n> +\t/*\n> +\t * Clamp the gain values to the hardware, according to the gainmin_\n> +\t * and gainmax_ values.\n> +\t */\n> +\tawbResult.gains = awbResult.gains.max(gainmin_).min(gainmax_);\n> +\n> +\t/* Smooth color gains adjustments. */\n> +\tdouble speed = 0.2;\n> +\tdouble ct = awbResult.colourTemperature;\n> +\tct = ct * speed + state.automatic.temperatureK * (1 - speed);\n> +\n> +\tstate.automatic.temperatureK = awbResult.colourTemperature;\n> +\tstate.automatic.gains = awbResult.gains * speed +\n> +\t\t\t\tstate.automatic.gains * (1 - speed);\n> +\n> +\t/* Populate metadata. */\n> +\tmetadata.set(controls::AwbEnable, frameContext.autoEnabled);\n> +\tmetadata.set(controls::ColourGains, { static_cast<float>(frameContext.gains.r()),\n> +\t\t\t\t\t      static_cast<float>(frameContext.gains.b()) });\n> +\tmetadata.set(controls::ColourTemperature, frameContext.temperatureK);\n> +\n> +\tLOG(Awb, Debug) << std::showpoint << \"Means \" << stats.rgbMeans()\n> +\t\t\t<< \", gains \" << state.automatic.gains\n> +\t\t\t<< \", temp \" << state.automatic.temperatureK << \"K\";\n> +}\n> +\n> +/*\n>   * \\brief Parse the mode configurations from the tuning data\n>   * \\param[in] tuningData the ValueNode representing the tuning data\n>   * \\param[in] def The default value for the AwbMode control\n>   *\n>   * Utility function to parse the tuning data for an AwbMode entry and read all\n> - * provided modes. It adds controls::AwbMode to AwbAlgorithm::controls_ and\n> - * populates AwbAlgorithm::modes_. For a list of possible modes see \\ref\n> + * provided modes. It adds controls::AwbMode to AwbAlgorithmBase::controls_ and\n> + * populates AwbAlgorithmBase::modes_. For a list of possible modes see \\ref\n>   * controls::AwbModeEnum.\n>   *\n>   * Each mode entry must contain a \"lo\" and \"hi\" key to specify the lower and\n> @@ -156,15 +536,23 @@ namespace ipa {\n>   *       ...\n>   * \\endcode\n>   *\n> - * If \\a def is supplied but not contained in the the \\a tuningData, -EINVAL is\n> + * If \\a def is supplied but not contained in the \\a tuningData, -EINVAL is\n>   * returned.\n>   *\n> + * AwbModes are only used by the AwbBayes implementation.\n> + *\n>   * \\sa controls::AwbModeEnum\n>   * \\return Zero on success, negative error code otherwise\n>   */\n> -int AwbAlgorithm::parseModeConfigs(const ValueNode &tuningData,\n> +int AwbAlgorithmBase::parseModeConfigs(const ValueNode &tuningData,\n>  \t\t\t\t   const ControlValue &def)\n>  {\n> +\tif (!bayes_) {\n\nThe `bayes_' thing doesn't look pretty.  What if we add another white\nbalance algorithm?  When `bayes_' is set, `impl_' is already set too,\ncould it be queried for this information?\n\n> +\t\t/* AwbGrey does not support and does not use modes. */\n> +\t\tcurrentMode_ = &AwbGreyMode;\n> +\t\treturn 0;\n> +\t}\n> +\n>  \tstd::vector<ControlValue> availableModes;\n>  \n>  \tconst ValueNode &yamlModes = tuningData[controls::AwbMode.name()];\n> @@ -227,37 +615,83 @@ int AwbAlgorithm::parseModeConfigs(const ValueNode &tuningData,\n>  \t}\n>  \n>  \tcontrols_[&controls::AwbMode] = ControlInfo(availableModes, def);\n> +\tcurrentMode_ = &modes_[controls::AwbAuto];\n>  \n>  \treturn 0;\n>  }\n>  \n>  /**\n> - * \\class AwbAlgorithm::ModeConfig\n> - * \\brief Holds the configuration of a single AWB mode\n> + * \\var AwbAlgorithmBase::controls_\n> + * \\brief Controls info map for the controls registered by the awb algorithm\n> + */\n> +\n> +/**\n> + * \\var AwbAlgorithmBase::gainmin_\n> + * \\brief The minimum supported gain value\n> + *\n> + * Minimum gain value used to clamp the awb algorithm calculation results in the\n> + * range supported by the platform awb engine.\n>   *\n> - * AWB modes limit the regulation of the AWB algorithm to a specific range of\n> - * colour temperatures.\n> + * The min and max gain values are initialized by AwbAlgorithm::init().\n>   */\n>  \n>  /**\n> - * \\var AwbAlgorithm::ModeConfig::ctLo\n> + * \\var AwbAlgorithmBase::gainmax_\n> + * \\brief The maximum supported gain value\n> + *\n> + * Maximum gain value used to clamp the awb algorithm calculation results in the\n> + * range supported by the platform awb engine.\n> + *\n> + * The min and max gain values are initialized by AwbAlgorithm::init().\n> + */\n> +\n> +/*\n> + * \\class AwbAlgorithmBase::ModeConfig\n> + * \\brief Holds the configuration of a single awb mode\n> + *\n> + * AWB modes limit the regulation of the awb algorithm to a specific range of\n> + * colour temperatures. Use by AwbBayes only.\n> + */\n> +\n> +/*\n> + * \\var AwbAlgorithmBase::ModeConfig::ctLo\n>   * \\brief The lowest valid colour temperature of that mode\n>   */\n>  \n> -/**\n> - * \\var AwbAlgorithm::ModeConfig::ctHi\n> +/*\n> + * \\var AwbAlgorithmBase::ModeConfig::ctHi\n>   * \\brief The highest valid colour temperature of that mode\n>   */\n>  \n> +/*\n> + * \\var AwbAlgorithmBase::modes_\n> + * \\brief Map of all configured modes\n> + * \\sa AwbAlgorithmBase::parseModeConfigs\n> + */\n> +\n>  /**\n> - * \\var AwbAlgorithm::controls_\n> - * \\brief Controls info map for the controls provided by the algorithm\n> + * \\class AwbAlgorithm\n> + * \\brief The libipa awb algorithm\n> + * \\tparam Q The fixedpoint register representation of gain values\n> + *\n> + * Implement the awb algorithm for libipa.\n> + *\n> + * The AwbAlgorithm class implement an interface similar in spirit to the one\n\ns/implement/implements/\n\n> + * of the Algorithm class. IPA modules are expected to store an instance of\n> + * AwbAlgorithm as class member, template it with the awb engine gain register\n> + * representation and call its function in their implementations of the\n> + * Algorithm interface.\n> + *\n> + * The AwbAlgorithm instantiate an AwbImplementation implementation (AwbGrey or\n\ns/instantiate/instantiates/\n\n> + * AwbBayes) at init() time by parsing the tuning data and uses it to compute\n> + * the RGB gains and estimate a colour temperature given a set of statistics\n> + * in the form of a AwbStats derived implementation.\n>   */\n>  \n>  /**\n> - * \\var AwbAlgorithm::modes_\n> - * \\brief Map of all configured modes\n> - * \\sa AwbAlgorithm::parseModeConfigs\n> + * \\fn AwbAlgorithm::init()\n> + * \\param[in] controls The info map of the IPA controls\n> + * \\copydoc AwbAlgorithmBase::init()\n>   */\n>  \n>  } /* namespace ipa */\n> diff --git a/src/ipa/libipa/awb.h b/src/ipa/libipa/awb.h\n> index 09c00e47d604..622640318d5d 100644\n> --- a/src/ipa/libipa/awb.h\n> +++ b/src/ipa/libipa/awb.h\n> @@ -2,11 +2,12 @@\n>  /*\n>   * Copyright (C) 2024 Ideas on Board Oy\n>   *\n> - * Generic AWB algorithms\n> + * libIPA AWB algorithms\n>   */\n>  \n>  #pragma once\n>  \n> +#include <array>\n>  #include <map>\n>  #include <optional>\n>  \n> @@ -20,46 +21,130 @@ namespace libcamera {\n>  \n>  namespace ipa {\n>  \n> -struct AwbResult {\n> +namespace awb {\n> +\n> +struct Session {\n> +\tbool enabled;\n> +};\n> +\n> +struct ActiveState {\n> +\tstruct AwbState {\n> +\t\tRGB<double> gains;\n> +\t\tunsigned int temperatureK;\n> +\t};\n> +\n> +\tAwbState manual;\n> +\tAwbState automatic;\n> +\n> +\tbool autoEnabled;\n> +};\n> +\n> +struct FrameContext {\n>  \tRGB<double> gains;\n> -\tdouble colourTemperature;\n> +\tbool autoEnabled;\n> +\tunsigned int temperatureK;\n>  };\n>  \n> +} /* namespace awb */\n> +\n>  struct AwbStats {\n> -\tvirtual double computeColourError(const RGB<double> &gains) const = 0;\n> -\tvirtual RGB<double> rgbMeans() const = 0;\n> +\tAwbStats() = default;\n> +\tAwbStats(const RGB<double> &means);\n> +\tvirtual ~AwbStats() = default;\n> +\n> +\tbool valid() const;\n> +\n> +\tvirtual double rgRatio() const { return rg_; }\n> +\tvirtual double bgRatio() const { return bg_; }\n> +\tvirtual double computeColourError(const RGB<double> &gains) const;\n> +\tvirtual RGB<double> rgbMeans() const;\n>  \n>  protected:\n> -\t~AwbStats() = default;\n> +\tvirtual double minColourValue() const = 0;\n> +\n> +\tRGB<double> rgbMeans_;\n> +\tdouble rg_;\n> +\tdouble bg_;\n>  };\n>  \n> -class AwbAlgorithm\n> +class AwbImplementation\n>  {\n>  public:\n> -\tvirtual ~AwbAlgorithm() = default;\n> +\tstruct AwbResult {\n> +\t\tRGB<double> gains;\n> +\t\tdouble colourTemperature;\n> +\t};\n>  \n> +\tvirtual ~AwbImplementation() = default;\n>  \tvirtual int init(const ValueNode &tuningData) = 0;\n> -\tvirtual AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) = 0;\n> -\tvirtual std::optional<RGB<double>> gainsFromColourTemperature(double colourTemperature) = 0;\n> +\tvirtual AwbResult calculateAwb(const AwbStats &stats, unsigned int lux,\n> +\t\t\t\t       std::array<double, 2> ranges) = 0;\n> +\tvirtual std::optional<RGB<double>>\n> +\tgainsFromColourTemperature(double temperatureK) = 0;\n> +};\n>  \n> -\tconst ControlInfoMap::Map &controls() const\n> -\t{\n> -\t\treturn controls_;\n> -\t}\n> +class AwbAlgorithmBase\n> +{\n> +public:\n> +\tint init(const ValueNode &tuningData);\n> +\n> +\tint configure(awb::ActiveState &state, awb::Session &session);\n> +\n> +\tvoid queueRequest(awb::ActiveState &state,\n> +\t\t\t  const uint32_t frame,\n> +\t\t\t  awb::FrameContext &frameContext,\n> +\t\t\t  const ControlList &controls);\n>  \n> -\tvirtual void handleControls([[maybe_unused]] const ControlList &controls) {}\n> +\tvoid prepare(awb::ActiveState &state, awb::FrameContext &frameContext);\n> +\n> +\tvoid process(awb::ActiveState &state, awb::FrameContext &frameContext,\n> +\t\t     const AwbStats &stats, unsigned int lux,\n> +\t\t     ControlList &metadata);\n>  \n>  protected:\n> -\tint parseModeConfigs(const ValueNode &tuningData,\n> -\t\t\t     const ControlValue &def = {});\n> +\tAwbAlgorithmBase() = default;\n> +\n> +\tControlInfoMap::Map controls_;\n> +\tfloat gainmin_;\n> +\tfloat gainmax_;\n\nThe mixture of float and double types for gains is probably mostly\nharmless, but still, is there any chance to unify them to either of them\neasily?\n\n>  \n> +private:\n>  \tstruct ModeConfig {\n>  \t\tdouble ctHi;\n>  \t\tdouble ctLo;\n>  \t};\n>  \n> -\tControlInfoMap::Map controls_;\n> -\tstd::map<controls::AwbModeEnum, AwbAlgorithm::ModeConfig> modes_;\n> +\t/* AwbGrey does not support modes; */\n> +\tstatic constexpr ModeConfig AwbGreyMode = { 0.0, 0.0 };\n> +\n> +\tint parseModeConfigs(const ValueNode &tuningData,\n> +\t\t\t     const ControlValue &def = {});\n> +\n> +\tstd::map<controls::AwbModeEnum, AwbAlgorithmBase::ModeConfig> modes_;\n> +\tconst ModeConfig *currentMode_ = nullptr;\n> +\tstd::unique_ptr<AwbImplementation> impl_;\n> +\tbool bayes_ = false;\n> +};\n> +\n> +template<typename Q>\n> +class AwbAlgorithm : public AwbAlgorithmBase\n> +{\n> +public:\n> +\tint init(const ValueNode &tuningData, ControlInfoMap::Map &controls)\n> +\t{\n> +\t\tAwbAlgorithmBase::init(tuningData);\n> +\n> +\t\tgainmin_ = std::max(Q::TraitsType::min, 1.0f);\n\nWhy the 1.0 limit?\n\n> +\t\tgainmax_ = Q::TraitsType::max;\n> +\n> +\t\tcontrols_[&controls::ColourGains] =\n> +\t\t\tControlInfo(gainmin_, gainmax_,\n> +\t\t\t\t    Span<const float, 2>{ { 1.0f, 1.0f } });\n> +\n> +\t\tcontrols.insert(controls_.begin(), controls_.end());\n> +\n> +\t\treturn 0;\n> +\t}\n>  };\n>  \n>  } /* namespace ipa */\n> diff --git a/src/ipa/libipa/awb_bayes.cpp b/src/ipa/libipa/awb_bayes.cpp\n> index 9fd85e5a4505..c740663fa381 100644\n> --- a/src/ipa/libipa/awb_bayes.cpp\n> +++ b/src/ipa/libipa/awb_bayes.cpp\n> @@ -140,11 +140,6 @@ void Interpolator<Pwl>::interpolate(const Pwl &a, const Pwl &b, Pwl &dest, doubl\n>   * \\brief How far to wander off CT curve towards \"more green\"\n>   */\n>  \n> -/**\n> - * \\var AwbBayes::currentMode_\n> - * \\brief The currently selected mode\n> - */\n> -\n>  int AwbBayes::init(const ValueNode &tuningData)\n>  {\n>  \tint ret = colourGainCurve_.readYaml(tuningData[\"colourGains\"], \"ct\", \"gains\");\n> @@ -172,14 +167,6 @@ int AwbBayes::init(const ValueNode &tuningData)\n>  \t\treturn ret;\n>  \t}\n>  \n> -\tret = parseModeConfigs(tuningData, controls::AwbAuto);\n> -\tif (ret) {\n> -\t\tLOG(Awb, Error)\n> -\t\t\t<< \"Failed to parse mode parameter from tuning file\";\n> -\t\treturn ret;\n> -\t}\n> -\tcurrentMode_ = &modes_[controls::AwbAuto];\n> -\n>  \ttransversePos_ = tuningData[\"transversePos\"].get<double>(0.01);\n>  \ttransverseNeg_ = tuningData[\"transverseNeg\"].get<double>(0.01);\n>  \tif (transversePos_ <= 0 || transverseNeg_ <= 0) {\n> @@ -260,18 +247,6 @@ int AwbBayes::readPriors(const ValueNode &tuningData)\n>  \treturn 0;\n>  }\n>  \n> -void AwbBayes::handleControls(const ControlList &controls)\n> -{\n> -\tauto mode = controls.get(controls::AwbMode);\n> -\tif (mode) {\n> -\t\tauto it = modes_.find(static_cast<controls::AwbModeEnum>(*mode));\n> -\t\tif (it != modes_.end())\n> -\t\t\tcurrentMode_ = &it->second;\n> -\t\telse\n> -\t\t\tLOG(Awb, Error) << \"Unsupported AWB mode \" << *mode;\n> -\t}\n> -}\n> -\n>  std::optional<RGB<double>> AwbBayes::gainsFromColourTemperature(double colourTemperature)\n>  {\n>  \t/*\n> @@ -283,7 +258,9 @@ std::optional<RGB<double>> AwbBayes::gainsFromColourTemperature(double colourTem\n>  \treturn RGB<double>{ { gains[0], 1.0, gains[1] } };\n>  }\n>  \n> -AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux)\n> +AwbImplementation::AwbResult\n> +AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux,\n> +\t\t       std::array<double, 2> ranges)\n>  {\n>  \tipa::Pwl prior;\n>  \tif (lux > 0) {\n> @@ -295,7 +272,7 @@ AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux)\n>  \t\tprior.append(0, 1.0);\n>  \t}\n>  \n> -\tdouble t = coarseSearch(prior, stats);\n> +\tdouble t = coarseSearch(prior, stats, ranges);\n>  \tdouble r = ctR_.eval(t);\n>  \tdouble b = ctB_.eval(t);\n>  \tLOG(Awb, Debug)\n> @@ -319,11 +296,12 @@ AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux)\n>  \treturn { { { 1.0 / r, 1.0, 1.0 / b } }, t };\n>  }\n>  \n> -double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) const\n> +double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats,\n> +\t\t\t      std::array<double, 2> ranges) const\n>  {\n>  \tstd::vector<Pwl::Point> points;\n>  \tsize_t bestPoint = 0;\n> -\tdouble t = currentMode_->ctLo;\n> +\tdouble t = ranges[0];\n>  \tint spanR = -1;\n>  \tint spanB = -1;\n>  \tLimitsRecorder<double> errorLimits;\n> @@ -345,14 +323,14 @@ double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) cons\n>  \t\tif (points.back().y() < points[bestPoint].y())\n>  \t\t\tbestPoint = points.size() - 1;\n>  \n> -\t\tif (t == currentMode_->ctHi)\n> +\t\tif (t == ranges[1])\n>  \t\t\tbreak;\n>  \n>  \t\t/*\n>  \t\t * Ensure even steps along the r/b curve by scaling them by the\n>  \t\t * current t.\n>  \t\t */\n> -\t\tt = std::min(t + t / 10 * kSearchStep, currentMode_->ctHi);\n> +\t\tt = std::min(t + t / 10 * kSearchStep, ranges[1]);\n>  \t}\n>  \n>  \tt = points[bestPoint].x();\n> diff --git a/src/ipa/libipa/awb_bayes.h b/src/ipa/libipa/awb_bayes.h\n> index 1e3373676bc0..fdf55dcb553f 100644\n> --- a/src/ipa/libipa/awb_bayes.h\n> +++ b/src/ipa/libipa/awb_bayes.h\n> @@ -20,22 +20,23 @@ namespace libcamera {\n>  \n>  namespace ipa {\n>  \n> -class AwbBayes : public AwbAlgorithm\n> +class AwbBayes : public AwbImplementation\n>  {\n>  public:\n>  \tAwbBayes() = default;\n>  \n>  \tint init(const ValueNode &tuningData) override;\n> -\tAwbResult calculateAwb(const AwbStats &stats, unsigned int lux) override;\n> +\tAwbResult calculateAwb(const AwbStats &stats, unsigned int lux,\n> +\t\t\t       std::array<double, 2> ranges) override;\n>  \tstd::optional<RGB<double>> gainsFromColourTemperature(double temperatureK) override;\n> -\tvoid handleControls(const ControlList &controls) override;\n>  \n>  private:\n>  \tint readPriors(const ValueNode &tuningData);\n>  \n>  \tvoid fineSearch(double &t, double &r, double &b, ipa::Pwl const &prior,\n>  \t\t\tconst AwbStats &stats) const;\n> -\tdouble coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) const;\n> +\tdouble coarseSearch(const ipa::Pwl &prior, const AwbStats &stats,\n> +\t\t\t    std::array<double, 2> ranges) const;\n>  \tdouble interpolateQuadratic(ipa::Pwl::Point const &a,\n>  \t\t\t\t    ipa::Pwl::Point const &b,\n>  \t\t\t\t    ipa::Pwl::Point const &c) const;\n> @@ -50,8 +51,6 @@ private:\n>  \n>  \tdouble transversePos_;\n>  \tdouble transverseNeg_;\n> -\n> -\tModeConfig *currentMode_ = nullptr;\n>  };\n>  \n>  } /* namespace ipa */\n> diff --git a/src/ipa/libipa/awb_grey.cpp b/src/ipa/libipa/awb_grey.cpp\n> index b14b096810ae..76c8a2231ac1 100644\n> --- a/src/ipa/libipa/awb_grey.cpp\n> +++ b/src/ipa/libipa/awb_grey.cpp\n> @@ -60,6 +60,7 @@ int AwbGrey::init(const ValueNode &tuningData)\n>   * \\brief Calculate AWB data from the given statistics\n>   * \\param[in] stats The statistics to use for the calculation\n>   * \\param[in] lux The lux value of the scene\n> + * \\param[in] ranges The colour temperature search limits (AwbBayes only)\n>   *\n>   * The colour temperature is estimated based on the colours::estimateCCT()\n>   * function. The gains are calculated purely based on the RGB means provided by\n> @@ -70,20 +71,23 @@ int AwbGrey::init(const ValueNode &tuningData)\n>   *\n>   * \\return The AWB result\n>   */\n> -AwbResult AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned int lux)\n> +AwbImplementation::AwbResult\n> +AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned int lux,\n> +\t\t      [[maybe_unused]] std::array<double, 2> ranges)\n>  {\n>  \tAwbResult result;\n>  \tauto means = stats.rgbMeans();\n>  \tresult.colourTemperature = estimateCCT(means);\n>  \n>  \t/*\n> -\t * Estimate the red and blue gains to apply in a grey world. The green\n> -\t * gain is hardcoded to 1.0. Avoid divisions by zero by clamping the\n> -\t * divisor to a minimum value of 1.0.\n> +\t * Calculate the red and blue gains to apply in a grey world by simply\n> +\t * inverting the red/green and blue/green ratios as reported in\n> +\t * statistics. The green gain is hardcoded to 1.0.\n>  \t */\n> -\tresult.gains.r() = means.g() / std::max(means.r(), 1.0);\n> +\tresult.gains.r() = 1.0 / stats.rgRatio();\n>  \tresult.gains.g() = 1.0;\n> -\tresult.gains.b() = means.g() / std::max(means.b(), 1.0);\n> +\tresult.gains.b() = 1.0 / stats.bgRatio();\n> +\n>  \treturn result;\n>  }\n>  \n> diff --git a/src/ipa/libipa/awb_grey.h b/src/ipa/libipa/awb_grey.h\n> index 154a2af97f15..ceeee237cb30 100644\n> --- a/src/ipa/libipa/awb_grey.h\n> +++ b/src/ipa/libipa/awb_grey.h\n> @@ -18,13 +18,14 @@ namespace libcamera {\n>  \n>  namespace ipa {\n>  \n> -class AwbGrey : public AwbAlgorithm\n> +class AwbGrey : public AwbImplementation\n>  {\n>  public:\n>  \tAwbGrey() = default;\n>  \n>  \tint init(const ValueNode &tuningData) override;\n> -\tAwbResult calculateAwb(const AwbStats &stats, unsigned int lux) override;\n> +\tAwbResult calculateAwb(const AwbStats &stats, unsigned int lux,\n> +\t\t\t       std::array<double, 2> ranges) override;\n>  \tstd::optional<RGB<double>> gainsFromColourTemperature(double colourTemperature) override;\n>  \n>  private:\n> diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp\n> index 5ae5b6471643..be0caaf7bd40 100644\n> --- a/src/ipa/rkisp1/algorithms/awb.cpp\n> +++ b/src/ipa/rkisp1/algorithms/awb.cpp\n> @@ -8,17 +8,12 @@\n>  #include \"awb.h\"\n>  \n>  #include <algorithm>\n> -#include <ios>\n>  \n>  #include <libcamera/base/log.h>\n>  \n> -#include <libcamera/control_ids.h>\n> -\n>  #include <libcamera/ipa/core_ipa_interface.h>\n>  \n> -#include \"libipa/awb_bayes.h\"\n> -#include \"libipa/awb_grey.h\"\n> -#include \"libipa/colours.h\"\n> +#include \"libcamera/internal/vector.h\"\n>  \n>  /**\n>   * \\file awb.h\n> @@ -28,54 +23,29 @@ namespace libcamera {\n>  \n>  namespace ipa::rkisp1::algorithms {\n>  \n> -/**\n> - * \\class Awb\n> - * \\brief Manage the white balance with automatic and manual controls\n> - */\n> -\n>  LOG_DEFINE_CATEGORY(RkISP1Awb)\n>  \n> -constexpr int32_t kMinColourTemperature = 2500;\n> -constexpr int32_t kMaxColourTemperature = 10000;\n> -constexpr int32_t kDefaultColourTemperature = 5000;\n> -\n> -/* Minimum mean value below which AWB can't operate. */\n> -constexpr double kMeanMinThreshold = 2.0;\n> -\n>  class RkISP1AwbStats final : public AwbStats\n>  {\n>  public:\n> -\tRkISP1AwbStats(const RGB<double> &rgbMeans)\n> -\t\t: rgbMeans_(rgbMeans)\n> +\tRkISP1AwbStats() = default;\n> +\tRkISP1AwbStats(const RGB<double> means)\n> +\t\t: AwbStats(means)\n>  \t{\n> -\t\trg_ = rgbMeans_.r() / rgbMeans_.g();\n> -\t\tbg_ = rgbMeans_.b() / rgbMeans_.g();\n>  \t}\n>  \n> -\tdouble computeColourError(const RGB<double> &gains) const override\n> +\t/* Minimum mean value below which AWB can't operate. */\n> +\tdouble minColourValue() const override\n>  \t{\n> -\t\t/*\n> -\t\t * Compute the sum of the squared colour error (non-greyness) as\n> -\t\t * it appears in the log likelihood equation.\n> -\t\t */\n> -\t\tdouble deltaR = gains.r() * rg_ - 1.0;\n> -\t\tdouble deltaB = gains.b() * bg_ - 1.0;\n> -\t\tdouble delta2 = deltaR * deltaR + deltaB * deltaB;\n> -\n> -\t\treturn delta2;\n> -\t}\n> -\n> -\tRGB<double> rgbMeans() const override\n> -\t{\n> -\t\treturn rgbMeans_;\n> +\t\treturn 2.0;\n>  \t}\n> -\n> -private:\n> -\tRGB<double> rgbMeans_;\n> -\tdouble rg_;\n> -\tdouble bg_;\n>  };\n>  \n> +/**\n> + * \\class Awb\n> + * \\brief Manage the white balance with automatic and manual controls\n> + */\n> +\n>  Awb::Awb()\n>  \t: rgbMode_(false)\n>  {\n> @@ -86,40 +56,7 @@ Awb::Awb()\n>   */\n>  int Awb::init(IPAContext &context, const ValueNode &tuningData)\n>  {\n> -\tauto &cmap = context.ctrlMap;\n> -\tcmap[&controls::ColourTemperature] = ControlInfo(kMinColourTemperature,\n> -\t\t\t\t\t\t\t kMaxColourTemperature,\n> -\t\t\t\t\t\t\t kDefaultColourTemperature);\n> -\tcmap[&controls::AwbEnable] = ControlInfo(false, true);\n> -\n> -\tcmap[&controls::ColourGains] = ControlInfo(0.0f, 3.996f,\n> -\t\t\t\t\t\t   Span<const float, 2>{ { 1.0f, 1.0f } });\n> -\n> -\tif (!tuningData.contains(\"algorithm\"))\n> -\t\tLOG(RkISP1Awb, Info) << \"No AWB algorithm specified.\"\n> -\t\t\t\t     << \" Default to grey world\";\n> -\n> -\tauto mode = tuningData[\"algorithm\"].get<std::string>(\"grey\");\n> -\tif (mode == \"grey\") {\n> -\t\tawbAlgo_ = std::make_unique<AwbGrey>();\n> -\t} else if (mode == \"bayes\") {\n> -\t\tawbAlgo_ = std::make_unique<AwbBayes>();\n> -\t} else {\n> -\t\tLOG(RkISP1Awb, Error) << \"Unknown AWB algorithm: \" << mode;\n> -\t\treturn -EINVAL;\n> -\t}\n> -\tLOG(RkISP1Awb, Debug) << \"Using AWB algorithm: \" << mode;\n> -\n> -\tint ret = awbAlgo_->init(tuningData);\n> -\tif (ret) {\n> -\t\tLOG(RkISP1Awb, Error) << \"Failed to init AWB algorithm\";\n> -\t\treturn ret;\n> -\t}\n> -\n> -\tconst auto &src = awbAlgo_->controls();\n> -\tcmap.insert(src.begin(), src.end());\n> -\n> -\treturn 0;\n> +\treturn awbAlgo_.init(tuningData, context.ctrlMap);\n>  }\n>  \n>  /**\n> @@ -128,16 +65,7 @@ int Awb::init(IPAContext &context, const ValueNode &tuningData)\n>  int Awb::configure(IPAContext &context,\n>  \t\t   const IPACameraSensorInfo &configInfo)\n>  {\n> -\tcontext.activeState.awb.manual.gains = RGB<double>{ 1.0 };\n> -\tauto gains = awbAlgo_->gainsFromColourTemperature(kDefaultColourTemperature);\n> -\tif (gains)\n> -\t\tcontext.activeState.awb.automatic.gains = *gains;\n> -\telse\n> -\t\tcontext.activeState.awb.automatic.gains = RGB<double>{ 1.0 };\n> -\n> -\tcontext.activeState.awb.autoEnabled = true;\n> -\tcontext.activeState.awb.manual.temperatureK = kDefaultColourTemperature;\n> -\tcontext.activeState.awb.automatic.temperatureK = kDefaultColourTemperature;\n> +\tawbAlgo_.configure(context.activeState.awb, context.configuration.awb);\n>  \n>  \t/*\n>  \t * Define the measurement window for AWB as a centered rectangle\n> @@ -148,64 +76,18 @@ int Awb::configure(IPAContext &context,\n>  \tcontext.configuration.awb.measureWindow.h_size = 3 * configInfo.outputSize.width / 4;\n>  \tcontext.configuration.awb.measureWindow.v_size = 3 * configInfo.outputSize.height / 4;\n>  \n> -\tcontext.configuration.awb.enabled = true;\n> -\n>  \treturn 0;\n>  }\n>  \n>  /**\n>   * \\copydoc libcamera::ipa::Algorithm::queueRequest\n>   */\n> -void Awb::queueRequest(IPAContext &context,\n> -\t\t       [[maybe_unused]] const uint32_t frame,\n> +void Awb::queueRequest(IPAContext &context, const uint32_t frame,\n>  \t\t       IPAFrameContext &frameContext,\n>  \t\t       const ControlList &controls)\n>  {\n> -\tauto &awb = context.activeState.awb;\n> -\n> -\tconst auto &awbEnable = controls.get(controls::AwbEnable);\n> -\tif (awbEnable && *awbEnable != awb.autoEnabled) {\n> -\t\tawb.autoEnabled = *awbEnable;\n> -\n> -\t\tLOG(RkISP1Awb, Debug)\n> -\t\t\t<< (*awbEnable ? \"Enabling\" : \"Disabling\") << \" AWB\";\n> -\t}\n> -\n> -\tawbAlgo_->handleControls(controls);\n> -\n> -\tframeContext.awb.autoEnabled = awb.autoEnabled;\n> -\n> -\tif (awb.autoEnabled)\n> -\t\treturn;\n> -\n> -\tconst auto &colourGains = controls.get(controls::ColourGains);\n> -\tconst auto &colourTemperature = controls.get(controls::ColourTemperature);\n> -\tbool update = false;\n> -\tif (colourGains) {\n> -\t\tawb.manual.gains.r() = (*colourGains)[0];\n> -\t\tawb.manual.gains.b() = (*colourGains)[1];\n> -\t\t/*\n> -\t\t * \\todo Colour temperature reported in metadata is now\n> -\t\t * incorrect, as we can't deduce the temperature from the gains.\n> -\t\t * This will be fixed with the bayes AWB algorithm.\n> -\t\t */\n> -\t\tupdate = true;\n> -\t} else if (colourTemperature) {\n> -\t\tawb.manual.temperatureK = *colourTemperature;\n> -\t\tconst auto &gains = awbAlgo_->gainsFromColourTemperature(*colourTemperature);\n> -\t\tif (gains) {\n> -\t\t\tawb.manual.gains.r() = gains->r();\n> -\t\t\tawb.manual.gains.b() = gains->b();\n> -\t\t\tupdate = true;\n> -\t\t}\n> -\t}\n> -\n> -\tif (update)\n> -\t\tLOG(RkISP1Awb, Debug)\n> -\t\t\t<< \"Set colour gains to \" << awb.manual.gains;\n> -\n> -\tframeContext.awb.gains = awb.manual.gains;\n> -\tframeContext.awb.temperatureK = awb.manual.temperatureK;\n> +\tawbAlgo_.queueRequest(context.activeState.awb, frame, frameContext.awb,\n> +\t\t\t      controls);\n>  }\n>  \n>  /**\n> @@ -214,15 +96,7 @@ void Awb::queueRequest(IPAContext &context,\n>  void Awb::prepare(IPAContext &context, const uint32_t frame,\n>  \t\t  IPAFrameContext &frameContext, RkISP1Params *params)\n>  {\n> -\t/*\n> -\t * This is the latest time we can read the active state. This is the\n> -\t * most up-to-date automatic values we can read.\n> -\t */\n> -\tif (frameContext.awb.autoEnabled) {\n> -\t\tconst auto &awb = context.activeState.awb;\n> -\t\tframeContext.awb.gains = awb.automatic.gains;\n> -\t\tframeContext.awb.temperatureK = awb.automatic.temperatureK;\n> -\t}\n> +\tawbAlgo_.prepare(context.activeState.awb, frameContext.awb);\n>  \n>  \tauto gainConfig = params->block<BlockType::AwbGain>();\n>  \tgainConfig.setEnabled(true);\n> @@ -291,68 +165,27 @@ void Awb::process(IPAContext &context,\n>  \t\t  const rkisp1_stat_buffer *stats,\n>  \t\t  ControlList &metadata)\n>  {\n> -\tIPAActiveState &activeState = context.activeState;\n> +\tRkISP1AwbStats awbStats = calculateRgbMeans(frameContext, stats);\n>  \n> -\tmetadata.set(controls::AwbEnable, frameContext.awb.autoEnabled);\n> -\tmetadata.set(controls::ColourGains, {\n> -\t\t\tstatic_cast<float>(frameContext.awb.gains.r()),\n> -\t\t\tstatic_cast<float>(frameContext.awb.gains.b())\n> -\t\t});\n> -\tmetadata.set(controls::ColourTemperature, frameContext.awb.temperatureK);\n> +\tawbAlgo_.process(context.activeState.awb, frameContext.awb, awbStats,\n> +\t\t\t frameContext.lux.lux, metadata);\n> +}\n>  \n> +RkISP1AwbStats Awb::calculateRgbMeans(const IPAFrameContext &frameContext,\n> +\t\t\t\t      const rkisp1_stat_buffer *stats) const\n> +{\n>  \tif (!stats || !(stats->meas_type & RKISP1_CIF_ISP_STAT_AWB)) {\n>  \t\tLOG(RkISP1Awb, Error) << \"AWB data is missing in statistics\";\n> -\t\treturn;\n> +\t\treturn {};\n>  \t}\n>  \n> -\tconst rkisp1_cif_isp_stat *params = &stats->params;\n> -\tconst rkisp1_cif_isp_awb_stat *awb = &params->awb;\n> +\tconst rkisp1_cif_isp_awb_stat *awb = &stats->params.awb;\n>  \n>  \tif (awb->awb_mean[0].cnt == 0) {\n>  \t\tLOG(RkISP1Awb, Debug) << \"AWB statistics are empty\";\n> -\t\treturn;\n> +\t\treturn {};\n>  \t}\n>  \n> -\tRGB<double> rgbMeans = calculateRgbMeans(frameContext, awb);\n> -\n> -\t/*\n> -\t * If the means are too small we don't have enough information to\n> -\t * meaningfully calculate gains. Freeze the algorithm in that case.\n> -\t */\n> -\tif (rgbMeans.r() < kMeanMinThreshold && rgbMeans.g() < kMeanMinThreshold &&\n> -\t    rgbMeans.b() < kMeanMinThreshold)\n> -\t\treturn;\n> -\n> -\tRkISP1AwbStats awbStats{ rgbMeans };\n> -\tAwbResult awbResult = awbAlgo_->calculateAwb(awbStats, frameContext.lux.lux);\n> -\n> -\t/*\n> -\t * Clamp the gain values to the hardware, which expresses gains as Q2.8\n> -\t * unsigned integer values. Set the minimum just above zero to avoid\n> -\t * divisions by zero when computing the raw means in subsequent\n> -\t * iterations.\n> -\t */\n> -\tawbResult.gains = awbResult.gains.max(1.0 / 256).min(1023.0 / 256);\n> -\n> -\t/* Filter the values to avoid oscillations. */\n> -\tdouble speed = 0.2;\n> -\tdouble ct = awbResult.colourTemperature;\n> -\tct = ct * speed + activeState.awb.automatic.temperatureK * (1 - speed);\n> -\tawbResult.gains = awbResult.gains * speed +\n> -\t\t\t  activeState.awb.automatic.gains * (1 - speed);\n> -\n> -\tactiveState.awb.automatic.temperatureK = static_cast<unsigned int>(ct);\n> -\tactiveState.awb.automatic.gains = awbResult.gains;\n> -\n> -\tLOG(RkISP1Awb, Debug)\n> -\t\t<< std::showpoint\n> -\t\t<< \"Means \" << rgbMeans << \", gains \"\n> -\t\t<< activeState.awb.automatic.gains << \", temp \"\n> -\t\t<< activeState.awb.automatic.temperatureK << \"K\";\n> -}\n> -\n> -RGB<double> Awb::calculateRgbMeans(const IPAFrameContext &frameContext, const rkisp1_cif_isp_awb_stat *awb) const\n> -{\n>  \tVector<double, 3> rgbMeans;\n>  \n>  \tif (rgbMode_) {\n> @@ -419,7 +252,7 @@ RGB<double> Awb::calculateRgbMeans(const IPAFrameContext &frameContext, const rk\n>  \t */\n>  \trgbMeans /= frameContext.awb.gains.max(0.01);\n>  \n> -\treturn rgbMeans;\n> +\treturn RkISP1AwbStats(rgbMeans);\n>  }\n>  \n>  REGISTER_IPA_ALGORITHM(Awb, \"Awb\")\n> diff --git a/src/ipa/rkisp1/algorithms/awb.h b/src/ipa/rkisp1/algorithms/awb.h\n> index 60d9ef111495..c7cb59a75bde 100644\n> --- a/src/ipa/rkisp1/algorithms/awb.h\n> +++ b/src/ipa/rkisp1/algorithms/awb.h\n> @@ -7,17 +7,25 @@\n>  \n>  #pragma once\n>  \n> -#include \"libcamera/internal/vector.h\"\n> +#include <linux/rkisp1-config.h>\n> +\n> +#include <libcamera/controls.h>\n> +\n> +#include \"libcamera/internal/value_node.h\"\n>  \n>  #include \"libipa/awb.h\"\n> -#include \"libipa/interpolator.h\"\n> +#include \"libipa/fixedpoint.h\"\n>  \n>  #include \"algorithm.h\"\n> +#include \"ipa_context.h\"\n> +#include \"params.h\"\n>  \n>  namespace libcamera {\n>  \n>  namespace ipa::rkisp1::algorithms {\n>  \n> +class RkISP1AwbStats;\n> +\n>  class Awb : public Algorithm\n>  {\n>  public:\n> @@ -38,10 +46,10 @@ public:\n>  \t\t     ControlList &metadata) override;\n>  \n>  private:\n> -\tRGB<double> calculateRgbMeans(const IPAFrameContext &frameContext,\n> -\t\t\t\t      const rkisp1_cif_isp_awb_stat *awb) const;\n> +\tRkISP1AwbStats calculateRgbMeans(const IPAFrameContext &frameContext,\n> +\t\t\t\t\t const rkisp1_stat_buffer *stats) const;\n>  \n> -\tstd::unique_ptr<AwbAlgorithm> awbAlgo_;\n> +\tAwbAlgorithm<UQ<2, 8>> awbAlgo_;\n>  \n>  \tbool rgbMode_;\n>  };\n> diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h\n> index e61391bb1510..81b1c7499706 100644\n> --- a/src/ipa/rkisp1/ipa_context.h\n> +++ b/src/ipa/rkisp1/ipa_context.h\n> @@ -25,6 +25,7 @@\n>  #include \"libcamera/internal/vector.h\"\n>  \n>  #include \"libipa/agc_mean_luminance.h\"\n> +#include \"libipa/awb.h\"\n>  #include \"libipa/camera_sensor_helper.h\"\n>  #include \"libipa/fc_queue.h\"\n>  #include \"libipa/fixedpoint.h\"\n> @@ -48,15 +49,16 @@ struct IPAHwSettings {\n>  \tbool compand;\n>  };\n>  \n> +struct RKISP1AwbSession : public ipa::awb::Session {\n> +\tstruct rkisp1_cif_isp_window measureWindow;\n> +};\n> +\n>  struct IPASessionConfiguration {\n>  \tstruct {\n>  \t\tstruct rkisp1_cif_isp_window measureWindow;\n>  \t} agc;\n>  \n> -\tstruct {\n> -\t\tstruct rkisp1_cif_isp_window measureWindow;\n> -\t\tbool enabled;\n> -\t} awb;\n> +\tstruct RKISP1AwbSession awb;\n>  \n>  \tstruct {\n>  \t\tbool supported;\n> @@ -100,17 +102,7 @@ struct IPAActiveState {\n>  \t\tutils::Duration maxFrameDuration;\n>  \t} agc;\n>  \n> -\tstruct {\n> -\t\tstruct AwbState {\n> -\t\t\tRGB<double> gains;\n> -\t\t\tunsigned int temperatureK;\n> -\t\t};\n> -\n> -\t\tAwbState manual;\n> -\t\tAwbState automatic;\n> -\n> -\t\tbool autoEnabled;\n> -\t} awb;\n> +\tipa::awb::ActiveState awb;\n>  \n>  \tstruct {\n>  \t\tMatrix<float, 3, 3> manual;\n> @@ -174,11 +166,7 @@ struct IPAFrameContext : public FrameContext {\n>  \t\tbool autoGainModeChange;\n>  \t} agc;\n>  \n> -\tstruct {\n> -\t\tRGB<double> gains;\n> -\t\tbool autoEnabled;\n> -\t\tunsigned int temperatureK;\n> -\t} awb;\n> +\tipa::awb::FrameContext awb;\n>  \n>  \tstruct {\n>  \t\tBrightnessQ brightness;","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id A3E2FC324C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 15 Jun 2026 19:13:42 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 7E820623F8;\n\tMon, 15 Jun 2026 21:13:41 +0200 (CEST)","from us-smtp-delivery-124.mimecast.com\n\t(us-smtp-delivery-124.mimecast.com [170.10.129.124])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 04E65623CB\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 15 Jun 2026 21:13:39 +0200 (CEST)","from mail-wr1-f72.google.com (mail-wr1-f72.google.com\n\t[209.85.221.72]) by relay.mimecast.com with ESMTP with STARTTLS\n\t(version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id\n\tus-mta-67-wnBSVI4_Pt-lG5dlei39qQ-1; Mon, 15 Jun 2026 15:13:37 -0400","by mail-wr1-f72.google.com with SMTP id\n\tffacd0b85a97d-45efa7755faso1523192f8f.1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 15 Jun 2026 12:13:37 -0700 (PDT)","from mzamazal-thinkpadp1gen7.tpbc.csb\n\t(ip-77-48-47-4.net.vodafone.cz. [77.48.47.4])\n\tby smtp.gmail.com with ESMTPSA id\n\tffacd0b85a97d-4606f20e77asm39113239f8f.0.2026.06.15.12.13.32\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tMon, 15 Jun 2026 12:13:33 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=redhat.com header.i=@redhat.com\n\theader.b=\"JoTbovN6\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n\ts=mimecast20190719; t=1781550818;\n\th=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n\tto:to:cc:cc:mime-version:mime-version:content-type:content-type:\n\tin-reply-to:in-reply-to:references:references;\n\tbh=se8PFZOm41Ics9zslmhdnGAM6WHPxV5rqFbItjPeKo8=;\n\tb=JoTbovN6cxjAjftPp5yAYkcHNXENKMH/ovCPb6jMyV5m+QmOzzn71Ov1wOhFlakHzsTOqQ\n\tv1iqU/6vTDr346fNyAMhC7oSkqi7WjnfQKwO54OR0ohzWijQsPHVF27aGHH5Ox4YlRGmkl\n\tJ6X2V3q0/Jr17k6nNGP4UrI/wQwBD/4=","X-MC-Unique":"wnBSVI4_Pt-lG5dlei39qQ-1","X-Mimecast-MFC-AGG-ID":"wnBSVI4_Pt-lG5dlei39qQ_1781550816","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20251104; t=1781550816; x=1782155616;\n\th=mime-version:user-agent:message-id:date:references:in-reply-to\n\t:subject:cc:to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject\n\t:date:message-id:reply-to;\n\tbh=se8PFZOm41Ics9zslmhdnGAM6WHPxV5rqFbItjPeKo8=;\n\tb=PXY52Hv5xNEtHOzAtmpwAUMWQlAHH427qTrB/+ZiUnwyjo1j1/4fk8Qqsrrb0ecn3B\n\tfE418OJc/Lo7csHG6E8gw7OkuIilh8iOCQ9G4bf9tBP18wHRgfCBxDi8eFlSnaXh0Sk6\n\t+rriD7jo59STYBFez4mt7lSg93zn4uwaeb1bMCX3Uhhd3PTGvzhZ7J5wnZJbGlD36syw\n\tLe5GubAepP5oJnIGUcJIQLh7mUAUHm25+1Zcqj7BFCY7DqkpIHBgyCDDoBdKko1MJfFK\n\tLZFmcgGmySljZkZneexYALKXGBAF0qn4ZMGZPbIQcrEORBuBiAeDUas/91P2h1/v8qMP\n\tLu3A==","X-Gm-Message-State":"AOJu0YxrkYKC9jvg+yLaK0pxzOCRtLumnxUHjAWT//ARwuiiAnrHPAjl\n\t/DNtphgXRSi16lCiVtCJSVd0FcC9Rw529HjonUlQ72f0VSycxpKuwwWSo8lOqY0MlUzoxImgn3U\n\t5yHmSumhWvMVO6N3PaOKbGUKvqr8MKkEpOCjvwSp+zdi/gtqczMko8R5HHcP1dO3GQiF3audArc\n\tk=","X-Gm-Gg":"Acq92OEZCGL+od8vjISLn32iQ8ELDH3L0+lyMtPw7oI4kNyGxQOqpzX6DAg0i4EVD3b\n\t3LTOtzbA/3r3Hn1L3r0I/OxrLfPTsVmJgRvGL2innChFDp4tTZCSfIz2aDMY4/Y1iGtOzo3/EVX\n\tygfLiNli+eL5VygTQyrrr4uADKQLPppz/ygTNvNn4QrAGcC/aQwMm+OR0neRNvBC8m8d+lj4qVD\n\tCT6s4wUexxDBgDd2U8Ht8J3WyWO/OgB3Zhum5fPY+U6O5p47L/XsHzXDEtSH2EP34IVEykqGY2K\n\t7gcdzTWr0XFd1iyP/PWorVLDxBhWmvYNbPoPcp5Kl7U6VrEkGwK2Pd8BR0DS1dxt3nRSBpav1yA\n\tayuBVVuM/yXtqMQCnwVWgLQdesbtO2jQuZh2lj1fqOnhBQ4pcty2uIQOW4vRBuGlJPXWVpr7PHl\n\tY=","X-Received":["by 2002:a5d:5f86:0:b0:45e:e1a4:c4c3 with SMTP id\n\tffacd0b85a97d-46074a625damr16629280f8f.15.1781550815141; \n\tMon, 15 Jun 2026 12:13:35 -0700 (PDT)","by 2002:a5d:5f86:0:b0:45e:e1a4:c4c3 with SMTP id\n\tffacd0b85a97d-46074a625damr16629197f8f.15.1781550814133; \n\tMon, 15 Jun 2026 12:13:34 -0700 (PDT)"],"From":"Milan Zamazal <mzamazal@redhat.com>","To":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org,  Kieran Bingham\n\t<kieran.bingham@ideasonboard.com>","Subject":"Re: [PATCH 01/11] ipa: libipa: awb: Reimplement AwbAlgorithm","In-Reply-To":"<20260615-libipa-algorithms-v1-1-e949c937422e@ideasonboard.com>\n\t(Jacopo Mondi's message of \"Mon, 15 Jun 2026 16:05:26 +0200\")","References":"<20260615-libipa-algorithms-v1-0-e949c937422e@ideasonboard.com>\n\t<20260615-libipa-algorithms-v1-1-e949c937422e@ideasonboard.com>","Date":"Mon, 15 Jun 2026 21:13:32 +0200","Message-ID":"<855x3j1usj.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","User-Agent":"Gnus/5.13 (Gnus v5.13)","MIME-Version":"1.0","X-Mimecast-Spam-Score":"0","X-Mimecast-MFC-PROC-ID":"40fs6blhVY0TM_GQfSOE7oJqpVcnRytAaDusna_DzEo_1781550816","X-Mimecast-Originator":"redhat.com","Content-Type":"text/plain","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]