[{"id":39140,"web_url":"https://patchwork.libcamera.org/comment/39140/","msgid":"<178163431966.3598163.10202931193127440648@ping.linuxembedded.co.uk>","date":"2026-06-16T18:25:19","subject":"Re: [PATCH 03/10] ipa: ipu3: awb: Port to the new libipa\n\tAwbAlgorithm","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"Quoting Daniel Scally (2026-06-16 07:41:37)\n> Port the IPU3 Awb algorithm to use the new libipa implementation\n> of AwbAlgorithm.\n> \n> In this implementation the awbAlgo_ class member is initialised as\n> UQ<3, 13> following the IPU3 params format documentation.\n> \n> Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>\n> ---\n>  src/ipa/ipu3/algorithms/agc.cpp |  12 ++-\n>  src/ipa/ipu3/algorithms/awb.cpp | 194 ++++++++++++++++++----------------------\n>  src/ipa/ipu3/algorithms/awb.h   |  21 ++---\n>  src/ipa/ipu3/ipa_context.cpp    |  35 ++++----\n>  src/ipa/ipu3/ipa_context.h      |  15 ++--\n>  5 files changed, 128 insertions(+), 149 deletions(-)\n> \n> diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp\n> index d6a7036c6504acb106f5c773b529ad80b8349f85..5120b3bcb177c75ebd61d82c9684779f73305499 100644\n> --- a/src/ipa/ipu3/algorithms/agc.cpp\n> +++ b/src/ipa/ipu3/algorithms/agc.cpp\n> @@ -208,9 +208,15 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n>                   ControlList &metadata)\n>  {\n>         Histogram hist = parseStatistics(stats, context.configuration.grid.bdsGrid);\n> -       rGain_ = context.activeState.awb.gains.red;\n> -       gGain_ = context.activeState.awb.gains.blue;\n> -       bGain_ = context.activeState.awb.gains.green;\n> +       rGain_ = context.activeState.awb.autoEnabled ?\n> +                context.activeState.awb.automatic.gains[0] :\n> +                context.activeState.awb.manual.gains[0];\n> +       gGain_ = context.activeState.awb.autoEnabled ?\n> +                context.activeState.awb.automatic.gains[1] :\n> +                context.activeState.awb.manual.gains[1];\n> +       bGain_ = context.activeState.awb.autoEnabled ?\n> +                context.activeState.awb.automatic.gains[2] :\n> +                context.activeState.awb.manual.gains[2];\n\nCan we drop rGain_, gGain_ and bGain_ and change that into an RGB type?\nPerhaps as a patch beforehand.\n\n\nThen we can simply do\n\tif (context.activeState.awb.autoEnabled)\n\t\tgains = context.activeState.awb.automatic.gains;\n\telse\n\t\tgains = context.activeState.awb.manual.gains;\n\nBut maybe we need to check what those local class members are being\nused for - now I see we're in agc.cpp!\n\n>  \n>         /*\n>          * The Agc algorithm needs to know the effective exposure value that was\n> diff --git a/src/ipa/ipu3/algorithms/awb.cpp b/src/ipa/ipu3/algorithms/awb.cpp\n> index 613bdbbf51cd127e03906571813260a6857067dc..fa1c70856a3647b188531473e9d9f8b77c747230 100644\n> --- a/src/ipa/ipu3/algorithms/awb.cpp\n> +++ b/src/ipa/ipu3/algorithms/awb.cpp\n> @@ -26,11 +26,40 @@ namespace ipa::ipu3::algorithms {\n>  LOG_DEFINE_CATEGORY(IPU3Awb)\n>  \n>  /*\n> - * When zones are used for the grey world algorithm, they are only considered if\n> - * their average green value is at least 16/255 (after black level subtraction)\n> - * to exclude zones that are too dark and don't provide relevant colour\n> - * information (on the opposite side of the spectrum, saturated regions are\n> - * excluded by the ImgU statistics engine).\n> + * \\todo IPU3 doesn't support the Lux algorithm.\n> + */\n\nThat's really easy to add ;-) (can be later)\n\n> +static constexpr unsigned int kDefaultLux = 500;\n> +\n> +/**\n> + * \\brief The IPU3 implementation of AwbStats\n> + */\n> +class Ipu3AwbStats final : public AwbStats\n> +{\n> +public:\n> +       Ipu3AwbStats(){};\n> +       Ipu3AwbStats(const RGB<double> means)\n> +               : AwbStats(means)\n> +       {\n> +       }\n> +\n> +       double minColourValue() const override\n> +       {\n> +               return 0.0;\n\nis 0.0 a valid number here? I saw Jacopo's series put 0.2 which I\nthought was to prevent some divide by 0. But I'm curious what this would\nbe used for anyway, or if any platform would use the same numbers?\n\n> +       }\n> +};\n> +\n> +/**\n> + * \\fn Ipu3AwbStats::Ipu3AwbStats(const RGB<double> means)\n> + * \\brief Construct an instance of the class with RGB means\n> + * \\param[in] means The mean R, G and B values from the statistics\n> + */\n> +\n> +/*\n> + * Zones are only considered if their average green value is at least\n> + * kMinGreenLevelInZone/255 (after black level subtraction) to exclude zones\n> + * that are too dark and don't provide relevant colour information (on the\n> + * opposite side of the spectrum, saturated regions are excluded by the ImgU\n> + * statistics engine).\n>   */\n>  static constexpr uint32_t kMinGreenLevelInZone = 16;\n>  \n> @@ -74,26 +103,6 @@ static constexpr uint32_t kMinCellsPerZoneRatio = 255 * 90 / 100;\n>   * \\brief Sum of the average blue values of each unsaturated cell in the zone\n>   */\n>  \n> -/**\n> - * \\struct Awb::AwbStatus\n> - * \\brief AWB parameters calculated\n> - *\n> - * The AwbStatus structure is intended to store the AWB\n> - * parameters calculated by the algorithm\n> - *\n> - * \\var AwbStatus::temperatureK\n> - * \\brief Color temperature calculated\n> - *\n> - * \\var AwbStatus::redGain\n> - * \\brief Gain calculated for the red channel\n> - *\n> - * \\var AwbStatus::greenGain\n> - * \\brief Gain calculated for the green channel\n> - *\n> - * \\var AwbStatus::blueGain\n> - * \\brief Gain calculated for the blue channel\n> - */\n> -\n>  /* Default settings for Bayer noise reduction replicated from the Kernel */\n>  static const struct ipu3_uapi_bnr_static_config imguCssBnrDefaults = {\n>         .wb_gains = { 16, 16, 16, 16 },\n> @@ -114,11 +123,7 @@ static const struct ipu3_uapi_bnr_static_config imguCssBnrDefaults = {\n>  \n>  /**\n>   * \\class Awb\n> - * \\brief A Grey world white balance correction algorithm\n> - *\n> - * The Grey World algorithm assumes that the scene, in average, is neutral grey.\n> - * Reference: Lam, Edmund & Fung, George. (2008). Automatic White Balancing in\n> - * Digital Photography. 10.1201/9781420054538.ch10.\n> + * \\brief The IPU3 white balance correction algorithm implementation\n>   *\n>   * The IPU3 generates statistics from the Bayer Down Scaler output into a grid\n>   * defined in the ipu3_uapi_awb_config_s structure.\n> @@ -168,26 +173,26 @@ static const struct ipu3_uapi_bnr_static_config imguCssBnrDefaults = {\n>   * cells are ignored. The grid configuration is computed by\n>   * IPAIPU3::calculateBdsGrid().\n>   *\n> - * Before calculating the gains, the algorithm aggregates the cell averages for\n> - * each zone in generateAwbStats(). Cells that have a too high ratio of\n> - * saturated pixels are ignored, and only zones that contain enough\n> - * non-saturated cells are then used by the algorithm.\n> - *\n> - * The Grey World algorithm will then estimate the red and blue gains to apply, and\n> - * store the results in the metadata. The green gain is always set to 1.\n> + * Before running the AWB algorithm, we aggregate the cell averages for each\n> + * zone in generateAwbStats(). Cells that have a too high ratio of saturated\n> + * pixels are ignored, and only zones that contain enough non-saturated cells\n> + * are then used by the algorithm.\n>   */\n>  \n>  Awb::Awb()\n>         : Algorithm()\n>  {\n> -       asyncResults_.blueGain = 1.0;\n> -       asyncResults_.greenGain = 1.0;\n> -       asyncResults_.redGain = 1.0;\n> -       asyncResults_.temperatureK = 4500;\n> -\n>         zones_.reserve(kAwbStatsSizeX * kAwbStatsSizeY);\n>  }\n>  \n> +/**\n> + * \\copydoc libcamera::ipa::Algorithm::init\n> + */\n> +int Awb::init(IPAContext &context, const ValueNode &tuningData)\n> +{\n> +       return awbAlgo_.init(tuningData, context.ctrlMap);\n> +}\n> +\n>  /**\n>   * \\copydoc libcamera::ipa::Algorithm::configure\n>   */\n> @@ -197,12 +202,14 @@ int Awb::configure(IPAContext &context,\n>         const ipu3_uapi_grid_config &grid = context.configuration.grid.bdsGrid;\n>         stride_ = context.configuration.grid.stride;\n>  \n> +       awbAlgo_.configure(context.activeState.awb, context.configuration.awb);\n> +\n>         cellsPerZoneX_ = std::round(grid.width / static_cast<double>(kAwbStatsSizeX));\n>         cellsPerZoneY_ = std::round(grid.height / static_cast<double>(kAwbStatsSizeY));\n>  \n>         /*\n>          * Configure the minimum proportion of cells counted within a zone\n> -        * for it to be relevant for the grey world algorithm.\n> +        * for it to be used.\n>          * \\todo This proportion could be configured.\n>          */\n>         cellsPerZoneThreshold_ = cellsPerZoneX_ * cellsPerZoneY_ * kMaxCellSaturationRatio;\n> @@ -211,6 +218,17 @@ int Awb::configure(IPAContext &context,\n>         return 0;\n>  }\n>  \n> +/**\n> + * \\copydoc libcamera::ipa::Algorithm::queueRequest\n> + */\n> +void Awb::queueRequest(IPAContext &context, const uint32_t frame,\n> +                      IPAFrameContext &frameContext,\n> +                      const ControlList &controls)\n> +{\n> +       awbAlgo_.queueRequest(context.activeState.awb, frame, frameContext.awb,\n> +                             controls);\n> +}\n> +\n>  constexpr uint16_t Awb::threshold(float value)\n>  {\n>         /* AWB thresholds are in the range [0, 8191] */\n> @@ -237,11 +255,12 @@ constexpr uint16_t Awb::gainValue(double gain)\n>  /**\n>   * \\copydoc libcamera::ipa::Algorithm::prepare\n>   */\n> -void Awb::prepare(IPAContext &context,\n> -                 [[maybe_unused]] const uint32_t frame,\n> -                 [[maybe_unused]] IPAFrameContext &frameContext,\n> -                 ipu3_uapi_params *params)\n> +void Awb::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n> +                 IPAFrameContext &frameContext,\n> +                 [[maybe_unused]] ipu3_uapi_params *params)\n>  {\n> +       awbAlgo_.prepare(context.activeState.awb, frameContext.awb);\n> +\n>         /*\n>          * Green saturation thresholds are reduced because we are using the\n>          * green channel only in the exposure computation.\n> @@ -279,13 +298,11 @@ void Awb::prepare(IPAContext &context,\n>         params->acc_param.bnr.opt_center_sqr.y_sqr_reset = params->acc_param.bnr.opt_center.y_reset\n>                                                         * params->acc_param.bnr.opt_center.y_reset;\n>  \n> -       params->acc_param.bnr.wb_gains.gr = gainValue(context.activeState.awb.gains.green);\n> -       params->acc_param.bnr.wb_gains.r  = gainValue(context.activeState.awb.gains.red);\n> -       params->acc_param.bnr.wb_gains.b  = gainValue(context.activeState.awb.gains.blue);\n> -       params->acc_param.bnr.wb_gains.gb = gainValue(context.activeState.awb.gains.green);\n> -\n> -       LOG(IPU3Awb, Debug) << \"Color temperature estimated: \" << asyncResults_.temperatureK;\n>  \n> +       params->acc_param.bnr.wb_gains.gr = gainValue(frameContext.awb.gains.g());\n> +       params->acc_param.bnr.wb_gains.r = gainValue(frameContext.awb.gains.r());\n> +       params->acc_param.bnr.wb_gains.b = gainValue(frameContext.awb.gains.b());\n> +       params->acc_param.bnr.wb_gains.gb = gainValue(frameContext.awb.gains.g());\n\nCoool, now we're doing AWB correctly here!\n\nA quick look at Awb::gainValue looks like it could be converted to a\nUQ<3,13> I guess, but it would have to be checked and verified, so lets\nkeep that as a separate topic, as ther'es that +/- 1.0 which I suspect\nisn't a straight translation...\n\n>  \n>         params->use.acc_awb = 1;\n>         params->use.acc_bnr = 1;\n> @@ -366,9 +383,17 @@ void Awb::clearAwbStats()\n>         }\n>  }\n>  \n> -void Awb::awbGreyWorld()\n> +Ipu3AwbStats Awb::calculateRgbMeans(const ipu3_uapi_stats_3a *stats)\n>  {\n> -       LOG(IPU3Awb, Debug) << \"Grey world AWB\";\n> +       ASSERT(stats->stats_3a_status.awb_en);\n> +\n> +       clearAwbStats();\n> +       generateAwbStats(stats);\n> +       generateZones();\n> +\n> +       if (zones_.size() <= 10)\n> +               return {};\n> +\n>         /*\n>          * Make a separate list of the derivatives for each of red and blue, so\n>          * that we can sort them to exclude the extreme gains. We could\n> @@ -399,66 +424,21 @@ void Awb::awbGreyWorld()\n>         double redGain = sumRed.g() / (sumRed.r() + 1),\n>                blueGain = sumBlue.g() / (sumBlue.b() + 1);\n>  \n> -       /* Color temperature is not relevant in Grey world but still useful to estimate it :-) */\n> -       asyncResults_.temperatureK = estimateCCT({{ sumRed.r(), sumRed.g(), sumBlue.b() }});\n> -\n> -       /*\n> -        * Gain values are unsigned integer value ranging [0, 8) with 13 bit\n> -        * fractional part.\n> -        */\n> -       redGain = std::clamp(redGain, 0.0, 65535.0 / 8192);\n> -       blueGain = std::clamp(blueGain, 0.0, 65535.0 / 8192);\n> -\n> -       asyncResults_.redGain = redGain;\n> -       /* Hardcode the green gain to 1.0. */\n> -       asyncResults_.greenGain = 1.0;\n> -       asyncResults_.blueGain = blueGain;\n> -}\n> -\n> -void Awb::calculateWBGains(const ipu3_uapi_stats_3a *stats)\n> -{\n> -       ASSERT(stats->stats_3a_status.awb_en);\n> -\n> -       clearAwbStats();\n> -       generateAwbStats(stats);\n> -       generateZones();\n> -\n> -       LOG(IPU3Awb, Debug) << \"Valid zones: \" << zones_.size();\n> -\n> -       if (zones_.size() > 10) {\n> -               awbGreyWorld();\n> -               LOG(IPU3Awb, Debug) << \"Gain found for red: \" << asyncResults_.redGain\n> -                                   << \" and for blue: \" << asyncResults_.blueGain;\n> -       }\n> +       return Ipu3AwbStats({ { 1.0 / redGain, 1.0, 1.0 / blueGain } });\n>  }\n>  \n>  /**\n>   * \\copydoc libcamera::ipa::Algorithm::process\n>   */\n>  void Awb::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n> -                 [[maybe_unused]] IPAFrameContext &frameContext,\n> -                 const ipu3_uapi_stats_3a *stats,\n> -                 [[maybe_unused]] ControlList &metadata)\n> +                 IPAFrameContext &frameContext,\n> +                 [[maybe_unused]] const ipu3_uapi_stats_3a *stats,\n> +                 ControlList &metadata)\n>  {\n> -       calculateWBGains(stats);\n> +       Ipu3AwbStats awbStats = calculateRgbMeans(stats);\n>  \n> -       /*\n> -        * Gains are only recalculated if enough zones were detected.\n> -        * The results are cached, so if no results were calculated, we set the\n> -        * cached values from asyncResults_ here.\n> -        */\n> -       context.activeState.awb.gains.blue = asyncResults_.blueGain;\n> -       context.activeState.awb.gains.green = asyncResults_.greenGain;\n> -       context.activeState.awb.gains.red = asyncResults_.redGain;\n> -       context.activeState.awb.temperatureK = asyncResults_.temperatureK;\n> -\n> -       metadata.set(controls::AwbEnable, true);\n> -       metadata.set(controls::ColourGains, {\n> -                       static_cast<float>(context.activeState.awb.gains.red),\n> -                       static_cast<float>(context.activeState.awb.gains.blue)\n> -               });\n> -       metadata.set(controls::ColourTemperature,\n> -                    context.activeState.awb.temperatureK);\n> +       awbAlgo_.process(context.activeState.awb, frameContext.awb, awbStats,\n> +                        kDefaultLux, metadata);\n>  }\n>  \n>  REGISTER_IPA_ALGORITHM(Awb, \"Awb\")\n> diff --git a/src/ipa/ipu3/algorithms/awb.h b/src/ipa/ipu3/algorithms/awb.h\n> index 7ec322318dab54ae7c8a647a67a0cf5815a36eb6..3f3996e6c846558acd3902c9fb3cb804cb5b65d4 100644\n> --- a/src/ipa/ipu3/algorithms/awb.h\n> +++ b/src/ipa/ipu3/algorithms/awb.h\n> @@ -13,6 +13,9 @@\n>  \n>  #include <libcamera/geometry.h>\n>  \n> +#include \"libipa/awb.h\"\n> +#include \"libipa/fixedpoint.h\"\n> +\n>  #include \"libcamera/internal/vector.h\"\n>  \n>  #include \"algorithm.h\"\n> @@ -21,6 +24,8 @@ namespace libcamera {\n>  \n>  namespace ipa::ipu3::algorithms {\n>  \n> +class Ipu3AwbStats;\n> +\n>  /* Region size for the statistics generation algorithm */\n>  static constexpr uint32_t kAwbStatsSizeX = 16;\n>  static constexpr uint32_t kAwbStatsSizeY = 12;\n> @@ -39,7 +44,11 @@ class Awb : public Algorithm\n>  public:\n>         Awb();\n>  \n> +       int init(IPAContext &context, const ValueNode &tuningData) override;\n>         int configure(IPAContext &context, const IPAConfigInfo &configInfo) override;\n> +       void queueRequest(IPAContext &context, const uint32_t frame,\n> +                         IPAFrameContext &frameContext,\n> +                         const ControlList &controls) override;\n>         void prepare(IPAContext &context, const uint32_t frame,\n>                      IPAFrameContext &frameContext,\n>                      ipu3_uapi_params *params) override;\n> @@ -49,15 +58,7 @@ public:\n>                      ControlList &metadata) override;\n>  \n>  private:\n> -       struct AwbStatus {\n> -               double temperatureK;\n> -               double redGain;\n> -               double greenGain;\n> -               double blueGain;\n> -       };\n> -\n> -private:\n> -       void calculateWBGains(const ipu3_uapi_stats_3a *stats);\n> +       Ipu3AwbStats calculateRgbMeans(const ipu3_uapi_stats_3a *stats);\n>         void generateZones();\n>         void generateAwbStats(const ipu3_uapi_stats_3a *stats);\n>         void clearAwbStats();\n> @@ -67,7 +68,7 @@ private:\n>  \n>         std::vector<RGB<double>> zones_;\n>         Accumulator awbStats_[kAwbStatsSizeX * kAwbStatsSizeY];\n> -       AwbStatus asyncResults_;\n> +       AwbAlgorithm<UQ<3, 13>> awbAlgo_;\n>  \n>         uint32_t stride_;\n>         uint32_t cellsPerZoneX_;\n> diff --git a/src/ipa/ipu3/ipa_context.cpp b/src/ipa/ipu3/ipa_context.cpp\n> index 3b22f7917650d9e400d5368c2f890d6b2dc846a0..b35c925d959027c540257e47944047c238f85571 100644\n> --- a/src/ipa/ipu3/ipa_context.cpp\n> +++ b/src/ipa/ipu3/ipa_context.cpp\n> @@ -109,6 +109,11 @@ namespace libcamera::ipa::ipu3 {\n>   * \\brief Maximum analogue gain supported with the configured sensor\n>   */\n>  \n> +/**\n> + * \\var IPAActiveState::awb\n> + * \\brief Active auto-white balance parameters for the IPA\n> + */\n> +\n>  /**\n>   * \\var IPASessionConfiguration::sensor\n>   * \\brief Sensor-specific configuration of the IPA\n> @@ -123,6 +128,11 @@ namespace libcamera::ipa::ipu3 {\n>   * \\brief Sensor output resolution\n>   */\n>  \n> +/**\n> + * \\var IPASessionConfiguration::awb\n> + * \\brief Auto-white balance specific session configuration data\n> + */\n> +\n>  /**\n>   * \\var IPAActiveState::agc\n>   * \\brief Context for the Automatic Gain Control algorithm\n> @@ -139,26 +149,6 @@ namespace libcamera::ipa::ipu3 {\n>   * The gain should be adapted to the sensor specific gain code before applying.\n>   */\n>  \n> -/**\n> - * \\var IPAActiveState::awb\n> - * \\brief Context for the Automatic White Balance algorithm\n> - *\n> - * \\var IPAActiveState::awb.gains\n> - * \\brief White balance gains\n> - *\n> - * \\var IPAActiveState::awb.gains.red\n> - * \\brief White balance gain for R channel\n> - *\n> - * \\var IPAActiveState::awb.gains.green\n> - * \\brief White balance gain for G channel\n> - *\n> - * \\var IPAActiveState::awb.gains.blue\n> - * \\brief White balance gain for B channel\n> - *\n> - * \\var IPAActiveState::awb.temperatureK\n> - * \\brief Estimated color temperature\n> - */\n> -\n>  /**\n>   * \\var IPAActiveState::toneMapping\n>   * \\brief Context for ToneMapping and Gamma control\n> @@ -187,4 +177,9 @@ namespace libcamera::ipa::ipu3 {\n>   * \\brief Analogue gain multiplier\n>   */\n>  \n> +/**\n> + * \\var IPAFrameContext::awb\n> + * \\brief Per-frame auto-white balance parameters for the IPA\n> + */\n> +\n>  } /* namespace libcamera::ipa::ipu3 */\n> diff --git a/src/ipa/ipu3/ipa_context.h b/src/ipa/ipu3/ipa_context.h\n> index 97fcf06cd4ac9ac6d64c4933fcea80ace0e572df..245cf8b50b270a61df863e314128bede40d30541 100644\n> --- a/src/ipa/ipu3/ipa_context.h\n> +++ b/src/ipa/ipu3/ipa_context.h\n> @@ -15,6 +15,7 @@\n>  #include <libcamera/controls.h>\n>  #include <libcamera/geometry.h>\n>  \n> +#include <libipa/awb.h>\n>  #include <libipa/fc_queue.h>\n>  \n>  namespace libcamera {\n> @@ -44,6 +45,8 @@ struct IPASessionConfiguration {\n>                 utils::Duration lineDuration;\n>                 Size size;\n>         } sensor;\n> +\n> +       ipa::awb::Session awb;\n>  };\n>  \n>  struct IPAActiveState {\n> @@ -60,15 +63,7 @@ struct IPAActiveState {\n>                 uint32_t exposureMode;\n>         } agc;\n>  \n> -       struct {\n> -               struct {\n> -                       double red;\n> -                       double green;\n> -                       double blue;\n> -               } gains;\n> -\n> -               double temperatureK;\n> -       } awb;\n> +       ipa::awb::ActiveState awb;\n\nI love all this simplification ;-)\n\nOverall - I think we're going in the right direction so:\n\n\nReviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n\n>  \n>         struct {\n>                 double gamma;\n> @@ -81,6 +76,8 @@ struct IPAFrameContext : public FrameContext {\n>                 uint32_t exposure;\n>                 double gain;\n>         } sensor;\n> +\n> +       ipa::awb::FrameContext awb;\n>  };\n>  \n>  struct IPAContext {\n> \n> -- \n> 2.43.0\n>","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 AD13ABF415\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 16 Jun 2026 18:25:25 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id AC2A1626D7;\n\tTue, 16 Jun 2026 20:25:24 +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 1A2BB623CC\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 16 Jun 2026 20:25:23 +0200 (CEST)","from monstersaurus.ideasonboard.com\n\t(cpc89244-aztw30-2-0-cust6594.18-1.cable.virginm.net [86.31.185.195])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 4C3A3227;\n\tTue, 16 Jun 2026 20:24:49 +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=\"d+nE84ao\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1781634289;\n\tbh=jldypXlyLzFoDyq+LNDLY04PBFoKthaLFQzgEhYfeUE=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=d+nE84aoKp7T+FWwmGUIG/Iln0KN9CNeHAUyw7kNpaPG1v8ZePn3VQ8YR2TiUpP72\n\trbYFetutRL6ayXCLtpBRzvV2oSgtGmRKnY2U70hztrn2jpk4jB23sFMeqgaregAStP\n\tJZM5HnnzVi1P/ynGSW4mwKl2Bjf5bkFlDiUYAc/M=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<20260616-ipu3-libipa-rework-v1-3-d4448b54f1d8@ideasonboard.com>","References":"<20260616-ipu3-libipa-rework-v1-0-d4448b54f1d8@ideasonboard.com>\n\t<20260616-ipu3-libipa-rework-v1-3-d4448b54f1d8@ideasonboard.com>","Subject":"Re: [PATCH 03/10] ipa: ipu3: awb: Port to the new libipa\n\tAwbAlgorithm","From":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Cc":"Daniel Scally <dan.scally@ideasonboard.com>","To":"Daniel Scally <dan.scally@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","Date":"Tue, 16 Jun 2026 19:25:19 +0100","Message-ID":"<178163431966.3598163.10202931193127440648@ping.linuxembedded.co.uk>","User-Agent":"alot/0.9.1","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>"}},{"id":39149,"web_url":"https://patchwork.libcamera.org/comment/39149/","msgid":"<ajJVxxICN7hZPweb@zed>","date":"2026-06-17T08:20:44","subject":"Re: [PATCH 03/10] ipa: ipu3: awb: Port to the new libipa\n\tAwbAlgorithm","submitter":{"id":143,"url":"https://patchwork.libcamera.org/api/people/143/","name":"Jacopo Mondi","email":"jacopo.mondi@ideasonboard.com"},"content":"Hi Dan\n\nOn Tue, Jun 16, 2026 at 07:41:37AM +0100, Daniel Scally wrote:\n> Port the IPU3 Awb algorithm to use the new libipa implementation\n> of AwbAlgorithm.\n>\n> In this implementation the awbAlgo_ class member is initialised as\n> UQ<3, 13> following the IPU3 params format documentation.\n>\n> Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>\n> ---\n>  src/ipa/ipu3/algorithms/agc.cpp |  12 ++-\n>  src/ipa/ipu3/algorithms/awb.cpp | 194 ++++++++++++++++++----------------------\n>  src/ipa/ipu3/algorithms/awb.h   |  21 ++---\n>  src/ipa/ipu3/ipa_context.cpp    |  35 ++++----\n>  src/ipa/ipu3/ipa_context.h      |  15 ++--\n>  5 files changed, 128 insertions(+), 149 deletions(-)\n>\n> diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp\n> index d6a7036c6504acb106f5c773b529ad80b8349f85..5120b3bcb177c75ebd61d82c9684779f73305499 100644\n> --- a/src/ipa/ipu3/algorithms/agc.cpp\n> +++ b/src/ipa/ipu3/algorithms/agc.cpp\n> @@ -208,9 +208,15 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n>  \t\t  ControlList &metadata)\n>  {\n>  \tHistogram hist = parseStatistics(stats, context.configuration.grid.bdsGrid);\n> -\trGain_ = context.activeState.awb.gains.red;\n> -\tgGain_ = context.activeState.awb.gains.blue;\n> -\tbGain_ = context.activeState.awb.gains.green;\n> +\trGain_ = context.activeState.awb.autoEnabled ?\n> +\t\t context.activeState.awb.automatic.gains[0] :\n> +\t\t context.activeState.awb.manual.gains[0];\n> +\tgGain_ = context.activeState.awb.autoEnabled ?\n> +\t\t context.activeState.awb.automatic.gains[1] :\n> +\t\t context.activeState.awb.manual.gains[1];\n> +\tbGain_ = context.activeState.awb.autoEnabled ?\n> +\t\t context.activeState.awb.automatic.gains[2] :\n> +\t\t context.activeState.awb.manual.gains[2];\n>\n>  \t/*\n>  \t * The Agc algorithm needs to know the effective exposure value that was\n> diff --git a/src/ipa/ipu3/algorithms/awb.cpp b/src/ipa/ipu3/algorithms/awb.cpp\n> index 613bdbbf51cd127e03906571813260a6857067dc..fa1c70856a3647b188531473e9d9f8b77c747230 100644\n> --- a/src/ipa/ipu3/algorithms/awb.cpp\n> +++ b/src/ipa/ipu3/algorithms/awb.cpp\n> @@ -26,11 +26,40 @@ namespace ipa::ipu3::algorithms {\n>  LOG_DEFINE_CATEGORY(IPU3Awb)\n>\n>  /*\n> - * When zones are used for the grey world algorithm, they are only considered if\n> - * their average green value is at least 16/255 (after black level subtraction)\n> - * to exclude zones that are too dark and don't provide relevant colour\n> - * information (on the opposite side of the spectrum, saturated regions are\n> - * excluded by the ImgU statistics engine).\n> + * \\todo IPU3 doesn't support the Lux algorithm.\n> + */\n> +static constexpr unsigned int kDefaultLux = 500;\n> +\n> +/**\n> + * \\brief The IPU3 implementation of AwbStats\n> + */\n> +class Ipu3AwbStats final : public AwbStats\n> +{\n> +public:\n> +\tIpu3AwbStats(){};\n> +\tIpu3AwbStats(const RGB<double> means)\n> +\t\t: AwbStats(means)\n> +\t{\n> +\t}\n> +\n> +\tdouble minColourValue() const override\n> +\t{\n> +\t\treturn 0.0;\n> +\t}\n> +};\n> +\n> +/**\n> + * \\fn Ipu3AwbStats::Ipu3AwbStats(const RGB<double> means)\n> + * \\brief Construct an instance of the class with RGB means\n> + * \\param[in] means The mean R, G and B values from the statistics\n> + */\n> +\n> +/*\n> + * Zones are only considered if their average green value is at least\n> + * kMinGreenLevelInZone/255 (after black level subtraction) to exclude zones\n> + * that are too dark and don't provide relevant colour information (on the\n> + * opposite side of the spectrum, saturated regions are excluded by the ImgU\n> + * statistics engine).\n>   */\n>  static constexpr uint32_t kMinGreenLevelInZone = 16;\n>\n> @@ -74,26 +103,6 @@ static constexpr uint32_t kMinCellsPerZoneRatio = 255 * 90 / 100;\n>   * \\brief Sum of the average blue values of each unsaturated cell in the zone\n>   */\n>\n> -/**\n> - * \\struct Awb::AwbStatus\n> - * \\brief AWB parameters calculated\n> - *\n> - * The AwbStatus structure is intended to store the AWB\n> - * parameters calculated by the algorithm\n> - *\n> - * \\var AwbStatus::temperatureK\n> - * \\brief Color temperature calculated\n> - *\n> - * \\var AwbStatus::redGain\n> - * \\brief Gain calculated for the red channel\n> - *\n> - * \\var AwbStatus::greenGain\n> - * \\brief Gain calculated for the green channel\n> - *\n> - * \\var AwbStatus::blueGain\n> - * \\brief Gain calculated for the blue channel\n> - */\n> -\n>  /* Default settings for Bayer noise reduction replicated from the Kernel */\n>  static const struct ipu3_uapi_bnr_static_config imguCssBnrDefaults = {\n>  \t.wb_gains = { 16, 16, 16, 16 },\n> @@ -114,11 +123,7 @@ static const struct ipu3_uapi_bnr_static_config imguCssBnrDefaults = {\n>\n>  /**\n>   * \\class Awb\n> - * \\brief A Grey world white balance correction algorithm\n> - *\n> - * The Grey World algorithm assumes that the scene, in average, is neutral grey.\n> - * Reference: Lam, Edmund & Fung, George. (2008). Automatic White Balancing in\n> - * Digital Photography. 10.1201/9781420054538.ch10.\n> + * \\brief The IPU3 white balance correction algorithm implementation\n>   *\n>   * The IPU3 generates statistics from the Bayer Down Scaler output into a grid\n>   * defined in the ipu3_uapi_awb_config_s structure.\n> @@ -168,26 +173,26 @@ static const struct ipu3_uapi_bnr_static_config imguCssBnrDefaults = {\n>   * cells are ignored. The grid configuration is computed by\n>   * IPAIPU3::calculateBdsGrid().\n>   *\n> - * Before calculating the gains, the algorithm aggregates the cell averages for\n> - * each zone in generateAwbStats(). Cells that have a too high ratio of\n> - * saturated pixels are ignored, and only zones that contain enough\n> - * non-saturated cells are then used by the algorithm.\n> - *\n> - * The Grey World algorithm will then estimate the red and blue gains to apply, and\n> - * store the results in the metadata. The green gain is always set to 1.\n> + * Before running the AWB algorithm, we aggregate the cell averages for each\n> + * zone in generateAwbStats(). Cells that have a too high ratio of saturated\n> + * pixels are ignored, and only zones that contain enough non-saturated cells\n> + * are then used by the algorithm.\n\nI guess this was the intended purpose of minColourValue() in AwbStats,\nbut it seems IPU3 does a per-zone filtering instead of looking at the\nper-channel mean values.\n\nCan you add a note on minColourValue() as it feels a bit weird it\nreturns 0.0 ?\n\n>   */\n>\n>  Awb::Awb()\n>  \t: Algorithm()\n>  {\n> -\tasyncResults_.blueGain = 1.0;\n> -\tasyncResults_.greenGain = 1.0;\n> -\tasyncResults_.redGain = 1.0;\n> -\tasyncResults_.temperatureK = 4500;\n> -\n>  \tzones_.reserve(kAwbStatsSizeX * kAwbStatsSizeY);\n>  }\n>\n> +/**\n> + * \\copydoc libcamera::ipa::Algorithm::init\n> + */\n> +int Awb::init(IPAContext &context, const ValueNode &tuningData)\n> +{\n> +\treturn awbAlgo_.init(tuningData, context.ctrlMap);\n> +}\n> +\n>  /**\n>   * \\copydoc libcamera::ipa::Algorithm::configure\n>   */\n> @@ -197,12 +202,14 @@ int Awb::configure(IPAContext &context,\n>  \tconst ipu3_uapi_grid_config &grid = context.configuration.grid.bdsGrid;\n>  \tstride_ = context.configuration.grid.stride;\n>\n> +\tawbAlgo_.configure(context.activeState.awb, context.configuration.awb);\n> +\n>  \tcellsPerZoneX_ = std::round(grid.width / static_cast<double>(kAwbStatsSizeX));\n>  \tcellsPerZoneY_ = std::round(grid.height / static_cast<double>(kAwbStatsSizeY));\n>\n>  \t/*\n>  \t * Configure the minimum proportion of cells counted within a zone\n> -\t * for it to be relevant for the grey world algorithm.\n> +\t * for it to be used.\n>  \t * \\todo This proportion could be configured.\n>  \t */\n>  \tcellsPerZoneThreshold_ = cellsPerZoneX_ * cellsPerZoneY_ * kMaxCellSaturationRatio;\n> @@ -211,6 +218,17 @@ int Awb::configure(IPAContext &context,\n>  \treturn 0;\n>  }\n>\n> +/**\n> + * \\copydoc libcamera::ipa::Algorithm::queueRequest\n> + */\n> +void Awb::queueRequest(IPAContext &context, const uint32_t frame,\n> +\t\t       IPAFrameContext &frameContext,\n> +\t\t       const ControlList &controls)\n> +{\n> +\tawbAlgo_.queueRequest(context.activeState.awb, frame, frameContext.awb,\n> +\t\t\t      controls);\n> +}\n> +\n>  constexpr uint16_t Awb::threshold(float value)\n>  {\n>  \t/* AWB thresholds are in the range [0, 8191] */\n> @@ -237,11 +255,12 @@ constexpr uint16_t Awb::gainValue(double gain)\n>  /**\n>   * \\copydoc libcamera::ipa::Algorithm::prepare\n>   */\n> -void Awb::prepare(IPAContext &context,\n> -\t\t  [[maybe_unused]] const uint32_t frame,\n> -\t\t  [[maybe_unused]] IPAFrameContext &frameContext,\n> -\t\t  ipu3_uapi_params *params)\n> +void Awb::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n> +\t\t  IPAFrameContext &frameContext,\n> +\t\t  [[maybe_unused]] ipu3_uapi_params *params)\n>  {\n> +\tawbAlgo_.prepare(context.activeState.awb, frameContext.awb);\n> +\n>  \t/*\n>  \t * Green saturation thresholds are reduced because we are using the\n>  \t * green channel only in the exposure computation.\n> @@ -279,13 +298,11 @@ void Awb::prepare(IPAContext &context,\n>  \tparams->acc_param.bnr.opt_center_sqr.y_sqr_reset = params->acc_param.bnr.opt_center.y_reset\n>  \t\t\t\t\t\t\t* params->acc_param.bnr.opt_center.y_reset;\n>\n> -\tparams->acc_param.bnr.wb_gains.gr = gainValue(context.activeState.awb.gains.green);\n> -\tparams->acc_param.bnr.wb_gains.r  = gainValue(context.activeState.awb.gains.red);\n> -\tparams->acc_param.bnr.wb_gains.b  = gainValue(context.activeState.awb.gains.blue);\n> -\tparams->acc_param.bnr.wb_gains.gb = gainValue(context.activeState.awb.gains.green);\n> -\n> -\tLOG(IPU3Awb, Debug) << \"Color temperature estimated: \" << asyncResults_.temperatureK;\n>\n> +\tparams->acc_param.bnr.wb_gains.gr = gainValue(frameContext.awb.gains.g());\n> +\tparams->acc_param.bnr.wb_gains.r = gainValue(frameContext.awb.gains.r());\n> +\tparams->acc_param.bnr.wb_gains.b = gainValue(frameContext.awb.gains.b());\n> +\tparams->acc_param.bnr.wb_gains.gb = gainValue(frameContext.awb.gains.g());\n>\n>  \tparams->use.acc_awb = 1;\n>  \tparams->use.acc_bnr = 1;\n> @@ -366,9 +383,17 @@ void Awb::clearAwbStats()\n>  \t}\n>  }\n>\n> -void Awb::awbGreyWorld()\n> +Ipu3AwbStats Awb::calculateRgbMeans(const ipu3_uapi_stats_3a *stats)\n>  {\n> -\tLOG(IPU3Awb, Debug) << \"Grey world AWB\";\n> +\tASSERT(stats->stats_3a_status.awb_en);\n> +\n> +\tclearAwbStats();\n> +\tgenerateAwbStats(stats);\n> +\tgenerateZones();\n> +\n> +\tif (zones_.size() <= 10)\n> +\t\treturn {};\n> +\n\nYou know already I think it's very weird the IPU3 awb keeps stats as\nglobal variables. But this is not about reworking the algo, so let's\nnot dwell on it.\n\n>  \t/*\n>  \t * Make a separate list of the derivatives for each of red and blue, so\n>  \t * that we can sort them to exclude the extreme gains. We could\n> @@ -399,66 +424,21 @@ void Awb::awbGreyWorld()\n>  \tdouble redGain = sumRed.g() / (sumRed.r() + 1),\n>  \t       blueGain = sumBlue.g() / (sumBlue.b() + 1);\n>\n> -\t/* Color temperature is not relevant in Grey world but still useful to estimate it :-) */\n> -\tasyncResults_.temperatureK = estimateCCT({{ sumRed.r(), sumRed.g(), sumBlue.b() }});\n\nCould you check your includes, not what you're not calling estimateCCT\nyou should be able to remove\n\n#include \"libipa/colours.h\"\n\n> -\n> -\t/*\n> -\t * Gain values are unsigned integer value ranging [0, 8) with 13 bit\n> -\t * fractional part.\n> -\t */\n> -\tredGain = std::clamp(redGain, 0.0, 65535.0 / 8192);\n> -\tblueGain = std::clamp(blueGain, 0.0, 65535.0 / 8192);\n> -\n> -\tasyncResults_.redGain = redGain;\n> -\t/* Hardcode the green gain to 1.0. */\n> -\tasyncResults_.greenGain = 1.0;\n> -\tasyncResults_.blueGain = blueGain;\n> -}\n> -\n> -void Awb::calculateWBGains(const ipu3_uapi_stats_3a *stats)\n> -{\n> -\tASSERT(stats->stats_3a_status.awb_en);\n> -\n> -\tclearAwbStats();\n> -\tgenerateAwbStats(stats);\n> -\tgenerateZones();\n> -\n> -\tLOG(IPU3Awb, Debug) << \"Valid zones: \" << zones_.size();\n> -\n> -\tif (zones_.size() > 10) {\n> -\t\tawbGreyWorld();\n> -\t\tLOG(IPU3Awb, Debug) << \"Gain found for red: \" << asyncResults_.redGain\n> -\t\t\t\t    << \" and for blue: \" << asyncResults_.blueGain;\n> -\t}\n> +\treturn Ipu3AwbStats({ { 1.0 / redGain, 1.0, 1.0 / blueGain } });\n>  }\n>\n>  /**\n>   * \\copydoc libcamera::ipa::Algorithm::process\n>   */\n>  void Awb::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n> -\t\t  [[maybe_unused]] IPAFrameContext &frameContext,\n> -\t\t  const ipu3_uapi_stats_3a *stats,\n> -\t\t  [[maybe_unused]] ControlList &metadata)\n> +\t\t  IPAFrameContext &frameContext,\n> +\t\t  [[maybe_unused]] const ipu3_uapi_stats_3a *stats,\n> +\t\t  ControlList &metadata)\n>  {\n> -\tcalculateWBGains(stats);\n> +\tIpu3AwbStats awbStats = calculateRgbMeans(stats);\n>\n> -\t/*\n> -\t * Gains are only recalculated if enough zones were detected.\n> -\t * The results are cached, so if no results were calculated, we set the\n> -\t * cached values from asyncResults_ here.\n> -\t */\n> -\tcontext.activeState.awb.gains.blue = asyncResults_.blueGain;\n> -\tcontext.activeState.awb.gains.green = asyncResults_.greenGain;\n> -\tcontext.activeState.awb.gains.red = asyncResults_.redGain;\n> -\tcontext.activeState.awb.temperatureK = asyncResults_.temperatureK;\n> -\n> -\tmetadata.set(controls::AwbEnable, true);\n> -\tmetadata.set(controls::ColourGains, {\n> -\t\t\tstatic_cast<float>(context.activeState.awb.gains.red),\n> -\t\t\tstatic_cast<float>(context.activeState.awb.gains.blue)\n> -\t\t});\n> -\tmetadata.set(controls::ColourTemperature,\n> -\t\t     context.activeState.awb.temperatureK);\n> +\tawbAlgo_.process(context.activeState.awb, frameContext.awb, awbStats,\n> +\t\t\t kDefaultLux, metadata);\n>  }\n>\n>  REGISTER_IPA_ALGORITHM(Awb, \"Awb\")\n> diff --git a/src/ipa/ipu3/algorithms/awb.h b/src/ipa/ipu3/algorithms/awb.h\n> index 7ec322318dab54ae7c8a647a67a0cf5815a36eb6..3f3996e6c846558acd3902c9fb3cb804cb5b65d4 100644\n> --- a/src/ipa/ipu3/algorithms/awb.h\n> +++ b/src/ipa/ipu3/algorithms/awb.h\n> @@ -13,6 +13,9 @@\n>\n>  #include <libcamera/geometry.h>\n>\n> +#include \"libipa/awb.h\"\n> +#include \"libipa/fixedpoint.h\"\n> +\n>  #include \"libcamera/internal/vector.h\"\n\nIs this still needed here ?\n\n>\n>  #include \"algorithm.h\"\n> @@ -21,6 +24,8 @@ namespace libcamera {\n>\n>  namespace ipa::ipu3::algorithms {\n>\n> +class Ipu3AwbStats;\n> +\n>  /* Region size for the statistics generation algorithm */\n>  static constexpr uint32_t kAwbStatsSizeX = 16;\n>  static constexpr uint32_t kAwbStatsSizeY = 12;\n> @@ -39,7 +44,11 @@ class Awb : public Algorithm\n>  public:\n>  \tAwb();\n>\n> +\tint init(IPAContext &context, const ValueNode &tuningData) override;\n>  \tint configure(IPAContext &context, const IPAConfigInfo &configInfo) override;\n> +\tvoid queueRequest(IPAContext &context, const uint32_t frame,\n> +\t\t\t  IPAFrameContext &frameContext,\n> +\t\t\t  const ControlList &controls) override;\n>  \tvoid prepare(IPAContext &context, const uint32_t frame,\n>  \t\t     IPAFrameContext &frameContext,\n>  \t\t     ipu3_uapi_params *params) override;\n> @@ -49,15 +58,7 @@ public:\n>  \t\t     ControlList &metadata) override;\n>\n>  private:\n> -\tstruct AwbStatus {\n> -\t\tdouble temperatureK;\n> -\t\tdouble redGain;\n> -\t\tdouble greenGain;\n> -\t\tdouble blueGain;\n> -\t};\n> -\n> -private:\n> -\tvoid calculateWBGains(const ipu3_uapi_stats_3a *stats);\n> +\tIpu3AwbStats calculateRgbMeans(const ipu3_uapi_stats_3a *stats);\n>  \tvoid generateZones();\n>  \tvoid generateAwbStats(const ipu3_uapi_stats_3a *stats);\n>  \tvoid clearAwbStats();\n> @@ -67,7 +68,7 @@ private:\n>\n>  \tstd::vector<RGB<double>> zones_;\n>  \tAccumulator awbStats_[kAwbStatsSizeX * kAwbStatsSizeY];\n> -\tAwbStatus asyncResults_;\n> +\tAwbAlgorithm<UQ<3, 13>> awbAlgo_;\n>\n>  \tuint32_t stride_;\n>  \tuint32_t cellsPerZoneX_;\n> diff --git a/src/ipa/ipu3/ipa_context.cpp b/src/ipa/ipu3/ipa_context.cpp\n> index 3b22f7917650d9e400d5368c2f890d6b2dc846a0..b35c925d959027c540257e47944047c238f85571 100644\n> --- a/src/ipa/ipu3/ipa_context.cpp\n> +++ b/src/ipa/ipu3/ipa_context.cpp\n> @@ -109,6 +109,11 @@ namespace libcamera::ipa::ipu3 {\n>   * \\brief Maximum analogue gain supported with the configured sensor\n>   */\n>\n> +/**\n> + * \\var IPAActiveState::awb\n> + * \\brief Active auto-white balance parameters for the IPA\n> + */\n> +\n>  /**\n>   * \\var IPASessionConfiguration::sensor\n>   * \\brief Sensor-specific configuration of the IPA\n> @@ -123,6 +128,11 @@ namespace libcamera::ipa::ipu3 {\n>   * \\brief Sensor output resolution\n>   */\n>\n> +/**\n> + * \\var IPASessionConfiguration::awb\n> + * \\brief Auto-white balance specific session configuration data\n> + */\n> +\n\nThis will have to be rebased, as I've now dropped this\n\n>  /**\n>   * \\var IPAActiveState::agc\n>   * \\brief Context for the Automatic Gain Control algorithm\n> @@ -139,26 +149,6 @@ namespace libcamera::ipa::ipu3 {\n>   * The gain should be adapted to the sensor specific gain code before applying.\n>   */\n>\n> -/**\n> - * \\var IPAActiveState::awb\n> - * \\brief Context for the Automatic White Balance algorithm\n> - *\n> - * \\var IPAActiveState::awb.gains\n> - * \\brief White balance gains\n> - *\n> - * \\var IPAActiveState::awb.gains.red\n> - * \\brief White balance gain for R channel\n> - *\n> - * \\var IPAActiveState::awb.gains.green\n> - * \\brief White balance gain for G channel\n> - *\n> - * \\var IPAActiveState::awb.gains.blue\n> - * \\brief White balance gain for B channel\n> - *\n> - * \\var IPAActiveState::awb.temperatureK\n> - * \\brief Estimated color temperature\n> - */\n> -\n>  /**\n>   * \\var IPAActiveState::toneMapping\n>   * \\brief Context for ToneMapping and Gamma control\n> @@ -187,4 +177,9 @@ namespace libcamera::ipa::ipu3 {\n>   * \\brief Analogue gain multiplier\n>   */\n>\n> +/**\n> + * \\var IPAFrameContext::awb\n> + * \\brief Per-frame auto-white balance parameters for the IPA\n> + */\n> +\n>  } /* namespace libcamera::ipa::ipu3 */\n> diff --git a/src/ipa/ipu3/ipa_context.h b/src/ipa/ipu3/ipa_context.h\n> index 97fcf06cd4ac9ac6d64c4933fcea80ace0e572df..245cf8b50b270a61df863e314128bede40d30541 100644\n> --- a/src/ipa/ipu3/ipa_context.h\n> +++ b/src/ipa/ipu3/ipa_context.h\n> @@ -15,6 +15,7 @@\n>  #include <libcamera/controls.h>\n>  #include <libcamera/geometry.h>\n>\n> +#include <libipa/awb.h>\n>  #include <libipa/fc_queue.h>\n>\n>  namespace libcamera {\n> @@ -44,6 +45,8 @@ struct IPASessionConfiguration {\n>  \t\tutils::Duration lineDuration;\n>  \t\tSize size;\n>  \t} sensor;\n> +\n> +\tipa::awb::Session awb;\n>  };\n>\n>  struct IPAActiveState {\n> @@ -60,15 +63,7 @@ struct IPAActiveState {\n>  \t\tuint32_t exposureMode;\n>  \t} agc;\n>\n> -\tstruct {\n> -\t\tstruct {\n> -\t\t\tdouble red;\n> -\t\t\tdouble green;\n> -\t\t\tdouble blue;\n> -\t\t} gains;\n> -\n> -\t\tdouble temperatureK;\n> -\t} awb;\n> +\tipa::awb::ActiveState awb;\n>\n>  \tstruct {\n>  \t\tdouble gamma;\n> @@ -81,6 +76,8 @@ struct IPAFrameContext : public FrameContext {\n>  \t\tuint32_t exposure;\n>  \t\tdouble gain;\n>  \t} sensor;\n> +\n> +\tipa::awb::FrameContext awb;\n\nAll minors, thanks for the work!\nReviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n\n>  };\n>\n>  struct IPAContext {\n>\n> --\n> 2.43.0\n>","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 4F117C3261\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 17 Jun 2026 08:20:51 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 6BDEC626F0;\n\tWed, 17 Jun 2026 10:20:50 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 2529B60579\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 17 Jun 2026 10:20:48 +0200 (CEST)","from ideasonboard.com (mob-109-113-4-199.net.vodafone.it\n\t[109.113.4.199])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id BF0477CA;\n\tWed, 17 Jun 2026 10:20:13 +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=\"uHP/4AfZ\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1781684414;\n\tbh=0p2DmACiDZOu8et+/UFJ7BacQkKnJXPylqAcFP2UA7E=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=uHP/4AfZr5aW+z8j6Moek5e9oKrr20N2ZPXdAUoXHZ/+9u6+cnNWFacAJLy0PcDNR\n\t1zMFd16X40ebZor8SBixaJF2ET6eDiWxfY+3nKRXw/TGeujD6XruEbDv4nNRr0SMwA\n\tqvf40pC0JXAEcRa1+G6WzfJT6/11FicHylJjAzW0=","Date":"Wed, 17 Jun 2026 10:20:44 +0200","From":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","To":"Daniel Scally <dan.scally@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Subject":"Re: [PATCH 03/10] ipa: ipu3: awb: Port to the new libipa\n\tAwbAlgorithm","Message-ID":"<ajJVxxICN7hZPweb@zed>","References":"<20260616-ipu3-libipa-rework-v1-0-d4448b54f1d8@ideasonboard.com>\n\t<20260616-ipu3-libipa-rework-v1-3-d4448b54f1d8@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20260616-ipu3-libipa-rework-v1-3-d4448b54f1d8@ideasonboard.com>","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>"}},{"id":39419,"web_url":"https://patchwork.libcamera.org/comment/39419/","msgid":"<a6607316-d417-4df8-ba0f-1bf71b6eaea5@ideasonboard.com>","date":"2026-06-25T22:16:42","subject":"Re: [PATCH 03/10] ipa: ipu3: awb: Port to the new libipa\n\tAwbAlgorithm","submitter":{"id":156,"url":"https://patchwork.libcamera.org/api/people/156/","name":"Dan Scally","email":"dan.scally@ideasonboard.com"},"content":"Hi Kieran\n\nOn 16/06/2026 19:25, Kieran Bingham wrote:\n> Quoting Daniel Scally (2026-06-16 07:41:37)\n>> Port the IPU3 Awb algorithm to use the new libipa implementation\n>> of AwbAlgorithm.\n>>\n>> In this implementation the awbAlgo_ class member is initialised as\n>> UQ<3, 13> following the IPU3 params format documentation.\n>>\n>> Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>\n>> ---\n>>   src/ipa/ipu3/algorithms/agc.cpp |  12 ++-\n>>   src/ipa/ipu3/algorithms/awb.cpp | 194 ++++++++++++++++++----------------------\n>>   src/ipa/ipu3/algorithms/awb.h   |  21 ++---\n>>   src/ipa/ipu3/ipa_context.cpp    |  35 ++++----\n>>   src/ipa/ipu3/ipa_context.h      |  15 ++--\n>>   5 files changed, 128 insertions(+), 149 deletions(-)\n>>\n>> diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp\n>> index d6a7036c6504acb106f5c773b529ad80b8349f85..5120b3bcb177c75ebd61d82c9684779f73305499 100644\n>> --- a/src/ipa/ipu3/algorithms/agc.cpp\n>> +++ b/src/ipa/ipu3/algorithms/agc.cpp\n>> @@ -208,9 +208,15 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n>>                    ControlList &metadata)\n>>   {\n>>          Histogram hist = parseStatistics(stats, context.configuration.grid.bdsGrid);\n>> -       rGain_ = context.activeState.awb.gains.red;\n>> -       gGain_ = context.activeState.awb.gains.blue;\n>> -       bGain_ = context.activeState.awb.gains.green;\n>> +       rGain_ = context.activeState.awb.autoEnabled ?\n>> +                context.activeState.awb.automatic.gains[0] :\n>> +                context.activeState.awb.manual.gains[0];\n>> +       gGain_ = context.activeState.awb.autoEnabled ?\n>> +                context.activeState.awb.automatic.gains[1] :\n>> +                context.activeState.awb.manual.gains[1];\n>> +       bGain_ = context.activeState.awb.autoEnabled ?\n>> +                context.activeState.awb.automatic.gains[2] :\n>> +                context.activeState.awb.manual.gains[2];\n> \n> Can we drop rGain_, gGain_ and bGain_ and change that into an RGB type?\n> Perhaps as a patch beforehand.\n> \n> \n> Then we can simply do\n> \tif (context.activeState.awb.autoEnabled)\n> \t\tgains = context.activeState.awb.automatic.gains;\n> \telse\n> \t\tgains = context.activeState.awb.manual.gains;\n> \n> But maybe we need to check what those local class members are being\n> used for - now I see we're in agc.cpp!\n> \nThey're actually just used as...\n\nRGB<double> gains{{ rGain_, gGain_, bGain_ }};\n\nSo yeah that's probably quite viable.\n\n>>   \n>>          /*\n>>           * The Agc algorithm needs to know the effective exposure value that was\n>> diff --git a/src/ipa/ipu3/algorithms/awb.cpp b/src/ipa/ipu3/algorithms/awb.cpp\n>> index 613bdbbf51cd127e03906571813260a6857067dc..fa1c70856a3647b188531473e9d9f8b77c747230 100644\n>> --- a/src/ipa/ipu3/algorithms/awb.cpp\n>> +++ b/src/ipa/ipu3/algorithms/awb.cpp\n>> @@ -26,11 +26,40 @@ namespace ipa::ipu3::algorithms {\n>>   LOG_DEFINE_CATEGORY(IPU3Awb)\n>>   \n>>   /*\n>> - * When zones are used for the grey world algorithm, they are only considered if\n>> - * their average green value is at least 16/255 (after black level subtraction)\n>> - * to exclude zones that are too dark and don't provide relevant colour\n>> - * information (on the opposite side of the spectrum, saturated regions are\n>> - * excluded by the ImgU statistics engine).\n>> + * \\todo IPU3 doesn't support the Lux algorithm.\n>> + */\n> \n> That's really easy to add ;-) (can be later)\n> \n>> +static constexpr unsigned int kDefaultLux = 500;\n>> +\n>> +/**\n>> + * \\brief The IPU3 implementation of AwbStats\n>> + */\n>> +class Ipu3AwbStats final : public AwbStats\n>> +{\n>> +public:\n>> +       Ipu3AwbStats(){};\n>> +       Ipu3AwbStats(const RGB<double> means)\n>> +               : AwbStats(means)\n>> +       {\n>> +       }\n>> +\n>> +       double minColourValue() const override\n>> +       {\n>> +               return 0.0;\n> \n> is 0.0 a valid number here? I saw Jacopo's series put 0.2 which I\n> thought was to prevent some divide by 0. But I'm curious what this would\n> be used for anyway, or if any platform would use the same numbers?\n\nIt's the minimum average colour for a channel that needs to be met for the statistics to be \nconsidered valid, and so probably platform specific...but the existing implementation instead \nfilters out any stats zones that don't have a green channel level above a particular value (set to \n16)...\n> \n>> +       }\n>> +};\n>> +\n>> +/**\n>> + * \\fn Ipu3AwbStats::Ipu3AwbStats(const RGB<double> means)\n>> + * \\brief Construct an instance of the class with RGB means\n>> + * \\param[in] means The mean R, G and B values from the statistics\n>> + */\n>> +\n>> +/*\n>> + * Zones are only considered if their average green value is at least\n>> + * kMinGreenLevelInZone/255 (after black level subtraction) to exclude zones\n>> + * that are too dark and don't provide relevant colour information (on the\n>> + * opposite side of the spectrum, saturated regions are excluded by the ImgU\n>> + * statistics engine).\n>>    */\n>>   static constexpr uint32_t kMinGreenLevelInZone = 16;\n\n...which is the purpose of this constant here. So with that filtering, I think that the statistics \nshould always be considered valid, since we already dropped zones that are too dim to be useful. I \nthink  it's probably better kept as-is, as it should allow the algorithm to keep going even if some \nsectors were so dark that they would otherwise drag the average below the threshold.\n\nThanks\nDan\n\n>>   \n>> @@ -74,26 +103,6 @@ static constexpr uint32_t kMinCellsPerZoneRatio = 255 * 90 / 100;\n>>    * \\brief Sum of the average blue values of each unsaturated cell in the zone\n>>    */\n>>   \n>> -/**\n>> - * \\struct Awb::AwbStatus\n>> - * \\brief AWB parameters calculated\n>> - *\n>> - * The AwbStatus structure is intended to store the AWB\n>> - * parameters calculated by the algorithm\n>> - *\n>> - * \\var AwbStatus::temperatureK\n>> - * \\brief Color temperature calculated\n>> - *\n>> - * \\var AwbStatus::redGain\n>> - * \\brief Gain calculated for the red channel\n>> - *\n>> - * \\var AwbStatus::greenGain\n>> - * \\brief Gain calculated for the green channel\n>> - *\n>> - * \\var AwbStatus::blueGain\n>> - * \\brief Gain calculated for the blue channel\n>> - */\n>> -\n>>   /* Default settings for Bayer noise reduction replicated from the Kernel */\n>>   static const struct ipu3_uapi_bnr_static_config imguCssBnrDefaults = {\n>>          .wb_gains = { 16, 16, 16, 16 },\n>> @@ -114,11 +123,7 @@ static const struct ipu3_uapi_bnr_static_config imguCssBnrDefaults = {\n>>   \n>>   /**\n>>    * \\class Awb\n>> - * \\brief A Grey world white balance correction algorithm\n>> - *\n>> - * The Grey World algorithm assumes that the scene, in average, is neutral grey.\n>> - * Reference: Lam, Edmund & Fung, George. (2008). Automatic White Balancing in\n>> - * Digital Photography. 10.1201/9781420054538.ch10.\n>> + * \\brief The IPU3 white balance correction algorithm implementation\n>>    *\n>>    * The IPU3 generates statistics from the Bayer Down Scaler output into a grid\n>>    * defined in the ipu3_uapi_awb_config_s structure.\n>> @@ -168,26 +173,26 @@ static const struct ipu3_uapi_bnr_static_config imguCssBnrDefaults = {\n>>    * cells are ignored. The grid configuration is computed by\n>>    * IPAIPU3::calculateBdsGrid().\n>>    *\n>> - * Before calculating the gains, the algorithm aggregates the cell averages for\n>> - * each zone in generateAwbStats(). Cells that have a too high ratio of\n>> - * saturated pixels are ignored, and only zones that contain enough\n>> - * non-saturated cells are then used by the algorithm.\n>> - *\n>> - * The Grey World algorithm will then estimate the red and blue gains to apply, and\n>> - * store the results in the metadata. The green gain is always set to 1.\n>> + * Before running the AWB algorithm, we aggregate the cell averages for each\n>> + * zone in generateAwbStats(). Cells that have a too high ratio of saturated\n>> + * pixels are ignored, and only zones that contain enough non-saturated cells\n>> + * are then used by the algorithm.\n>>    */\n>>   \n>>   Awb::Awb()\n>>          : Algorithm()\n>>   {\n>> -       asyncResults_.blueGain = 1.0;\n>> -       asyncResults_.greenGain = 1.0;\n>> -       asyncResults_.redGain = 1.0;\n>> -       asyncResults_.temperatureK = 4500;\n>> -\n>>          zones_.reserve(kAwbStatsSizeX * kAwbStatsSizeY);\n>>   }\n>>   \n>> +/**\n>> + * \\copydoc libcamera::ipa::Algorithm::init\n>> + */\n>> +int Awb::init(IPAContext &context, const ValueNode &tuningData)\n>> +{\n>> +       return awbAlgo_.init(tuningData, context.ctrlMap);\n>> +}\n>> +\n>>   /**\n>>    * \\copydoc libcamera::ipa::Algorithm::configure\n>>    */\n>> @@ -197,12 +202,14 @@ int Awb::configure(IPAContext &context,\n>>          const ipu3_uapi_grid_config &grid = context.configuration.grid.bdsGrid;\n>>          stride_ = context.configuration.grid.stride;\n>>   \n>> +       awbAlgo_.configure(context.activeState.awb, context.configuration.awb);\n>> +\n>>          cellsPerZoneX_ = std::round(grid.width / static_cast<double>(kAwbStatsSizeX));\n>>          cellsPerZoneY_ = std::round(grid.height / static_cast<double>(kAwbStatsSizeY));\n>>   \n>>          /*\n>>           * Configure the minimum proportion of cells counted within a zone\n>> -        * for it to be relevant for the grey world algorithm.\n>> +        * for it to be used.\n>>           * \\todo This proportion could be configured.\n>>           */\n>>          cellsPerZoneThreshold_ = cellsPerZoneX_ * cellsPerZoneY_ * kMaxCellSaturationRatio;\n>> @@ -211,6 +218,17 @@ int Awb::configure(IPAContext &context,\n>>          return 0;\n>>   }\n>>   \n>> +/**\n>> + * \\copydoc libcamera::ipa::Algorithm::queueRequest\n>> + */\n>> +void Awb::queueRequest(IPAContext &context, const uint32_t frame,\n>> +                      IPAFrameContext &frameContext,\n>> +                      const ControlList &controls)\n>> +{\n>> +       awbAlgo_.queueRequest(context.activeState.awb, frame, frameContext.awb,\n>> +                             controls);\n>> +}\n>> +\n>>   constexpr uint16_t Awb::threshold(float value)\n>>   {\n>>          /* AWB thresholds are in the range [0, 8191] */\n>> @@ -237,11 +255,12 @@ constexpr uint16_t Awb::gainValue(double gain)\n>>   /**\n>>    * \\copydoc libcamera::ipa::Algorithm::prepare\n>>    */\n>> -void Awb::prepare(IPAContext &context,\n>> -                 [[maybe_unused]] const uint32_t frame,\n>> -                 [[maybe_unused]] IPAFrameContext &frameContext,\n>> -                 ipu3_uapi_params *params)\n>> +void Awb::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n>> +                 IPAFrameContext &frameContext,\n>> +                 [[maybe_unused]] ipu3_uapi_params *params)\n>>   {\n>> +       awbAlgo_.prepare(context.activeState.awb, frameContext.awb);\n>> +\n>>          /*\n>>           * Green saturation thresholds are reduced because we are using the\n>>           * green channel only in the exposure computation.\n>> @@ -279,13 +298,11 @@ void Awb::prepare(IPAContext &context,\n>>          params->acc_param.bnr.opt_center_sqr.y_sqr_reset = params->acc_param.bnr.opt_center.y_reset\n>>                                                          * params->acc_param.bnr.opt_center.y_reset;\n>>   \n>> -       params->acc_param.bnr.wb_gains.gr = gainValue(context.activeState.awb.gains.green);\n>> -       params->acc_param.bnr.wb_gains.r  = gainValue(context.activeState.awb.gains.red);\n>> -       params->acc_param.bnr.wb_gains.b  = gainValue(context.activeState.awb.gains.blue);\n>> -       params->acc_param.bnr.wb_gains.gb = gainValue(context.activeState.awb.gains.green);\n>> -\n>> -       LOG(IPU3Awb, Debug) << \"Color temperature estimated: \" << asyncResults_.temperatureK;\n>>   \n>> +       params->acc_param.bnr.wb_gains.gr = gainValue(frameContext.awb.gains.g());\n>> +       params->acc_param.bnr.wb_gains.r = gainValue(frameContext.awb.gains.r());\n>> +       params->acc_param.bnr.wb_gains.b = gainValue(frameContext.awb.gains.b());\n>> +       params->acc_param.bnr.wb_gains.gb = gainValue(frameContext.awb.gains.g());\n> \n> Coool, now we're doing AWB correctly here!\n> \n> A quick look at Awb::gainValue looks like it could be converted to a\n> UQ<3,13> I guess, but it would have to be checked and verified, so lets\n> keep that as a separate topic, as ther'es that +/- 1.0 which I suspect\n> isn't a straight translation...\n> \n>>   \n>>          params->use.acc_awb = 1;\n>>          params->use.acc_bnr = 1;\n>> @@ -366,9 +383,17 @@ void Awb::clearAwbStats()\n>>          }\n>>   }\n>>   \n>> -void Awb::awbGreyWorld()\n>> +Ipu3AwbStats Awb::calculateRgbMeans(const ipu3_uapi_stats_3a *stats)\n>>   {\n>> -       LOG(IPU3Awb, Debug) << \"Grey world AWB\";\n>> +       ASSERT(stats->stats_3a_status.awb_en);\n>> +\n>> +       clearAwbStats();\n>> +       generateAwbStats(stats);\n>> +       generateZones();\n>> +\n>> +       if (zones_.size() <= 10)\n>> +               return {};\n>> +\n>>          /*\n>>           * Make a separate list of the derivatives for each of red and blue, so\n>>           * that we can sort them to exclude the extreme gains. We could\n>> @@ -399,66 +424,21 @@ void Awb::awbGreyWorld()\n>>          double redGain = sumRed.g() / (sumRed.r() + 1),\n>>                 blueGain = sumBlue.g() / (sumBlue.b() + 1);\n>>   \n>> -       /* Color temperature is not relevant in Grey world but still useful to estimate it :-) */\n>> -       asyncResults_.temperatureK = estimateCCT({{ sumRed.r(), sumRed.g(), sumBlue.b() }});\n>> -\n>> -       /*\n>> -        * Gain values are unsigned integer value ranging [0, 8) with 13 bit\n>> -        * fractional part.\n>> -        */\n>> -       redGain = std::clamp(redGain, 0.0, 65535.0 / 8192);\n>> -       blueGain = std::clamp(blueGain, 0.0, 65535.0 / 8192);\n>> -\n>> -       asyncResults_.redGain = redGain;\n>> -       /* Hardcode the green gain to 1.0. */\n>> -       asyncResults_.greenGain = 1.0;\n>> -       asyncResults_.blueGain = blueGain;\n>> -}\n>> -\n>> -void Awb::calculateWBGains(const ipu3_uapi_stats_3a *stats)\n>> -{\n>> -       ASSERT(stats->stats_3a_status.awb_en);\n>> -\n>> -       clearAwbStats();\n>> -       generateAwbStats(stats);\n>> -       generateZones();\n>> -\n>> -       LOG(IPU3Awb, Debug) << \"Valid zones: \" << zones_.size();\n>> -\n>> -       if (zones_.size() > 10) {\n>> -               awbGreyWorld();\n>> -               LOG(IPU3Awb, Debug) << \"Gain found for red: \" << asyncResults_.redGain\n>> -                                   << \" and for blue: \" << asyncResults_.blueGain;\n>> -       }\n>> +       return Ipu3AwbStats({ { 1.0 / redGain, 1.0, 1.0 / blueGain } });\n>>   }\n>>   \n>>   /**\n>>    * \\copydoc libcamera::ipa::Algorithm::process\n>>    */\n>>   void Awb::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n>> -                 [[maybe_unused]] IPAFrameContext &frameContext,\n>> -                 const ipu3_uapi_stats_3a *stats,\n>> -                 [[maybe_unused]] ControlList &metadata)\n>> +                 IPAFrameContext &frameContext,\n>> +                 [[maybe_unused]] const ipu3_uapi_stats_3a *stats,\n>> +                 ControlList &metadata)\n>>   {\n>> -       calculateWBGains(stats);\n>> +       Ipu3AwbStats awbStats = calculateRgbMeans(stats);\n>>   \n>> -       /*\n>> -        * Gains are only recalculated if enough zones were detected.\n>> -        * The results are cached, so if no results were calculated, we set the\n>> -        * cached values from asyncResults_ here.\n>> -        */\n>> -       context.activeState.awb.gains.blue = asyncResults_.blueGain;\n>> -       context.activeState.awb.gains.green = asyncResults_.greenGain;\n>> -       context.activeState.awb.gains.red = asyncResults_.redGain;\n>> -       context.activeState.awb.temperatureK = asyncResults_.temperatureK;\n>> -\n>> -       metadata.set(controls::AwbEnable, true);\n>> -       metadata.set(controls::ColourGains, {\n>> -                       static_cast<float>(context.activeState.awb.gains.red),\n>> -                       static_cast<float>(context.activeState.awb.gains.blue)\n>> -               });\n>> -       metadata.set(controls::ColourTemperature,\n>> -                    context.activeState.awb.temperatureK);\n>> +       awbAlgo_.process(context.activeState.awb, frameContext.awb, awbStats,\n>> +                        kDefaultLux, metadata);\n>>   }\n>>   \n>>   REGISTER_IPA_ALGORITHM(Awb, \"Awb\")\n>> diff --git a/src/ipa/ipu3/algorithms/awb.h b/src/ipa/ipu3/algorithms/awb.h\n>> index 7ec322318dab54ae7c8a647a67a0cf5815a36eb6..3f3996e6c846558acd3902c9fb3cb804cb5b65d4 100644\n>> --- a/src/ipa/ipu3/algorithms/awb.h\n>> +++ b/src/ipa/ipu3/algorithms/awb.h\n>> @@ -13,6 +13,9 @@\n>>   \n>>   #include <libcamera/geometry.h>\n>>   \n>> +#include \"libipa/awb.h\"\n>> +#include \"libipa/fixedpoint.h\"\n>> +\n>>   #include \"libcamera/internal/vector.h\"\n>>   \n>>   #include \"algorithm.h\"\n>> @@ -21,6 +24,8 @@ namespace libcamera {\n>>   \n>>   namespace ipa::ipu3::algorithms {\n>>   \n>> +class Ipu3AwbStats;\n>> +\n>>   /* Region size for the statistics generation algorithm */\n>>   static constexpr uint32_t kAwbStatsSizeX = 16;\n>>   static constexpr uint32_t kAwbStatsSizeY = 12;\n>> @@ -39,7 +44,11 @@ class Awb : public Algorithm\n>>   public:\n>>          Awb();\n>>   \n>> +       int init(IPAContext &context, const ValueNode &tuningData) override;\n>>          int configure(IPAContext &context, const IPAConfigInfo &configInfo) override;\n>> +       void queueRequest(IPAContext &context, const uint32_t frame,\n>> +                         IPAFrameContext &frameContext,\n>> +                         const ControlList &controls) override;\n>>          void prepare(IPAContext &context, const uint32_t frame,\n>>                       IPAFrameContext &frameContext,\n>>                       ipu3_uapi_params *params) override;\n>> @@ -49,15 +58,7 @@ public:\n>>                       ControlList &metadata) override;\n>>   \n>>   private:\n>> -       struct AwbStatus {\n>> -               double temperatureK;\n>> -               double redGain;\n>> -               double greenGain;\n>> -               double blueGain;\n>> -       };\n>> -\n>> -private:\n>> -       void calculateWBGains(const ipu3_uapi_stats_3a *stats);\n>> +       Ipu3AwbStats calculateRgbMeans(const ipu3_uapi_stats_3a *stats);\n>>          void generateZones();\n>>          void generateAwbStats(const ipu3_uapi_stats_3a *stats);\n>>          void clearAwbStats();\n>> @@ -67,7 +68,7 @@ private:\n>>   \n>>          std::vector<RGB<double>> zones_;\n>>          Accumulator awbStats_[kAwbStatsSizeX * kAwbStatsSizeY];\n>> -       AwbStatus asyncResults_;\n>> +       AwbAlgorithm<UQ<3, 13>> awbAlgo_;\n>>   \n>>          uint32_t stride_;\n>>          uint32_t cellsPerZoneX_;\n>> diff --git a/src/ipa/ipu3/ipa_context.cpp b/src/ipa/ipu3/ipa_context.cpp\n>> index 3b22f7917650d9e400d5368c2f890d6b2dc846a0..b35c925d959027c540257e47944047c238f85571 100644\n>> --- a/src/ipa/ipu3/ipa_context.cpp\n>> +++ b/src/ipa/ipu3/ipa_context.cpp\n>> @@ -109,6 +109,11 @@ namespace libcamera::ipa::ipu3 {\n>>    * \\brief Maximum analogue gain supported with the configured sensor\n>>    */\n>>   \n>> +/**\n>> + * \\var IPAActiveState::awb\n>> + * \\brief Active auto-white balance parameters for the IPA\n>> + */\n>> +\n>>   /**\n>>    * \\var IPASessionConfiguration::sensor\n>>    * \\brief Sensor-specific configuration of the IPA\n>> @@ -123,6 +128,11 @@ namespace libcamera::ipa::ipu3 {\n>>    * \\brief Sensor output resolution\n>>    */\n>>   \n>> +/**\n>> + * \\var IPASessionConfiguration::awb\n>> + * \\brief Auto-white balance specific session configuration data\n>> + */\n>> +\n>>   /**\n>>    * \\var IPAActiveState::agc\n>>    * \\brief Context for the Automatic Gain Control algorithm\n>> @@ -139,26 +149,6 @@ namespace libcamera::ipa::ipu3 {\n>>    * The gain should be adapted to the sensor specific gain code before applying.\n>>    */\n>>   \n>> -/**\n>> - * \\var IPAActiveState::awb\n>> - * \\brief Context for the Automatic White Balance algorithm\n>> - *\n>> - * \\var IPAActiveState::awb.gains\n>> - * \\brief White balance gains\n>> - *\n>> - * \\var IPAActiveState::awb.gains.red\n>> - * \\brief White balance gain for R channel\n>> - *\n>> - * \\var IPAActiveState::awb.gains.green\n>> - * \\brief White balance gain for G channel\n>> - *\n>> - * \\var IPAActiveState::awb.gains.blue\n>> - * \\brief White balance gain for B channel\n>> - *\n>> - * \\var IPAActiveState::awb.temperatureK\n>> - * \\brief Estimated color temperature\n>> - */\n>> -\n>>   /**\n>>    * \\var IPAActiveState::toneMapping\n>>    * \\brief Context for ToneMapping and Gamma control\n>> @@ -187,4 +177,9 @@ namespace libcamera::ipa::ipu3 {\n>>    * \\brief Analogue gain multiplier\n>>    */\n>>   \n>> +/**\n>> + * \\var IPAFrameContext::awb\n>> + * \\brief Per-frame auto-white balance parameters for the IPA\n>> + */\n>> +\n>>   } /* namespace libcamera::ipa::ipu3 */\n>> diff --git a/src/ipa/ipu3/ipa_context.h b/src/ipa/ipu3/ipa_context.h\n>> index 97fcf06cd4ac9ac6d64c4933fcea80ace0e572df..245cf8b50b270a61df863e314128bede40d30541 100644\n>> --- a/src/ipa/ipu3/ipa_context.h\n>> +++ b/src/ipa/ipu3/ipa_context.h\n>> @@ -15,6 +15,7 @@\n>>   #include <libcamera/controls.h>\n>>   #include <libcamera/geometry.h>\n>>   \n>> +#include <libipa/awb.h>\n>>   #include <libipa/fc_queue.h>\n>>   \n>>   namespace libcamera {\n>> @@ -44,6 +45,8 @@ struct IPASessionConfiguration {\n>>                  utils::Duration lineDuration;\n>>                  Size size;\n>>          } sensor;\n>> +\n>> +       ipa::awb::Session awb;\n>>   };\n>>   \n>>   struct IPAActiveState {\n>> @@ -60,15 +63,7 @@ struct IPAActiveState {\n>>                  uint32_t exposureMode;\n>>          } agc;\n>>   \n>> -       struct {\n>> -               struct {\n>> -                       double red;\n>> -                       double green;\n>> -                       double blue;\n>> -               } gains;\n>> -\n>> -               double temperatureK;\n>> -       } awb;\n>> +       ipa::awb::ActiveState awb;\n> \n> I love all this simplification ;-)\n> \n> Overall - I think we're going in the right direction so:\n> \n> \n> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n> \n>>   \n>>          struct {\n>>                  double gamma;\n>> @@ -81,6 +76,8 @@ struct IPAFrameContext : public FrameContext {\n>>                  uint32_t exposure;\n>>                  double gain;\n>>          } sensor;\n>> +\n>> +       ipa::awb::FrameContext awb;\n>>   };\n>>   \n>>   struct IPAContext {\n>>\n>> -- \n>> 2.43.0\n>>","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 B10CAC3264\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 25 Jun 2026 22:16:48 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id AA693658D3;\n\tFri, 26 Jun 2026 00:16:47 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 821FD65718\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 26 Jun 2026 00:16:45 +0200 (CEST)","from [192.168.0.43]\n\t(chfd-03-b2-v4wan-176392-cust229.vm15.cable.virginm.net\n\t[82.19.20.230])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id C0FC39C;\n\tFri, 26 Jun 2026 00:16:04 +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=\"F2bXbOmS\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1782425764;\n\tbh=w5c0uI4WO+Omxrd7aT5IzzV+t2BfTqKBNcJ4ZxWQXT0=;\n\th=Date:Subject:To:References:From:In-Reply-To:From;\n\tb=F2bXbOmSYR0Xu/8JaLglfUpFF/qocAz7XHdQEnpmEqF5qruPVGgrpbiIvfDotOIVs\n\t4MBAFk0+FdkpSEQuVOobhQDibWagp6sSy/jSNhSYDz6drfeFUCU0ok6NMPajpFTXXH\n\tKjNaMYZy5Jtlq/u+XbJjySBig4ZJiEhnr28sTSfs=","Message-ID":"<a6607316-d417-4df8-ba0f-1bf71b6eaea5@ideasonboard.com>","Date":"Thu, 25 Jun 2026 23:16:42 +0100","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH 03/10] ipa: ipu3: awb: Port to the new libipa\n\tAwbAlgorithm","To":"Kieran Bingham <kieran.bingham@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","References":"<20260616-ipu3-libipa-rework-v1-0-d4448b54f1d8@ideasonboard.com>\n\t<20260616-ipu3-libipa-rework-v1-3-d4448b54f1d8@ideasonboard.com>\n\t<178163431966.3598163.10202931193127440648@ping.linuxembedded.co.uk>","Content-Language":"en-US","From":"Dan Scally <dan.scally@ideasonboard.com>","In-Reply-To":"<178163431966.3598163.10202931193127440648@ping.linuxembedded.co.uk>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"7bit","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>"}}]