[{"id":16062,"web_url":"https://patchwork.libcamera.org/comment/16062/","msgid":"<93e66cd2-7e46-d4d4-d44c-192bf4f597f8@ideasonboard.com>","date":"2021-03-30T22:48:11","subject":"Re: [libcamera-devel] [PATCH v4 3/4] ipa: ipu3: Add support for\n\tIPU3 AWB algorithm","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"Hi JM\n\nOn 30/03/2021 22:12, Jean-Michel Hautbois wrote:\n> The IPA will locally modify the parameters before they are passed down\n> to the ImgU. Use a local parameter object to give a reference to those\n> algorithms.\n> \n> Inherit from the Algorithm class to implement basic AWB functions.\n> \n> Once AWB is done, a color temperature is estimated and a default CCM matrix\n> will be used (yet to be tuned).\n> Implement a basic \"grey-world\" AWB algorithm just for demonstration purpose.\n> \n> The BDS output size is passed by the pipeline handler to the IPA.\n> The best grid is then calculated to maximize the number of pixels taken\n> into account in each cells.\n> \n> As commented in the source code, it can be improved, as it has (at least)\n> one limitation: if a cell is big (say 128 pixels wide) and indicated as\n> saturated, it won't be taken into account at all.\n> Maybe is it possible to have a smaller one, at the cost of a few pixels\n> to lose, in which case we can center the grid using the x_start and\n> y_start parameters.\n> \n> Signed-off-by: Jean-Michel Hautbois <jeanmichel.hautbois@ideasonboard.com>\n> ---\n>  src/ipa/ipu3/ipu3.cpp     |  86 ++++++++++++--\n>  src/ipa/ipu3/ipu3_awb.cpp | 235 ++++++++++++++++++++++++++++++++++++++\n>  src/ipa/ipu3/ipu3_awb.h   |  44 +++++++\n>  src/ipa/ipu3/meson.build  |   7 +-\n>  4 files changed, 363 insertions(+), 9 deletions(-)\n>  create mode 100644 src/ipa/ipu3/ipu3_awb.cpp\n>  create mode 100644 src/ipa/ipu3/ipu3_awb.h\n> \n> diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp\n> index 34a907f2..1cce11c9 100644\n> --- a/src/ipa/ipu3/ipu3.cpp\n> +++ b/src/ipa/ipu3/ipu3.cpp\n> @@ -21,6 +21,11 @@\n>  #include \"libcamera/internal/buffer.h\"\n>  #include \"libcamera/internal/log.h\"\n>  \n> +#include \"ipu3_awb.h\"\n> +\n> +static constexpr uint32_t kMaxCellWidthPerSet = 160;\n> +static constexpr uint32_t kMaxCellHeightPerSet = 80;\n> +\n>  namespace libcamera {\n>  \n>  LOG_DEFINE_CATEGORY(IPAIPU3)\n> @@ -49,6 +54,7 @@ private:\n>  \t\t\t     const ipu3_uapi_stats_3a *stats);\n>  \n>  \tvoid setControls(unsigned int frame);\n> +\tvoid calculateBdsGrid(const Size &bdsOutputSize);\n>  \n>  \tstd::map<unsigned int, MappedFrameBuffer> buffers_;\n>  \n> @@ -61,6 +67,14 @@ private:\n>  \tuint32_t gain_;\n>  \tuint32_t minGain_;\n>  \tuint32_t maxGain_;\n> +\n> +\t/* Interface to the AWB algorithm */\n> +\tstd::unique_ptr<ipa::IPU3Awb> awbAlgo_;\n> +\n> +\t/* Local parameter storage */\n> +\tstruct ipu3_uapi_params params_;\n> +\n> +\tstruct ipu3_uapi_grid_config bdsGrid_;\n>  };\n>  \n>  int IPAIPU3::start()\n> @@ -70,8 +84,59 @@ int IPAIPU3::start()\n>  \treturn 0;\n>  }\n>  \n> +/**\n> + * This method calculates a grid for the AWB algorithm in the IPU3 firmware.\n> + * Its input is the BDS output size calculated in the ImgU.\n> + * It is limited for now to the simplest method: find the lesser error\n> + * with the width/height and respective log2 width/height of the cells.\n> + *\n> + * \\todo The frame is divided into cells which can be 8x8 => 128x128.\n> + * As a smaller cell improves the algorithm precision, adapting the\n> + * x_start and y_start parameters of the grid would provoke a loss of\n> + * some pixels but would also result in more accurate algorithms.\n> + */\n> +void IPAIPU3::calculateBdsGrid(const Size &bdsOutputSize)\n> +{\n> +\tuint32_t minError = std::numeric_limits<uint32_t>::max();\n> +\tuint32_t bestWidth = 0;\n> +\tuint32_t bestHeight = 0;\n> +\tuint32_t bestLog2Width = 0;\n> +\tuint32_t bestLog2Height = 0;\n> +\tbdsGrid_ = {};\n> +\n> +\tfor (uint32_t widthShift = 3; widthShift <= 7; ++widthShift) {\n> +\t\tuint32_t width = std::min(kMaxCellWidthPerSet,\n> +\t\t\t\t\t  bdsOutputSize.width >> widthShift);\n> +\t\twidth = width << widthShift;\n> +\t\tfor (uint32_t heightShift = 3; heightShift <= 7; ++heightShift) {\n> +\t\t\tint32_t height = std::min(kMaxCellHeightPerSet,\n> +\t\t\t\t\t\t  bdsOutputSize.height >> heightShift);\n> +\t\t\theight = height << heightShift;\n> +\t\t\tuint32_t error = std::abs(static_cast<int>(width - bdsOutputSize.width)) + std::abs(static_cast<int>(height - bdsOutputSize.height));\n\nThat's a long line ...\n\n> +\n> +\t\t\tif (error > minError)\n> +\t\t\t\tcontinue;\n> +\n> +\t\t\tminError = error;\n> +\t\t\tbestWidth = width;\n> +\t\t\tbestHeight = height;\n> +\t\t\tbestLog2Width = widthShift;\n> +\t\t\tbestLog2Height = heightShift;\n> +\t\t}\n> +\t}\n> +\n> +\tbdsGrid_.width = bestWidth >> bestLog2Width;\n> +\tbdsGrid_.block_width_log2 = bestLog2Width;\n> +\tbdsGrid_.height = bestHeight >> bestLog2Height;\n> +\tbdsGrid_.block_height_log2 = bestLog2Height;\n> +\n> +\tLOG(IPAIPU3, Debug) << \"Best grid found is: (\"\n> +\t\t\t    << (int)bdsGrid_.width << \" << \" << (int)bdsGrid_.block_width_log2 << \") x (\"\n> +\t\t\t    << (int)bdsGrid_.height << \"<<\" << (int)bdsGrid_.block_height_log2 << \")\";\n> +}\n> +\n>  void IPAIPU3::configure(const std::map<uint32_t, ControlInfoMap> &entityControls,\n> -\t\t\t[[maybe_unused]] const Size &bdsOutputSize)\n> +\t\t\tconst Size &bdsOutputSize)\n>  {\n>  \tif (entityControls.empty())\n>  \t\treturn;\n> @@ -92,11 +157,18 @@ void IPAIPU3::configure(const std::map<uint32_t, ControlInfoMap> &entityControls\n>  \n>  \tminExposure_ = std::max(itExp->second.min().get<int32_t>(), 1);\n>  \tmaxExposure_ = itExp->second.max().get<int32_t>();\n> -\texposure_ = maxExposure_;\n> +\texposure_ = minExposure_;\n>  \n>  \tminGain_ = std::max(itGain->second.min().get<int32_t>(), 1);\n>  \tmaxGain_ = itGain->second.max().get<int32_t>();\n> -\tgain_ = maxGain_;\n> +\tgain_ = minGain_;\n> +\n> +\tparams_ = {};\n> +\n> +\tcalculateBdsGrid(bdsOutputSize);\n> +\n> +\tawbAlgo_ = std::make_unique<ipa::IPU3Awb>();\n> +\tawbAlgo_->initialise(params_, bdsOutputSize, bdsGrid_);\n>  }\n>  \n>  void IPAIPU3::mapBuffers(const std::vector<IPABuffer> &buffers)\n> @@ -168,10 +240,9 @@ void IPAIPU3::processControls([[maybe_unused]] unsigned int frame,\n>  \n>  void IPAIPU3::fillParams(unsigned int frame, ipu3_uapi_params *params)\n>  {\n> -\t/* Prepare parameters buffer. */\n> -\tmemset(params, 0, sizeof(*params));\n> +\tawbAlgo_->updateWbParameters(params_, 1.0);\n>  \n> -\t/* \\todo Fill in parameters buffer. */\n> +\t*params = params_;\n>  \n>  \tipa::ipu3::IPU3Action op;\n>  \top.op = ipa::ipu3::ActionParamFilled;\n> @@ -184,8 +255,7 @@ void IPAIPU3::parseStatistics(unsigned int frame,\n>  {\n>  \tControlList ctrls(controls::controls);\n>  \n> -\t/* \\todo React to statistics and update internal state machine. */\n> -\t/* \\todo Add meta-data information to ctrls. */\n> +\tawbAlgo_->calculateWBGains(stats);\n>  \n>  \tipa::ipu3::IPU3Action op;\n>  \top.op = ipa::ipu3::ActionMetadataReady;\n> diff --git a/src/ipa/ipu3/ipu3_awb.cpp b/src/ipa/ipu3/ipu3_awb.cpp\n> new file mode 100644\n> index 00000000..18d19d36\n> --- /dev/null\n> +++ b/src/ipa/ipu3/ipu3_awb.cpp\n> @@ -0,0 +1,235 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2021, Ideas On Board\n> + *\n> + * ipu3_awb.cpp - AWB control algorithm\n> + */\n> +#include \"ipu3_awb.h\"\n> +\n> +#include <cmath>\n> +#include <numeric>\n> +#include <unordered_map>\n> +\n> +#include \"libcamera/internal/log.h\"\n> +\n> +namespace libcamera {\n> +\n> +namespace ipa {\n> +\n> +LOG_DEFINE_CATEGORY(IPU3Awb)\n> +\n> +static const struct ipu3_uapi_bnr_static_config imguCssBnrDefaults = {\n> +\t.wb_gains = { 16, 16, 16, 16 },\n> +\t.wb_gains_thr = { 255, 255, 255, 255 },\n> +\t.thr_coeffs = { 1700, 0, 31, 31, 0, 16 },\n> +\t.thr_ctrl_shd = { 26, 26, 26, 26 },\n> +\t.opt_center{ -648, 0, -366, 0 },\n> +\t.lut = {\n> +\t\t{ 17, 23, 28, 32, 36, 39, 42, 45,\n> +\t\t  48, 51, 53, 55, 58, 60, 62, 64,\n> +\t\t  66, 68, 70, 72, 73, 75, 77, 78,\n> +\t\t  80, 82, 83, 85, 86, 88, 89, 90 } },\n> +\t.bp_ctrl = { 20, 0, 1, 40, 0, 6, 0, 6, 0 },\n> +\t.dn_detect_ctrl{ 9, 3, 4, 0, 8, 0, 1, 1, 1, 1, 0 },\n> +\t.column_size = 1296,\n> +\t.opt_center_sqr = { 419904, 133956 },\n> +};\n> +\n> +/* settings for Auto White Balance */\n> +static const struct ipu3_uapi_awb_config_s imguCssAwbDefaults = {\n> +\t.rgbs_thr_gr = 8191,\n> +\t.rgbs_thr_r = 8191,\n> +\t.rgbs_thr_gb = 8191,\n> +\t.rgbs_thr_b = 8191 | IPU3_UAPI_AWB_RGBS_THR_B_EN | IPU3_UAPI_AWB_RGBS_THR_B_INCL_SAT,\n> +\t.grid = {\n> +\t\t.width = 160,\n> +\t\t.height = 36,\n> +\t\t.block_width_log2 = 3,\n> +\t\t.block_height_log2 = 4,\n> +\t\t.height_per_slice = 1, /* Overridden by kernel. */\n> +\t\t.x_start = 0,\n> +\t\t.y_start = 0,\n> +\t\t.x_end = 0,\n> +\t\t.y_end = 0,\n> +\t},\n> +};\n> +\n> +static const struct ipu3_uapi_ccm_mat_config imguCssCcmDefault = {\n> +\t8191, 0, 0, 0,\n> +\t0, 8191, 0, 0,\n> +\t0, 0, 8191, 0\n> +};\n> +\n> +IPU3Awb::IPU3Awb()\n> +\t: Algorithm()\n> +{\n> +}\n> +\n> +IPU3Awb::~IPU3Awb()\n> +{\n> +}\n> +\n> +void IPU3Awb::initialise(ipu3_uapi_params &params, const Size &bdsOutputSize, struct ipu3_uapi_grid_config &bdsGrid)\n> +{\n> +\tparams.use.acc_awb = 1;\n> +\tparams.acc_param.awb.config = imguCssAwbDefaults;\n> +\n> +\tawbGrid_ = bdsGrid;\n> +\tparams.acc_param.awb.config.grid = awbGrid_;\n> +\n> +\tparams.use.obgrid = 0;\n> +\tparams.obgrid_param.gr = 20;\n> +\tparams.obgrid_param.r = 28;\n> +\tparams.obgrid_param.b = 28;\n> +\tparams.obgrid_param.gb = 20;\n> +\n> +\tparams.use.acc_bnr = 1;\n> +\tparams.acc_param.bnr = imguCssBnrDefaults;\n> +\tparams.acc_param.bnr.opt_center.x_reset = -1 * (bdsOutputSize.width / 2);\n> +\tparams.acc_param.bnr.opt_center.y_reset = -1 * (bdsOutputSize.height / 2);\n> +\tparams.acc_param.bnr.column_size = bdsOutputSize.width;\n> +\tparams.acc_param.bnr.opt_center_sqr.x_sqr_reset = params.acc_param.bnr.opt_center.x_reset * params.acc_param.bnr.opt_center.x_reset;\n> +\tparams.acc_param.bnr.opt_center_sqr.y_sqr_reset = params.acc_param.bnr.opt_center.y_reset * params.acc_param.bnr.opt_center.y_reset;\n> +\n> +\tparams.use.acc_ccm = 1;\n> +\tparams.acc_param.ccm = imguCssCcmDefault;\n> +\n> +\tparams.use.acc_gamma = 1;\n> +\tparams.acc_param.gamma.gc_ctrl.enable = 1;\n> +\n> +\tparams.use.acc_green_disparity = 0;\n> +\tparams.acc_param.green_disparity.gd_black = 2440;\n> +\tparams.acc_param.green_disparity.gd_red = 4;\n> +\tparams.acc_param.green_disparity.gd_blue = 4;\n> +\tparams.acc_param.green_disparity.gd_green = 4;\n> +\tparams.acc_param.green_disparity.gd_shading = 24;\n> +\tparams.acc_param.green_disparity.gd_support = 2;\n> +\tparams.acc_param.green_disparity.gd_clip = 1;\n> +\tparams.acc_param.green_disparity.gd_central_weight = 5;\n> +\n> +\tparams.use.acc_cds = 1;\n> +\tparams.acc_param.cds.csc_en = 1;\n> +\tparams.acc_param.cds.uv_bin_output = 0;\n> +\tparams.acc_param.cds.ds_c00 = 0;\n> +\tparams.acc_param.cds.ds_c01 = 1;\n> +\tparams.acc_param.cds.ds_c02 = 1;\n> +\tparams.acc_param.cds.ds_c03 = 0;\n> +\tparams.acc_param.cds.ds_c10 = 0;\n> +\tparams.acc_param.cds.ds_c11 = 1;\n> +\tparams.acc_param.cds.ds_c12 = 1;\n> +\tparams.acc_param.cds.ds_c13 = 0;\n> +\tparams.acc_param.cds.ds_nf = 2;\n> +\n> +\twbGains_[0] = 16;\n> +\twbGains_[1] = 4096;\n> +\twbGains_[2] = 4096;\n> +\twbGains_[3] = 16;\n> +\n> +\tframe_count_ = 0;\n> +}\n> +\n> +uint32_t IPU3Awb::estimateCCT(uint8_t red, uint8_t green, uint8_t blue)\n> +{\n> +\tdouble X = (-0.14282) * (red) + (1.54924) * (green) + (-0.95641) * (blue);\n> +\tdouble Y = (-0.32466) * (red) + (1.57837) * (green) + (-0.73191) * (blue);\n> +\tdouble Z = (-0.68202) * (red) + (0.77073) * (green) + (0.56332) * (blue);\n> +\n> +\tdouble x = X / (X + Y + Z);\n> +\tdouble y = Y / (X + Y + Z);\n> +\n> +\tdouble n = (x - 0.3320) / (0.1858 - y);\n> +\treturn static_cast<uint32_t>(449 * n * n * n + 3525 * n * n + 6823.3 * n + 5520.33);\n> +}\n> +\n> +double meanValue(std::vector<uint32_t> colorValues)\n> +{\n> +\tuint32_t count = 0;\n> +\tuint32_t hist[256] = { 0 };\n> +\tfor (uint32_t const &val : colorValues) {\n> +\t\thist[val]++;\n> +\t\tcount++;\n> +\t}\n> +\n> +\tdouble mean = 0.0;\n> +\tfor (uint32_t i = 0; i < 256; i++) {\n> +\t\tmean += hist[i] * i;\n> +\t}\n> +\treturn mean /= count;\n> +}\n> +\n> +void IPU3Awb::calculateWBGains(const ipu3_uapi_stats_3a *stats)\n> +{\n\nI suspect we'll want to profile this function carefully sometime.\n\n> +\tASSERT(stats->stats_3a_status.awb_en);\n> +\n> +\tstd::vector<uint32_t> redValues, greenValues, blueValues;\n> +\tconst struct ipu3_uapi_grid_config statsAwbGrid = stats->stats_4a_config.awb_config.grid;\n> +\tRectangle awbRegion = { statsAwbGrid.x_start,\n> +\t\t\t\tstatsAwbGrid.y_start,\n> +\t\t\t\tstatic_cast<unsigned int>(statsAwbGrid.x_end - statsAwbGrid.x_start) + 1,\n> +\t\t\t\tstatic_cast<unsigned int>(statsAwbGrid.y_end - statsAwbGrid.y_start) + 1 };\n> +\n> +\tPoint topleft = awbRegion.topLeft();\n> +\tuint32_t startY = (topleft.y >> awbGrid_.block_height_log2) * awbGrid_.width << awbGrid_.block_width_log2;\n> +\tuint32_t startX = (topleft.x >> awbGrid_.block_width_log2) << awbGrid_.block_width_log2;\n> +\tuint32_t endX = (startX + (awbRegion.size().width >> awbGrid_.block_width_log2)) << awbGrid_.block_width_log2;\n> +\tuint32_t count = 0;\n> +\tuint32_t i, j;\n> +\n\nCan we reserve the sizes for each of the redValues, greenValues blueValues?\n\nPushing back each iteration will incur lots of reallocations everytime\nthe initial reserved size is met ... I bet we're losing quite a bit of\ntime here.\n\nI see both Gr Gb greens go into the same pot below, I guess that's expected.\n\n\n> +\tawbCounted_ = 0;\n> +\tfor (j = (topleft.y >> awbGrid_.block_height_log2);\n> +\t     j < (topleft.y >> awbGrid_.block_height_log2) + (awbRegion.size().height >> awbGrid_.block_height_log2);\n> +\t     j++) {\n> +\t\tfor (i = startX + startY; i < endX + startY; i += 8) {\n> +\t\t\tif (stats->awb_raw_buffer.meta_data[i + 4 + j * awbGrid_.width] == 0) {\n> +\t\t\t\tgreenValues.push_back(stats->awb_raw_buffer.meta_data[i + j * awbGrid_.width]);\n> +\t\t\t\tredValues.push_back(stats->awb_raw_buffer.meta_data[i + 1 + j * awbGrid_.width]);\n> +\t\t\t\tblueValues.push_back(stats->awb_raw_buffer.meta_data[i + 2 + j * awbGrid_.width]);\n> +\t\t\t\tgreenValues.push_back(stats->awb_raw_buffer.meta_data[i + 3 + j * awbGrid_.width]);\n> +\t\t\t\tawbCounted_++;\n> +\t\t\t}\n> +\t\t\tcount++;\n> +\t\t}\n> +\t}\n> +\n> +\tdouble rMean = meanValue(redValues);\n> +\tdouble bMean = meanValue(blueValues);\n> +\tdouble gMean = meanValue(greenValues);\n\nI'm wondering if we might be able to optimise how we calculate these\nmeans - but ... I don't want to optimise things before they're profiled,\nand this works (I believe).\n\n> +\n> +\tdouble rGain = gMean / rMean;\n> +\tdouble bGain = gMean / bMean;\n> +\n> +\twbGains_[0] = 16;\n> +\twbGains_[1] = 4096 * rGain;\n> +\twbGains_[2] = 4096 * bGain;\n> +\twbGains_[3] = 16;\n> +\n> +\tframe_count_++;\n> +\n> +\tcct_ = estimateCCT(rMean, gMean, bMean);\n> +}\n> +\n> +void IPU3Awb::updateWbParameters(ipu3_uapi_params &params, double agcGamma)\n> +{\n> +\tif ((wbGains_[0] == 0) || (wbGains_[1] == 0) || (wbGains_[2] == 0) || (wbGains_[3] == 0)) {\n> +\t\tLOG(IPU3Awb, Error) << \"Gains can't be 0, check the stats\";\n> +\t} else {\n> +\t\tparams.acc_param.bnr.wb_gains.gr = wbGains_[0];\n> +\t\tparams.acc_param.bnr.wb_gains.r = wbGains_[1];\n> +\t\tparams.acc_param.bnr.wb_gains.b = wbGains_[2];\n> +\t\tparams.acc_param.bnr.wb_gains.gb = wbGains_[3];\n> +\n> +\t\tLOG(IPU3Awb, Debug) << \"Color temperature estimated: \" << cct_\n> +\t\t\t\t    << \" and gamma calculated: \" << agcGamma;\n> +\t\tparams.acc_param.ccm = imguCssCcmDefault;\n> +\n> +\t\tfor (uint32_t i = 0; i < 256; i++) {\n> +\t\t\tdouble j = i / 255.0;\n> +\t\t\tdouble gamma = std::pow(j, 1.0 / agcGamma);\n> +\t\t\tparams.acc_param.gamma.gc_lut.lut[i] = gamma * 8191;\n> +\t\t}\n> +\t}\n> +}\n> +\n> +} /* namespace ipa */\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/ipa/ipu3/ipu3_awb.h b/src/ipa/ipu3/ipu3_awb.h\n> new file mode 100644\n> index 00000000..a14401a0\n> --- /dev/null\n> +++ b/src/ipa/ipu3/ipu3_awb.h\n> @@ -0,0 +1,44 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2021, Ideas On Board\n> + *\n> + * ipu3_awb.h - IPU3 AWB control algorithm\n> + */\n> +#ifndef __LIBCAMERA_IPU3_AWB_H__\n> +#define __LIBCAMERA_IPU3_AWB_H__\n> +\n> +#include <linux/intel-ipu3.h>\n> +\n> +#include <libcamera/geometry.h>\n> +\n> +#include \"libipa/algorithm.h\"\n> +\n> +namespace libcamera {\n> +\n> +namespace ipa {\n> +\n> +class IPU3Awb : public Algorithm\n> +{\n> +public:\n> +\tIPU3Awb();\n> +\t~IPU3Awb();\n> +\n> +\tvoid initialise(ipu3_uapi_params &params, const Size &bdsOutputSize, struct ipu3_uapi_grid_config &bdsGrid);\n> +\tvoid calculateWBGains(const ipu3_uapi_stats_3a *stats);\n> +\tvoid updateWbParameters(ipu3_uapi_params &params, double agcGamma);\n> +\n> +private:\n> +\tuint32_t estimateCCT(uint8_t red, uint8_t green, uint8_t blue);\n> +\n> +\t/* WB calculated gains */\n> +\tuint16_t wbGains_[4];\n> +\tuint32_t cct_;\n> +\tuint32_t awbCounted_;\n> +\tstruct ipu3_uapi_grid_config awbGrid_;\n> +\tuint32_t frame_count_;\n> +};\n> +\n> +} /* namespace ipa */\n> +\n> +} /* namespace libcamera*/\n> +#endif /* __LIBCAMERA_IPU3_AWB_H__ */\n> diff --git a/src/ipa/ipu3/meson.build b/src/ipa/ipu3/meson.build\n> index a241f617..1040698e 100644\n> --- a/src/ipa/ipu3/meson.build\n> +++ b/src/ipa/ipu3/meson.build\n> @@ -2,8 +2,13 @@\n>  \n>  ipa_name = 'ipa_ipu3'\n>  \n> +ipu3_ipa_sources = files([\n> +    'ipu3.cpp',\n> +    'ipu3_awb.cpp',\n> +])\n> +\n>  mod = shared_module(ipa_name,\n> -                    ['ipu3.cpp', libcamera_generated_ipa_headers],\n> +                    [ipu3_ipa_sources, libcamera_generated_ipa_headers],\n>                      name_prefix : '',\n>                      include_directories : [ipa_includes, libipa_includes],\n>                      dependencies : libcamera_dep,\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 C1549C0DA4\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 30 Mar 2021 22:48:17 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 8F15868783;\n\tWed, 31 Mar 2021 00:48:16 +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 863AE602DB\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 31 Mar 2021 00:48:14 +0200 (CEST)","from [192.168.0.20]\n\t(cpc89244-aztw30-2-0-cust3082.18-1.cable.virginm.net [86.31.172.11])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id BF28E8DA;\n\tWed, 31 Mar 2021 00:48:13 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"XgITC4FL\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1617144493;\n\tbh=Mlhp+UZn7t39YZDyhXCK3B1yzbWYeuY8H82iJhv/KoY=;\n\th=Reply-To:Subject:To:References:From:Date:In-Reply-To:From;\n\tb=XgITC4FL0TCdmzFkeOBb6cX8X1/qzq1VYE0Xnqk9XFBH4knd6vtMDpKkarF11Jvn8\n\tjHetq+Ufv4nqT19k9uv9HqAwE49EfHIo8gfy7utFJnKbcHrXqInHUaBT3DvW/inRN2\n\tGlljiAVg1InpyhePrQow8BqXqgwhocybd1J30So0=","To":"Jean-Michel Hautbois <jeanmichel.hautbois@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","References":"<20210330211210.194806-1-jeanmichel.hautbois@ideasonboard.com>\n\t<20210330211210.194806-4-jeanmichel.hautbois@ideasonboard.com>","From":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Autocrypt":"addr=kieran.bingham@ideasonboard.com; keydata=\n\t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\n\tWZw01QYHU/GUV/zHJSFk","Organization":"Ideas on Board","Message-ID":"<93e66cd2-7e46-d4d4-d44c-192bf4f597f8@ideasonboard.com>","Date":"Tue, 30 Mar 2021 23:48:11 +0100","User-Agent":"Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101\n\tThunderbird/68.10.0","MIME-Version":"1.0","In-Reply-To":"<20210330211210.194806-4-jeanmichel.hautbois@ideasonboard.com>","Content-Language":"en-GB","Subject":"Re: [libcamera-devel] [PATCH v4 3/4] ipa: ipu3: Add support for\n\tIPU3 AWB algorithm","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>","Reply-To":"kieran.bingham@ideasonboard.com","Content-Type":"text/plain; charset=\"us-ascii\"","Content-Transfer-Encoding":"7bit","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":16065,"web_url":"https://patchwork.libcamera.org/comment/16065/","msgid":"<c6b2e256-e76a-1a6f-8b0a-02ce930b5e3f@ideasonboard.com>","date":"2021-03-31T05:44:35","subject":"Re: [libcamera-devel] [PATCH v4 3/4] ipa: ipu3: Add support for\n\tIPU3 AWB algorithm","submitter":{"id":75,"url":"https://patchwork.libcamera.org/api/people/75/","name":"Jean-Michel Hautbois","email":"jeanmichel.hautbois@ideasonboard.com"},"content":"Hi Kieran,\n\nOn 31/03/2021 00:48, Kieran Bingham wrote:\n> Hi JM\n> \n> On 30/03/2021 22:12, Jean-Michel Hautbois wrote:\n>> The IPA will locally modify the parameters before they are passed down\n>> to the ImgU. Use a local parameter object to give a reference to those\n>> algorithms.\n>>\n>> Inherit from the Algorithm class to implement basic AWB functions.\n>>\n>> Once AWB is done, a color temperature is estimated and a default CCM matrix\n>> will be used (yet to be tuned).\n>> Implement a basic \"grey-world\" AWB algorithm just for demonstration purpose.\n>>\n>> The BDS output size is passed by the pipeline handler to the IPA.\n>> The best grid is then calculated to maximize the number of pixels taken\n>> into account in each cells.\n>>\n>> As commented in the source code, it can be improved, as it has (at least)\n>> one limitation: if a cell is big (say 128 pixels wide) and indicated as\n>> saturated, it won't be taken into account at all.\n>> Maybe is it possible to have a smaller one, at the cost of a few pixels\n>> to lose, in which case we can center the grid using the x_start and\n>> y_start parameters.\n>>\n>> Signed-off-by: Jean-Michel Hautbois <jeanmichel.hautbois@ideasonboard.com>\n>> ---\n>>  src/ipa/ipu3/ipu3.cpp     |  86 ++++++++++++--\n>>  src/ipa/ipu3/ipu3_awb.cpp | 235 ++++++++++++++++++++++++++++++++++++++\n>>  src/ipa/ipu3/ipu3_awb.h   |  44 +++++++\n>>  src/ipa/ipu3/meson.build  |   7 +-\n>>  4 files changed, 363 insertions(+), 9 deletions(-)\n>>  create mode 100644 src/ipa/ipu3/ipu3_awb.cpp\n>>  create mode 100644 src/ipa/ipu3/ipu3_awb.h\n>>\n>> diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp\n>> index 34a907f2..1cce11c9 100644\n>> --- a/src/ipa/ipu3/ipu3.cpp\n>> +++ b/src/ipa/ipu3/ipu3.cpp\n>> @@ -21,6 +21,11 @@\n>>  #include \"libcamera/internal/buffer.h\"\n>>  #include \"libcamera/internal/log.h\"\n>>  \n>> +#include \"ipu3_awb.h\"\n>> +\n>> +static constexpr uint32_t kMaxCellWidthPerSet = 160;\n>> +static constexpr uint32_t kMaxCellHeightPerSet = 80;\n>> +\n>>  namespace libcamera {\n>>  \n>>  LOG_DEFINE_CATEGORY(IPAIPU3)\n>> @@ -49,6 +54,7 @@ private:\n>>  \t\t\t     const ipu3_uapi_stats_3a *stats);\n>>  \n>>  \tvoid setControls(unsigned int frame);\n>> +\tvoid calculateBdsGrid(const Size &bdsOutputSize);\n>>  \n>>  \tstd::map<unsigned int, MappedFrameBuffer> buffers_;\n>>  \n>> @@ -61,6 +67,14 @@ private:\n>>  \tuint32_t gain_;\n>>  \tuint32_t minGain_;\n>>  \tuint32_t maxGain_;\n>> +\n>> +\t/* Interface to the AWB algorithm */\n>> +\tstd::unique_ptr<ipa::IPU3Awb> awbAlgo_;\n>> +\n>> +\t/* Local parameter storage */\n>> +\tstruct ipu3_uapi_params params_;\n>> +\n>> +\tstruct ipu3_uapi_grid_config bdsGrid_;\n>>  };\n>>  \n>>  int IPAIPU3::start()\n>> @@ -70,8 +84,59 @@ int IPAIPU3::start()\n>>  \treturn 0;\n>>  }\n>>  \n>> +/**\n>> + * This method calculates a grid for the AWB algorithm in the IPU3 firmware.\n>> + * Its input is the BDS output size calculated in the ImgU.\n>> + * It is limited for now to the simplest method: find the lesser error\n>> + * with the width/height and respective log2 width/height of the cells.\n>> + *\n>> + * \\todo The frame is divided into cells which can be 8x8 => 128x128.\n>> + * As a smaller cell improves the algorithm precision, adapting the\n>> + * x_start and y_start parameters of the grid would provoke a loss of\n>> + * some pixels but would also result in more accurate algorithms.\n>> + */\n>> +void IPAIPU3::calculateBdsGrid(const Size &bdsOutputSize)\n>> +{\n>> +\tuint32_t minError = std::numeric_limits<uint32_t>::max();\n>> +\tuint32_t bestWidth = 0;\n>> +\tuint32_t bestHeight = 0;\n>> +\tuint32_t bestLog2Width = 0;\n>> +\tuint32_t bestLog2Height = 0;\n>> +\tbdsGrid_ = {};\n>> +\n>> +\tfor (uint32_t widthShift = 3; widthShift <= 7; ++widthShift) {\n>> +\t\tuint32_t width = std::min(kMaxCellWidthPerSet,\n>> +\t\t\t\t\t  bdsOutputSize.width >> widthShift);\n>> +\t\twidth = width << widthShift;\n>> +\t\tfor (uint32_t heightShift = 3; heightShift <= 7; ++heightShift) {\n>> +\t\t\tint32_t height = std::min(kMaxCellHeightPerSet,\n>> +\t\t\t\t\t\t  bdsOutputSize.height >> heightShift);\n>> +\t\t\theight = height << heightShift;\n>> +\t\t\tuint32_t error = std::abs(static_cast<int>(width - bdsOutputSize.width)) + std::abs(static_cast<int>(height - bdsOutputSize.height));\n> \n> That's a long line ...\n\nCheckstyle has corrected that one :-/ !\n\n>> +\n>> +\t\t\tif (error > minError)\n>> +\t\t\t\tcontinue;\n>> +\n>> +\t\t\tminError = error;\n>> +\t\t\tbestWidth = width;\n>> +\t\t\tbestHeight = height;\n>> +\t\t\tbestLog2Width = widthShift;\n>> +\t\t\tbestLog2Height = heightShift;\n>> +\t\t}\n>> +\t}\n>> +\n>> +\tbdsGrid_.width = bestWidth >> bestLog2Width;\n>> +\tbdsGrid_.block_width_log2 = bestLog2Width;\n>> +\tbdsGrid_.height = bestHeight >> bestLog2Height;\n>> +\tbdsGrid_.block_height_log2 = bestLog2Height;\n>> +\n>> +\tLOG(IPAIPU3, Debug) << \"Best grid found is: (\"\n>> +\t\t\t    << (int)bdsGrid_.width << \" << \" << (int)bdsGrid_.block_width_log2 << \") x (\"\n>> +\t\t\t    << (int)bdsGrid_.height << \"<<\" << (int)bdsGrid_.block_height_log2 << \")\";\n>> +}\n>> +\n>>  void IPAIPU3::configure(const std::map<uint32_t, ControlInfoMap> &entityControls,\n>> -\t\t\t[[maybe_unused]] const Size &bdsOutputSize)\n>> +\t\t\tconst Size &bdsOutputSize)\n>>  {\n>>  \tif (entityControls.empty())\n>>  \t\treturn;\n>> @@ -92,11 +157,18 @@ void IPAIPU3::configure(const std::map<uint32_t, ControlInfoMap> &entityControls\n>>  \n>>  \tminExposure_ = std::max(itExp->second.min().get<int32_t>(), 1);\n>>  \tmaxExposure_ = itExp->second.max().get<int32_t>();\n>> -\texposure_ = maxExposure_;\n>> +\texposure_ = minExposure_;\n>>  \n>>  \tminGain_ = std::max(itGain->second.min().get<int32_t>(), 1);\n>>  \tmaxGain_ = itGain->second.max().get<int32_t>();\n>> -\tgain_ = maxGain_;\n>> +\tgain_ = minGain_;\n>> +\n>> +\tparams_ = {};\n>> +\n>> +\tcalculateBdsGrid(bdsOutputSize);\n>> +\n>> +\tawbAlgo_ = std::make_unique<ipa::IPU3Awb>();\n>> +\tawbAlgo_->initialise(params_, bdsOutputSize, bdsGrid_);\n>>  }\n>>  \n>>  void IPAIPU3::mapBuffers(const std::vector<IPABuffer> &buffers)\n>> @@ -168,10 +240,9 @@ void IPAIPU3::processControls([[maybe_unused]] unsigned int frame,\n>>  \n>>  void IPAIPU3::fillParams(unsigned int frame, ipu3_uapi_params *params)\n>>  {\n>> -\t/* Prepare parameters buffer. */\n>> -\tmemset(params, 0, sizeof(*params));\n>> +\tawbAlgo_->updateWbParameters(params_, 1.0);\n>>  \n>> -\t/* \\todo Fill in parameters buffer. */\n>> +\t*params = params_;\n>>  \n>>  \tipa::ipu3::IPU3Action op;\n>>  \top.op = ipa::ipu3::ActionParamFilled;\n>> @@ -184,8 +255,7 @@ void IPAIPU3::parseStatistics(unsigned int frame,\n>>  {\n>>  \tControlList ctrls(controls::controls);\n>>  \n>> -\t/* \\todo React to statistics and update internal state machine. */\n>> -\t/* \\todo Add meta-data information to ctrls. */\n>> +\tawbAlgo_->calculateWBGains(stats);\n>>  \n>>  \tipa::ipu3::IPU3Action op;\n>>  \top.op = ipa::ipu3::ActionMetadataReady;\n>> diff --git a/src/ipa/ipu3/ipu3_awb.cpp b/src/ipa/ipu3/ipu3_awb.cpp\n>> new file mode 100644\n>> index 00000000..18d19d36\n>> --- /dev/null\n>> +++ b/src/ipa/ipu3/ipu3_awb.cpp\n>> @@ -0,0 +1,235 @@\n>> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n>> +/*\n>> + * Copyright (C) 2021, Ideas On Board\n>> + *\n>> + * ipu3_awb.cpp - AWB control algorithm\n>> + */\n>> +#include \"ipu3_awb.h\"\n>> +\n>> +#include <cmath>\n>> +#include <numeric>\n>> +#include <unordered_map>\n>> +\n>> +#include \"libcamera/internal/log.h\"\n>> +\n>> +namespace libcamera {\n>> +\n>> +namespace ipa {\n>> +\n>> +LOG_DEFINE_CATEGORY(IPU3Awb)\n>> +\n>> +static const struct ipu3_uapi_bnr_static_config imguCssBnrDefaults = {\n>> +\t.wb_gains = { 16, 16, 16, 16 },\n>> +\t.wb_gains_thr = { 255, 255, 255, 255 },\n>> +\t.thr_coeffs = { 1700, 0, 31, 31, 0, 16 },\n>> +\t.thr_ctrl_shd = { 26, 26, 26, 26 },\n>> +\t.opt_center{ -648, 0, -366, 0 },\n>> +\t.lut = {\n>> +\t\t{ 17, 23, 28, 32, 36, 39, 42, 45,\n>> +\t\t  48, 51, 53, 55, 58, 60, 62, 64,\n>> +\t\t  66, 68, 70, 72, 73, 75, 77, 78,\n>> +\t\t  80, 82, 83, 85, 86, 88, 89, 90 } },\n>> +\t.bp_ctrl = { 20, 0, 1, 40, 0, 6, 0, 6, 0 },\n>> +\t.dn_detect_ctrl{ 9, 3, 4, 0, 8, 0, 1, 1, 1, 1, 0 },\n>> +\t.column_size = 1296,\n>> +\t.opt_center_sqr = { 419904, 133956 },\n>> +};\n>> +\n>> +/* settings for Auto White Balance */\n>> +static const struct ipu3_uapi_awb_config_s imguCssAwbDefaults = {\n>> +\t.rgbs_thr_gr = 8191,\n>> +\t.rgbs_thr_r = 8191,\n>> +\t.rgbs_thr_gb = 8191,\n>> +\t.rgbs_thr_b = 8191 | IPU3_UAPI_AWB_RGBS_THR_B_EN | IPU3_UAPI_AWB_RGBS_THR_B_INCL_SAT,\n>> +\t.grid = {\n>> +\t\t.width = 160,\n>> +\t\t.height = 36,\n>> +\t\t.block_width_log2 = 3,\n>> +\t\t.block_height_log2 = 4,\n>> +\t\t.height_per_slice = 1, /* Overridden by kernel. */\n>> +\t\t.x_start = 0,\n>> +\t\t.y_start = 0,\n>> +\t\t.x_end = 0,\n>> +\t\t.y_end = 0,\n>> +\t},\n>> +};\n>> +\n>> +static const struct ipu3_uapi_ccm_mat_config imguCssCcmDefault = {\n>> +\t8191, 0, 0, 0,\n>> +\t0, 8191, 0, 0,\n>> +\t0, 0, 8191, 0\n>> +};\n>> +\n>> +IPU3Awb::IPU3Awb()\n>> +\t: Algorithm()\n>> +{\n>> +}\n>> +\n>> +IPU3Awb::~IPU3Awb()\n>> +{\n>> +}\n>> +\n>> +void IPU3Awb::initialise(ipu3_uapi_params &params, const Size &bdsOutputSize, struct ipu3_uapi_grid_config &bdsGrid)\n>> +{\n>> +\tparams.use.acc_awb = 1;\n>> +\tparams.acc_param.awb.config = imguCssAwbDefaults;\n>> +\n>> +\tawbGrid_ = bdsGrid;\n>> +\tparams.acc_param.awb.config.grid = awbGrid_;\n>> +\n>> +\tparams.use.obgrid = 0;\n>> +\tparams.obgrid_param.gr = 20;\n>> +\tparams.obgrid_param.r = 28;\n>> +\tparams.obgrid_param.b = 28;\n>> +\tparams.obgrid_param.gb = 20;\n>> +\n>> +\tparams.use.acc_bnr = 1;\n>> +\tparams.acc_param.bnr = imguCssBnrDefaults;\n>> +\tparams.acc_param.bnr.opt_center.x_reset = -1 * (bdsOutputSize.width / 2);\n>> +\tparams.acc_param.bnr.opt_center.y_reset = -1 * (bdsOutputSize.height / 2);\n>> +\tparams.acc_param.bnr.column_size = bdsOutputSize.width;\n>> +\tparams.acc_param.bnr.opt_center_sqr.x_sqr_reset = params.acc_param.bnr.opt_center.x_reset * params.acc_param.bnr.opt_center.x_reset;\n>> +\tparams.acc_param.bnr.opt_center_sqr.y_sqr_reset = params.acc_param.bnr.opt_center.y_reset * params.acc_param.bnr.opt_center.y_reset;\n>> +\n>> +\tparams.use.acc_ccm = 1;\n>> +\tparams.acc_param.ccm = imguCssCcmDefault;\n>> +\n>> +\tparams.use.acc_gamma = 1;\n>> +\tparams.acc_param.gamma.gc_ctrl.enable = 1;\n>> +\n>> +\tparams.use.acc_green_disparity = 0;\n>> +\tparams.acc_param.green_disparity.gd_black = 2440;\n>> +\tparams.acc_param.green_disparity.gd_red = 4;\n>> +\tparams.acc_param.green_disparity.gd_blue = 4;\n>> +\tparams.acc_param.green_disparity.gd_green = 4;\n>> +\tparams.acc_param.green_disparity.gd_shading = 24;\n>> +\tparams.acc_param.green_disparity.gd_support = 2;\n>> +\tparams.acc_param.green_disparity.gd_clip = 1;\n>> +\tparams.acc_param.green_disparity.gd_central_weight = 5;\n>> +\n>> +\tparams.use.acc_cds = 1;\n>> +\tparams.acc_param.cds.csc_en = 1;\n>> +\tparams.acc_param.cds.uv_bin_output = 0;\n>> +\tparams.acc_param.cds.ds_c00 = 0;\n>> +\tparams.acc_param.cds.ds_c01 = 1;\n>> +\tparams.acc_param.cds.ds_c02 = 1;\n>> +\tparams.acc_param.cds.ds_c03 = 0;\n>> +\tparams.acc_param.cds.ds_c10 = 0;\n>> +\tparams.acc_param.cds.ds_c11 = 1;\n>> +\tparams.acc_param.cds.ds_c12 = 1;\n>> +\tparams.acc_param.cds.ds_c13 = 0;\n>> +\tparams.acc_param.cds.ds_nf = 2;\n>> +\n>> +\twbGains_[0] = 16;\n>> +\twbGains_[1] = 4096;\n>> +\twbGains_[2] = 4096;\n>> +\twbGains_[3] = 16;\n>> +\n>> +\tframe_count_ = 0;\n>> +}\n>> +\n>> +uint32_t IPU3Awb::estimateCCT(uint8_t red, uint8_t green, uint8_t blue)\n>> +{\n>> +\tdouble X = (-0.14282) * (red) + (1.54924) * (green) + (-0.95641) * (blue);\n>> +\tdouble Y = (-0.32466) * (red) + (1.57837) * (green) + (-0.73191) * (blue);\n>> +\tdouble Z = (-0.68202) * (red) + (0.77073) * (green) + (0.56332) * (blue);\n>> +\n>> +\tdouble x = X / (X + Y + Z);\n>> +\tdouble y = Y / (X + Y + Z);\n>> +\n>> +\tdouble n = (x - 0.3320) / (0.1858 - y);\n>> +\treturn static_cast<uint32_t>(449 * n * n * n + 3525 * n * n + 6823.3 * n + 5520.33);\n>> +}\n>> +\n>> +double meanValue(std::vector<uint32_t> colorValues)\n>> +{\n>> +\tuint32_t count = 0;\n>> +\tuint32_t hist[256] = { 0 };\n>> +\tfor (uint32_t const &val : colorValues) {\n>> +\t\thist[val]++;\n>> +\t\tcount++;\n>> +\t}\n>> +\n>> +\tdouble mean = 0.0;\n>> +\tfor (uint32_t i = 0; i < 256; i++) {\n>> +\t\tmean += hist[i] * i;\n>> +\t}\n>> +\treturn mean /= count;\n>> +}\n>> +\n>> +void IPU3Awb::calculateWBGains(const ipu3_uapi_stats_3a *stats)\n>> +{\n> \n> I suspect we'll want to profile this function carefully sometime.\n> \n\nYes, but no premature optimization :-).\nI agree at some point we need to profile globally, and this function\nwill probably appear as a costly one ;-).\n\n>> +\tASSERT(stats->stats_3a_status.awb_en);\n>> +\n>> +\tstd::vector<uint32_t> redValues, greenValues, blueValues;\n>> +\tconst struct ipu3_uapi_grid_config statsAwbGrid = stats->stats_4a_config.awb_config.grid;\n>> +\tRectangle awbRegion = { statsAwbGrid.x_start,\n>> +\t\t\t\tstatsAwbGrid.y_start,\n>> +\t\t\t\tstatic_cast<unsigned int>(statsAwbGrid.x_end - statsAwbGrid.x_start) + 1,\n>> +\t\t\t\tstatic_cast<unsigned int>(statsAwbGrid.y_end - statsAwbGrid.y_start) + 1 };\n>> +\n>> +\tPoint topleft = awbRegion.topLeft();\n>> +\tuint32_t startY = (topleft.y >> awbGrid_.block_height_log2) * awbGrid_.width << awbGrid_.block_width_log2;\n>> +\tuint32_t startX = (topleft.x >> awbGrid_.block_width_log2) << awbGrid_.block_width_log2;\n>> +\tuint32_t endX = (startX + (awbRegion.size().width >> awbGrid_.block_width_log2)) << awbGrid_.block_width_log2;\n>> +\tuint32_t count = 0;\n>> +\tuint32_t i, j;\n>> +\n> \n> Can we reserve the sizes for each of the redValues, greenValues blueValues?\n> \n> Pushing back each iteration will incur lots of reallocations everytime\n> the initial reserved size is met ... I bet we're losing quite a bit of\n> time here.\n\nYes, but there is a better one: we don't need the individual cells at\nall, only the mean value. So, accumulating and dividing at the end by\nthe number of cells accumulated is lighter.\n\n> I see both Gr Gb greens go into the same pot below, I guess that's expected.\n\nYes, it one one green at the end (I did not see any real improvment when\nsplitting them when I tested, but I can give it a new try).\n\n> \n>> +\tawbCounted_ = 0;\n>> +\tfor (j = (topleft.y >> awbGrid_.block_height_log2);\n>> +\t     j < (topleft.y >> awbGrid_.block_height_log2) + (awbRegion.size().height >> awbGrid_.block_height_log2);\n>> +\t     j++) {\n>> +\t\tfor (i = startX + startY; i < endX + startY; i += 8) {\n>> +\t\t\tif (stats->awb_raw_buffer.meta_data[i + 4 + j * awbGrid_.width] == 0) {\n>> +\t\t\t\tgreenValues.push_back(stats->awb_raw_buffer.meta_data[i + j * awbGrid_.width]);\n>> +\t\t\t\tredValues.push_back(stats->awb_raw_buffer.meta_data[i + 1 + j * awbGrid_.width]);\n>> +\t\t\t\tblueValues.push_back(stats->awb_raw_buffer.meta_data[i + 2 + j * awbGrid_.width]);\n>> +\t\t\t\tgreenValues.push_back(stats->awb_raw_buffer.meta_data[i + 3 + j * awbGrid_.width]);\n>> +\t\t\t\tawbCounted_++;\n>> +\t\t\t}\n>> +\t\t\tcount++;\n>> +\t\t}\n>> +\t}\n>> +\n>> +\tdouble rMean = meanValue(redValues);\n>> +\tdouble bMean = meanValue(blueValues);\n>> +\tdouble gMean = meanValue(greenValues);\n> \n> I'm wondering if we might be able to optimise how we calculate these\n> means - but ... I don't want to optimise things before they're profiled,\n> and this works (I believe).\n\nSure we can ;-). It is a relicat from all the moments calculations I think.\nWe could store the sums in an internal structure, as it is done in\nbcm2835_isp_stats_region.\n\n>> +\n>> +\tdouble rGain = gMean / rMean;\n>> +\tdouble bGain = gMean / bMean;\n>> +\n>> +\twbGains_[0] = 16;\n>> +\twbGains_[1] = 4096 * rGain;\n>> +\twbGains_[2] = 4096 * bGain;\n>> +\twbGains_[3] = 16;\n>> +\n>> +\tframe_count_++;\n>> +\n>> +\tcct_ = estimateCCT(rMean, gMean, bMean);\n>> +}\n>> +\n>> +void IPU3Awb::updateWbParameters(ipu3_uapi_params &params, double agcGamma)\n>> +{\n>> +\tif ((wbGains_[0] == 0) || (wbGains_[1] == 0) || (wbGains_[2] == 0) || (wbGains_[3] == 0)) {\n>> +\t\tLOG(IPU3Awb, Error) << \"Gains can't be 0, check the stats\";\n>> +\t} else {\n>> +\t\tparams.acc_param.bnr.wb_gains.gr = wbGains_[0];\n>> +\t\tparams.acc_param.bnr.wb_gains.r = wbGains_[1];\n>> +\t\tparams.acc_param.bnr.wb_gains.b = wbGains_[2];\n>> +\t\tparams.acc_param.bnr.wb_gains.gb = wbGains_[3];\n>> +\n>> +\t\tLOG(IPU3Awb, Debug) << \"Color temperature estimated: \" << cct_\n>> +\t\t\t\t    << \" and gamma calculated: \" << agcGamma;\n>> +\t\tparams.acc_param.ccm = imguCssCcmDefault;\n>> +\n>> +\t\tfor (uint32_t i = 0; i < 256; i++) {\n>> +\t\t\tdouble j = i / 255.0;\n>> +\t\t\tdouble gamma = std::pow(j, 1.0 / agcGamma);\n>> +\t\t\tparams.acc_param.gamma.gc_lut.lut[i] = gamma * 8191;\n>> +\t\t}\n>> +\t}\n>> +}\n>> +\n>> +} /* namespace ipa */\n>> +\n>> +} /* namespace libcamera */\n>> diff --git a/src/ipa/ipu3/ipu3_awb.h b/src/ipa/ipu3/ipu3_awb.h\n>> new file mode 100644\n>> index 00000000..a14401a0\n>> --- /dev/null\n>> +++ b/src/ipa/ipu3/ipu3_awb.h\n>> @@ -0,0 +1,44 @@\n>> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n>> +/*\n>> + * Copyright (C) 2021, Ideas On Board\n>> + *\n>> + * ipu3_awb.h - IPU3 AWB control algorithm\n>> + */\n>> +#ifndef __LIBCAMERA_IPU3_AWB_H__\n>> +#define __LIBCAMERA_IPU3_AWB_H__\n>> +\n>> +#include <linux/intel-ipu3.h>\n>> +\n>> +#include <libcamera/geometry.h>\n>> +\n>> +#include \"libipa/algorithm.h\"\n>> +\n>> +namespace libcamera {\n>> +\n>> +namespace ipa {\n>> +\n>> +class IPU3Awb : public Algorithm\n>> +{\n>> +public:\n>> +\tIPU3Awb();\n>> +\t~IPU3Awb();\n>> +\n>> +\tvoid initialise(ipu3_uapi_params &params, const Size &bdsOutputSize, struct ipu3_uapi_grid_config &bdsGrid);\n>> +\tvoid calculateWBGains(const ipu3_uapi_stats_3a *stats);\n>> +\tvoid updateWbParameters(ipu3_uapi_params &params, double agcGamma);\n>> +\n>> +private:\n>> +\tuint32_t estimateCCT(uint8_t red, uint8_t green, uint8_t blue);\n>> +\n>> +\t/* WB calculated gains */\n>> +\tuint16_t wbGains_[4];\n>> +\tuint32_t cct_;\n>> +\tuint32_t awbCounted_;\n>> +\tstruct ipu3_uapi_grid_config awbGrid_;\n>> +\tuint32_t frame_count_;\n>> +};\n>> +\n>> +} /* namespace ipa */\n>> +\n>> +} /* namespace libcamera*/\n>> +#endif /* __LIBCAMERA_IPU3_AWB_H__ */\n>> diff --git a/src/ipa/ipu3/meson.build b/src/ipa/ipu3/meson.build\n>> index a241f617..1040698e 100644\n>> --- a/src/ipa/ipu3/meson.build\n>> +++ b/src/ipa/ipu3/meson.build\n>> @@ -2,8 +2,13 @@\n>>  \n>>  ipa_name = 'ipa_ipu3'\n>>  \n>> +ipu3_ipa_sources = files([\n>> +    'ipu3.cpp',\n>> +    'ipu3_awb.cpp',\n>> +])\n>> +\n>>  mod = shared_module(ipa_name,\n>> -                    ['ipu3.cpp', libcamera_generated_ipa_headers],\n>> +                    [ipu3_ipa_sources, libcamera_generated_ipa_headers],\n>>                      name_prefix : '',\n>>                      include_directories : [ipa_includes, libipa_includes],\n>>                      dependencies : libcamera_dep,\n>>\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 BCAA8C0DA3\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 31 Mar 2021 05:44:38 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 333C968782;\n\tWed, 31 Mar 2021 07:44:38 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 8DB73602DB\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 31 Mar 2021 07:44:36 +0200 (CEST)","from [IPv6:2a01:e0a:169:7140:e35b:b719:e52b:ac44] (unknown\n\t[IPv6:2a01:e0a:169:7140:e35b:b719:e52b:ac44])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 160828C4;\n\tWed, 31 Mar 2021 07:44:36 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"C5Xs2vRw\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1617169476;\n\tbh=O7Uc/AH3yuyPPkpA6Z/TmCidC8EyLBMCcP+DZ7ON/oU=;\n\th=To:References:From:Subject:Date:In-Reply-To:From;\n\tb=C5Xs2vRwIT2VO+PfGaqK+UZn9RL0iB2hytDESHo5pbIszNNZsz3clWgHBNPHw3DSn\n\t4dqG5ZvYxw1V4qssneTppnEAb8QDEUkXRBQoef6ArWRbGQ50tGnMAMlSpqLc6aYFVd\n\tfi8PV5k4e0wGD2AvNMtAcxWLQ7p5p9wqnDj2KS3U=","To":"kieran.bingham@ideasonboard.com,\n\tJean-Michel Hautbois <jeanmichel.hautbois@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","References":"<20210330211210.194806-1-jeanmichel.hautbois@ideasonboard.com>\n\t<20210330211210.194806-4-jeanmichel.hautbois@ideasonboard.com>\n\t<93e66cd2-7e46-d4d4-d44c-192bf4f597f8@ideasonboard.com>","From":"Jean-Michel Hautbois <jeanmichel.hautbois@ideasonboard.com>","Message-ID":"<c6b2e256-e76a-1a6f-8b0a-02ce930b5e3f@ideasonboard.com>","Date":"Wed, 31 Mar 2021 07:44:35 +0200","User-Agent":"Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101\n\tThunderbird/78.7.1","MIME-Version":"1.0","In-Reply-To":"<93e66cd2-7e46-d4d4-d44c-192bf4f597f8@ideasonboard.com>","Content-Language":"en-US","Subject":"Re: [libcamera-devel] [PATCH v4 3/4] ipa: ipu3: Add support for\n\tIPU3 AWB algorithm","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>","Content-Type":"text/plain; charset=\"us-ascii\"","Content-Transfer-Encoding":"7bit","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":16184,"web_url":"https://patchwork.libcamera.org/comment/16184/","msgid":"<d63c9081-3516-6923-5c0d-5029c5d5b722@ideasonboard.com>","date":"2021-04-12T14:09:34","subject":"Re: [libcamera-devel] [PATCH v4 3/4] ipa: ipu3: Add support for\n\tIPU3 AWB algorithm","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"Hi JM,\n\nOn 31/03/2021 06:44, Jean-Michel Hautbois wrote:\n> Hi Kieran,\n> \n> On 31/03/2021 00:48, Kieran Bingham wrote:\n>> Hi JM\n>>\n>> On 30/03/2021 22:12, Jean-Michel Hautbois wrote:\n>>> The IPA will locally modify the parameters before they are passed down\n>>> to the ImgU. Use a local parameter object to give a reference to those\n>>> algorithms.\n>>>\n>>> Inherit from the Algorithm class to implement basic AWB functions.\n>>>\n>>> Once AWB is done, a color temperature is estimated and a default CCM matrix\n>>> will be used (yet to be tuned).\n>>> Implement a basic \"grey-world\" AWB algorithm just for demonstration purpose.\n>>>\n>>> The BDS output size is passed by the pipeline handler to the IPA.\n>>> The best grid is then calculated to maximize the number of pixels taken\n>>> into account in each cells.\n>>>\n>>> As commented in the source code, it can be improved, as it has (at least)\n>>> one limitation: if a cell is big (say 128 pixels wide) and indicated as\n>>> saturated, it won't be taken into account at all.\n>>> Maybe is it possible to have a smaller one, at the cost of a few pixels\n>>> to lose, in which case we can center the grid using the x_start and\n>>> y_start parameters.\n>>>\n>>> Signed-off-by: Jean-Michel Hautbois <jeanmichel.hautbois@ideasonboard.com>\n>>> ---\n>>>  src/ipa/ipu3/ipu3.cpp     |  86 ++++++++++++--\n>>>  src/ipa/ipu3/ipu3_awb.cpp | 235 ++++++++++++++++++++++++++++++++++++++\n>>>  src/ipa/ipu3/ipu3_awb.h   |  44 +++++++\n>>>  src/ipa/ipu3/meson.build  |   7 +-\n>>>  4 files changed, 363 insertions(+), 9 deletions(-)\n>>>  create mode 100644 src/ipa/ipu3/ipu3_awb.cpp\n>>>  create mode 100644 src/ipa/ipu3/ipu3_awb.h\n>>>\n>>> diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp\n>>> index 34a907f2..1cce11c9 100644\n>>> --- a/src/ipa/ipu3/ipu3.cpp\n>>> +++ b/src/ipa/ipu3/ipu3.cpp\n>>> @@ -21,6 +21,11 @@\n>>>  #include \"libcamera/internal/buffer.h\"\n>>>  #include \"libcamera/internal/log.h\"\n>>>  \n>>> +#include \"ipu3_awb.h\"\n>>> +\n>>> +static constexpr uint32_t kMaxCellWidthPerSet = 160;\n>>> +static constexpr uint32_t kMaxCellHeightPerSet = 80;\n>>> +\n>>>  namespace libcamera {\n>>>  \n>>>  LOG_DEFINE_CATEGORY(IPAIPU3)\n>>> @@ -49,6 +54,7 @@ private:\n>>>  \t\t\t     const ipu3_uapi_stats_3a *stats);\n>>>  \n>>>  \tvoid setControls(unsigned int frame);\n>>> +\tvoid calculateBdsGrid(const Size &bdsOutputSize);\n>>>  \n>>>  \tstd::map<unsigned int, MappedFrameBuffer> buffers_;\n>>>  \n>>> @@ -61,6 +67,14 @@ private:\n>>>  \tuint32_t gain_;\n>>>  \tuint32_t minGain_;\n>>>  \tuint32_t maxGain_;\n>>> +\n>>> +\t/* Interface to the AWB algorithm */\n>>> +\tstd::unique_ptr<ipa::IPU3Awb> awbAlgo_;\n>>> +\n>>> +\t/* Local parameter storage */\n>>> +\tstruct ipu3_uapi_params params_;\n>>> +\n>>> +\tstruct ipu3_uapi_grid_config bdsGrid_;\n>>>  };\n>>>  \n>>>  int IPAIPU3::start()\n>>> @@ -70,8 +84,59 @@ int IPAIPU3::start()\n>>>  \treturn 0;\n>>>  }\n>>>  \n>>> +/**\n>>> + * This method calculates a grid for the AWB algorithm in the IPU3 firmware.\n>>> + * Its input is the BDS output size calculated in the ImgU.\n>>> + * It is limited for now to the simplest method: find the lesser error\n>>> + * with the width/height and respective log2 width/height of the cells.\n>>> + *\n>>> + * \\todo The frame is divided into cells which can be 8x8 => 128x128.\n>>> + * As a smaller cell improves the algorithm precision, adapting the\n>>> + * x_start and y_start parameters of the grid would provoke a loss of\n>>> + * some pixels but would also result in more accurate algorithms.\n>>> + */\n>>> +void IPAIPU3::calculateBdsGrid(const Size &bdsOutputSize)\n>>> +{\n>>> +\tuint32_t minError = std::numeric_limits<uint32_t>::max();\n>>> +\tuint32_t bestWidth = 0;\n>>> +\tuint32_t bestHeight = 0;\n\ncan we use a Size here?\n\nSize best; ?\n\n>>> +\tuint32_t bestLog2Width = 0;\n>>> +\tuint32_t bestLog2Height = 0;\n\nand here?\n\nI might not be too surprised if the answer is no ... but perhaps...\n\nSeems it won't gain much as we use the width/heights separately\nthroughout this....\n\n>>> +\tbdsGrid_ = {};\n>>> +\n>>> +\tfor (uint32_t widthShift = 3; widthShift <= 7; ++widthShift) {\n>>> +\t\tuint32_t width = std::min(kMaxCellWidthPerSet,\n>>> +\t\t\t\t\t  bdsOutputSize.width >> widthShift);\n>>> +\t\twidth = width << widthShift;\n>>> +\t\tfor (uint32_t heightShift = 3; heightShift <= 7; ++heightShift) {\n>>> +\t\t\tint32_t height = std::min(kMaxCellHeightPerSet,\n>>> +\t\t\t\t\t\t  bdsOutputSize.height >> heightShift);\n>>> +\t\t\theight = height << heightShift;\n>>> +\t\t\tuint32_t error = std::abs(static_cast<int>(width - bdsOutputSize.width)) + std::abs(static_cast<int>(height - bdsOutputSize.height));\n>>\n>> That's a long line ...\n> \n> Checkstyle has corrected that one :-/ !\n\nYes, sometimes checkstyle gets it wrong I'm afraid.\nI'd break this one down at the + ...\n\n\n> uint32_t error = std::abs(static_cast<int>(width - bdsOutputSize.width))\n>\t\t + std::abs(static_cast<int>(height - bdsOutputSize.height));\n\nIf width/height were a Size ... I'd wonder if we should have a standard\noperation on the Size class for comparing two sizes like this ...\n\nBut that's not for this patch.\n\n>>> +\n>>> +\t\t\tif (error > minError)\n>>> +\t\t\t\tcontinue;\n>>> +\n>>> +\t\t\tminError = error;\n>>> +\t\t\tbestWidth = width;\n>>> +\t\t\tbestHeight = height;\n>>> +\t\t\tbestLog2Width = widthShift;\n>>> +\t\t\tbestLog2Height = heightShift;\n>>> +\t\t}\n>>> +\t}\n>>> +\n>>> +\tbdsGrid_.width = bestWidth >> bestLog2Width;\n>>> +\tbdsGrid_.block_width_log2 = bestLog2Width;\n>>> +\tbdsGrid_.height = bestHeight >> bestLog2Height;\n>>> +\tbdsGrid_.block_height_log2 = bestLog2Height;\n>>> +\n>>> +\tLOG(IPAIPU3, Debug) << \"Best grid found is: (\"\n>>> +\t\t\t    << (int)bdsGrid_.width << \" << \" << (int)bdsGrid_.block_width_log2 << \") x (\"\n>>> +\t\t\t    << (int)bdsGrid_.height << \"<<\" << (int)bdsGrid_.block_height_log2 << \")\";\n\nSpacing in one \" << \", but not the other \"<<\" ?\n\n\n>>> +}\n>>> +\n>>>  void IPAIPU3::configure(const std::map<uint32_t, ControlInfoMap> &entityControls,\n>>> -\t\t\t[[maybe_unused]] const Size &bdsOutputSize)\n>>> +\t\t\tconst Size &bdsOutputSize)\n>>>  {\n>>>  \tif (entityControls.empty())\n>>>  \t\treturn;\n>>> @@ -92,11 +157,18 @@ void IPAIPU3::configure(const std::map<uint32_t, ControlInfoMap> &entityControls\n>>>  \n>>>  \tminExposure_ = std::max(itExp->second.min().get<int32_t>(), 1);\n>>>  \tmaxExposure_ = itExp->second.max().get<int32_t>();\n>>> -\texposure_ = maxExposure_;\n>>> +\texposure_ = minExposure_;\n>>>  \n>>>  \tminGain_ = std::max(itGain->second.min().get<int32_t>(), 1);\n>>>  \tmaxGain_ = itGain->second.max().get<int32_t>();\n>>> -\tgain_ = maxGain_;\n>>> +\tgain_ = minGain_;\n\nWe're going from starting up with a 'bright' image to starting up with a\n'dark' image with this.\n\nMight be worth noting that in the commit message, but it probably\ndoesn't matter, as the algorithm is going to adjust that once we start.\n\nBut if we start up with this, and have AE/AGC switched off... it would\nbe a noticible difference.\n\nNo code change needed, just documenting that it's a change is all.\n\n\n>>> +\n>>> +\tparams_ = {};\n>>> +\n>>> +\tcalculateBdsGrid(bdsOutputSize);\n>>> +\n>>> +\tawbAlgo_ = std::make_unique<ipa::IPU3Awb>();\n>>> +\tawbAlgo_->initialise(params_, bdsOutputSize, bdsGrid_);\n>>>  }\n>>>  \n>>>  void IPAIPU3::mapBuffers(const std::vector<IPABuffer> &buffers)\n>>> @@ -168,10 +240,9 @@ void IPAIPU3::processControls([[maybe_unused]] unsigned int frame,\n>>>  \n>>>  void IPAIPU3::fillParams(unsigned int frame, ipu3_uapi_params *params)\n>>>  {\n>>> -\t/* Prepare parameters buffer. */\n>>> -\tmemset(params, 0, sizeof(*params));\n>>> +\tawbAlgo_->updateWbParameters(params_, 1.0);\n\nWhat is 1.0 here?\n\n>>>  \n>>> -\t/* \\todo Fill in parameters buffer. */\n>>> +\t*params = params_;\n>>>  \n>>>  \tipa::ipu3::IPU3Action op;\n>>>  \top.op = ipa::ipu3::ActionParamFilled;\n>>> @@ -184,8 +255,7 @@ void IPAIPU3::parseStatistics(unsigned int frame,\n>>>  {\n>>>  \tControlList ctrls(controls::controls);\n>>>  \n>>> -\t/* \\todo React to statistics and update internal state machine. */\n>>> -\t/* \\todo Add meta-data information to ctrls. */\n>>> +\tawbAlgo_->calculateWBGains(stats);\n>>>  \n>>>  \tipa::ipu3::IPU3Action op;\n>>>  \top.op = ipa::ipu3::ActionMetadataReady;\n>>> diff --git a/src/ipa/ipu3/ipu3_awb.cpp b/src/ipa/ipu3/ipu3_awb.cpp\n>>> new file mode 100644\n>>> index 00000000..18d19d36\n>>> --- /dev/null\n>>> +++ b/src/ipa/ipu3/ipu3_awb.cpp\n>>> @@ -0,0 +1,235 @@\n>>> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n>>> +/*\n>>> + * Copyright (C) 2021, Ideas On Board\n>>> + *\n>>> + * ipu3_awb.cpp - AWB control algorithm\n>>> + */\n>>> +#include \"ipu3_awb.h\"\n>>> +\n>>> +#include <cmath>\n>>> +#include <numeric>\n>>> +#include <unordered_map>\n>>> +\n>>> +#include \"libcamera/internal/log.h\"\n>>> +\n>>> +namespace libcamera {\n>>> +\n>>> +namespace ipa {\n>>> +\n>>> +LOG_DEFINE_CATEGORY(IPU3Awb)\n>>> +\n\nShould we document that these defaults are replicated from the Kernel?\n\n(FWIW, I like that they are set here, rather than using\n'unknown/invisible' defaults in the kernel).\n\n>>> +static const struct ipu3_uapi_bnr_static_config imguCssBnrDefaults = {\n>>> +\t.wb_gains = { 16, 16, 16, 16 },\n>>> +\t.wb_gains_thr = { 255, 255, 255, 255 },\n>>> +\t.thr_coeffs = { 1700, 0, 31, 31, 0, 16 },\n>>> +\t.thr_ctrl_shd = { 26, 26, 26, 26 },\n>>> +\t.opt_center{ -648, 0, -366, 0 },\n>>> +\t.lut = {\n>>> +\t\t{ 17, 23, 28, 32, 36, 39, 42, 45,\n>>> +\t\t  48, 51, 53, 55, 58, 60, 62, 64,\n>>> +\t\t  66, 68, 70, 72, 73, 75, 77, 78,\n>>> +\t\t  80, 82, 83, 85, 86, 88, 89, 90 } },\n>>> +\t.bp_ctrl = { 20, 0, 1, 40, 0, 6, 0, 6, 0 },\n>>> +\t.dn_detect_ctrl{ 9, 3, 4, 0, 8, 0, 1, 1, 1, 1, 0 },\n>>> +\t.column_size = 1296,\n>>> +\t.opt_center_sqr = { 419904, 133956 },\n>>> +};\n>>> +\n>>> +/* settings for Auto White Balance */\n>>> +static const struct ipu3_uapi_awb_config_s imguCssAwbDefaults = {\n>>> +\t.rgbs_thr_gr = 8191,\n>>> +\t.rgbs_thr_r = 8191,\n>>> +\t.rgbs_thr_gb = 8191,\n>>> +\t.rgbs_thr_b = 8191 | IPU3_UAPI_AWB_RGBS_THR_B_EN | IPU3_UAPI_AWB_RGBS_THR_B_INCL_SAT,\n>>> +\t.grid = {\n>>> +\t\t.width = 160,\n>>> +\t\t.height = 36,\n>>> +\t\t.block_width_log2 = 3,\n>>> +\t\t.block_height_log2 = 4,\n>>> +\t\t.height_per_slice = 1, /* Overridden by kernel. */\n>>> +\t\t.x_start = 0,\n>>> +\t\t.y_start = 0,\n>>> +\t\t.x_end = 0,\n>>> +\t\t.y_end = 0,\n>>> +\t},\n>>> +};\n>>> +\n\nI would label this CCM as an identity mapping.\n(assuming that's what it is?)\n\n>>> +static const struct ipu3_uapi_ccm_mat_config imguCssCcmDefault = {\n>>> +\t8191, 0, 0, 0,\n>>> +\t0, 8191, 0, 0,\n>>> +\t0, 0, 8191, 0\n>>> +};\n>>> +\n>>> +IPU3Awb::IPU3Awb()\n>>> +\t: Algorithm()\n>>> +{\n>>> +}\n>>> +\n>>> +IPU3Awb::~IPU3Awb()\n>>> +{\n>>> +}\n>>> +\n>>> +void IPU3Awb::initialise(ipu3_uapi_params &params, const Size &bdsOutputSize, struct ipu3_uapi_grid_config &bdsGrid)\n>>> +{\n>>> +\tparams.use.acc_awb = 1;\n>>> +\tparams.acc_param.awb.config = imguCssAwbDefaults;\n>>> +\n>>> +\tawbGrid_ = bdsGrid;\n>>> +\tparams.acc_param.awb.config.grid = awbGrid_;\n>>> +\n>>> +\tparams.use.obgrid = 0;\n\nIf this is 0, why do we fill in values below?\nIf we're keeping it for defaults label it with a comment so it's clear\nit's intended.\n\n\n>>> +\tparams.obgrid_param.gr = 20;\n>>> +\tparams.obgrid_param.r = 28;\n>>> +\tparams.obgrid_param.b = 28;\n>>> +\tparams.obgrid_param.gb = 20;\n\nIs there any comment that could describe 'why' 20/28 are chosen there?\nDo the values come from somewhere specifically?\n\n>>> +\n>>> +\tparams.use.acc_bnr = 1;\n>>> +\tparams.acc_param.bnr = imguCssBnrDefaults;\n>>> +\tparams.acc_param.bnr.opt_center.x_reset = -1 * (bdsOutputSize.width / 2);\n>>> +\tparams.acc_param.bnr.opt_center.y_reset = -1 * (bdsOutputSize.height / 2);\n\nWhy are these -1 * ? To make it negative?\nwhy not just prefix with a negative? Is there something else going on ?\n\nWhy are they negative anyway?\n\n>>> +\tparams.acc_param.bnr.column_size = bdsOutputSize.width;\n>>> +\tparams.acc_param.bnr.opt_center_sqr.x_sqr_reset = params.acc_param.bnr.opt_center.x_reset * params.acc_param.bnr.opt_center.x_reset;\n>>> +\tparams.acc_param.bnr.opt_center_sqr.y_sqr_reset = params.acc_param.bnr.opt_center.y_reset * params.acc_param.bnr.opt_center.y_reset;\n\n\n\nI know checkstyle probably put this all on one line (we should try to\nimprove that) but I think that's better as split. Again at least at the\n* ...\n\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\n\n>>> +\n>>> +\tparams.use.acc_ccm = 1;\n>>> +\tparams.acc_param.ccm = imguCssCcmDefault;\n>>> +\n>>> +\tparams.use.acc_gamma = 1;\n>>> +\tparams.acc_param.gamma.gc_ctrl.enable = 1;\n>>> +\n>>> +\tparams.use.acc_green_disparity = 0;\n>>> +\tparams.acc_param.green_disparity.gd_black = 2440;\n>>> +\tparams.acc_param.green_disparity.gd_red = 4;\n>>> +\tparams.acc_param.green_disparity.gd_blue = 4;\n>>> +\tparams.acc_param.green_disparity.gd_green = 4;\n>>> +\tparams.acc_param.green_disparity.gd_shading = 24;\n>>> +\tparams.acc_param.green_disparity.gd_support = 2;\n>>> +\tparams.acc_param.green_disparity.gd_clip = 1;\n>>> +\tparams.acc_param.green_disparity.gd_central_weight = 5;\n\nSome note on the reason/source/values above would be useful if it's\navailable.\n\n\n>>> +\n>>> +\tparams.use.acc_cds = 1;\n>>> +\tparams.acc_param.cds.csc_en = 1;\n>>> +\tparams.acc_param.cds.uv_bin_output = 0;\n>>> +\tparams.acc_param.cds.ds_c00 = 0;\n>>> +\tparams.acc_param.cds.ds_c01 = 1;\n>>> +\tparams.acc_param.cds.ds_c02 = 1;\n>>> +\tparams.acc_param.cds.ds_c03 = 0;\n>>> +\tparams.acc_param.cds.ds_c10 = 0;\n>>> +\tparams.acc_param.cds.ds_c11 = 1;\n>>> +\tparams.acc_param.cds.ds_c12 = 1;\n>>> +\tparams.acc_param.cds.ds_c13 = 0;\n>>> +\tparams.acc_param.cds.ds_nf = 2;\n\nAnd same here, as it's not just initialising to 0 or such. A brief\nexplanation as to why this is the init values would be helpful.\n\n>>> +\n>>> +\twbGains_[0] = 16;\n>>> +\twbGains_[1] = 4096;\n>>> +\twbGains_[2] = 4096;\n>>> +\twbGains_[3] = 16;\n>>> +\n\nAnd same on the gains ;-)\n\n\n>>> +\tframe_count_ = 0;\n>>> +}\n>>> +\n\nIs there anything we can document on this function?\nWhere is the calculation referenced from?\n\nAre there any limitations or such? (I.e. because it's an estimate...)\n\nIs it based upon a specific colorspace?\n\n>>> +uint32_t IPU3Awb::estimateCCT(uint8_t red, uint8_t green, uint8_t blue)\n>>> +{\n>>> +\tdouble X = (-0.14282) * (red) + (1.54924) * (green) + (-0.95641) * (blue);\n>>> +\tdouble Y = (-0.32466) * (red) + (1.57837) * (green) + (-0.73191) * (blue);\n>>> +\tdouble Z = (-0.68202) * (red) + (0.77073) * (green) + (0.56332) * (blue);\n>>> +\n>>> +\tdouble x = X / (X + Y + Z);\n>>> +\tdouble y = Y / (X + Y + Z);\n>>> +\n>>> +\tdouble n = (x - 0.3320) / (0.1858 - y);\n>>> +\treturn static_cast<uint32_t>(449 * n * n * n + 3525 * n * n + 6823.3 * n + 5520.33);\n>>> +}\n>>> +\n>>> +double meanValue(std::vector<uint32_t> colorValues)\n>>> +{\n>>> +\tuint32_t count = 0;\n>>> +\tuint32_t hist[256] = { 0 };\n>>> +\tfor (uint32_t const &val : colorValues) {\n>>> +\t\thist[val]++;\n>>> +\t\tcount++;\n>>> +\t}\n>>> +\n>>> +\tdouble mean = 0.0;\n>>> +\tfor (uint32_t i = 0; i < 256; i++) {\n>>> +\t\tmean += hist[i] * i;\n>>> +\t}\n>>> +\treturn mean /= count;\n\nI suspect a simple helper class to maintain an average could be\nreplacing this... something like :\n (discard all of this if it's wrong or not helpful)\n\nclass Average() {\n\tAverage() : sum_ = 0, count_ = 0 {};\n\n\tvoid add(uint64_t value) { sum_ += value; count++; }\n\tuint64_t mean() const { return sum_ / count_; }\n\nprivate:\n\tuint64_t sum_;\n\tsize_t\t count_;\n}\n\nAnd then we won't re-loop on each one... or store data that we don't need.\n\n>>> +}\n>>> +\n>>> +void IPU3Awb::calculateWBGains(const ipu3_uapi_stats_3a *stats)\n>>> +{\n>>\n>> I suspect we'll want to profile this function carefully sometime.\n>>\n> \n> Yes, but no premature optimization :-).\n> I agree at some point we need to profile globally, and this function\n> will probably appear as a costly one ;-).\n> \n>>> +\tASSERT(stats->stats_3a_status.awb_en);\n>>> +\n>>> +\tstd::vector<uint32_t> redValues, greenValues, blueValues;\n>>> +\tconst struct ipu3_uapi_grid_config statsAwbGrid = stats->stats_4a_config.awb_config.grid;\n>>> +\tRectangle awbRegion = { statsAwbGrid.x_start,\n>>> +\t\t\t\tstatsAwbGrid.y_start,\n>>> +\t\t\t\tstatic_cast<unsigned int>(statsAwbGrid.x_end - statsAwbGrid.x_start) + 1,\n>>> +\t\t\t\tstatic_cast<unsigned int>(statsAwbGrid.y_end - statsAwbGrid.y_start) + 1 };\n>>> +\n>>> +\tPoint topleft = awbRegion.topLeft();\n>>> +\tuint32_t startY = (topleft.y >> awbGrid_.block_height_log2) * awbGrid_.width << awbGrid_.block_width_log2;\n>>> +\tuint32_t startX = (topleft.x >> awbGrid_.block_width_log2) << awbGrid_.block_width_log2;\n>>> +\tuint32_t endX = (startX + (awbRegion.size().width >> awbGrid_.block_width_log2)) << awbGrid_.block_width_log2;\n>>> +\tuint32_t count = 0;\n>>> +\tuint32_t i, j;\n>>> +\n>>\n>> Can we reserve the sizes for each of the redValues, greenValues blueValues?\n>>\n>> Pushing back each iteration will incur lots of reallocations everytime\n>> the initial reserved size is met ... I bet we're losing quite a bit of\n>> time here.\n> \n> Yes, but there is a better one: we don't need the individual cells at\n> all, only the mean value. So, accumulating and dividing at the end by\n> the number of cells accumulated is lighter.\n\nEven better.\n\n\n>> I see both Gr Gb greens go into the same pot below, I guess that's expected.\n> \n> Yes, it one one green at the end (I did not see any real improvement when\n> splitting them when I tested, but I can give it a new try).\n\nprobably doesn't matter.\n\nI guess the average of all Gr,Gb put together, is the same as\nSum(Gr)+Sum(Gb)/2 ...\n\n(assuming there are the same quantity of Gr's as there are Gb's)\n\nIf there's no specific advantage to keeping them separate then putting\nthem all into one average is likely simpler.\n\n\n\n>>> +\tawbCounted_ = 0;\n>>> +\tfor (j = (topleft.y >> awbGrid_.block_height_log2);\n>>> +\t     j < (topleft.y >> awbGrid_.block_height_log2) + (awbRegion.size().height >> awbGrid_.block_height_log2);\n>>> +\t     j++) {\n>>> +\t\tfor (i = startX + startY; i < endX + startY; i += 8) {\n>>> +\t\t\tif (stats->awb_raw_buffer.meta_data[i + 4 + j * awbGrid_.width] == 0) {\n>>> +\t\t\t\tgreenValues.push_back(stats->awb_raw_buffer.meta_data[i + j * awbGrid_.width]);\n>>> +\t\t\t\tredValues.push_back(stats->awb_raw_buffer.meta_data[i + 1 + j * awbGrid_.width]);\n>>> +\t\t\t\tblueValues.push_back(stats->awb_raw_buffer.meta_data[i + 2 + j * awbGrid_.width]);\n>>> +\t\t\t\tgreenValues.push_back(stats->awb_raw_buffer.meta_data[i + 3 + j * awbGrid_.width]);\n>>> +\t\t\t\tawbCounted_++;\n>>> +\t\t\t}\n>>> +\t\t\tcount++;\n>>> +\t\t}\n>>> +\t}\n>>> +\n>>> +\tdouble rMean = meanValue(redValues);\n>>> +\tdouble bMean = meanValue(blueValues);\n>>> +\tdouble gMean = meanValue(greenValues);\n>>\n>> I'm wondering if we might be able to optimise how we calculate these\n>> means - but ... I don't want to optimise things before they're profiled,\n>> and this works (I believe).\n> \n> Sure we can ;-). It is a relicat from all the moments calculations I think.\n> We could store the sums in an internal structure, as it is done in\n> bcm2835_isp_stats_region.\n> \n>>> +\n>>> +\tdouble rGain = gMean / rMean;\n>>> +\tdouble bGain = gMean / bMean;\n>>> +\n>>> +\twbGains_[0] = 16;\n>>> +\twbGains_[1] = 4096 * rGain;\n>>> +\twbGains_[2] = 4096 * bGain;\n>>> +\twbGains_[3] = 16;\n\n16 and 4096 seem like some magic values here ... can we explain them in\nsome way?\n\n\n>>> +\n>>> +\tframe_count_++;\n>>> +\n>>> +\tcct_ = estimateCCT(rMean, gMean, bMean);\n>>> +}\n>>> +\n>>> +void IPU3Awb::updateWbParameters(ipu3_uapi_params &params, double agcGamma)\n>>> +{\n>>> +\tif ((wbGains_[0] == 0) || (wbGains_[1] == 0) || (wbGains_[2] == 0) || (wbGains_[3] == 0)) {\n>>> +\t\tLOG(IPU3Awb, Error) << \"Gains can't be 0, check the stats\";\n\nI've seen this error message in the past when testing I think.\n\nIt might help to print the 4 values so we can see which one is zero.\nIt looks like 0, 3 are 'always' set to 16, so presumably it means one of\nthe rGain/bGain ended up as 0 somehow?\n\n>>> +\t} else {\n>>> +\t\tparams.acc_param.bnr.wb_gains.gr = wbGains_[0];\n>>> +\t\tparams.acc_param.bnr.wb_gains.r = wbGains_[1];\n>>> +\t\tparams.acc_param.bnr.wb_gains.b = wbGains_[2];\n>>> +\t\tparams.acc_param.bnr.wb_gains.gb = wbGains_[3];\n>>> +\n>>> +\t\tLOG(IPU3Awb, Debug) << \"Color temperature estimated: \" << cct_\n>>> +\t\t\t\t    << \" and gamma calculated: \" << agcGamma;\n>>> +\t\tparams.acc_param.ccm = imguCssCcmDefault;\n>>> +\n>>> +\t\tfor (uint32_t i = 0; i < 256; i++) {\n>>> +\t\t\tdouble j = i / 255.0;\n>>> +\t\t\tdouble gamma = std::pow(j, 1.0 / agcGamma);\n>>> +\t\t\tparams.acc_param.gamma.gc_lut.lut[i] = gamma * 8191;\n\nWhat's the magic value for 8191 ? Is it just somethign IPU3 specific? Is\nit a particular gain value? or an encoding because of the LUT?\n\n>>> +\t\t}\n>>> +\t}\n>>> +}\n>>> +\n>>> +} /* namespace ipa */\n>>> +\n>>> +} /* namespace libcamera */\n>>> diff --git a/src/ipa/ipu3/ipu3_awb.h b/src/ipa/ipu3/ipu3_awb.h\n>>> new file mode 100644\n>>> index 00000000..a14401a0\n>>> --- /dev/null\n>>> +++ b/src/ipa/ipu3/ipu3_awb.h\n>>> @@ -0,0 +1,44 @@\n>>> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n>>> +/*\n>>> + * Copyright (C) 2021, Ideas On Board\n>>> + *\n>>> + * ipu3_awb.h - IPU3 AWB control algorithm\n>>> + */\n>>> +#ifndef __LIBCAMERA_IPU3_AWB_H__\n>>> +#define __LIBCAMERA_IPU3_AWB_H__\n>>> +\n>>> +#include <linux/intel-ipu3.h>\n>>> +\n>>> +#include <libcamera/geometry.h>\n>>> +\n>>> +#include \"libipa/algorithm.h\"\n>>> +\n>>> +namespace libcamera {\n>>> +\n>>> +namespace ipa {\n>>> +\n>>> +class IPU3Awb : public Algorithm\n>>> +{\n>>> +public:\n>>> +\tIPU3Awb();\n>>> +\t~IPU3Awb();\n>>> +\n>>> +\tvoid initialise(ipu3_uapi_params &params, const Size &bdsOutputSize, struct ipu3_uapi_grid_config &bdsGrid);\n>>> +\tvoid calculateWBGains(const ipu3_uapi_stats_3a *stats);\n>>> +\tvoid updateWbParameters(ipu3_uapi_params &params, double agcGamma);\n>>> +\n>>> +private:\n>>> +\tuint32_t estimateCCT(uint8_t red, uint8_t green, uint8_t blue);\n>>> +\n>>> +\t/* WB calculated gains */\n>>> +\tuint16_t wbGains_[4];\n>>> +\tuint32_t cct_;\n>>> +\tuint32_t awbCounted_;\n>>> +\tstruct ipu3_uapi_grid_config awbGrid_;\n>>> +\tuint32_t frame_count_;\n>>> +};\n>>> +\n>>> +} /* namespace ipa */\n>>> +\n>>> +} /* namespace libcamera*/\n>>> +#endif /* __LIBCAMERA_IPU3_AWB_H__ */\n>>> diff --git a/src/ipa/ipu3/meson.build b/src/ipa/ipu3/meson.build\n>>> index a241f617..1040698e 100644\n>>> --- a/src/ipa/ipu3/meson.build\n>>> +++ b/src/ipa/ipu3/meson.build\n>>> @@ -2,8 +2,13 @@\n>>>  \n>>>  ipa_name = 'ipa_ipu3'\n>>>  \n>>> +ipu3_ipa_sources = files([\n>>> +    'ipu3.cpp',\n>>> +    'ipu3_awb.cpp',\n>>> +])\n>>> +\n>>>  mod = shared_module(ipa_name,\n>>> -                    ['ipu3.cpp', libcamera_generated_ipa_headers],\n>>> +                    [ipu3_ipa_sources, libcamera_generated_ipa_headers],\n>>>                      name_prefix : '',\n>>>                      include_directories : [ipa_includes, libipa_includes],\n>>>                      dependencies : libcamera_dep,\n>>>\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 A42FEBD224\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 12 Apr 2021 14:09:41 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 07DA3602CB;\n\tMon, 12 Apr 2021 16:09:41 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 14FA7602C8\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 12 Apr 2021 16:09:39 +0200 (CEST)","from [192.168.0.20]\n\t(cpc89244-aztw30-2-0-cust3082.18-1.cable.virginm.net [86.31.172.11])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id D661E3F0;\n\tMon, 12 Apr 2021 16:09:37 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"d1s50qVg\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1618236578;\n\tbh=1KS4JzsHH4CDah8Clm0LAGBDWQ4fmWtpqRe0XQgyP54=;\n\th=Reply-To:Subject:To:References:From:Date:In-Reply-To:From;\n\tb=d1s50qVgMOGkJvLNtf7f1aMVijYOtxVXpAfPDmTPvfSSVlW/UruhByUq093zqDZxt\n\tg+3evxkUC/YkVR8fsTPXymANKni07t3wCJa1gpmB2TZ/RWP3H/CmfNijqmKztCZdA1\n\trZqhO1+ZeRWafs2SNKxt6b8mBkw3dZfMbdTdSRlE=","To":"Jean-Michel Hautbois <jeanmichel.hautbois@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","References":"<20210330211210.194806-1-jeanmichel.hautbois@ideasonboard.com>\n\t<20210330211210.194806-4-jeanmichel.hautbois@ideasonboard.com>\n\t<93e66cd2-7e46-d4d4-d44c-192bf4f597f8@ideasonboard.com>\n\t<c6b2e256-e76a-1a6f-8b0a-02ce930b5e3f@ideasonboard.com>","From":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Autocrypt":"addr=kieran.bingham@ideasonboard.com; keydata=\n\t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\n\tWZw01QYHU/GUV/zHJSFk","Organization":"Ideas on Board","Message-ID":"<d63c9081-3516-6923-5c0d-5029c5d5b722@ideasonboard.com>","Date":"Mon, 12 Apr 2021 15:09:34 +0100","User-Agent":"Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101\n\tThunderbird/68.10.0","MIME-Version":"1.0","In-Reply-To":"<c6b2e256-e76a-1a6f-8b0a-02ce930b5e3f@ideasonboard.com>","Content-Language":"en-GB","Subject":"Re: [libcamera-devel] [PATCH v4 3/4] ipa: ipu3: Add support for\n\tIPU3 AWB algorithm","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>","Reply-To":"kieran.bingham@ideasonboard.com","Content-Type":"text/plain; charset=\"us-ascii\"","Content-Transfer-Encoding":"7bit","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]