{"id":26878,"url":"https://patchwork.libcamera.org/api/patches/26878/?format=json","web_url":"https://patchwork.libcamera.org/patch/26878/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20260615-libipa-algorithms-v1-1-e949c937422e@ideasonboard.com>","date":"2026-06-15T14:05:26","name":"[01/11] ipa: libipa: awb: Reimplement AwbAlgorithm","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"7620fdd5b5b7f192e475eac24fe6e8f8c93f638a","submitter":{"id":143,"url":"https://patchwork.libcamera.org/api/people/143/?format=json","name":"Jacopo Mondi","email":"jacopo.mondi@ideasonboard.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/26878/mbox/","series":[{"id":5992,"url":"https://patchwork.libcamera.org/api/series/5992/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=5992","date":"2026-06-15T14:05:25","name":"ipa: libipa: Introduce libipa algorithms","version":1,"mbox":"https://patchwork.libcamera.org/series/5992/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/26878/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/26878/checks/","tags":{},"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 9F01AC324C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 15 Jun 2026 14:05:54 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 8631B623F7;\n\tMon, 15 Jun 2026 16:05:51 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 51FC5623DF\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 15 Jun 2026 16:05:47 +0200 (CEST)","from [192.168.1.104] (net-93-65-100-155.cust.vodafonedsl.it\n\t[93.65.100.155])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 343F8227;\n\tMon, 15 Jun 2026 16:05:14 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"JYy2FNsa\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1781532314;\n\tbh=uEp9/WTPzzDC0CH0TgkGl1QFfqOX3F64c0recN4hoq8=;\n\th=From:Date:Subject:References:In-Reply-To:To:Cc:From;\n\tb=JYy2FNsaSS3RAtrJwg+m6v51g0sb+W8D0KUoNTFLsKWPPHUTelU/9WZEHGZ+IxeGh\n\tngavINcmF1lhKUGPWul+YHGcW+H7iFim7tYE2m44RWsUBU2cmqpFUE4lrPag+PAPSe\n\tvDNwzvPpMFhLZ71u+JbTFcGo/4O1XcRo5iXMAUAQ=","From":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","Date":"Mon, 15 Jun 2026 16:05:26 +0200","Subject":"[PATCH 01/11] ipa: libipa: awb: Reimplement AwbAlgorithm","MIME-Version":"1.0","Content-Type":"text/plain; charset=\"utf-8\"","Content-Transfer-Encoding":"7bit","Message-Id":"<20260615-libipa-algorithms-v1-1-e949c937422e@ideasonboard.com>","References":"<20260615-libipa-algorithms-v1-0-e949c937422e@ideasonboard.com>","In-Reply-To":"<20260615-libipa-algorithms-v1-0-e949c937422e@ideasonboard.com>","To":"libcamera-devel@lists.libcamera.org","Cc":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>, \n\tKieran Bingham <kieran.bingham@ideasonboard.com>","X-Mailer":"b4 0.14.3","X-Developer-Signature":"v=1; a=openpgp-sha256; l=49306;\n\ti=jacopo.mondi@ideasonboard.com; h=from:subject:message-id;\n\tbh=uEp9/WTPzzDC0CH0TgkGl1QFfqOX3F64c0recN4hoq8=;\n\tb=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqMAa4duQFA7ohHHtCgl+kuQeGOFmE5SkLY9vkF\n\tByGbB0LowaJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCajAGuAAKCRByNAaPFqFW\n\tPHOaD/oCm2SImAH0WSEDDOrawpJPDvsbxW2FFHTcIqV36JjTdFCWbo98kU4Wv3/uBbzdXIv9t0l\n\tG+Ca9GGLVd9cYRhgBVLZKPVwuURGazq1OXTewsFgTIvuOAsHdE28RVmbgmZrrnbQRfub4KbMcF6\n\tVTDwjWZXD2+GUhc4cLfUL1U8P7+TsDzNuqWoeOVuJn2VQlMbOwcWrO/Hv9pwqfZWfrt7whOFlGP\n\tW1IfMxIjkAI2248ce7fDYWRoKjk4j0EKDFbXiP+nHVsHpQHuYvrHnQV6mPQSZ1EqgLPsXehX1N1\n\tWgQ/VPmgIchJmZtR6AzRgIO9jml68i8NEdT1TIM3XyaIUE6cLTIC3KysJwXepRNPDYi6iLKZfOh\n\tj3axc2D6y/ZFNYmzA1hs7C4tPazPk7IiSs7QqXdcjI1ghcvI9pJczO482slXEef6d2Xu6OLk1Me\n\tDal7oFD6PmmHbTV37M68/4u20rdJC/BisSLXvtbgiarIOx9ttWyl+aq4qVz78mmiy7bq6UNRpIB\n\tzx2zw1YANkt7zEBc6bmQWvdzwFjAaRljigBZC5epoCrNrNXsLuMC90poy9TOdkOPIWSv4+wTuY6\n\t2AwEWMEAtqR8C8Jy+3sgBeTkHQEO+dJdiFDW8p3LfECxz8wvXlVo5LDDkR10H+CzPpoY6qi7Fmt\n\tRsw3uTwlkeUrw/g==","X-Developer-Key":"i=jacopo.mondi@ideasonboard.com; a=openpgp;\n\tfpr=72392EDC88144A65C701EA9BA5826A2587AD026B","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>"},"content":"The current implementation of AwbAlgorithm is the following one:\n\nAwbAlgorithm o---- RkISP1Awb\n    ^\n    |\n |-----|\n |     |\nBayes Grey\n\nThe platform-specific Awb implementation instantiate one of\nthe Bayes or Grey Awb helper classes and both inherit from the\nAwbAlgorithm base class.\n\nThe handling of libcamera controls is split between the base and the\nderived (mostly Bayes) class.\n\nWe what instead the AwbAlgorithm to handle all-things-libcamera and let\nthe Bayes and Grey classes implement the logic. In order to achieve that\nuse composition also in the AwbAlgorithm class and introduce an\nAwbImplementation base class from which Bayes and Grey derive from.\n\nAwbImplementation o---- AwbAlgorithmBase\n    ^                           ^\n    |                           |\n |-----|                AwbAlgorithm<Q<x,y>> o---- RkISP1Awb\n |     |\nBayes Grey\n\nThe AwbAlgorithm<> class is instantiated with a Q<x, y> template\nargument that represents the platform-specific register format which\nis used to initialize controls and clamp gains to the hardware limits.\n\nSigned-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\nSigned-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(-)","diff":"diff --git a/src/ipa/libipa/awb.cpp b/src/ipa/libipa/awb.cpp\nindex 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 \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+ */\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+\n+\treturn true;\n+}\n+\n+/**\n+ * \\fn AwbStats::rgRatio()\n+ * \\brief Retriev the Red/Green ratio\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+ * 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  */\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+\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+\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+\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+ * 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+ * 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 */\ndiff --git a/src/ipa/libipa/awb.h b/src/ipa/libipa/awb.h\nindex 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 \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+\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 */\ndiff --git a/src/ipa/libipa/awb_bayes.cpp b/src/ipa/libipa/awb_bayes.cpp\nindex 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();\ndiff --git a/src/ipa/libipa/awb_bayes.h b/src/ipa/libipa/awb_bayes.h\nindex 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 */\ndiff --git a/src/ipa/libipa/awb_grey.cpp b/src/ipa/libipa/awb_grey.cpp\nindex 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 \ndiff --git a/src/ipa/libipa/awb_grey.h b/src/ipa/libipa/awb_grey.h\nindex 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:\ndiff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp\nindex 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\")\ndiff --git a/src/ipa/rkisp1/algorithms/awb.h b/src/ipa/rkisp1/algorithms/awb.h\nindex 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 };\ndiff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h\nindex 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;\n","prefixes":["01/11"]}