[{"id":16346,"web_url":"https://patchwork.libcamera.org/comment/16346/","msgid":"<b4aa1985-3015-b773-76e8-243a325e47ae@ideasonboard.com>","date":"2021-04-19T11:01:40","subject":"Re: [libcamera-devel] [PATCH v5 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 16/04/2021 08:49, 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> The configure() call will set exposure and gain to their minimum value,\n> so while AGC is not there, the frames will be dark.\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 | 351 ++++++++++++++++++++++++++++++++++++++\n>  src/ipa/ipu3/ipu3_awb.h   |  91 ++++++++++\n>  src/ipa/ipu3/meson.build  |   7 +-\n>  4 files changed, 526 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..ab052a8a 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,58 @@ 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> +\tSize best;\n> +\tSize bestLog2;\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))\n> +\t\t\t\t\t\t\t+ std::abs(static_cast<int>(height - bdsOutputSize.height));\n> +\n> +\t\t\tif (error > minError)\n> +\t\t\t\tcontinue;\n> +\n> +\t\t\tminError = error;\n> +\t\t\tbest.width = width;\n> +\t\t\tbest.height = height;\n> +\t\t\tbestLog2.width = widthShift;\n> +\t\t\tbestLog2.height = heightShift;\n> +\t\t}\n> +\t}\n> +\n> +\tbdsGrid_.width = best.width >> bestLog2.width;\n> +\tbdsGrid_.block_width_log2 = bestLog2.width;\n> +\tbdsGrid_.height = best.height >> bestLog2.height;\n> +\tbdsGrid_.block_height_log2 = bestLog2.height;\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\nSomehow there's still an odd spacing around those << ;-)\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 +156,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 +239,10 @@ 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> +\t/* Pass a default gamma of 1.0 (default linear correction) */\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..b4920e4b\n> --- /dev/null\n> +++ b/src/ipa/ipu3/ipu3_awb.cpp\n> @@ -0,0 +1,351 @@\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> +/**\n> + * \\struct IspStatsRegion\n> + * \\brief RGB statistics for a given region\n> + *\n> + * The IspStatsRegion structure is intended to abstract the ISP specific\n> + * statistics and use an agnostic algorithm to compute AWB.\n> + *\n> + * \\var IspStatsRegion::counted\n> + * \\brief Number of pixels used to calculate the sums\n> + *\n> + * \\var IspStatsRegion::notcounted\n\nshould this be notCounted ? (or uncounted to be a single word ?)\n\n> + * \\brief Remaining number of pixels in the region\n> + *\n> + * \\var IspStatsRegion::rSum\n> + * \\brief Sum of the red values in the region\n> + *\n> + * \\var IspStatsRegion::gSum\n> + * \\brief Sum of the green values in the region\n> + *\n> + * \\var IspStatsRegion::bSum\n> + * \\brief Sum of the blue values in the region\n> + */\n> +\n> +/**\n> + * \\struct 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> +/**\n> + * \\struct Ipu3AwbCell\n> + * \\brief Memory layout for each cell in AWB metadata\n> + *\n> + * The Ipu3AwbCell structure is used to get individual values\n> + * such as red average or saturation ratio in a particular cell.\n> + *\n> + * \\var Ipu3AwbCell::greenRedAvg\n> + * \\brief Green average for red lines in the cell\n> + *\n> + * \\var Ipu3AwbCell::redAvg\n> + * \\brief Red average in the cell\n> + *\n> + * \\var Ipu3AwbCell::blueAvg\n> + * \\brief blue average in the cell\n> + *\n> + * \\var Ipu3AwbCell::greenBlueAvg\n> + * \\brief Green average for blue lines\n> + *\n> + * \\var Ipu3AwbCell::satRatio\n> + * \\brief Saturation ratio in the cell\n> + *\n> + * \\var Ipu3AwbCell::padding\n> + * \\brief array of unused bytes for padding\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> +\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> +/* Default settings for Auto White Balance replicated from the Kernel*/\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> +/* Default color correction matrix defined as an identity matrix */\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> +\tasyncResults_.blueGain = 1.0;\n> +\tasyncResults_.greenGain = 1.0;\n> +\tasyncResults_.redGain = 1.0;\n> +\tasyncResults_.temperatureK = 4500;\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.acc_bnr = 1;\n> +\tparams.acc_param.bnr = imguCssBnrDefaults;\n> +\t/**\n> +\t * Optical center is colum (resp row) start - X (resp Y) center.\n\ns/colum/column/\n\nWhat is resp?\n\n\n> +\t * For the moment use BDS as a first approximation, but it should\n> +\t * be calculated based on Shading (SHD) parameters.\n> +\t */\n> +\tparams.acc_param.bnr.column_size = bdsOutputSize.width;\n> +\tparams.acc_param.bnr.opt_center.x_reset = awbGrid_.x_start - (bdsOutputSize.width / 2);\n> +\tparams.acc_param.bnr.opt_center.y_reset = awbGrid_.y_start - (bdsOutputSize.height / 2);\n> +\tparams.acc_param.bnr.opt_center_sqr.x_sqr_reset = params.acc_param.bnr.opt_center.x_reset\n> +\t\t\t\t\t\t\t* 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\n> +\t\t\t\t\t\t\t* 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> +\tzones_.reserve(kAwbStatsSizeX * kAwbStatsSizeY);\n> +}\n> +\n> +/**\n> + * The function estimates the correlated color temperature using\n> + * from RGB color space input.\n> + * In physics and color science, the Planckian locus or black body locus is\n> + * the path or locus that the color of an incandescent black body would take\n> + * in a particular chromaticity space as the blackbody temperature changes.\n> + *\n> + * If a narrow range of color temperatures is considered (those encapsulating\n> + * daylight being the most practical case) one can approximate the Planckian\n> + * locus in order to calculate the CCT in terms of chromaticity coordinates.\n> + *\n> + * More detailed information can be found in:\n> + * https://en.wikipedia.org/wiki/Color_temperature#Approximation\n> + */\n> +uint32_t IPU3Awb::estimateCCT(double red, double green, double blue)\n> +{\n> +\t/* Convert the RGB values to CIE tristimulus values (XYZ) */\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> +\t/* Calculate the normalized chromaticity values */\n> +\tdouble x = X / (X + Y + Z);\n> +\tdouble y = Y / (X + Y + Z);\n> +\n> +\t/* Calculate CCT */\n> +\tdouble n = (x - 0.3320) / (0.1858 - y);\n> +\treturn 449 * n * n * n + 3525 * n * n + 6823.3 * n + 5520.33;\n> +}\n> +\n> +/* Generate a RGB vector with the average values for each region */\n\ns/a/an/\n\n\n> +void IPU3Awb::generateZones(std::vector<RGB> &zones)\n> +{\n> +\tfor (unsigned int i = 0; i < kAwbStatsSizeX * kAwbStatsSizeY; i++) {\n> +\t\tRGB zone;\n> +\t\tdouble counted = awbStats_[i].counted;\n> +\t\tif (counted >= 16) {\n> +\t\t\tzone.G = awbStats_[i].gSum / counted;\n> +\t\t\tif (zone.G >= 32) {\n\nWhat is the selection criteria or meaning behind 16 and 32 here?\n\nPerhaps we should define them as documented constexpr's at the top of\nthe function?\n\n\n> +\t\t\t\tzone.R = awbStats_[i].rSum / counted;\n> +\t\t\t\tzone.B = awbStats_[i].bSum / counted;\n> +\t\t\t\tzones.push_back(zone);\n> +\t\t\t}\n> +\t\t}\n> +\t}\n> +}\n> +\n> +/* Translate the IPU3 statistics into the default statistics region array */\n> +void IPU3Awb::generateAwbStats(const ipu3_uapi_stats_3a *stats)\n> +{\n> +\tuint32_t regionWidth = round(awbGrid_.width / static_cast<double>(kAwbStatsSizeX));\n> +\tuint32_t regionHeight = round(awbGrid_.height / static_cast<double>(kAwbStatsSizeY));\n> +\n> +\t/**\n\nonly /* needed here.\n\n/** is only for doxygen comments\n\n\n> +\t * Generate a (kAwbStatsSizeX x kAwbStatsSizeY) array from the IPU3 grid which is\n> +\t * (awbGrid_.width x awbGrid_.height).\n> +\t */\n> +\tfor (unsigned int j = 0; j < kAwbStatsSizeY * regionHeight; j++) {\n> +\t\tfor (unsigned int i = 0; i < kAwbStatsSizeX * regionWidth; i++) {\n> +\t\t\tuint32_t cellPosition = j * awbGrid_.width + i;\n> +\t\t\tuint32_t cellX = (cellPosition / regionWidth) % kAwbStatsSizeX;\n> +\t\t\tuint32_t cellY = ((cellPosition / awbGrid_.width) / regionHeight) % kAwbStatsSizeY;\n> +\n> +\t\t\tuint32_t awbRegionPosition = cellY * kAwbStatsSizeX + cellX;\n> +\t\t\tcellPosition *= 8;\n> +\n> +\t\t\t/* Cast the initial IPU3 structure to simplify the reading */\n> +\t\t\tIpu3AwbCell *currentCell = reinterpret_cast<Ipu3AwbCell *>(const_cast<uint8_t *>(&stats->awb_raw_buffer.meta_data[cellPosition]));\n> +\t\t\tif (currentCell->satRatio == 0) {\n> +\t\t\t\t/* The cell is not saturated, use the current cell */\n> +\t\t\t\tawbStats_[awbRegionPosition].counted++;\n> +\t\t\t\tuint32_t greenValue = currentCell->greenRedAvg + currentCell->greenBlueAvg;\n> +\t\t\t\tawbStats_[awbRegionPosition].gSum += greenValue / 2;\n> +\t\t\t\tawbStats_[awbRegionPosition].rSum += currentCell->redAvg;\n> +\t\t\t\tawbStats_[awbRegionPosition].bSum += currentCell->blueAvg;\n> +\t\t\t}\n> +\t\t}\n> +\t}\n> +}\n> +\n> +void IPU3Awb::clearAwbStats()\n> +{\n> +\tfor (unsigned int i = 0; i < kAwbStatsSizeX * kAwbStatsSizeY; i++) {\n> +\t\tawbStats_[i].bSum = 0;\n> +\t\tawbStats_[i].rSum = 0;\n> +\t\tawbStats_[i].gSum = 0;\n> +\t\tawbStats_[i].counted = 0;\n> +\t\tawbStats_[i].notcounted = 0;\n> +\t}\n> +}\n> +\n> +void IPU3Awb::awbGrey()\n\nawbGreyWorld?\n\n> +{\n> +\tLOG(IPU3Awb, Debug) << \"Grey world AWB\";\n> +\t/**\n\n/*\n\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\ns/  / /\n(double space to single before We)\n\n> +\t * consider some variations, such as normalising all the zones first, or\n> +\t * doing an L2 average etc.\n> +\t */\n> +\tstd::vector<RGB> &redDerivative(zones_);\n\nIs the & intentional here?\n\nI'm a little confused as to what that's doing. Creating a local reference?\n\n> +\tstd::vector<RGB> blueDerivative(redDerivative);\n\nIs this equivalent to blueDerivative(zones_) ? or is there a specific\nreason to initiliase it to the redDerivative?\n\nI don't expect there's much difference between either, but initialising\nboth from zones_ would look more obvious that they are initialised from\nthe same dataset.\n\n\n> +\tstd::sort(redDerivative.begin(), redDerivative.end(),\n> +\t\t  [](RGB const &a, RGB const &b) {\n> +\t\t\t  return a.G * b.R < b.G * a.R;\n> +\t\t  });\n> +\tstd::sort(blueDerivative.begin(), blueDerivative.end(),\n> +\t\t  [](RGB const &a, RGB const &b) {\n> +\t\t\t  return a.G * b.B < b.G * a.B;\n> +\t\t  });\n> +\n> +\t/* Average the middle half of the values. */\n> +\tint discard = redDerivative.size() / 4;\n> +\tRGB sumRed(0, 0, 0), sumBlue(0, 0, 0);\n\nHrm ... Perhaps those are better on two lines. ... Not sure, it doesn't\nhurt but I haven't seen us use single line multiple constructs much I\ndon't think.\n\n\n> +\tfor (auto ri = redDerivative.begin() + discard,\n> +\t\t  bi = blueDerivative.begin() + discard;\n> +\t     ri != redDerivative.end() - discard; ri++, bi++)\n> +\t\tsumRed += *ri, sumBlue += *bi;\n> +\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 Gray world but still useful to estimate it :-) */\n\nGray or Grey?\n\n\n> +\tasyncResults_.temperatureK = estimateCCT(sumRed.R, sumRed.G, sumBlue.B);\n> +\tasyncResults_.redGain = redGain;\n> +\tasyncResults_.greenGain = 1.0;\n> +\tasyncResults_.blueGain = blueGain;\n\nAre these results really calculated asynchronously already?\nOr is that a future improvement?\n\n\n> +}\n> +\n> +void IPU3Awb::calculateWBGains(const ipu3_uapi_stats_3a *stats)\n> +{\n> +\tASSERT(stats->stats_3a_status.awb_en);\n> +\tzones_.clear();\n> +\tclearAwbStats();\n> +\tgenerateAwbStats(stats);\n> +\tgenerateZones(zones_);\n> +\tLOG(IPU3Awb, Debug) << \"Valid zones: \" << zones_.size();\n> +\tif (zones_.size() > 10)\n> +\t\tawbGrey();\n> +\n> +\tLOG(IPU3Awb, Debug) << \"Gain found for red: \" << asyncResults_.redGain\n> +\t\t\t    << \" and for blue: \" << asyncResults_.blueGain;\n\nShould the debug print be in the context scope of the if (zones_... ) ?\n\nIt won't change otherwise will it?\n\n\n> +}\n> +\n> +void IPU3Awb::updateWbParameters(ipu3_uapi_params &params, double agcGamma)\n> +{\n> +\t/**\n\n/*\n\n> +\t * Green gains should not be touched and considered 1.\n> +\t * Default is 16, so do not change it at all.\n> +\t * 4096 is the value for a gain of 1.0\n> +\t */\n> +\tparams.acc_param.bnr.wb_gains.gr = 16;\n> +\tparams.acc_param.bnr.wb_gains.r = 4096 * asyncResults_.redGain;\n> +\tparams.acc_param.bnr.wb_gains.b = 4096 * asyncResults_.blueGain;\n> +\tparams.acc_param.bnr.wb_gains.gb = 16;\n> +\n> +\tLOG(IPU3Awb, Debug) << \"Color temperature estimated: \" << asyncResults_.temperatureK\n> +\t\t\t    << \" and gamma calculated: \" << agcGamma;\n> +\n> +\t/* The CCM matrix may change when color temperature will be used */\n> +\tparams.acc_param.ccm = imguCssCcmDefault;\n> +\n> +\tfor (uint32_t i = 0; i < 256; i++) {\n> +\t\tdouble j = i / 255.0;\n> +\t\tdouble gamma = std::pow(j, 1.0 / agcGamma);\n> +\t\t/* The maximum value 255 is represented on 13 bits in the IPU3 */\n> +\t\tparams.acc_param.gamma.gc_lut.lut[i] = gamma * 8191;\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..0994d18d\n> --- /dev/null\n> +++ b/src/ipa/ipu3/ipu3_awb.h\n> @@ -0,0 +1,91 @@\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 <vector>\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> +/* Region size for the statistics generation algorithm */\n> +static constexpr uint32_t kAwbStatsSizeX = 16;\n> +static constexpr uint32_t kAwbStatsSizeY = 12;\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> +\tstruct Ipu3AwbCell {\n> +\t\tunsigned char greenRedAvg;\n> +\t\tunsigned char redAvg;\n> +\t\tunsigned char blueAvg;\n> +\t\tunsigned char greenBlueAvg;\n> +\t\tunsigned char satRatio;\n> +\t\tunsigned char padding[3];\n> +\t} __attribute__((packed));\n> +\n> +\t/* \\todo Make these three structs available to all the ISPs ? */\n\nInteresting, we might indeed want some colour helpers.\nBut this can be later.\n\n> +\tstruct RGB {\n> +\t\tRGB(double _R = 0, double _G = 0, double _B = 0)\n> +\t\t\t: R(_R), G(_G), B(_B)\n> +\t\t{\n> +\t\t}\n> +\t\tdouble R, G, B;\n> +\t\tRGB &operator+=(RGB const &other)\n> +\t\t{\n> +\t\t\tR += other.R, G += other.G, B += other.B;\n> +\t\t\treturn *this;\n> +\t\t}\n> +\t};\n> +\n> +\tstruct IspStatsRegion {\n> +\t\tunsigned int counted;\n> +\t\tunsigned int notcounted;\n\nmentioned above, this might be 'uncounted'\n\nAll my comments here seem fairly trivial.\n\nwith those:\n\nReviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n\n\n> +\t\tunsigned long long rSum;\n> +\t\tunsigned long long gSum;\n> +\t\tunsigned long long bSum;\n> +\t};\n> +\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 generateZones(std::vector<RGB> &zones);\n> +\tvoid generateAwbStats(const ipu3_uapi_stats_3a *stats);\n> +\tvoid clearAwbStats();\n> +\tvoid awbGrey();\n> +\tuint32_t estimateCCT(double red, double green, double blue);\n> +\n> +\tstruct ipu3_uapi_grid_config awbGrid_;\n> +\n> +\tstd::vector<RGB> zones_;\n> +\tIspStatsRegion awbStats_[kAwbStatsSizeX * kAwbStatsSizeY];\n> +\tAwbStatus asyncResults_;\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 1ACA3BD814\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 19 Apr 2021 11:01:45 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 33D8768827;\n\tMon, 19 Apr 2021 13:01:44 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id E441768818\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 19 Apr 2021 13:01:42 +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 620E5D4A;\n\tMon, 19 Apr 2021 13:01:42 +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=\"PUPDr1Dp\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1618830102;\n\tbh=EFTNn7kOtqJTOmlNpsT8CdEmsAg5zvwfKPBd98mvm7U=;\n\th=Reply-To:Subject:To:References:From:Date:In-Reply-To:From;\n\tb=PUPDr1DpN5WhSWVf3AmesZkAnC2PQrGisB62OAJedp6DOODnpS1lMtRluGIb493/J\n\tzrPGIUsPQcSXZ/LSL1t94Rddqpm+vgyyF5NnHrf8TJuYBxhftVF5uXXLofB51lD6wC\n\tIyGrEogoLP392/xF+DBzLHMR34A92rDwzyIKyhD4=","To":"Jean-Michel Hautbois <jeanmichel.hautbois@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","References":"<20210416074909.24218-1-jeanmichel.hautbois@ideasonboard.com>\n\t<20210416074909.24218-4-jeanmichel.hautbois@ideasonboard.com>","From":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Organization":"Ideas on Board","Message-ID":"<b4aa1985-3015-b773-76e8-243a325e47ae@ideasonboard.com>","Date":"Mon, 19 Apr 2021 12:01:40 +0100","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":"<20210416074909.24218-4-jeanmichel.hautbois@ideasonboard.com>","Content-Language":"en-GB","Subject":"Re: [libcamera-devel] [PATCH v5 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":16358,"web_url":"https://patchwork.libcamera.org/comment/16358/","msgid":"<b07e76ee-a798-f184-6839-e3d308414cef@ideasonboard.com>","date":"2021-04-19T13:52:26","subject":"Re: [libcamera-devel] [PATCH v5 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 19/04/2021 13:01, Kieran Bingham wrote:\n> Hi JM,\n> \n> On 16/04/2021 08:49, 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>> The configure() call will set exposure and gain to their minimum value,\n>> so while AGC is not there, the frames will be dark.\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 | 351 ++++++++++++++++++++++++++++++++++++++\n>>  src/ipa/ipu3/ipu3_awb.h   |  91 ++++++++++\n>>  src/ipa/ipu3/meson.build  |   7 +-\n>>  4 files changed, 526 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..ab052a8a 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,58 @@ 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>> +\tSize best;\n>> +\tSize bestLog2;\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))\n>> +\t\t\t\t\t\t\t+ std::abs(static_cast<int>(height - bdsOutputSize.height));\n>> +\n>> +\t\t\tif (error > minError)\n>> +\t\t\t\tcontinue;\n>> +\n>> +\t\t\tminError = error;\n>> +\t\t\tbest.width = width;\n>> +\t\t\tbest.height = height;\n>> +\t\t\tbestLog2.width = widthShift;\n>> +\t\t\tbestLog2.height = heightShift;\n>> +\t\t}\n>> +\t}\n>> +\n>> +\tbdsGrid_.width = best.width >> bestLog2.width;\n>> +\tbdsGrid_.block_width_log2 = bestLog2.width;\n>> +\tbdsGrid_.height = best.height >> bestLog2.height;\n>> +\tbdsGrid_.block_height_log2 = bestLog2.height;\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> Somehow there's still an odd spacing around those << ;-)\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 +156,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 +239,10 @@ 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>> +\t/* Pass a default gamma of 1.0 (default linear correction) */\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..b4920e4b\n>> --- /dev/null\n>> +++ b/src/ipa/ipu3/ipu3_awb.cpp\n>> @@ -0,0 +1,351 @@\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>> +/**\n>> + * \\struct IspStatsRegion\n>> + * \\brief RGB statistics for a given region\n>> + *\n>> + * The IspStatsRegion structure is intended to abstract the ISP specific\n>> + * statistics and use an agnostic algorithm to compute AWB.\n>> + *\n>> + * \\var IspStatsRegion::counted\n>> + * \\brief Number of pixels used to calculate the sums\n>> + *\n>> + * \\var IspStatsRegion::notcounted\n> \n> should this be notCounted ? (or uncounted to be a single word ?)\n\nOK, I voted for uncounted ;-).\n\n>> + * \\brief Remaining number of pixels in the region\n>> + *\n>> + * \\var IspStatsRegion::rSum\n>> + * \\brief Sum of the red values in the region\n>> + *\n>> + * \\var IspStatsRegion::gSum\n>> + * \\brief Sum of the green values in the region\n>> + *\n>> + * \\var IspStatsRegion::bSum\n>> + * \\brief Sum of the blue values in the region\n>> + */\n>> +\n>> +/**\n>> + * \\struct 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>> +/**\n>> + * \\struct Ipu3AwbCell\n>> + * \\brief Memory layout for each cell in AWB metadata\n>> + *\n>> + * The Ipu3AwbCell structure is used to get individual values\n>> + * such as red average or saturation ratio in a particular cell.\n>> + *\n>> + * \\var Ipu3AwbCell::greenRedAvg\n>> + * \\brief Green average for red lines in the cell\n>> + *\n>> + * \\var Ipu3AwbCell::redAvg\n>> + * \\brief Red average in the cell\n>> + *\n>> + * \\var Ipu3AwbCell::blueAvg\n>> + * \\brief blue average in the cell\n>> + *\n>> + * \\var Ipu3AwbCell::greenBlueAvg\n>> + * \\brief Green average for blue lines\n>> + *\n>> + * \\var Ipu3AwbCell::satRatio\n>> + * \\brief Saturation ratio in the cell\n>> + *\n>> + * \\var Ipu3AwbCell::padding\n>> + * \\brief array of unused bytes for padding\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>> +\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>> +/* Default settings for Auto White Balance replicated from the Kernel*/\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>> +/* Default color correction matrix defined as an identity matrix */\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>> +\tasyncResults_.blueGain = 1.0;\n>> +\tasyncResults_.greenGain = 1.0;\n>> +\tasyncResults_.redGain = 1.0;\n>> +\tasyncResults_.temperatureK = 4500;\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.acc_bnr = 1;\n>> +\tparams.acc_param.bnr = imguCssBnrDefaults;\n>> +\t/**\n>> +\t * Optical center is colum (resp row) start - X (resp Y) center.\n> \n> s/colum/column/\n> \n> What is resp?\n\nrespectively ?\n\n> \n>> +\t * For the moment use BDS as a first approximation, but it should\n>> +\t * be calculated based on Shading (SHD) parameters.\n>> +\t */\n>> +\tparams.acc_param.bnr.column_size = bdsOutputSize.width;\n>> +\tparams.acc_param.bnr.opt_center.x_reset = awbGrid_.x_start - (bdsOutputSize.width / 2);\n>> +\tparams.acc_param.bnr.opt_center.y_reset = awbGrid_.y_start - (bdsOutputSize.height / 2);\n>> +\tparams.acc_param.bnr.opt_center_sqr.x_sqr_reset = params.acc_param.bnr.opt_center.x_reset\n>> +\t\t\t\t\t\t\t* 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\n>> +\t\t\t\t\t\t\t* 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>> +\tzones_.reserve(kAwbStatsSizeX * kAwbStatsSizeY);\n>> +}\n>> +\n>> +/**\n>> + * The function estimates the correlated color temperature using\n>> + * from RGB color space input.\n>> + * In physics and color science, the Planckian locus or black body locus is\n>> + * the path or locus that the color of an incandescent black body would take\n>> + * in a particular chromaticity space as the blackbody temperature changes.\n>> + *\n>> + * If a narrow range of color temperatures is considered (those encapsulating\n>> + * daylight being the most practical case) one can approximate the Planckian\n>> + * locus in order to calculate the CCT in terms of chromaticity coordinates.\n>> + *\n>> + * More detailed information can be found in:\n>> + * https://en.wikipedia.org/wiki/Color_temperature#Approximation\n>> + */\n>> +uint32_t IPU3Awb::estimateCCT(double red, double green, double blue)\n>> +{\n>> +\t/* Convert the RGB values to CIE tristimulus values (XYZ) */\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>> +\t/* Calculate the normalized chromaticity values */\n>> +\tdouble x = X / (X + Y + Z);\n>> +\tdouble y = Y / (X + Y + Z);\n>> +\n>> +\t/* Calculate CCT */\n>> +\tdouble n = (x - 0.3320) / (0.1858 - y);\n>> +\treturn 449 * n * n * n + 3525 * n * n + 6823.3 * n + 5520.33;\n>> +}\n>> +\n>> +/* Generate a RGB vector with the average values for each region */\n> \n> s/a/an/\n> \n> \n>> +void IPU3Awb::generateZones(std::vector<RGB> &zones)\n>> +{\n>> +\tfor (unsigned int i = 0; i < kAwbStatsSizeX * kAwbStatsSizeY; i++) {\n>> +\t\tRGB zone;\n>> +\t\tdouble counted = awbStats_[i].counted;\n>> +\t\tif (counted >= 16) {\n>> +\t\t\tzone.G = awbStats_[i].gSum / counted;\n>> +\t\t\tif (zone.G >= 32) {\n> \n> What is the selection criteria or meaning behind 16 and 32 here?\n> \n> Perhaps we should define them as documented constexpr's at the top of\n> the function?\n\nIndeed the values are taken from RPi code, but it should be calculated\nbased on the original grid size.\n\n> \n>> +\t\t\t\tzone.R = awbStats_[i].rSum / counted;\n>> +\t\t\t\tzone.B = awbStats_[i].bSum / counted;\n>> +\t\t\t\tzones.push_back(zone);\n>> +\t\t\t}\n>> +\t\t}\n>> +\t}\n>> +}\n>> +\n>> +/* Translate the IPU3 statistics into the default statistics region array */\n>> +void IPU3Awb::generateAwbStats(const ipu3_uapi_stats_3a *stats)\n>> +{\n>> +\tuint32_t regionWidth = round(awbGrid_.width / static_cast<double>(kAwbStatsSizeX));\n>> +\tuint32_t regionHeight = round(awbGrid_.height / static_cast<double>(kAwbStatsSizeY));\n>> +\n>> +\t/**\n> \n> only /* needed here.\n> \n> /** is only for doxygen comments\n> \n> \n>> +\t * Generate a (kAwbStatsSizeX x kAwbStatsSizeY) array from the IPU3 grid which is\n>> +\t * (awbGrid_.width x awbGrid_.height).\n>> +\t */\n>> +\tfor (unsigned int j = 0; j < kAwbStatsSizeY * regionHeight; j++) {\n>> +\t\tfor (unsigned int i = 0; i < kAwbStatsSizeX * regionWidth; i++) {\n>> +\t\t\tuint32_t cellPosition = j * awbGrid_.width + i;\n>> +\t\t\tuint32_t cellX = (cellPosition / regionWidth) % kAwbStatsSizeX;\n>> +\t\t\tuint32_t cellY = ((cellPosition / awbGrid_.width) / regionHeight) % kAwbStatsSizeY;\n>> +\n>> +\t\t\tuint32_t awbRegionPosition = cellY * kAwbStatsSizeX + cellX;\n>> +\t\t\tcellPosition *= 8;\n>> +\n>> +\t\t\t/* Cast the initial IPU3 structure to simplify the reading */\n>> +\t\t\tIpu3AwbCell *currentCell = reinterpret_cast<Ipu3AwbCell *>(const_cast<uint8_t *>(&stats->awb_raw_buffer.meta_data[cellPosition]));\n>> +\t\t\tif (currentCell->satRatio == 0) {\n>> +\t\t\t\t/* The cell is not saturated, use the current cell */\n>> +\t\t\t\tawbStats_[awbRegionPosition].counted++;\n>> +\t\t\t\tuint32_t greenValue = currentCell->greenRedAvg + currentCell->greenBlueAvg;\n>> +\t\t\t\tawbStats_[awbRegionPosition].gSum += greenValue / 2;\n>> +\t\t\t\tawbStats_[awbRegionPosition].rSum += currentCell->redAvg;\n>> +\t\t\t\tawbStats_[awbRegionPosition].bSum += currentCell->blueAvg;\n>> +\t\t\t}\n>> +\t\t}\n>> +\t}\n>> +}\n>> +\n>> +void IPU3Awb::clearAwbStats()\n>> +{\n>> +\tfor (unsigned int i = 0; i < kAwbStatsSizeX * kAwbStatsSizeY; i++) {\n>> +\t\tawbStats_[i].bSum = 0;\n>> +\t\tawbStats_[i].rSum = 0;\n>> +\t\tawbStats_[i].gSum = 0;\n>> +\t\tawbStats_[i].counted = 0;\n>> +\t\tawbStats_[i].notcounted = 0;\n>> +\t}\n>> +}\n>> +\n>> +void IPU3Awb::awbGrey()\n> \n> awbGreyWorld?\n> \n>> +{\n>> +\tLOG(IPU3Awb, Debug) << \"Grey world AWB\";\n>> +\t/**\n> \n> /*\n> \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> \n> s/  / /\n> (double space to single before We)\n> \n>> +\t * consider some variations, such as normalising all the zones first, or\n>> +\t * doing an L2 average etc.\n>> +\t */\n>> +\tstd::vector<RGB> &redDerivative(zones_);\n> \n> Is the & intentional here?\n> \n> I'm a little confused as to what that's doing. Creating a local reference?\n> \n>> +\tstd::vector<RGB> blueDerivative(redDerivative);\n> \n> Is this equivalent to blueDerivative(zones_) ? or is there a specific\n> reason to initiliase it to the redDerivative?\n> \n> I don't expect there's much difference between either, but initialising\n> both from zones_ would look more obvious that they are initialised from\n> the same dataset.\n\nAgain, this is from RPi and I think it is optimizing an allocation (one\nnew/delete less) per call at lease.\nYou have a reference for the red values, and blue is using the reference\ntoo.\nI don't think there is a difference in the implementation by doing:\nstd::vector<RGB> redDerivative(zones_);\nstd::vector<RGB> blueDerivative(zones_);\n\nBut it may be lighter.\n\n> \n>> +\tstd::sort(redDerivative.begin(), redDerivative.end(),\n>> +\t\t  [](RGB const &a, RGB const &b) {\n>> +\t\t\t  return a.G * b.R < b.G * a.R;\n>> +\t\t  });\n>> +\tstd::sort(blueDerivative.begin(), blueDerivative.end(),\n>> +\t\t  [](RGB const &a, RGB const &b) {\n>> +\t\t\t  return a.G * b.B < b.G * a.B;\n>> +\t\t  });\n>> +\n>> +\t/* Average the middle half of the values. */\n>> +\tint discard = redDerivative.size() / 4;\n>> +\tRGB sumRed(0, 0, 0), sumBlue(0, 0, 0);\n> \n> Hrm ... Perhaps those are better on two lines. ... Not sure, it doesn't\n> hurt but I haven't seen us use single line multiple constructs much I\n> don't think.\n> \n> \n>> +\tfor (auto ri = redDerivative.begin() + discard,\n>> +\t\t  bi = blueDerivative.begin() + discard;\n>> +\t     ri != redDerivative.end() - discard; ri++, bi++)\n>> +\t\tsumRed += *ri, sumBlue += *bi;\n>> +\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 Gray world but still useful to estimate it :-) */\n> \n> Gray or Grey?\n> \n> \n>> +\tasyncResults_.temperatureK = estimateCCT(sumRed.R, sumRed.G, sumBlue.B);\n>> +\tasyncResults_.redGain = redGain;\n>> +\tasyncResults_.greenGain = 1.0;\n>> +\tasyncResults_.blueGain = blueGain;\n> \n> Are these results really calculated asynchronously already?\n> Or is that a future improvement?\n\nGood catch, they are calculated in the same thread, so not really async\nno...\n\n> \n>> +}\n>> +\n>> +void IPU3Awb::calculateWBGains(const ipu3_uapi_stats_3a *stats)\n>> +{\n>> +\tASSERT(stats->stats_3a_status.awb_en);\n>> +\tzones_.clear();\n>> +\tclearAwbStats();\n>> +\tgenerateAwbStats(stats);\n>> +\tgenerateZones(zones_);\n>> +\tLOG(IPU3Awb, Debug) << \"Valid zones: \" << zones_.size();\n>> +\tif (zones_.size() > 10)\n>> +\t\tawbGrey();\n>> +\n>> +\tLOG(IPU3Awb, Debug) << \"Gain found for red: \" << asyncResults_.redGain\n>> +\t\t\t    << \" and for blue: \" << asyncResults_.blueGain;\n> \n> Should the debug print be in the context scope of the if (zones_... ) ?\n> \n> It won't change otherwise will it?\n> \n> \n>> +}\n>> +\n>> +void IPU3Awb::updateWbParameters(ipu3_uapi_params &params, double agcGamma)\n>> +{\n>> +\t/**\n> \n> /*\n> \n>> +\t * Green gains should not be touched and considered 1.\n>> +\t * Default is 16, so do not change it at all.\n>> +\t * 4096 is the value for a gain of 1.0\n>> +\t */\n>> +\tparams.acc_param.bnr.wb_gains.gr = 16;\n>> +\tparams.acc_param.bnr.wb_gains.r = 4096 * asyncResults_.redGain;\n>> +\tparams.acc_param.bnr.wb_gains.b = 4096 * asyncResults_.blueGain;\n>> +\tparams.acc_param.bnr.wb_gains.gb = 16;\n>> +\n>> +\tLOG(IPU3Awb, Debug) << \"Color temperature estimated: \" << asyncResults_.temperatureK\n>> +\t\t\t    << \" and gamma calculated: \" << agcGamma;\n>> +\n>> +\t/* The CCM matrix may change when color temperature will be used */\n>> +\tparams.acc_param.ccm = imguCssCcmDefault;\n>> +\n>> +\tfor (uint32_t i = 0; i < 256; i++) {\n>> +\t\tdouble j = i / 255.0;\n>> +\t\tdouble gamma = std::pow(j, 1.0 / agcGamma);\n>> +\t\t/* The maximum value 255 is represented on 13 bits in the IPU3 */\n>> +\t\tparams.acc_param.gamma.gc_lut.lut[i] = gamma * 8191;\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..0994d18d\n>> --- /dev/null\n>> +++ b/src/ipa/ipu3/ipu3_awb.h\n>> @@ -0,0 +1,91 @@\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 <vector>\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>> +/* Region size for the statistics generation algorithm */\n>> +static constexpr uint32_t kAwbStatsSizeX = 16;\n>> +static constexpr uint32_t kAwbStatsSizeY = 12;\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>> +\tstruct Ipu3AwbCell {\n>> +\t\tunsigned char greenRedAvg;\n>> +\t\tunsigned char redAvg;\n>> +\t\tunsigned char blueAvg;\n>> +\t\tunsigned char greenBlueAvg;\n>> +\t\tunsigned char satRatio;\n>> +\t\tunsigned char padding[3];\n>> +\t} __attribute__((packed));\n>> +\n>> +\t/* \\todo Make these three structs available to all the ISPs ? */\n> \n> Interesting, we might indeed want some colour helpers.\n> But this can be later.\n> \n>> +\tstruct RGB {\n>> +\t\tRGB(double _R = 0, double _G = 0, double _B = 0)\n>> +\t\t\t: R(_R), G(_G), B(_B)\n>> +\t\t{\n>> +\t\t}\n>> +\t\tdouble R, G, B;\n>> +\t\tRGB &operator+=(RGB const &other)\n>> +\t\t{\n>> +\t\t\tR += other.R, G += other.G, B += other.B;\n>> +\t\t\treturn *this;\n>> +\t\t}\n>> +\t};\n>> +\n>> +\tstruct IspStatsRegion {\n>> +\t\tunsigned int counted;\n>> +\t\tunsigned int notcounted;\n> \n> mentioned above, this might be 'uncounted'\n> \n> All my comments here seem fairly trivial.\n> \n> with those:\n> \n> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n\nThanks !\n\n> \n>> +\t\tunsigned long long rSum;\n>> +\t\tunsigned long long gSum;\n>> +\t\tunsigned long long bSum;\n>> +\t};\n>> +\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 generateZones(std::vector<RGB> &zones);\n>> +\tvoid generateAwbStats(const ipu3_uapi_stats_3a *stats);\n>> +\tvoid clearAwbStats();\n>> +\tvoid awbGrey();\n>> +\tuint32_t estimateCCT(double red, double green, double blue);\n>> +\n>> +\tstruct ipu3_uapi_grid_config awbGrid_;\n>> +\n>> +\tstd::vector<RGB> zones_;\n>> +\tIspStatsRegion awbStats_[kAwbStatsSizeX * kAwbStatsSizeY];\n>> +\tAwbStatus asyncResults_;\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 C25ABBD812\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 19 Apr 2021 13:52:29 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 24DFC6883C;\n\tMon, 19 Apr 2021 15:52:29 +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 9933868824\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 19 Apr 2021 15:52:27 +0200 (CEST)","from [IPv6:2a01:e0a:169:7140:a7d5:7f02:3d62:8bfb] (unknown\n\t[IPv6:2a01:e0a:169:7140:a7d5:7f02:3d62:8bfb])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 32DD9D4A;\n\tMon, 19 Apr 2021 15:52:27 +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=\"c5PViCLa\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1618840347;\n\tbh=X5gCwLRP/ATkaIpCJqcC8AbYJMvo0rcmMvmP/3nVB2s=;\n\th=Subject:To:References:From:Date:In-Reply-To:From;\n\tb=c5PViCLaJ4KWnTclLWOQC/udhjCBNa++EAulRNCnigOrqptto6hgXoxozR6zhTzjo\n\t7q4urJTY/Lmq4XTtgnn4IvGCq56L9JuieeJt22I0k3vRVKj+3lmVOu2FymZqi6yZ50\n\tU/zNeQalmpmPxwRk2g8HNXEy5TcRSW7RmVqa5Wd8=","To":"kieran.bingham@ideasonboard.com,\n\tJean-Michel Hautbois <jeanmichel.hautbois@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","References":"<20210416074909.24218-1-jeanmichel.hautbois@ideasonboard.com>\n\t<20210416074909.24218-4-jeanmichel.hautbois@ideasonboard.com>\n\t<b4aa1985-3015-b773-76e8-243a325e47ae@ideasonboard.com>","From":"Jean-Michel Hautbois <jeanmichel.hautbois@ideasonboard.com>","Message-ID":"<b07e76ee-a798-f184-6839-e3d308414cef@ideasonboard.com>","Date":"Mon, 19 Apr 2021 15:52:26 +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":"<b4aa1985-3015-b773-76e8-243a325e47ae@ideasonboard.com>","Content-Language":"en-US","Subject":"Re: [libcamera-devel] [PATCH v5 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>"}}]