[{"id":26721,"web_url":"https://patchwork.libcamera.org/comment/26721/","msgid":"<20230324083008.untsxdgb63klpere@uno.localdomain>","date":"2023-03-24T08:30:08","subject":"Re: [libcamera-devel] [PATCH v1 03/10] ipa: raspberrypi: Generalise\n\tthe ALSC algorithm","submitter":{"id":143,"url":"https://patchwork.libcamera.org/api/people/143/","name":"Jacopo Mondi","email":"jacopo.mondi@ideasonboard.com"},"content":"On Wed, Mar 22, 2023 at 01:06:05PM +0000, Naushir Patuck via libcamera-devel wrote:\n> Remove any hard-coded assumptions about the target hardware platform\n> from the ALSC algorithm. Instead, use the \"target\" string provided by\n> the camera tuning config and generalised statistics structures to\n> determing parameters such as grid and region sizes.\n>\n> The ALSC calculations use run-time allocated arrays/vectors on every\n> frame. Allocating these might add a non-trivial run-time penalty.\n> Replace these dynamic allocations with a set of reusable pre-allocated\n> vectors during the init phase.\n>\n> Signed-off-by: Naushir Patuck <naush@raspberrypi.com>\n> Signed-off-by: David Plowman <david.plowman@raspberrypi.com>\n> ---\n>  src/ipa/raspberrypi/controller/alsc_status.h |  13 +-\n>  src/ipa/raspberrypi/controller/rpi/alsc.cpp  | 341 +++++++++++--------\n>  src/ipa/raspberrypi/controller/rpi/alsc.h    |  29 +-\n>  src/ipa/raspberrypi/raspberrypi.cpp          |   9 +-\n>  4 files changed, 224 insertions(+), 168 deletions(-)\n>\n> diff --git a/src/ipa/raspberrypi/controller/alsc_status.h b/src/ipa/raspberrypi/controller/alsc_status.h\n> index e5aa7e37c330..49a9f4a0cb5a 100644\n> --- a/src/ipa/raspberrypi/controller/alsc_status.h\n> +++ b/src/ipa/raspberrypi/controller/alsc_status.h\n> @@ -6,16 +6,17 @@\n>   */\n>  #pragma once\n>\n> +#include <vector>\n> +\n>  /*\n>   * The ALSC algorithm should post the following structure into the image's\n>   * \"alsc.status\" metadata.\n>   */\n>\n> -constexpr unsigned int AlscCellsX = 16;\n> -constexpr unsigned int AlscCellsY = 12;\n> -\n>  struct AlscStatus {\n> -\tdouble r[AlscCellsY][AlscCellsX];\n> -\tdouble g[AlscCellsY][AlscCellsX];\n> -\tdouble b[AlscCellsY][AlscCellsX];\n> +\tstd::vector<double> r;\n> +\tstd::vector<double> g;\n> +\tstd::vector<double> b;\n> +\tunsigned int rows;\n> +\tunsigned int cols;\n>  };\n> diff --git a/src/ipa/raspberrypi/controller/rpi/alsc.cpp b/src/ipa/raspberrypi/controller/rpi/alsc.cpp\n> index eb4e2f9496e1..51fe5d73f52d 100644\n> --- a/src/ipa/raspberrypi/controller/rpi/alsc.cpp\n> +++ b/src/ipa/raspberrypi/controller/rpi/alsc.cpp\n> @@ -5,6 +5,7 @@\n>   * alsc.cpp - ALSC (auto lens shading correction) control algorithm\n>   */\n>\n> +#include <algorithm>\n>  #include <functional>\n>  #include <math.h>\n>  #include <numeric>\n> @@ -24,9 +25,6 @@ LOG_DEFINE_CATEGORY(RPiAlsc)\n>\n>  #define NAME \"rpi.alsc\"\n>\n> -static const int X = AlscCellsX;\n> -static const int Y = AlscCellsY;\n> -static const int XY = X * Y;\n>  static const double InsufficientData = -1.0;\n>\n>  Alsc::Alsc(Controller *controller)\n> @@ -51,8 +49,11 @@ char const *Alsc::name() const\n>  \treturn NAME;\n>  }\n>\n> -static int generateLut(double *lut, const libcamera::YamlObject &params)\n> +static int generateLut(std::vector<double> &lut, const libcamera::YamlObject &params,\n> +\t\t       const Size &size)\n>  {\n> +\t/* These must be signed ints for the co-ordinate calculations below. */\n> +\tint X = size.width, Y = size.height;\n>  \tdouble cstrength = params[\"corner_strength\"].get<double>(2.0);\n>  \tif (cstrength <= 1.0) {\n>  \t\tLOG(RPiAlsc, Error) << \"corner_strength must be > 1.0\";\n> @@ -81,9 +82,9 @@ static int generateLut(double *lut, const libcamera::YamlObject &params)\n>  \treturn 0;\n>  }\n>\n> -static int readLut(double *lut, const libcamera::YamlObject &params)\n> +static int readLut(std::vector<double> &lut, const libcamera::YamlObject &params, const Size &size)\n>  {\n> -\tif (params.size() != XY) {\n> +\tif (params.size() != size.width * size.height) {\n>  \t\tLOG(RPiAlsc, Error) << \"Invalid number of entries in LSC table\";\n>  \t\treturn -EINVAL;\n>  \t}\n> @@ -101,7 +102,7 @@ static int readLut(double *lut, const libcamera::YamlObject &params)\n>\n>  static int readCalibrations(std::vector<AlscCalibration> &calibrations,\n>  \t\t\t    const libcamera::YamlObject &params,\n> -\t\t\t    std::string const &name)\n> +\t\t\t    std::string const &name, const Size &size)\n>  {\n>  \tif (params.contains(name)) {\n>  \t\tdouble lastCt = 0;\n> @@ -119,7 +120,7 @@ static int readCalibrations(std::vector<AlscCalibration> &calibrations,\n>  \t\t\tcalibration.ct = lastCt = ct;\n>\n>  \t\t\tconst libcamera::YamlObject &table = p[\"table\"];\n> -\t\t\tif (table.size() != XY) {\n> +\t\t\tif (table.size() != size.width * size.height) {\n>  \t\t\t\tLOG(RPiAlsc, Error)\n>  \t\t\t\t\t<< \"Incorrect number of values for ct \"\n>  \t\t\t\t\t<< ct << \" in \" << name;\n> @@ -127,6 +128,7 @@ static int readCalibrations(std::vector<AlscCalibration> &calibrations,\n>  \t\t\t}\n>\n>  \t\t\tint num = 0;\n> +\t\t\tcalibration.table.resize(size.width * size.height);\n>  \t\t\tfor (const auto &elem : table.asList()) {\n>  \t\t\t\tvalue = elem.get<double>();\n>  \t\t\t\tif (!value)\n> @@ -134,7 +136,7 @@ static int readCalibrations(std::vector<AlscCalibration> &calibrations,\n>  \t\t\t\tcalibration.table[num++] = *value;\n>  \t\t\t}\n>\n> -\t\t\tcalibrations.push_back(calibration);\n> +\t\t\tcalibrations.push_back(std::move(calibration));\n>  \t\t\tLOG(RPiAlsc, Debug)\n>  \t\t\t\t<< \"Read \" << name << \" calibration for ct \" << ct;\n>  \t\t}\n> @@ -144,6 +146,7 @@ static int readCalibrations(std::vector<AlscCalibration> &calibrations,\n>\n>  int Alsc::read(const libcamera::YamlObject &params)\n>  {\n> +\tconfig_.tableSize = getHardwareConfig().awbRegions;\n>  \tconfig_.framePeriod = params[\"frame_period\"].get<uint16_t>(12);\n>  \tconfig_.startupFrames = params[\"startup_frames\"].get<uint16_t>(10);\n>  \tconfig_.speed = params[\"speed\"].get<double>(0.05);\n> @@ -153,28 +156,29 @@ int Alsc::read(const libcamera::YamlObject &params)\n>  \tconfig_.minCount = params[\"min_count\"].get<double>(10.0);\n>  \tconfig_.minG = params[\"min_G\"].get<uint16_t>(50);\n>  \tconfig_.omega = params[\"omega\"].get<double>(1.3);\n> -\tconfig_.nIter = params[\"n_iter\"].get<uint32_t>(X + Y);\n> +\tconfig_.nIter = params[\"n_iter\"].get<uint32_t>(config_.tableSize.width + config_.tableSize.height);\n>  \tconfig_.luminanceStrength =\n>  \t\tparams[\"luminance_strength\"].get<double>(1.0);\n> -\tfor (int i = 0; i < XY; i++)\n> -\t\tconfig_.luminanceLut[i] = 1.0;\n>\n> +\tconfig_.luminanceLut.resize(config_.tableSize.width * config_.tableSize.height, 1.0);\n>  \tint ret = 0;\n>\n>  \tif (params.contains(\"corner_strength\"))\n> -\t\tret = generateLut(config_.luminanceLut, params);\n> +\t\tret = generateLut(config_.luminanceLut, params, config_.tableSize);\n>  \telse if (params.contains(\"luminance_lut\"))\n> -\t\tret = readLut(config_.luminanceLut, params[\"luminance_lut\"]);\n> +\t\tret = readLut(config_.luminanceLut, params[\"luminance_lut\"], config_.tableSize);\n>  \telse\n>  \t\tLOG(RPiAlsc, Warning)\n>  \t\t\t<< \"no luminance table - assume unity everywhere\";\n>  \tif (ret)\n>  \t\treturn ret;\n>\n> -\tret = readCalibrations(config_.calibrationsCr, params, \"calibrations_Cr\");\n> +\tret = readCalibrations(config_.calibrationsCr, params, \"calibrations_Cr\",\n> +\t\t\t       config_.tableSize);\n>  \tif (ret)\n>  \t\treturn ret;\n> -\tret = readCalibrations(config_.calibrationsCb, params, \"calibrations_Cb\");\n> +\tret = readCalibrations(config_.calibrationsCb, params, \"calibrations_Cb\",\n> +\t\t\t       config_.tableSize);\n>  \tif (ret)\n>  \t\treturn ret;\n>\n> @@ -187,13 +191,16 @@ int Alsc::read(const libcamera::YamlObject &params)\n>\n>  static double getCt(Metadata *metadata, double defaultCt);\n>  static void getCalTable(double ct, std::vector<AlscCalibration> const &calibrations,\n> -\t\t\tdouble calTable[XY]);\n> -static void resampleCalTable(double const calTableIn[XY], CameraMode const &cameraMode,\n> -\t\t\t     double calTableOut[XY]);\n> -static void compensateLambdasForCal(double const calTable[XY], double const oldLambdas[XY],\n> -\t\t\t\t    double newLambdas[XY]);\n> -static void addLuminanceToTables(double results[3][Y][X], double const lambdaR[XY], double lambdaG,\n> -\t\t\t\t double const lambdaB[XY], double const luminanceLut[XY],\n> +\t\t\tstd::vector<double> &calTable);\n> +static void resampleCalTable(const std::vector<double> &calTableIn, CameraMode const &cameraMode,\n> +\t\t\t     const Size &size, std::vector<double> &calTableOut);\n> +static void compensateLambdasForCal(const std::vector<double> &calTable,\n> +\t\t\t\t    const std::vector<double> &oldLambdas,\n> +\t\t\t\t    std::vector<double> &newLambdas);\n> +static void addLuminanceToTables(std::array<std::vector<double>, 3> &results,\n> +\t\t\t\t const std::vector<double> &lambdaR, double lambdaG,\n> +\t\t\t\t const std::vector<double> &lambdaB,\n> +\t\t\t\t const std::vector<double> &luminanceLut,\n>  \t\t\t\t double luminanceStrength);\n>\n>  void Alsc::initialise()\n> @@ -201,7 +208,28 @@ void Alsc::initialise()\n>  \tframeCount2_ = frameCount_ = framePhase_ = 0;\n>  \tfirstTime_ = true;\n>  \tct_ = config_.defaultCt;\n> +\n> +\tconst size_t XY = config_.tableSize.width * config_.tableSize.height;\n> +\n> +\tfor (auto &r : syncResults_)\n> +\t\tr.resize(XY);\n> +\tfor (auto &r : prevSyncResults_)\n> +\t\tr.resize(XY);\n> +\tfor (auto &r : asyncResults_)\n> +\t\tr.resize(XY);\n> +\n> +\tluminanceTable_.resize(XY);\n> +\tasyncLambdaR_.resize(XY);\n> +\tasyncLambdaB_.resize(XY);\n>  \t/* The lambdas are initialised in the SwitchMode. */\n> +\tlambdaR_.resize(XY);\n> +\tlambdaB_.resize(XY);\n> +\n> +\t/* Temporaries for the computations, but sensible to allocate this up-front! */\n> +\tfor (auto &c : tmpC_)\n> +\t\tc.resize(XY);\n> +\tfor (auto &m : tmpM_)\n> +\t\tm.resize(XY);\n>  }\n>\n>  void Alsc::waitForAysncThread()\n> @@ -262,7 +290,7 @@ void Alsc::switchMode(CameraMode const &cameraMode,\n>  \t * We must resample the luminance table like we do the others, but it's\n>  \t * fixed so we can simply do it up front here.\n>  \t */\n> -\tresampleCalTable(config_.luminanceLut, cameraMode_, luminanceTable_);\n> +\tresampleCalTable(config_.luminanceLut, cameraMode_, config_.tableSize, luminanceTable_);\n>\n>  \tif (resetTables) {\n>  \t\t/*\n> @@ -272,18 +300,18 @@ void Alsc::switchMode(CameraMode const &cameraMode,\n>  \t\t * the lambdas, but the rest of this code then echoes the code in\n>  \t\t * doAlsc, without the adaptive algorithm.\n>  \t\t */\n> -\t\tfor (int i = 0; i < XY; i++)\n> -\t\t\tlambdaR_[i] = lambdaB_[i] = 1.0;\n> -\t\tdouble calTableR[XY], calTableB[XY], calTableTmp[XY];\n> +\t\tstd::fill(lambdaR_.begin(), lambdaR_.end(), 1.0);\n> +\t\tstd::fill(lambdaB_.begin(), lambdaB_.end(), 1.0);\n> +\t\tstd::vector<double> &calTableR = tmpC_[0], &calTableB = tmpC_[1], &calTableTmp = tmpC_[2];\n>  \t\tgetCalTable(ct_, config_.calibrationsCr, calTableTmp);\n> -\t\tresampleCalTable(calTableTmp, cameraMode_, calTableR);\n> +\t\tresampleCalTable(calTableTmp, cameraMode_, config_.tableSize, calTableR);\n>  \t\tgetCalTable(ct_, config_.calibrationsCb, calTableTmp);\n> -\t\tresampleCalTable(calTableTmp, cameraMode_, calTableB);\n> +\t\tresampleCalTable(calTableTmp, cameraMode_, config_.tableSize, calTableB);\n>  \t\tcompensateLambdasForCal(calTableR, lambdaR_, asyncLambdaR_);\n>  \t\tcompensateLambdasForCal(calTableB, lambdaB_, asyncLambdaB_);\n>  \t\taddLuminanceToTables(syncResults_, asyncLambdaR_, 1.0, asyncLambdaB_,\n>  \t\t\t\t     luminanceTable_, config_.luminanceStrength);\n> -\t\tmemcpy(prevSyncResults_, syncResults_, sizeof(prevSyncResults_));\n> +\t\tprevSyncResults_ = syncResults_;\n>  \t\tframePhase_ = config_.framePeriod; /* run the algo again asap */\n>  \t\tfirstTime_ = false;\n>  \t}\n> @@ -294,7 +322,7 @@ void Alsc::fetchAsyncResults()\n>  \tLOG(RPiAlsc, Debug) << \"Fetch ALSC results\";\n>  \tasyncFinished_ = false;\n>  \tasyncStarted_ = false;\n> -\tmemcpy(syncResults_, asyncResults_, sizeof(syncResults_));\n> +\tsyncResults_ = asyncResults_;\n>  }\n>\n>  double getCt(Metadata *metadata, double defaultCt)\n> @@ -316,9 +344,9 @@ static void copyStats(RgbyRegions &regions, StatisticsPtr &stats,\n>  \tif (!regions.numRegions())\n>  \t\tregions.init(stats->awbRegions.size());\n>\n> -\tdouble *rTable = (double *)status.r;\n> -\tdouble *gTable = (double *)status.g;\n> -\tdouble *bTable = (double *)status.b;\n> +\tconst std::vector<double> &rTable = status.r;\n> +\tconst std::vector<double> &gTable = status.g;\n> +\tconst std::vector<double> &bTable = status.b;\n>  \tfor (unsigned int i = 0; i < stats->awbRegions.numRegions(); i++) {\n>  \t\tauto r = stats->awbRegions.get(i);\n>  \t\tr.val.rSum = static_cast<uint64_t>(r.val.rSum / rTable[i]);\n> @@ -344,12 +372,9 @@ void Alsc::restartAsync(StatisticsPtr &stats, Metadata *imageMetadata)\n>  \tif (imageMetadata->get(\"alsc.status\", alscStatus) != 0) {\n>  \t\tLOG(RPiAlsc, Warning)\n>  \t\t\t<< \"No ALSC status found for applied gains!\";\n> -\t\tfor (int y = 0; y < Y; y++)\n> -\t\t\tfor (int x = 0; x < X; x++) {\n> -\t\t\t\talscStatus.r[y][x] = 1.0;\n> -\t\t\t\talscStatus.g[y][x] = 1.0;\n> -\t\t\t\talscStatus.b[y][x] = 1.0;\n> -\t\t\t}\n> +\t\talscStatus.r.resize(config_.tableSize.width * config_.tableSize.height, 1.0);\n> +\t\talscStatus.g.resize(config_.tableSize.width * config_.tableSize.height, 1.0);\n> +\t\talscStatus.b.resize(config_.tableSize.width * config_.tableSize.height, 1.0);\n>  \t}\n>  \tcopyStats(statistics_, stats, alscStatus);\n>  \tframePhase_ = 0;\n> @@ -380,15 +405,15 @@ void Alsc::prepare(Metadata *imageMetadata)\n>  \t\t\tfetchAsyncResults();\n>  \t}\n>  \t/* Apply IIR filter to results and program into the pipeline. */\n> -\tdouble *ptr = (double *)syncResults_,\n> -\t       *pptr = (double *)prevSyncResults_;\n> -\tfor (unsigned int i = 0; i < sizeof(syncResults_) / sizeof(double); i++)\n> -\t\tpptr[i] = speed * ptr[i] + (1.0 - speed) * pptr[i];\n> +\tfor (unsigned int j = 0; j < syncResults_.size(); j++) {\n> +\t\tfor (unsigned int i = 0; i < syncResults_[j].size(); i++)\n> +\t\t\tprevSyncResults_[j][i] = speed * syncResults_[j][i] + (1.0 - speed) * prevSyncResults_[j][i];\n> +\t}\n>  \t/* Put output values into status metadata. */\n>  \tAlscStatus status;\n> -\tmemcpy(status.r, prevSyncResults_[0], sizeof(status.r));\n> -\tmemcpy(status.g, prevSyncResults_[1], sizeof(status.g));\n> -\tmemcpy(status.b, prevSyncResults_[2], sizeof(status.b));\n> +\tstatus.r = prevSyncResults_[0];\n> +\tstatus.g = prevSyncResults_[1];\n> +\tstatus.b = prevSyncResults_[2];\n>  \timageMetadata->set(\"alsc.status\", status);\n>  }\n>\n> @@ -432,18 +457,17 @@ void Alsc::asyncFunc()\n>  }\n>\n>  void getCalTable(double ct, std::vector<AlscCalibration> const &calibrations,\n> -\t\t double calTable[XY])\n> +\t\t std::vector<double> &calTable)\n>  {\n>  \tif (calibrations.empty()) {\n> -\t\tfor (int i = 0; i < XY; i++)\n> -\t\t\tcalTable[i] = 1.0;\n> +\t\tstd::fill(calTable.begin(), calTable.end(), 1.0);\n>  \t\tLOG(RPiAlsc, Debug) << \"no calibrations found\";\n>  \t} else if (ct <= calibrations.front().ct) {\n> -\t\tmemcpy(calTable, calibrations.front().table, XY * sizeof(double));\n> +\t\tcalTable = calibrations.front().table;\n>  \t\tLOG(RPiAlsc, Debug) << \"using calibration for \"\n>  \t\t\t\t    << calibrations.front().ct;\n>  \t} else if (ct >= calibrations.back().ct) {\n> -\t\tmemcpy(calTable, calibrations.back().table, XY * sizeof(double));\n> +\t\tcalTable = calibrations.back().table;\n>  \t\tLOG(RPiAlsc, Debug) << \"using calibration for \"\n>  \t\t\t\t    << calibrations.back().ct;\n>  \t} else {\n> @@ -454,7 +478,7 @@ void getCalTable(double ct, std::vector<AlscCalibration> const &calibrations,\n>  \t\tLOG(RPiAlsc, Debug)\n>  \t\t\t<< \"ct is \" << ct << \", interpolating between \"\n>  \t\t\t<< ct0 << \" and \" << ct1;\n> -\t\tfor (int i = 0; i < XY; i++)\n> +\t\tfor (unsigned int i = 0; i < calTable.size(); i++)\n>  \t\t\tcalTable[i] =\n>  \t\t\t\t(calibrations[idx].table[i] * (ct1 - ct) +\n>  \t\t\t\t calibrations[idx + 1].table[i] * (ct - ct0)) /\n> @@ -462,9 +486,13 @@ void getCalTable(double ct, std::vector<AlscCalibration> const &calibrations,\n>  \t}\n>  }\n>\n> -void resampleCalTable(double const calTableIn[XY],\n> -\t\t      CameraMode const &cameraMode, double calTableOut[XY])\n> +void resampleCalTable(const std::vector<double> &calTableIn,\n> +\t\t      CameraMode const &cameraMode, const Size &size,\n> +\t\t      std::vector<double> &calTableOut)\n>  {\n> +\tint X = size.width;\n> +\tint Y = size.height;\n> +\n>  \t/*\n>  \t * Precalculate and cache the x sampling locations and phases to save\n>  \t * recomputing them on every row.\n> @@ -501,23 +529,24 @@ void resampleCalTable(double const calTableIn[XY],\n>  \t\t\tyLo = Y - 1 - yLo;\n>  \t\t\tyHi = Y - 1 - yHi;\n>  \t\t}\n> -\t\tdouble const *rowAbove = calTableIn + X * yLo;\n> -\t\tdouble const *rowBelow = calTableIn + X * yHi;\n> +\t\tdouble const *rowAbove = calTableIn.data() + X * yLo;\n> +\t\tdouble const *rowBelow = calTableIn.data() + X * yHi;\n> +\t\tdouble *out = calTableOut.data() + X * j;\n>  \t\tfor (int i = 0; i < X; i++) {\n>  \t\t\tdouble above = rowAbove[xLo[i]] * (1 - xf[i]) +\n>  \t\t\t\t       rowAbove[xHi[i]] * xf[i];\n>  \t\t\tdouble below = rowBelow[xLo[i]] * (1 - xf[i]) +\n>  \t\t\t\t       rowBelow[xHi[i]] * xf[i];\n> -\t\t\t*(calTableOut++) = above * (1 - yf) + below * yf;\n> +\t\t\t*(out++) = above * (1 - yf) + below * yf;\n>  \t\t}\n>  \t}\n>  }\n>\n>  /* Calculate chrominance statistics (R/G and B/G) for each region. */\n> -static void calculateCrCb(const RgbyRegions &awbRegion, double cr[XY],\n> -\t\t\t  double cb[XY], uint32_t minCount, uint16_t minG)\n> +static void calculateCrCb(const RgbyRegions &awbRegion, std::vector<double> &cr,\n> +\t\t\t  std::vector<double> &cb, uint32_t minCount, uint16_t minG)\n>  {\n> -\tfor (int i = 0; i < XY; i++) {\n> +\tfor (unsigned int i = 0; i < cr.size(); i++) {\n>  \t\tauto s = awbRegion.get(i);\n>\n>  \t\tif (s.counted <= minCount || s.val.gSum / s.counted <= minG) {\n> @@ -530,33 +559,34 @@ static void calculateCrCb(const RgbyRegions &awbRegion, double cr[XY],\n>  \t}\n>  }\n>\n> -static void applyCalTable(double const calTable[XY], double C[XY])\n> +static void applyCalTable(const std::vector<double> &calTable, std::vector<double> &C)\n>  {\n> -\tfor (int i = 0; i < XY; i++)\n> +\tfor (unsigned int i = 0; i < C.size(); i++)\n>  \t\tif (C[i] != InsufficientData)\n>  \t\t\tC[i] *= calTable[i];\n>  }\n>\n> -void compensateLambdasForCal(double const calTable[XY],\n> -\t\t\t     double const oldLambdas[XY],\n> -\t\t\t     double newLambdas[XY])\n> +void compensateLambdasForCal(const std::vector<double> &calTable,\n> +\t\t\t     const std::vector<double> &oldLambdas,\n> +\t\t\t     std::vector<double> &newLambdas)\n>  {\n>  \tdouble minNewLambda = std::numeric_limits<double>::max();\n> -\tfor (int i = 0; i < XY; i++) {\n> +\tfor (unsigned int i = 0; i < newLambdas.size(); i++) {\n>  \t\tnewLambdas[i] = oldLambdas[i] * calTable[i];\n>  \t\tminNewLambda = std::min(minNewLambda, newLambdas[i]);\n>  \t}\n> -\tfor (int i = 0; i < XY; i++)\n> +\tfor (unsigned int i = 0; i < newLambdas.size(); i++)\n>  \t\tnewLambdas[i] /= minNewLambda;\n>  }\n>\n> -[[maybe_unused]] static void printCalTable(double const C[XY])\n> +[[maybe_unused]] static void printCalTable(const std::vector<double> &C,\n> +\t\t\t\t\t   const Size &size)\n>  {\n>  \tprintf(\"table: [\\n\");\n> -\tfor (int j = 0; j < Y; j++) {\n> -\t\tfor (int i = 0; i < X; i++) {\n> -\t\t\tprintf(\"%5.3f\", 1.0 / C[j * X + i]);\n> -\t\t\tif (i != X - 1 || j != Y - 1)\n> +\tfor (unsigned int j = 0; j < size.height; j++) {\n> +\t\tfor (unsigned int i = 0; i < size.width; i++) {\n> +\t\t\tprintf(\"%5.3f\", 1.0 / C[j * size.width + i]);\n> +\t\t\tif (i != size.width - 1 || j != size.height - 1)\n>  \t\t\t\tprintf(\",\");\n>  \t\t}\n>  \t\tprintf(\"\\n\");\n> @@ -577,9 +607,13 @@ static double computeWeight(double Ci, double Cj, double sigma)\n>  }\n>\n>  /* Compute all weights. */\n> -static void computeW(double const C[XY], double sigma, double W[XY][4])\n> +static void computeW(const std::vector<double> &C, double sigma,\n> +\t\t     std::vector<std::array<double, 4>> &W, const Size &size)\n>  {\n> -\tfor (int i = 0; i < XY; i++) {\n> +\tsize_t XY = size.width * size.height;\n> +\tsize_t X = size.width;\n> +\n> +\tfor (unsigned int i = 0; i < XY; i++) {\n>  \t\t/* Start with neighbour above and go clockwise. */\n>  \t\tW[i][0] = i >= X ? computeWeight(C[i], C[i - X], sigma) : 0;\n>  \t\tW[i][1] = i % X < X - 1 ? computeWeight(C[i], C[i + 1], sigma) : 0;\n> @@ -589,11 +623,16 @@ static void computeW(double const C[XY], double sigma, double W[XY][4])\n>  }\n>\n>  /* Compute M, the large but sparse matrix such that M * lambdas = 0. */\n> -static void constructM(double const C[XY], double const W[XY][4],\n> -\t\t       double M[XY][4])\n> +static void constructM(const std::vector<double> &C,\n> +\t\t       const std::vector<std::array<double, 4>> &W,\n> +\t\t       std::vector<std::array<double, 4>> &M,\n> +\t\t       const Size &size)\n>  {\n> +\tsize_t XY = size.width * size.height;\n> +\tsize_t X = size.width;\n> +\n>  \tdouble epsilon = 0.001;\n> -\tfor (int i = 0; i < XY; i++) {\n> +\tfor (unsigned int i = 0; i < XY; i++) {\n>  \t\t/*\n>  \t\t * Note how, if C[i] == INSUFFICIENT_DATA, the weights will all\n>  \t\t * be zero so the equation is still set up correctly.\n> @@ -614,79 +653,80 @@ static void constructM(double const C[XY], double const W[XY][4],\n>   * left/right neighbours are zero down the left/right edges, so we don't need\n>   * need to test the i value to exclude them.\n>   */\n> -static double computeLambdaBottom(int i, double const M[XY][4],\n> -\t\t\t\t  double lambda[XY])\n> +static double computeLambdaBottom(int i, const std::vector<std::array<double, 4>> &M,\n> +\t\t\t\t  std::vector<double> &lambda, const Size &size)\n>  {\n> -\treturn M[i][1] * lambda[i + 1] + M[i][2] * lambda[i + X] +\n> +\treturn M[i][1] * lambda[i + 1] + M[i][2] * lambda[i + size.width] +\n>  \t       M[i][3] * lambda[i - 1];\n>  }\n> -static double computeLambdaBottomStart(int i, double const M[XY][4],\n> -\t\t\t\t       double lambda[XY])\n> +static double computeLambdaBottomStart(int i, const std::vector<std::array<double, 4>> &M,\n> +\t\t\t\t       std::vector<double> &lambda, const Size &size)\n>  {\n> -\treturn M[i][1] * lambda[i + 1] + M[i][2] * lambda[i + X];\n> +\treturn M[i][1] * lambda[i + 1] + M[i][2] * lambda[i + size.width];\n>  }\n> -static double computeLambdaInterior(int i, double const M[XY][4],\n> -\t\t\t\t    double lambda[XY])\n> +static double computeLambdaInterior(int i, const std::vector<std::array<double, 4>> &M,\n> +\t\t\t\t    std::vector<double> &lambda, const Size &size)\n>  {\n> -\treturn M[i][0] * lambda[i - X] + M[i][1] * lambda[i + 1] +\n> -\t       M[i][2] * lambda[i + X] + M[i][3] * lambda[i - 1];\n> +\treturn M[i][0] * lambda[i - size.width] + M[i][1] * lambda[i + 1] +\n> +\t       M[i][2] * lambda[i + size.width] + M[i][3] * lambda[i - 1];\n>  }\n> -static double computeLambdaTop(int i, double const M[XY][4],\n> -\t\t\t       double lambda[XY])\n> +static double computeLambdaTop(int i, const std::vector<std::array<double, 4>> &M,\n> +\t\t\t       std::vector<double> &lambda, const Size &size)\n>  {\n> -\treturn M[i][0] * lambda[i - X] + M[i][1] * lambda[i + 1] +\n> +\treturn M[i][0] * lambda[i - size.width] + M[i][1] * lambda[i + 1] +\n>  \t       M[i][3] * lambda[i - 1];\n>  }\n> -static double computeLambdaTopEnd(int i, double const M[XY][4],\n> -\t\t\t\t  double lambda[XY])\n> +static double computeLambdaTopEnd(int i, const std::vector<std::array<double, 4>> &M,\n> +\t\t\t\t  std::vector<double> &lambda, const Size &size)\n>  {\n> -\treturn M[i][0] * lambda[i - X] + M[i][3] * lambda[i - 1];\n> +\treturn M[i][0] * lambda[i - size.width] + M[i][3] * lambda[i - 1];\n>  }\n>\n>  /* Gauss-Seidel iteration with over-relaxation. */\n> -static double gaussSeidel2Sor(double const M[XY][4], double omega,\n> -\t\t\t      double lambda[XY], double lambdaBound)\n> +static double gaussSeidel2Sor(const std::vector<std::array<double, 4>> &M, double omega,\n> +\t\t\t      std::vector<double> &lambda, double lambdaBound,\n> +\t\t\t      const Size &size)\n>  {\n> +\tint XY = size.width * size.height;\n> +\tint X = size.width;\n>  \tconst double min = 1 - lambdaBound, max = 1 + lambdaBound;\n> -\tdouble oldLambda[XY];\n> +\tstd::vector<double> oldLambda = lambda;\n>  \tint i;\n> -\tfor (i = 0; i < XY; i++)\n> -\t\toldLambda[i] = lambda[i];\n> -\tlambda[0] = computeLambdaBottomStart(0, M, lambda);\n> +\tlambda[0] = computeLambdaBottomStart(0, M, lambda, size);\n>  \tlambda[0] = std::clamp(lambda[0], min, max);\n>  \tfor (i = 1; i < X; i++) {\n> -\t\tlambda[i] = computeLambdaBottom(i, M, lambda);\n> +\t\tlambda[i] = computeLambdaBottom(i, M, lambda, size);\n>  \t\tlambda[i] = std::clamp(lambda[i], min, max);\n>  \t}\n>  \tfor (; i < XY - X; i++) {\n> -\t\tlambda[i] = computeLambdaInterior(i, M, lambda);\n> +\t\tlambda[i] = computeLambdaInterior(i, M, lambda, size);\n>  \t\tlambda[i] = std::clamp(lambda[i], min, max);\n>  \t}\n>  \tfor (; i < XY - 1; i++) {\n> -\t\tlambda[i] = computeLambdaTop(i, M, lambda);\n> +\t\tlambda[i] = computeLambdaTop(i, M, lambda, size);\n>  \t\tlambda[i] = std::clamp(lambda[i], min, max);\n>  \t}\n> -\tlambda[i] = computeLambdaTopEnd(i, M, lambda);\n> +\tlambda[i] = computeLambdaTopEnd(i, M, lambda, size);\n>  \tlambda[i] = std::clamp(lambda[i], min, max);\n>  \t/*\n>  \t * Also solve the system from bottom to top, to help spread the updates\n>  \t * better.\n>  \t */\n> -\tlambda[i] = computeLambdaTopEnd(i, M, lambda);\n> +\tlambda[i] = computeLambdaTopEnd(i, M, lambda, size);\n>  \tlambda[i] = std::clamp(lambda[i], min, max);\n>  \tfor (i = XY - 2; i >= XY - X; i--) {\n> -\t\tlambda[i] = computeLambdaTop(i, M, lambda);\n> +\t\tlambda[i] = computeLambdaTop(i, M, lambda, size);\n>  \t\tlambda[i] = std::clamp(lambda[i], min, max);\n>  \t}\n>  \tfor (; i >= X; i--) {\n> -\t\tlambda[i] = computeLambdaInterior(i, M, lambda);\n> +\t\tlambda[i] = computeLambdaInterior(i, M, lambda, size);\n>  \t\tlambda[i] = std::clamp(lambda[i], min, max);\n>  \t}\n>  \tfor (; i >= 1; i--) {\n> -\t\tlambda[i] = computeLambdaBottom(i, M, lambda);\n> +\t\tlambda[i] = computeLambdaBottom(i, M, lambda, size);\n>  \t\tlambda[i] = std::clamp(lambda[i], min, max);\n>  \t}\n> -\tlambda[0] = computeLambdaBottomStart(0, M, lambda);\n> +\tlambda[0] = computeLambdaBottomStart(0, M, lambda, size);\n>  \tlambda[0] = std::clamp(lambda[0], min, max);\n>  \tdouble maxDiff = 0;\n>  \tfor (i = 0; i < XY; i++) {\n> @@ -698,33 +738,33 @@ static double gaussSeidel2Sor(double const M[XY][4], double omega,\n>  }\n>\n>  /* Normalise the values so that the smallest value is 1. */\n> -static void normalise(double *ptr, size_t n)\n> +static void normalise(std::vector<double> &results)\n>  {\n> -\tdouble minval = ptr[0];\n> -\tfor (size_t i = 1; i < n; i++)\n> -\t\tminval = std::min(minval, ptr[i]);\n> -\tfor (size_t i = 0; i < n; i++)\n> -\t\tptr[i] /= minval;\n> +\tdouble minval = *std::min_element(results.begin(), results.end());\n> +\tstd::for_each(results.begin(), results.end(),\n> +\t\t      [minval](double val) { return val / minval; });\n>  }\n>\n>  /* Rescale the values so that the average value is 1. */\n> -static void reaverage(Span<double> data)\n> +static void reaverage(std::vector<double> &data)\n>  {\n>  \tdouble sum = std::accumulate(data.begin(), data.end(), 0.0);\n>  \tdouble ratio = 1 / (sum / data.size());\n> -\tfor (double &d : data)\n> -\t\td *= ratio;\n> +\tstd::for_each(data.begin(), data.end(),\n> +\t\t      [ratio](double val) { return val * ratio; });\n>  }\n>\n> -static void runMatrixIterations(double const C[XY], double lambda[XY],\n> -\t\t\t\tdouble const W[XY][4], double omega,\n> -\t\t\t\tint nIter, double threshold, double lambdaBound)\n> +static void runMatrixIterations(const std::vector<double> &C,\n> +\t\t\t\tstd::vector<double> &lambda,\n> +\t\t\t\tconst std::vector<std::array<double, 4>> &W,\n> +\t\t\t\tstd::vector<std::array<double, 4>> &M, double omega,\n> +\t\t\t\tunsigned int nIter, double threshold, double lambdaBound,\n> +\t\t\t\tconst Size &size)\n>  {\n> -\tdouble M[XY][4];\n> -\tconstructM(C, W, M);\n> +\tconstructM(C, W, M, size);\n>  \tdouble lastMaxDiff = std::numeric_limits<double>::max();\n> -\tfor (int i = 0; i < nIter; i++) {\n> -\t\tdouble maxDiff = fabs(gaussSeidel2Sor(M, omega, lambda, lambdaBound));\n> +\tfor (unsigned int i = 0; i < nIter; i++) {\n> +\t\tdouble maxDiff = fabs(gaussSeidel2Sor(M, omega, lambda, lambdaBound, size));\n>  \t\tif (maxDiff < threshold) {\n>  \t\t\tLOG(RPiAlsc, Debug)\n>  \t\t\t\t<< \"Stop after \" << i + 1 << \" iterations\";\n> @@ -741,39 +781,44 @@ static void runMatrixIterations(double const C[XY], double lambda[XY],\n>  \t\tlastMaxDiff = maxDiff;\n>  \t}\n>  \t/* We're going to normalise the lambdas so the total average is 1. */\n> -\treaverage({ lambda, XY });\n> +\treaverage(lambda);\n>  }\n>\n> -static void addLuminanceRb(double result[XY], double const lambda[XY],\n> -\t\t\t   double const luminanceLut[XY],\n> +static void addLuminanceRb(std::vector<double> &result, const std::vector<double> &lambda,\n> +\t\t\t   const std::vector<double> &luminanceLut,\n>  \t\t\t   double luminanceStrength)\n>  {\n> -\tfor (int i = 0; i < XY; i++)\n> +\tfor (unsigned int i = 0; i < result.size(); i++)\n>  \t\tresult[i] = lambda[i] * ((luminanceLut[i] - 1) * luminanceStrength + 1);\n>  }\n>\n> -static void addLuminanceG(double result[XY], double lambda,\n> -\t\t\t  double const luminanceLut[XY],\n> +static void addLuminanceG(std::vector<double> &result, double lambda,\n> +\t\t\t  const std::vector<double> &luminanceLut,\n>  \t\t\t  double luminanceStrength)\n>  {\n> -\tfor (int i = 0; i < XY; i++)\n> +\tfor (unsigned int i = 0; i < result.size(); i++)\n>  \t\tresult[i] = lambda * ((luminanceLut[i] - 1) * luminanceStrength + 1);\n>  }\n>\n> -void addLuminanceToTables(double results[3][Y][X], double const lambdaR[XY],\n> -\t\t\t  double lambdaG, double const lambdaB[XY],\n> -\t\t\t  double const luminanceLut[XY],\n> +void addLuminanceToTables(std::array<std::vector<double>, 3> &results,\n> +\t\t\t  const std::vector<double> &lambdaR,\n> +\t\t\t  double lambdaG, const std::vector<double> &lambdaB,\n> +\t\t\t  const std::vector<double> &luminanceLut,\n>  \t\t\t  double luminanceStrength)\n>  {\n> -\taddLuminanceRb((double *)results[0], lambdaR, luminanceLut, luminanceStrength);\n> -\taddLuminanceG((double *)results[1], lambdaG, luminanceLut, luminanceStrength);\n> -\taddLuminanceRb((double *)results[2], lambdaB, luminanceLut, luminanceStrength);\n> -\tnormalise((double *)results, 3 * XY);\n> +\taddLuminanceRb(results[0], lambdaR, luminanceLut, luminanceStrength);\n> +\taddLuminanceG(results[1], lambdaG, luminanceLut, luminanceStrength);\n> +\taddLuminanceRb(results[2], lambdaB, luminanceLut, luminanceStrength);\n> +\tfor (auto &r : results)\n> +\t\tnormalise(r);\n>  }\n>\n>  void Alsc::doAlsc()\n>  {\n> -\tdouble cr[XY], cb[XY], wr[XY][4], wb[XY][4], calTableR[XY], calTableB[XY], calTableTmp[XY];\n> +\tstd::vector<double> &cr = tmpC_[0], &cb = tmpC_[1], &calTableR = tmpC_[2],\n> +\t\t\t    &calTableB = tmpC_[3], &calTableTmp = tmpC_[4];\n> +\tstd::vector<std::array<double, 4>> &wr = tmpM_[0], &wb = tmpM_[1], &M = tmpM_[2];\n> +\n>  \t/*\n>  \t * Calculate our R/B (\"Cr\"/\"Cb\") colour statistics, and assess which are\n>  \t * usable.\n> @@ -784,9 +829,9 @@ void Alsc::doAlsc()\n>  \t * case the camera mode is not full-frame.\n>  \t */\n>  \tgetCalTable(ct_, config_.calibrationsCr, calTableTmp);\n> -\tresampleCalTable(calTableTmp, cameraMode_, calTableR);\n> +\tresampleCalTable(calTableTmp, cameraMode_, config_.tableSize, calTableR);\n>  \tgetCalTable(ct_, config_.calibrationsCb, calTableTmp);\n> -\tresampleCalTable(calTableTmp, cameraMode_, calTableB);\n> +\tresampleCalTable(calTableTmp, cameraMode_, config_.tableSize, calTableB);\n>  \t/*\n>  \t * You could print out the cal tables for this image here, if you're\n>  \t * tuning the algorithm...\n> @@ -796,13 +841,13 @@ void Alsc::doAlsc()\n>  \tapplyCalTable(calTableR, cr);\n>  \tapplyCalTable(calTableB, cb);\n>  \t/* Compute weights between zones. */\n> -\tcomputeW(cr, config_.sigmaCr, wr);\n> -\tcomputeW(cb, config_.sigmaCb, wb);\n> +\tcomputeW(cr, config_.sigmaCr, wr, config_.tableSize);\n> +\tcomputeW(cb, config_.sigmaCb, wb, config_.tableSize);\n>  \t/* Run Gauss-Seidel iterations over the resulting matrix, for R and B. */\n> -\trunMatrixIterations(cr, lambdaR_, wr, config_.omega, config_.nIter,\n> -\t\t\t    config_.threshold, config_.lambdaBound);\n> -\trunMatrixIterations(cb, lambdaB_, wb, config_.omega, config_.nIter,\n> -\t\t\t    config_.threshold, config_.lambdaBound);\n> +\trunMatrixIterations(cr, lambdaR_, wr, M, config_.omega, config_.nIter,\n> +\t\t\t    config_.threshold, config_.lambdaBound, config_.tableSize);\n> +\trunMatrixIterations(cb, lambdaB_, wb, M, config_.omega, config_.nIter,\n> +\t\t\t    config_.threshold, config_.lambdaBound, config_.tableSize);\n>  \t/*\n>  \t * Fold the calibrated gains into our final lambda values. (Note that on\n>  \t * the next run, we re-start with the lambda values that don't have the\n> diff --git a/src/ipa/raspberrypi/controller/rpi/alsc.h b/src/ipa/raspberrypi/controller/rpi/alsc.h\n> index 9167c9ffa2e3..85e998db40e9 100644\n> --- a/src/ipa/raspberrypi/controller/rpi/alsc.h\n> +++ b/src/ipa/raspberrypi/controller/rpi/alsc.h\n> @@ -6,9 +6,13 @@\n>   */\n>  #pragma once\n>\n> +#include <array>\n>  #include <mutex>\n>  #include <condition_variable>\n>  #include <thread>\n> +#include <vector>\n> +\n> +#include <libcamera/geometry.h>\n>\n>  #include \"../algorithm.h\"\n>  #include \"../alsc_status.h\"\n> @@ -20,7 +24,7 @@ namespace RPiController {\n>\n>  struct AlscCalibration {\n>  \tdouble ct;\n> -\tdouble table[AlscCellsX * AlscCellsY];\n> +\tstd::vector<double> table;\n>  };\n>\n>  struct AlscConfig {\n> @@ -36,13 +40,14 @@ struct AlscConfig {\n>  \tuint16_t minG;\n>  \tdouble omega;\n>  \tuint32_t nIter;\n> -\tdouble luminanceLut[AlscCellsX * AlscCellsY];\n> +\tstd::vector<double> luminanceLut;\n>  \tdouble luminanceStrength;\n>  \tstd::vector<AlscCalibration> calibrationsCr;\n>  \tstd::vector<AlscCalibration> calibrationsCb;\n>  \tdouble defaultCt; /* colour temperature if no metadata found */\n>  \tdouble threshold; /* iteration termination threshold */\n>  \tdouble lambdaBound; /* upper/lower bound for lambda from a value of 1 */\n> +\tlibcamera::Size tableSize;\n>  };\n>\n>  class Alsc : public Algorithm\n> @@ -62,7 +67,7 @@ private:\n>  \tAlscConfig config_;\n>  \tbool firstTime_;\n>  \tCameraMode cameraMode_;\n> -\tdouble luminanceTable_[AlscCellsX * AlscCellsY];\n> +\tstd::vector<double> luminanceTable_;\n>  \tstd::thread asyncThread_;\n>  \tvoid asyncFunc(); /* asynchronous thread function */\n>  \tstd::mutex mutex_;\n> @@ -88,8 +93,8 @@ private:\n>  \tint frameCount_;\n>  \t/* counts up to startupFrames for Process function */\n>  \tint frameCount2_;\n> -\tdouble syncResults_[3][AlscCellsY][AlscCellsX];\n> -\tdouble prevSyncResults_[3][AlscCellsY][AlscCellsX];\n> +\tstd::array<std::vector<double>, 3> syncResults_;\n> +\tstd::array<std::vector<double>, 3> prevSyncResults_;\n>  \tvoid waitForAysncThread();\n>  \t/*\n>  \t * The following are for the asynchronous thread to use, though the main\n> @@ -100,12 +105,16 @@ private:\n>  \tvoid fetchAsyncResults();\n>  \tdouble ct_;\n>  \tRgbyRegions statistics_;\n> -\tdouble asyncResults_[3][AlscCellsY][AlscCellsX];\n> -\tdouble asyncLambdaR_[AlscCellsX * AlscCellsY];\n> -\tdouble asyncLambdaB_[AlscCellsX * AlscCellsY];\n> +\tstd::array<std::vector<double>, 3> asyncResults_;\n> +\tstd::vector<double> asyncLambdaR_;\n> +\tstd::vector<double> asyncLambdaB_;\n>  \tvoid doAlsc();\n> -\tdouble lambdaR_[AlscCellsX * AlscCellsY];\n> -\tdouble lambdaB_[AlscCellsX * AlscCellsY];\n> +\tstd::vector<double> lambdaR_;\n> +\tstd::vector<double> lambdaB_;\n> +\n> +\t/* Temporaries for the computations */\n> +\tstd::array<std::vector<double>, 5> tmpC_;\n> +\tstd::array<std::vector<std::array<double, 4>>, 3> tmpM_;\n>  };\n>\n>  } /* namespace RPiController */\n> diff --git a/src/ipa/raspberrypi/raspberrypi.cpp b/src/ipa/raspberrypi/raspberrypi.cpp\n> index b64cb96e2dde..0fa79bb4af41 100644\n> --- a/src/ipa/raspberrypi/raspberrypi.cpp\n> +++ b/src/ipa/raspberrypi/raspberrypi.cpp\n> @@ -13,6 +13,7 @@\n>  #include <math.h>\n>  #include <stdint.h>\n>  #include <string.h>\n> +#include <vector>\n\nShould this be moved after <sys/mman.h> ?\n\n>  #include <sys/mman.h>\n>\n>  #include <linux/bcm2835-isp.h>\n> @@ -174,7 +175,7 @@ private:\n>  \tvoid applyDPC(const struct DpcStatus *dpcStatus, ControlList &ctrls);\n>  \tvoid applyLS(const struct AlscStatus *lsStatus, ControlList &ctrls);\n>  \tvoid applyAF(const struct AfStatus *afStatus, ControlList &lensCtrls);\n> -\tvoid resampleTable(uint16_t dest[], double const src[12][16], int destW, int destH);\n> +\tvoid resampleTable(uint16_t dest[], const std::vector<double> &src, int destW, int destH);\n>\n>  \tstd::map<unsigned int, MappedFrameBuffer> buffers_;\n>\n> @@ -1768,7 +1769,7 @@ void IPARPi::applyAF(const struct AfStatus *afStatus, ControlList &lensCtrls)\n>   * Resamples a 16x12 table with central sampling to destW x destH with corner\n\nThe 16x12 size is mentioned here\n\n>   * sampling.\n>   */\n> -void IPARPi::resampleTable(uint16_t dest[], double const src[12][16],\n> +void IPARPi::resampleTable(uint16_t dest[], const std::vector<double> &src,\n>  \t\t\t   int destW, int destH)\n>  {\n>  \t/*\n> @@ -1793,8 +1794,8 @@ void IPARPi::resampleTable(uint16_t dest[], double const src[12][16],\n>  \t\tdouble yf = y - yLo;\n>  \t\tint yHi = yLo < 11 ? yLo + 1 : 11;\n>  \t\tyLo = yLo > 0 ? yLo : 0;\n> -\t\tdouble const *rowAbove = src[yLo];\n> -\t\tdouble const *rowBelow = src[yHi];\n> +\t\tdouble const *rowAbove = src.data() + yLo * 16;\n> +\t\tdouble const *rowBelow = src.data() + yHi * 16;\n\nAs well as assumed here. Also the previous index was yLo and the new\none (yLo * 16). Is it ok ?\n\n>  \t\tfor (int i = 0; i < destW; i++) {\n>  \t\t\tdouble above = rowAbove[xLo[i]] * (1 - xf[i]) + rowAbove[xHi[i]] * xf[i];\n>  \t\t\tdouble below = rowBelow[xLo[i]] * (1 - xf[i]) + rowBelow[xHi[i]] * xf[i];\n> --\n> 2.34.1\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 5886BC0F2A\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 24 Mar 2023 08:30:15 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 94D03626D7;\n\tFri, 24 Mar 2023 09:30:14 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 7A07261ED0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 24 Mar 2023 09:30:12 +0100 (CET)","from ideasonboard.com (93-61-96-190.ip145.fastwebnet.it\n\t[93.61.96.190])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id C6C17A49;\n\tFri, 24 Mar 2023 09:30:11 +0100 (CET)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1679646614;\n\tbh=NNhRFeXOWYxe4NypIUarAz7BJUOw+82m9OwLRAYLCkk=;\n\th=Date:To:References:In-Reply-To:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc:\n\tFrom;\n\tb=yhPD9I2IEQJyT/1Mwv6smHQYCIvLzqUqlbyErJ+AdaX1W3RnmiEL/yeC+BCIFK0Mr\n\tsUUVQoI6SjfEqhaieuLIzPzZlt8JOUg/RVHZAQY+qQT+tpryjRONT/9GkhBJFV93ji\n\tDAfGDqobUmRxYqmctOL2NnXWg/aCJLcP2/AhxeyI0gz/zSI11Lqs4PIy5IsFuZYK80\n\tJn+kkZk5P5z8ABPN3SQPjXG8mnH8UG8qF4ZKyR1rCRv5W7g8KnuTMXJH/8DlLwYkF7\n\tZ9Fi42r40fqxCuKrj4XRLQYUcIfxSRqqQBC+nfBib2G2WUMYCi9jq9IjPVxU4/zJSt\n\tucLkg0/SCHFMA==","v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1679646612;\n\tbh=NNhRFeXOWYxe4NypIUarAz7BJUOw+82m9OwLRAYLCkk=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=LiXRfyDmrwvZI0QSfSdoguECSe+yCZRGS5xXNPrJXFJ9hjZMBpQ5CAh4TBwQYppk1\n\ttn2wW4j29yvGL8y83d7U29sEKFtkr/kZNRDamBl+vhaWi7+f4tB/mtK9gvlExL/Ggh\n\tlp2ykq3xyf0YIZXw2yK6m2OlpzemG+kyDMOVlC0A="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"LiXRfyDm\"; dkim-atps=neutral","Date":"Fri, 24 Mar 2023 09:30:08 +0100","To":"Naushir Patuck <naush@raspberrypi.com>","Message-ID":"<20230324083008.untsxdgb63klpere@uno.localdomain>","References":"<20230322130612.5208-1-naush@raspberrypi.com>\n\t<20230322130612.5208-4-naush@raspberrypi.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20230322130612.5208-4-naush@raspberrypi.com>","Subject":"Re: [libcamera-devel] [PATCH v1 03/10] ipa: raspberrypi: Generalise\n\tthe ALSC 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>","From":"Jacopo Mondi via libcamera-devel <libcamera-devel@lists.libcamera.org>","Reply-To":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":26758,"web_url":"https://patchwork.libcamera.org/comment/26758/","msgid":"<CAEmqJPonGg1-WDNCFY-xnFTC47Ky9y+cPOn4WByseR=7fiuSHA@mail.gmail.com>","date":"2023-03-27T10:42:17","subject":"Re: [libcamera-devel] [PATCH v1 03/10] ipa: raspberrypi: Generalise\n\tthe ALSC algorithm","submitter":{"id":34,"url":"https://patchwork.libcamera.org/api/people/34/","name":"Naushir Patuck","email":"naush@raspberrypi.com"},"content":"Hi Jacopo,\n\nThank you for your review!\n\nOn Fri, 24 Mar 2023 at 08:30, Jacopo Mondi <jacopo.mondi@ideasonboard.com>\nwrote:\n\n> On Wed, Mar 22, 2023 at 01:06:05PM +0000, Naushir Patuck via\n> libcamera-devel wrote:\n> > Remove any hard-coded assumptions about the target hardware platform\n> > from the ALSC algorithm. Instead, use the \"target\" string provided by\n> > the camera tuning config and generalised statistics structures to\n> > determing parameters such as grid and region sizes.\n> >\n> > The ALSC calculations use run-time allocated arrays/vectors on every\n> > frame. Allocating these might add a non-trivial run-time penalty.\n> > Replace these dynamic allocations with a set of reusable pre-allocated\n> > vectors during the init phase.\n> >\n> > Signed-off-by: Naushir Patuck <naush@raspberrypi.com>\n> > Signed-off-by: David Plowman <david.plowman@raspberrypi.com>\n> > ---\n> >  src/ipa/raspberrypi/controller/alsc_status.h |  13 +-\n> >  src/ipa/raspberrypi/controller/rpi/alsc.cpp  | 341 +++++++++++--------\n> >  src/ipa/raspberrypi/controller/rpi/alsc.h    |  29 +-\n> >  src/ipa/raspberrypi/raspberrypi.cpp          |   9 +-\n> >  4 files changed, 224 insertions(+), 168 deletions(-)\n> >\n> > diff --git a/src/ipa/raspberrypi/controller/alsc_status.h\n> b/src/ipa/raspberrypi/controller/alsc_status.h\n> > index e5aa7e37c330..49a9f4a0cb5a 100644\n> > --- a/src/ipa/raspberrypi/controller/alsc_status.h\n> > +++ b/src/ipa/raspberrypi/controller/alsc_status.h\n> > @@ -6,16 +6,17 @@\n> >   */\n> >  #pragma once\n> >\n> > +#include <vector>\n> > +\n> >  /*\n> >   * The ALSC algorithm should post the following structure into the\n> image's\n> >   * \"alsc.status\" metadata.\n> >   */\n> >\n> > -constexpr unsigned int AlscCellsX = 16;\n> > -constexpr unsigned int AlscCellsY = 12;\n> > -\n> >  struct AlscStatus {\n> > -     double r[AlscCellsY][AlscCellsX];\n> > -     double g[AlscCellsY][AlscCellsX];\n> > -     double b[AlscCellsY][AlscCellsX];\n> > +     std::vector<double> r;\n> > +     std::vector<double> g;\n> > +     std::vector<double> b;\n> > +     unsigned int rows;\n> > +     unsigned int cols;\n> >  };\n> > diff --git a/src/ipa/raspberrypi/controller/rpi/alsc.cpp\n> b/src/ipa/raspberrypi/controller/rpi/alsc.cpp\n> > index eb4e2f9496e1..51fe5d73f52d 100644\n> > --- a/src/ipa/raspberrypi/controller/rpi/alsc.cpp\n> > +++ b/src/ipa/raspberrypi/controller/rpi/alsc.cpp\n> > @@ -5,6 +5,7 @@\n> >   * alsc.cpp - ALSC (auto lens shading correction) control algorithm\n> >   */\n> >\n> > +#include <algorithm>\n> >  #include <functional>\n> >  #include <math.h>\n> >  #include <numeric>\n> > @@ -24,9 +25,6 @@ LOG_DEFINE_CATEGORY(RPiAlsc)\n> >\n> >  #define NAME \"rpi.alsc\"\n> >\n> > -static const int X = AlscCellsX;\n> > -static const int Y = AlscCellsY;\n> > -static const int XY = X * Y;\n> >  static const double InsufficientData = -1.0;\n> >\n> >  Alsc::Alsc(Controller *controller)\n> > @@ -51,8 +49,11 @@ char const *Alsc::name() const\n> >       return NAME;\n> >  }\n> >\n> > -static int generateLut(double *lut, const libcamera::YamlObject &params)\n> > +static int generateLut(std::vector<double> &lut, const\n> libcamera::YamlObject &params,\n> > +                    const Size &size)\n> >  {\n> > +     /* These must be signed ints for the co-ordinate calculations\n> below. */\n> > +     int X = size.width, Y = size.height;\n> >       double cstrength = params[\"corner_strength\"].get<double>(2.0);\n> >       if (cstrength <= 1.0) {\n> >               LOG(RPiAlsc, Error) << \"corner_strength must be > 1.0\";\n> > @@ -81,9 +82,9 @@ static int generateLut(double *lut, const\n> libcamera::YamlObject &params)\n> >       return 0;\n> >  }\n> >\n> > -static int readLut(double *lut, const libcamera::YamlObject &params)\n> > +static int readLut(std::vector<double> &lut, const\n> libcamera::YamlObject &params, const Size &size)\n> >  {\n> > -     if (params.size() != XY) {\n> > +     if (params.size() != size.width * size.height) {\n> >               LOG(RPiAlsc, Error) << \"Invalid number of entries in LSC\n> table\";\n> >               return -EINVAL;\n> >       }\n> > @@ -101,7 +102,7 @@ static int readLut(double *lut, const\n> libcamera::YamlObject &params)\n> >\n> >  static int readCalibrations(std::vector<AlscCalibration> &calibrations,\n> >                           const libcamera::YamlObject &params,\n> > -                         std::string const &name)\n> > +                         std::string const &name, const Size &size)\n> >  {\n> >       if (params.contains(name)) {\n> >               double lastCt = 0;\n> > @@ -119,7 +120,7 @@ static int\n> readCalibrations(std::vector<AlscCalibration> &calibrations,\n> >                       calibration.ct = lastCt = ct;\n> >\n> >                       const libcamera::YamlObject &table = p[\"table\"];\n> > -                     if (table.size() != XY) {\n> > +                     if (table.size() != size.width * size.height) {\n> >                               LOG(RPiAlsc, Error)\n> >                                       << \"Incorrect number of values for\n> ct \"\n> >                                       << ct << \" in \" << name;\n> > @@ -127,6 +128,7 @@ static int\n> readCalibrations(std::vector<AlscCalibration> &calibrations,\n> >                       }\n> >\n> >                       int num = 0;\n> > +                     calibration.table.resize(size.width * size.height);\n> >                       for (const auto &elem : table.asList()) {\n> >                               value = elem.get<double>();\n> >                               if (!value)\n> > @@ -134,7 +136,7 @@ static int\n> readCalibrations(std::vector<AlscCalibration> &calibrations,\n> >                               calibration.table[num++] = *value;\n> >                       }\n> >\n> > -                     calibrations.push_back(calibration);\n> > +                     calibrations.push_back(std::move(calibration));\n> >                       LOG(RPiAlsc, Debug)\n> >                               << \"Read \" << name << \" calibration for ct\n> \" << ct;\n> >               }\n> > @@ -144,6 +146,7 @@ static int\n> readCalibrations(std::vector<AlscCalibration> &calibrations,\n> >\n> >  int Alsc::read(const libcamera::YamlObject &params)\n> >  {\n> > +     config_.tableSize = getHardwareConfig().awbRegions;\n> >       config_.framePeriod = params[\"frame_period\"].get<uint16_t>(12);\n> >       config_.startupFrames = params[\"startup_frames\"].get<uint16_t>(10);\n> >       config_.speed = params[\"speed\"].get<double>(0.05);\n> > @@ -153,28 +156,29 @@ int Alsc::read(const libcamera::YamlObject &params)\n> >       config_.minCount = params[\"min_count\"].get<double>(10.0);\n> >       config_.minG = params[\"min_G\"].get<uint16_t>(50);\n> >       config_.omega = params[\"omega\"].get<double>(1.3);\n> > -     config_.nIter = params[\"n_iter\"].get<uint32_t>(X + Y);\n> > +     config_.nIter =\n> params[\"n_iter\"].get<uint32_t>(config_.tableSize.width +\n> config_.tableSize.height);\n> >       config_.luminanceStrength =\n> >               params[\"luminance_strength\"].get<double>(1.0);\n> > -     for (int i = 0; i < XY; i++)\n> > -             config_.luminanceLut[i] = 1.0;\n> >\n> > +     config_.luminanceLut.resize(config_.tableSize.width *\n> config_.tableSize.height, 1.0);\n> >       int ret = 0;\n> >\n> >       if (params.contains(\"corner_strength\"))\n> > -             ret = generateLut(config_.luminanceLut, params);\n> > +             ret = generateLut(config_.luminanceLut, params,\n> config_.tableSize);\n> >       else if (params.contains(\"luminance_lut\"))\n> > -             ret = readLut(config_.luminanceLut,\n> params[\"luminance_lut\"]);\n> > +             ret = readLut(config_.luminanceLut,\n> params[\"luminance_lut\"], config_.tableSize);\n> >       else\n> >               LOG(RPiAlsc, Warning)\n> >                       << \"no luminance table - assume unity everywhere\";\n> >       if (ret)\n> >               return ret;\n> >\n> > -     ret = readCalibrations(config_.calibrationsCr, params,\n> \"calibrations_Cr\");\n> > +     ret = readCalibrations(config_.calibrationsCr, params,\n> \"calibrations_Cr\",\n> > +                            config_.tableSize);\n> >       if (ret)\n> >               return ret;\n> > -     ret = readCalibrations(config_.calibrationsCb, params,\n> \"calibrations_Cb\");\n> > +     ret = readCalibrations(config_.calibrationsCb, params,\n> \"calibrations_Cb\",\n> > +                            config_.tableSize);\n> >       if (ret)\n> >               return ret;\n> >\n> > @@ -187,13 +191,16 @@ int Alsc::read(const libcamera::YamlObject &params)\n> >\n> >  static double getCt(Metadata *metadata, double defaultCt);\n> >  static void getCalTable(double ct, std::vector<AlscCalibration> const\n> &calibrations,\n> > -                     double calTable[XY]);\n> > -static void resampleCalTable(double const calTableIn[XY], CameraMode\n> const &cameraMode,\n> > -                          double calTableOut[XY]);\n> > -static void compensateLambdasForCal(double const calTable[XY], double\n> const oldLambdas[XY],\n> > -                                 double newLambdas[XY]);\n> > -static void addLuminanceToTables(double results[3][Y][X], double const\n> lambdaR[XY], double lambdaG,\n> > -                              double const lambdaB[XY], double const\n> luminanceLut[XY],\n> > +                     std::vector<double> &calTable);\n> > +static void resampleCalTable(const std::vector<double> &calTableIn,\n> CameraMode const &cameraMode,\n> > +                          const Size &size, std::vector<double>\n> &calTableOut);\n> > +static void compensateLambdasForCal(const std::vector<double> &calTable,\n> > +                                 const std::vector<double> &oldLambdas,\n> > +                                 std::vector<double> &newLambdas);\n> > +static void addLuminanceToTables(std::array<std::vector<double>, 3>\n> &results,\n> > +                              const std::vector<double> &lambdaR,\n> double lambdaG,\n> > +                              const std::vector<double> &lambdaB,\n> > +                              const std::vector<double> &luminanceLut,\n> >                                double luminanceStrength);\n> >\n> >  void Alsc::initialise()\n> > @@ -201,7 +208,28 @@ void Alsc::initialise()\n> >       frameCount2_ = frameCount_ = framePhase_ = 0;\n> >       firstTime_ = true;\n> >       ct_ = config_.defaultCt;\n> > +\n> > +     const size_t XY = config_.tableSize.width *\n> config_.tableSize.height;\n> > +\n> > +     for (auto &r : syncResults_)\n> > +             r.resize(XY);\n> > +     for (auto &r : prevSyncResults_)\n> > +             r.resize(XY);\n> > +     for (auto &r : asyncResults_)\n> > +             r.resize(XY);\n> > +\n> > +     luminanceTable_.resize(XY);\n> > +     asyncLambdaR_.resize(XY);\n> > +     asyncLambdaB_.resize(XY);\n> >       /* The lambdas are initialised in the SwitchMode. */\n> > +     lambdaR_.resize(XY);\n> > +     lambdaB_.resize(XY);\n> > +\n> > +     /* Temporaries for the computations, but sensible to allocate this\n> up-front! */\n> > +     for (auto &c : tmpC_)\n> > +             c.resize(XY);\n> > +     for (auto &m : tmpM_)\n> > +             m.resize(XY);\n> >  }\n> >\n> >  void Alsc::waitForAysncThread()\n> > @@ -262,7 +290,7 @@ void Alsc::switchMode(CameraMode const &cameraMode,\n> >        * We must resample the luminance table like we do the others, but\n> it's\n> >        * fixed so we can simply do it up front here.\n> >        */\n> > -     resampleCalTable(config_.luminanceLut, cameraMode_,\n> luminanceTable_);\n> > +     resampleCalTable(config_.luminanceLut, cameraMode_,\n> config_.tableSize, luminanceTable_);\n> >\n> >       if (resetTables) {\n> >               /*\n> > @@ -272,18 +300,18 @@ void Alsc::switchMode(CameraMode const &cameraMode,\n> >                * the lambdas, but the rest of this code then echoes the\n> code in\n> >                * doAlsc, without the adaptive algorithm.\n> >                */\n> > -             for (int i = 0; i < XY; i++)\n> > -                     lambdaR_[i] = lambdaB_[i] = 1.0;\n> > -             double calTableR[XY], calTableB[XY], calTableTmp[XY];\n> > +             std::fill(lambdaR_.begin(), lambdaR_.end(), 1.0);\n> > +             std::fill(lambdaB_.begin(), lambdaB_.end(), 1.0);\n> > +             std::vector<double> &calTableR = tmpC_[0], &calTableB =\n> tmpC_[1], &calTableTmp = tmpC_[2];\n> >               getCalTable(ct_, config_.calibrationsCr, calTableTmp);\n> > -             resampleCalTable(calTableTmp, cameraMode_, calTableR);\n> > +             resampleCalTable(calTableTmp, cameraMode_,\n> config_.tableSize, calTableR);\n> >               getCalTable(ct_, config_.calibrationsCb, calTableTmp);\n> > -             resampleCalTable(calTableTmp, cameraMode_, calTableB);\n> > +             resampleCalTable(calTableTmp, cameraMode_,\n> config_.tableSize, calTableB);\n> >               compensateLambdasForCal(calTableR, lambdaR_,\n> asyncLambdaR_);\n> >               compensateLambdasForCal(calTableB, lambdaB_,\n> asyncLambdaB_);\n> >               addLuminanceToTables(syncResults_, asyncLambdaR_, 1.0,\n> asyncLambdaB_,\n> >                                    luminanceTable_,\n> config_.luminanceStrength);\n> > -             memcpy(prevSyncResults_, syncResults_,\n> sizeof(prevSyncResults_));\n> > +             prevSyncResults_ = syncResults_;\n> >               framePhase_ = config_.framePeriod; /* run the algo again\n> asap */\n> >               firstTime_ = false;\n> >       }\n> > @@ -294,7 +322,7 @@ void Alsc::fetchAsyncResults()\n> >       LOG(RPiAlsc, Debug) << \"Fetch ALSC results\";\n> >       asyncFinished_ = false;\n> >       asyncStarted_ = false;\n> > -     memcpy(syncResults_, asyncResults_, sizeof(syncResults_));\n> > +     syncResults_ = asyncResults_;\n> >  }\n> >\n> >  double getCt(Metadata *metadata, double defaultCt)\n> > @@ -316,9 +344,9 @@ static void copyStats(RgbyRegions &regions,\n> StatisticsPtr &stats,\n> >       if (!regions.numRegions())\n> >               regions.init(stats->awbRegions.size());\n> >\n> > -     double *rTable = (double *)status.r;\n> > -     double *gTable = (double *)status.g;\n> > -     double *bTable = (double *)status.b;\n> > +     const std::vector<double> &rTable = status.r;\n> > +     const std::vector<double> &gTable = status.g;\n> > +     const std::vector<double> &bTable = status.b;\n> >       for (unsigned int i = 0; i < stats->awbRegions.numRegions(); i++) {\n> >               auto r = stats->awbRegions.get(i);\n> >               r.val.rSum = static_cast<uint64_t>(r.val.rSum / rTable[i]);\n> > @@ -344,12 +372,9 @@ void Alsc::restartAsync(StatisticsPtr &stats,\n> Metadata *imageMetadata)\n> >       if (imageMetadata->get(\"alsc.status\", alscStatus) != 0) {\n> >               LOG(RPiAlsc, Warning)\n> >                       << \"No ALSC status found for applied gains!\";\n> > -             for (int y = 0; y < Y; y++)\n> > -                     for (int x = 0; x < X; x++) {\n> > -                             alscStatus.r[y][x] = 1.0;\n> > -                             alscStatus.g[y][x] = 1.0;\n> > -                             alscStatus.b[y][x] = 1.0;\n> > -                     }\n> > +             alscStatus.r.resize(config_.tableSize.width *\n> config_.tableSize.height, 1.0);\n> > +             alscStatus.g.resize(config_.tableSize.width *\n> config_.tableSize.height, 1.0);\n> > +             alscStatus.b.resize(config_.tableSize.width *\n> config_.tableSize.height, 1.0);\n> >       }\n> >       copyStats(statistics_, stats, alscStatus);\n> >       framePhase_ = 0;\n> > @@ -380,15 +405,15 @@ void Alsc::prepare(Metadata *imageMetadata)\n> >                       fetchAsyncResults();\n> >       }\n> >       /* Apply IIR filter to results and program into the pipeline. */\n> > -     double *ptr = (double *)syncResults_,\n> > -            *pptr = (double *)prevSyncResults_;\n> > -     for (unsigned int i = 0; i < sizeof(syncResults_) /\n> sizeof(double); i++)\n> > -             pptr[i] = speed * ptr[i] + (1.0 - speed) * pptr[i];\n> > +     for (unsigned int j = 0; j < syncResults_.size(); j++) {\n> > +             for (unsigned int i = 0; i < syncResults_[j].size(); i++)\n> > +                     prevSyncResults_[j][i] = speed *\n> syncResults_[j][i] + (1.0 - speed) * prevSyncResults_[j][i];\n> > +     }\n> >       /* Put output values into status metadata. */\n> >       AlscStatus status;\n> > -     memcpy(status.r, prevSyncResults_[0], sizeof(status.r));\n> > -     memcpy(status.g, prevSyncResults_[1], sizeof(status.g));\n> > -     memcpy(status.b, prevSyncResults_[2], sizeof(status.b));\n> > +     status.r = prevSyncResults_[0];\n> > +     status.g = prevSyncResults_[1];\n> > +     status.b = prevSyncResults_[2];\n> >       imageMetadata->set(\"alsc.status\", status);\n> >  }\n> >\n> > @@ -432,18 +457,17 @@ void Alsc::asyncFunc()\n> >  }\n> >\n> >  void getCalTable(double ct, std::vector<AlscCalibration> const\n> &calibrations,\n> > -              double calTable[XY])\n> > +              std::vector<double> &calTable)\n> >  {\n> >       if (calibrations.empty()) {\n> > -             for (int i = 0; i < XY; i++)\n> > -                     calTable[i] = 1.0;\n> > +             std::fill(calTable.begin(), calTable.end(), 1.0);\n> >               LOG(RPiAlsc, Debug) << \"no calibrations found\";\n> >       } else if (ct <= calibrations.front().ct) {\n> > -             memcpy(calTable, calibrations.front().table, XY *\n> sizeof(double));\n> > +             calTable = calibrations.front().table;\n> >               LOG(RPiAlsc, Debug) << \"using calibration for \"\n> >                                   << calibrations.front().ct;\n> >       } else if (ct >= calibrations.back().ct) {\n> > -             memcpy(calTable, calibrations.back().table, XY *\n> sizeof(double));\n> > +             calTable = calibrations.back().table;\n> >               LOG(RPiAlsc, Debug) << \"using calibration for \"\n> >                                   << calibrations.back().ct;\n> >       } else {\n> > @@ -454,7 +478,7 @@ void getCalTable(double ct,\n> std::vector<AlscCalibration> const &calibrations,\n> >               LOG(RPiAlsc, Debug)\n> >                       << \"ct is \" << ct << \", interpolating between \"\n> >                       << ct0 << \" and \" << ct1;\n> > -             for (int i = 0; i < XY; i++)\n> > +             for (unsigned int i = 0; i < calTable.size(); i++)\n> >                       calTable[i] =\n> >                               (calibrations[idx].table[i] * (ct1 - ct) +\n> >                                calibrations[idx + 1].table[i] * (ct -\n> ct0)) /\n> > @@ -462,9 +486,13 @@ void getCalTable(double ct,\n> std::vector<AlscCalibration> const &calibrations,\n> >       }\n> >  }\n> >\n> > -void resampleCalTable(double const calTableIn[XY],\n> > -                   CameraMode const &cameraMode, double calTableOut[XY])\n> > +void resampleCalTable(const std::vector<double> &calTableIn,\n> > +                   CameraMode const &cameraMode, const Size &size,\n> > +                   std::vector<double> &calTableOut)\n> >  {\n> > +     int X = size.width;\n> > +     int Y = size.height;\n> > +\n> >       /*\n> >        * Precalculate and cache the x sampling locations and phases to\n> save\n> >        * recomputing them on every row.\n> > @@ -501,23 +529,24 @@ void resampleCalTable(double const calTableIn[XY],\n> >                       yLo = Y - 1 - yLo;\n> >                       yHi = Y - 1 - yHi;\n> >               }\n> > -             double const *rowAbove = calTableIn + X * yLo;\n> > -             double const *rowBelow = calTableIn + X * yHi;\n> > +             double const *rowAbove = calTableIn.data() + X * yLo;\n> > +             double const *rowBelow = calTableIn.data() + X * yHi;\n> > +             double *out = calTableOut.data() + X * j;\n> >               for (int i = 0; i < X; i++) {\n> >                       double above = rowAbove[xLo[i]] * (1 - xf[i]) +\n> >                                      rowAbove[xHi[i]] * xf[i];\n> >                       double below = rowBelow[xLo[i]] * (1 - xf[i]) +\n> >                                      rowBelow[xHi[i]] * xf[i];\n> > -                     *(calTableOut++) = above * (1 - yf) + below * yf;\n> > +                     *(out++) = above * (1 - yf) + below * yf;\n> >               }\n> >       }\n> >  }\n> >\n> >  /* Calculate chrominance statistics (R/G and B/G) for each region. */\n> > -static void calculateCrCb(const RgbyRegions &awbRegion, double cr[XY],\n> > -                       double cb[XY], uint32_t minCount, uint16_t minG)\n> > +static void calculateCrCb(const RgbyRegions &awbRegion,\n> std::vector<double> &cr,\n> > +                       std::vector<double> &cb, uint32_t minCount,\n> uint16_t minG)\n> >  {\n> > -     for (int i = 0; i < XY; i++) {\n> > +     for (unsigned int i = 0; i < cr.size(); i++) {\n> >               auto s = awbRegion.get(i);\n> >\n> >               if (s.counted <= minCount || s.val.gSum / s.counted <=\n> minG) {\n> > @@ -530,33 +559,34 @@ static void calculateCrCb(const RgbyRegions\n> &awbRegion, double cr[XY],\n> >       }\n> >  }\n> >\n> > -static void applyCalTable(double const calTable[XY], double C[XY])\n> > +static void applyCalTable(const std::vector<double> &calTable,\n> std::vector<double> &C)\n> >  {\n> > -     for (int i = 0; i < XY; i++)\n> > +     for (unsigned int i = 0; i < C.size(); i++)\n> >               if (C[i] != InsufficientData)\n> >                       C[i] *= calTable[i];\n> >  }\n> >\n> > -void compensateLambdasForCal(double const calTable[XY],\n> > -                          double const oldLambdas[XY],\n> > -                          double newLambdas[XY])\n> > +void compensateLambdasForCal(const std::vector<double> &calTable,\n> > +                          const std::vector<double> &oldLambdas,\n> > +                          std::vector<double> &newLambdas)\n> >  {\n> >       double minNewLambda = std::numeric_limits<double>::max();\n> > -     for (int i = 0; i < XY; i++) {\n> > +     for (unsigned int i = 0; i < newLambdas.size(); i++) {\n> >               newLambdas[i] = oldLambdas[i] * calTable[i];\n> >               minNewLambda = std::min(minNewLambda, newLambdas[i]);\n> >       }\n> > -     for (int i = 0; i < XY; i++)\n> > +     for (unsigned int i = 0; i < newLambdas.size(); i++)\n> >               newLambdas[i] /= minNewLambda;\n> >  }\n> >\n> > -[[maybe_unused]] static void printCalTable(double const C[XY])\n> > +[[maybe_unused]] static void printCalTable(const std::vector<double> &C,\n> > +                                        const Size &size)\n> >  {\n> >       printf(\"table: [\\n\");\n> > -     for (int j = 0; j < Y; j++) {\n> > -             for (int i = 0; i < X; i++) {\n> > -                     printf(\"%5.3f\", 1.0 / C[j * X + i]);\n> > -                     if (i != X - 1 || j != Y - 1)\n> > +     for (unsigned int j = 0; j < size.height; j++) {\n> > +             for (unsigned int i = 0; i < size.width; i++) {\n> > +                     printf(\"%5.3f\", 1.0 / C[j * size.width + i]);\n> > +                     if (i != size.width - 1 || j != size.height - 1)\n> >                               printf(\",\");\n> >               }\n> >               printf(\"\\n\");\n> > @@ -577,9 +607,13 @@ static double computeWeight(double Ci, double Cj,\n> double sigma)\n> >  }\n> >\n> >  /* Compute all weights. */\n> > -static void computeW(double const C[XY], double sigma, double W[XY][4])\n> > +static void computeW(const std::vector<double> &C, double sigma,\n> > +                  std::vector<std::array<double, 4>> &W, const Size\n> &size)\n> >  {\n> > -     for (int i = 0; i < XY; i++) {\n> > +     size_t XY = size.width * size.height;\n> > +     size_t X = size.width;\n> > +\n> > +     for (unsigned int i = 0; i < XY; i++) {\n> >               /* Start with neighbour above and go clockwise. */\n> >               W[i][0] = i >= X ? computeWeight(C[i], C[i - X], sigma) :\n> 0;\n> >               W[i][1] = i % X < X - 1 ? computeWeight(C[i], C[i + 1],\n> sigma) : 0;\n> > @@ -589,11 +623,16 @@ static void computeW(double const C[XY], double\n> sigma, double W[XY][4])\n> >  }\n> >\n> >  /* Compute M, the large but sparse matrix such that M * lambdas = 0. */\n> > -static void constructM(double const C[XY], double const W[XY][4],\n> > -                    double M[XY][4])\n> > +static void constructM(const std::vector<double> &C,\n> > +                    const std::vector<std::array<double, 4>> &W,\n> > +                    std::vector<std::array<double, 4>> &M,\n> > +                    const Size &size)\n> >  {\n> > +     size_t XY = size.width * size.height;\n> > +     size_t X = size.width;\n> > +\n> >       double epsilon = 0.001;\n> > -     for (int i = 0; i < XY; i++) {\n> > +     for (unsigned int i = 0; i < XY; i++) {\n> >               /*\n> >                * Note how, if C[i] == INSUFFICIENT_DATA, the weights\n> will all\n> >                * be zero so the equation is still set up correctly.\n> > @@ -614,79 +653,80 @@ static void constructM(double const C[XY], double\n> const W[XY][4],\n> >   * left/right neighbours are zero down the left/right edges, so we\n> don't need\n> >   * need to test the i value to exclude them.\n> >   */\n> > -static double computeLambdaBottom(int i, double const M[XY][4],\n> > -                               double lambda[XY])\n> > +static double computeLambdaBottom(int i, const\n> std::vector<std::array<double, 4>> &M,\n> > +                               std::vector<double> &lambda, const Size\n> &size)\n> >  {\n> > -     return M[i][1] * lambda[i + 1] + M[i][2] * lambda[i + X] +\n> > +     return M[i][1] * lambda[i + 1] + M[i][2] * lambda[i + size.width] +\n> >              M[i][3] * lambda[i - 1];\n> >  }\n> > -static double computeLambdaBottomStart(int i, double const M[XY][4],\n> > -                                    double lambda[XY])\n> > +static double computeLambdaBottomStart(int i, const\n> std::vector<std::array<double, 4>> &M,\n> > +                                    std::vector<double> &lambda, const\n> Size &size)\n> >  {\n> > -     return M[i][1] * lambda[i + 1] + M[i][2] * lambda[i + X];\n> > +     return M[i][1] * lambda[i + 1] + M[i][2] * lambda[i + size.width];\n> >  }\n> > -static double computeLambdaInterior(int i, double const M[XY][4],\n> > -                                 double lambda[XY])\n> > +static double computeLambdaInterior(int i, const\n> std::vector<std::array<double, 4>> &M,\n> > +                                 std::vector<double> &lambda, const\n> Size &size)\n> >  {\n> > -     return M[i][0] * lambda[i - X] + M[i][1] * lambda[i + 1] +\n> > -            M[i][2] * lambda[i + X] + M[i][3] * lambda[i - 1];\n> > +     return M[i][0] * lambda[i - size.width] + M[i][1] * lambda[i + 1] +\n> > +            M[i][2] * lambda[i + size.width] + M[i][3] * lambda[i - 1];\n> >  }\n> > -static double computeLambdaTop(int i, double const M[XY][4],\n> > -                            double lambda[XY])\n> > +static double computeLambdaTop(int i, const\n> std::vector<std::array<double, 4>> &M,\n> > +                            std::vector<double> &lambda, const Size\n> &size)\n> >  {\n> > -     return M[i][0] * lambda[i - X] + M[i][1] * lambda[i + 1] +\n> > +     return M[i][0] * lambda[i - size.width] + M[i][1] * lambda[i + 1] +\n> >              M[i][3] * lambda[i - 1];\n> >  }\n> > -static double computeLambdaTopEnd(int i, double const M[XY][4],\n> > -                               double lambda[XY])\n> > +static double computeLambdaTopEnd(int i, const\n> std::vector<std::array<double, 4>> &M,\n> > +                               std::vector<double> &lambda, const Size\n> &size)\n> >  {\n> > -     return M[i][0] * lambda[i - X] + M[i][3] * lambda[i - 1];\n> > +     return M[i][0] * lambda[i - size.width] + M[i][3] * lambda[i - 1];\n> >  }\n> >\n> >  /* Gauss-Seidel iteration with over-relaxation. */\n> > -static double gaussSeidel2Sor(double const M[XY][4], double omega,\n> > -                           double lambda[XY], double lambdaBound)\n> > +static double gaussSeidel2Sor(const std::vector<std::array<double, 4>>\n> &M, double omega,\n> > +                           std::vector<double> &lambda, double\n> lambdaBound,\n> > +                           const Size &size)\n> >  {\n> > +     int XY = size.width * size.height;\n> > +     int X = size.width;\n> >       const double min = 1 - lambdaBound, max = 1 + lambdaBound;\n> > -     double oldLambda[XY];\n> > +     std::vector<double> oldLambda = lambda;\n> >       int i;\n> > -     for (i = 0; i < XY; i++)\n> > -             oldLambda[i] = lambda[i];\n> > -     lambda[0] = computeLambdaBottomStart(0, M, lambda);\n> > +     lambda[0] = computeLambdaBottomStart(0, M, lambda, size);\n> >       lambda[0] = std::clamp(lambda[0], min, max);\n> >       for (i = 1; i < X; i++) {\n> > -             lambda[i] = computeLambdaBottom(i, M, lambda);\n> > +             lambda[i] = computeLambdaBottom(i, M, lambda, size);\n> >               lambda[i] = std::clamp(lambda[i], min, max);\n> >       }\n> >       for (; i < XY - X; i++) {\n> > -             lambda[i] = computeLambdaInterior(i, M, lambda);\n> > +             lambda[i] = computeLambdaInterior(i, M, lambda, size);\n> >               lambda[i] = std::clamp(lambda[i], min, max);\n> >       }\n> >       for (; i < XY - 1; i++) {\n> > -             lambda[i] = computeLambdaTop(i, M, lambda);\n> > +             lambda[i] = computeLambdaTop(i, M, lambda, size);\n> >               lambda[i] = std::clamp(lambda[i], min, max);\n> >       }\n> > -     lambda[i] = computeLambdaTopEnd(i, M, lambda);\n> > +     lambda[i] = computeLambdaTopEnd(i, M, lambda, size);\n> >       lambda[i] = std::clamp(lambda[i], min, max);\n> >       /*\n> >        * Also solve the system from bottom to top, to help spread the\n> updates\n> >        * better.\n> >        */\n> > -     lambda[i] = computeLambdaTopEnd(i, M, lambda);\n> > +     lambda[i] = computeLambdaTopEnd(i, M, lambda, size);\n> >       lambda[i] = std::clamp(lambda[i], min, max);\n> >       for (i = XY - 2; i >= XY - X; i--) {\n> > -             lambda[i] = computeLambdaTop(i, M, lambda);\n> > +             lambda[i] = computeLambdaTop(i, M, lambda, size);\n> >               lambda[i] = std::clamp(lambda[i], min, max);\n> >       }\n> >       for (; i >= X; i--) {\n> > -             lambda[i] = computeLambdaInterior(i, M, lambda);\n> > +             lambda[i] = computeLambdaInterior(i, M, lambda, size);\n> >               lambda[i] = std::clamp(lambda[i], min, max);\n> >       }\n> >       for (; i >= 1; i--) {\n> > -             lambda[i] = computeLambdaBottom(i, M, lambda);\n> > +             lambda[i] = computeLambdaBottom(i, M, lambda, size);\n> >               lambda[i] = std::clamp(lambda[i], min, max);\n> >       }\n> > -     lambda[0] = computeLambdaBottomStart(0, M, lambda);\n> > +     lambda[0] = computeLambdaBottomStart(0, M, lambda, size);\n> >       lambda[0] = std::clamp(lambda[0], min, max);\n> >       double maxDiff = 0;\n> >       for (i = 0; i < XY; i++) {\n> > @@ -698,33 +738,33 @@ static double gaussSeidel2Sor(double const\n> M[XY][4], double omega,\n> >  }\n> >\n> >  /* Normalise the values so that the smallest value is 1. */\n> > -static void normalise(double *ptr, size_t n)\n> > +static void normalise(std::vector<double> &results)\n> >  {\n> > -     double minval = ptr[0];\n> > -     for (size_t i = 1; i < n; i++)\n> > -             minval = std::min(minval, ptr[i]);\n> > -     for (size_t i = 0; i < n; i++)\n> > -             ptr[i] /= minval;\n> > +     double minval = *std::min_element(results.begin(), results.end());\n> > +     std::for_each(results.begin(), results.end(),\n> > +                   [minval](double val) { return val / minval; });\n> >  }\n> >\n> >  /* Rescale the values so that the average value is 1. */\n> > -static void reaverage(Span<double> data)\n> > +static void reaverage(std::vector<double> &data)\n> >  {\n> >       double sum = std::accumulate(data.begin(), data.end(), 0.0);\n> >       double ratio = 1 / (sum / data.size());\n> > -     for (double &d : data)\n> > -             d *= ratio;\n> > +     std::for_each(data.begin(), data.end(),\n> > +                   [ratio](double val) { return val * ratio; });\n> >  }\n> >\n> > -static void runMatrixIterations(double const C[XY], double lambda[XY],\n> > -                             double const W[XY][4], double omega,\n> > -                             int nIter, double threshold, double\n> lambdaBound)\n> > +static void runMatrixIterations(const std::vector<double> &C,\n> > +                             std::vector<double> &lambda,\n> > +                             const std::vector<std::array<double, 4>>\n> &W,\n> > +                             std::vector<std::array<double, 4>> &M,\n> double omega,\n> > +                             unsigned int nIter, double threshold,\n> double lambdaBound,\n> > +                             const Size &size)\n> >  {\n> > -     double M[XY][4];\n> > -     constructM(C, W, M);\n> > +     constructM(C, W, M, size);\n> >       double lastMaxDiff = std::numeric_limits<double>::max();\n> > -     for (int i = 0; i < nIter; i++) {\n> > -             double maxDiff = fabs(gaussSeidel2Sor(M, omega, lambda,\n> lambdaBound));\n> > +     for (unsigned int i = 0; i < nIter; i++) {\n> > +             double maxDiff = fabs(gaussSeidel2Sor(M, omega, lambda,\n> lambdaBound, size));\n> >               if (maxDiff < threshold) {\n> >                       LOG(RPiAlsc, Debug)\n> >                               << \"Stop after \" << i + 1 << \" iterations\";\n> > @@ -741,39 +781,44 @@ static void runMatrixIterations(double const\n> C[XY], double lambda[XY],\n> >               lastMaxDiff = maxDiff;\n> >       }\n> >       /* We're going to normalise the lambdas so the total average is 1.\n> */\n> > -     reaverage({ lambda, XY });\n> > +     reaverage(lambda);\n> >  }\n> >\n> > -static void addLuminanceRb(double result[XY], double const lambda[XY],\n> > -                        double const luminanceLut[XY],\n> > +static void addLuminanceRb(std::vector<double> &result, const\n> std::vector<double> &lambda,\n> > +                        const std::vector<double> &luminanceLut,\n> >                          double luminanceStrength)\n> >  {\n> > -     for (int i = 0; i < XY; i++)\n> > +     for (unsigned int i = 0; i < result.size(); i++)\n> >               result[i] = lambda[i] * ((luminanceLut[i] - 1) *\n> luminanceStrength + 1);\n> >  }\n> >\n> > -static void addLuminanceG(double result[XY], double lambda,\n> > -                       double const luminanceLut[XY],\n> > +static void addLuminanceG(std::vector<double> &result, double lambda,\n> > +                       const std::vector<double> &luminanceLut,\n> >                         double luminanceStrength)\n> >  {\n> > -     for (int i = 0; i < XY; i++)\n> > +     for (unsigned int i = 0; i < result.size(); i++)\n> >               result[i] = lambda * ((luminanceLut[i] - 1) *\n> luminanceStrength + 1);\n> >  }\n> >\n> > -void addLuminanceToTables(double results[3][Y][X], double const\n> lambdaR[XY],\n> > -                       double lambdaG, double const lambdaB[XY],\n> > -                       double const luminanceLut[XY],\n> > +void addLuminanceToTables(std::array<std::vector<double>, 3> &results,\n> > +                       const std::vector<double> &lambdaR,\n> > +                       double lambdaG, const std::vector<double>\n> &lambdaB,\n> > +                       const std::vector<double> &luminanceLut,\n> >                         double luminanceStrength)\n> >  {\n> > -     addLuminanceRb((double *)results[0], lambdaR, luminanceLut,\n> luminanceStrength);\n> > -     addLuminanceG((double *)results[1], lambdaG, luminanceLut,\n> luminanceStrength);\n> > -     addLuminanceRb((double *)results[2], lambdaB, luminanceLut,\n> luminanceStrength);\n> > -     normalise((double *)results, 3 * XY);\n> > +     addLuminanceRb(results[0], lambdaR, luminanceLut,\n> luminanceStrength);\n> > +     addLuminanceG(results[1], lambdaG, luminanceLut,\n> luminanceStrength);\n> > +     addLuminanceRb(results[2], lambdaB, luminanceLut,\n> luminanceStrength);\n> > +     for (auto &r : results)\n> > +             normalise(r);\n> >  }\n> >\n> >  void Alsc::doAlsc()\n> >  {\n> > -     double cr[XY], cb[XY], wr[XY][4], wb[XY][4], calTableR[XY],\n> calTableB[XY], calTableTmp[XY];\n> > +     std::vector<double> &cr = tmpC_[0], &cb = tmpC_[1], &calTableR =\n> tmpC_[2],\n> > +                         &calTableB = tmpC_[3], &calTableTmp = tmpC_[4];\n> > +     std::vector<std::array<double, 4>> &wr = tmpM_[0], &wb = tmpM_[1],\n> &M = tmpM_[2];\n> > +\n> >       /*\n> >        * Calculate our R/B (\"Cr\"/\"Cb\") colour statistics, and assess\n> which are\n> >        * usable.\n> > @@ -784,9 +829,9 @@ void Alsc::doAlsc()\n> >        * case the camera mode is not full-frame.\n> >        */\n> >       getCalTable(ct_, config_.calibrationsCr, calTableTmp);\n> > -     resampleCalTable(calTableTmp, cameraMode_, calTableR);\n> > +     resampleCalTable(calTableTmp, cameraMode_, config_.tableSize,\n> calTableR);\n> >       getCalTable(ct_, config_.calibrationsCb, calTableTmp);\n> > -     resampleCalTable(calTableTmp, cameraMode_, calTableB);\n> > +     resampleCalTable(calTableTmp, cameraMode_, config_.tableSize,\n> calTableB);\n> >       /*\n> >        * You could print out the cal tables for this image here, if\n> you're\n> >        * tuning the algorithm...\n> > @@ -796,13 +841,13 @@ void Alsc::doAlsc()\n> >       applyCalTable(calTableR, cr);\n> >       applyCalTable(calTableB, cb);\n> >       /* Compute weights between zones. */\n> > -     computeW(cr, config_.sigmaCr, wr);\n> > -     computeW(cb, config_.sigmaCb, wb);\n> > +     computeW(cr, config_.sigmaCr, wr, config_.tableSize);\n> > +     computeW(cb, config_.sigmaCb, wb, config_.tableSize);\n> >       /* Run Gauss-Seidel iterations over the resulting matrix, for R\n> and B. */\n> > -     runMatrixIterations(cr, lambdaR_, wr, config_.omega, config_.nIter,\n> > -                         config_.threshold, config_.lambdaBound);\n> > -     runMatrixIterations(cb, lambdaB_, wb, config_.omega, config_.nIter,\n> > -                         config_.threshold, config_.lambdaBound);\n> > +     runMatrixIterations(cr, lambdaR_, wr, M, config_.omega,\n> config_.nIter,\n> > +                         config_.threshold, config_.lambdaBound,\n> config_.tableSize);\n> > +     runMatrixIterations(cb, lambdaB_, wb, M, config_.omega,\n> config_.nIter,\n> > +                         config_.threshold, config_.lambdaBound,\n> config_.tableSize);\n> >       /*\n> >        * Fold the calibrated gains into our final lambda values. (Note\n> that on\n> >        * the next run, we re-start with the lambda values that don't\n> have the\n> > diff --git a/src/ipa/raspberrypi/controller/rpi/alsc.h\n> b/src/ipa/raspberrypi/controller/rpi/alsc.h\n> > index 9167c9ffa2e3..85e998db40e9 100644\n> > --- a/src/ipa/raspberrypi/controller/rpi/alsc.h\n> > +++ b/src/ipa/raspberrypi/controller/rpi/alsc.h\n> > @@ -6,9 +6,13 @@\n> >   */\n> >  #pragma once\n> >\n> > +#include <array>\n> >  #include <mutex>\n> >  #include <condition_variable>\n> >  #include <thread>\n> > +#include <vector>\n> > +\n> > +#include <libcamera/geometry.h>\n> >\n> >  #include \"../algorithm.h\"\n> >  #include \"../alsc_status.h\"\n> > @@ -20,7 +24,7 @@ namespace RPiController {\n> >\n> >  struct AlscCalibration {\n> >       double ct;\n> > -     double table[AlscCellsX * AlscCellsY];\n> > +     std::vector<double> table;\n> >  };\n> >\n> >  struct AlscConfig {\n> > @@ -36,13 +40,14 @@ struct AlscConfig {\n> >       uint16_t minG;\n> >       double omega;\n> >       uint32_t nIter;\n> > -     double luminanceLut[AlscCellsX * AlscCellsY];\n> > +     std::vector<double> luminanceLut;\n> >       double luminanceStrength;\n> >       std::vector<AlscCalibration> calibrationsCr;\n> >       std::vector<AlscCalibration> calibrationsCb;\n> >       double defaultCt; /* colour temperature if no metadata found */\n> >       double threshold; /* iteration termination threshold */\n> >       double lambdaBound; /* upper/lower bound for lambda from a value\n> of 1 */\n> > +     libcamera::Size tableSize;\n> >  };\n> >\n> >  class Alsc : public Algorithm\n> > @@ -62,7 +67,7 @@ private:\n> >       AlscConfig config_;\n> >       bool firstTime_;\n> >       CameraMode cameraMode_;\n> > -     double luminanceTable_[AlscCellsX * AlscCellsY];\n> > +     std::vector<double> luminanceTable_;\n> >       std::thread asyncThread_;\n> >       void asyncFunc(); /* asynchronous thread function */\n> >       std::mutex mutex_;\n> > @@ -88,8 +93,8 @@ private:\n> >       int frameCount_;\n> >       /* counts up to startupFrames for Process function */\n> >       int frameCount2_;\n> > -     double syncResults_[3][AlscCellsY][AlscCellsX];\n> > -     double prevSyncResults_[3][AlscCellsY][AlscCellsX];\n> > +     std::array<std::vector<double>, 3> syncResults_;\n> > +     std::array<std::vector<double>, 3> prevSyncResults_;\n> >       void waitForAysncThread();\n> >       /*\n> >        * The following are for the asynchronous thread to use, though\n> the main\n> > @@ -100,12 +105,16 @@ private:\n> >       void fetchAsyncResults();\n> >       double ct_;\n> >       RgbyRegions statistics_;\n> > -     double asyncResults_[3][AlscCellsY][AlscCellsX];\n> > -     double asyncLambdaR_[AlscCellsX * AlscCellsY];\n> > -     double asyncLambdaB_[AlscCellsX * AlscCellsY];\n> > +     std::array<std::vector<double>, 3> asyncResults_;\n> > +     std::vector<double> asyncLambdaR_;\n> > +     std::vector<double> asyncLambdaB_;\n> >       void doAlsc();\n> > -     double lambdaR_[AlscCellsX * AlscCellsY];\n> > -     double lambdaB_[AlscCellsX * AlscCellsY];\n> > +     std::vector<double> lambdaR_;\n> > +     std::vector<double> lambdaB_;\n> > +\n> > +     /* Temporaries for the computations */\n> > +     std::array<std::vector<double>, 5> tmpC_;\n> > +     std::array<std::vector<std::array<double, 4>>, 3> tmpM_;\n> >  };\n> >\n> >  } /* namespace RPiController */\n> > diff --git a/src/ipa/raspberrypi/raspberrypi.cpp\n> b/src/ipa/raspberrypi/raspberrypi.cpp\n> > index b64cb96e2dde..0fa79bb4af41 100644\n> > --- a/src/ipa/raspberrypi/raspberrypi.cpp\n> > +++ b/src/ipa/raspberrypi/raspberrypi.cpp\n> > @@ -13,6 +13,7 @@\n> >  #include <math.h>\n> >  #include <stdint.h>\n> >  #include <string.h>\n> > +#include <vector>\n>\n> Should this be moved after <sys/mman.h> ?\n>\n\nAck.\n\n\n>\n> >  #include <sys/mman.h>\n> >\n> >  #include <linux/bcm2835-isp.h>\n> > @@ -174,7 +175,7 @@ private:\n> >       void applyDPC(const struct DpcStatus *dpcStatus, ControlList\n> &ctrls);\n> >       void applyLS(const struct AlscStatus *lsStatus, ControlList\n> &ctrls);\n> >       void applyAF(const struct AfStatus *afStatus, ControlList\n> &lensCtrls);\n> > -     void resampleTable(uint16_t dest[], double const src[12][16], int\n> destW, int destH);\n> > +     void resampleTable(uint16_t dest[], const std::vector<double>\n> &src, int destW, int destH);\n> >\n> >       std::map<unsigned int, MappedFrameBuffer> buffers_;\n> >\n> > @@ -1768,7 +1769,7 @@ void IPARPi::applyAF(const struct AfStatus\n> *afStatus, ControlList &lensCtrls)\n> >   * Resamples a 16x12 table with central sampling to destW x destH with\n> corner\n>\n> The 16x12 size is mentioned here\n\n\nThe raspberrypi.cpp file is platform specific as it translates the platform\nindependent params from the algorithms into HW pipeline specific params.  As\nsuch, I think it's ok to reference the actual grid values in this file.\nDitto\nfor the reference below.\n\n\n>\n> >   * sampling.\n> >   */\n> > -void IPARPi::resampleTable(uint16_t dest[], double const src[12][16],\n> > +void IPARPi::resampleTable(uint16_t dest[], const std::vector<double>\n> &src,\n> >                          int destW, int destH)\n> >  {\n> >       /*\n> > @@ -1793,8 +1794,8 @@ void IPARPi::resampleTable(uint16_t dest[], double\n> const src[12][16],\n> >               double yf = y - yLo;\n> >               int yHi = yLo < 11 ? yLo + 1 : 11;\n> >               yLo = yLo > 0 ? yLo : 0;\n> > -             double const *rowAbove = src[yLo];\n> > -             double const *rowBelow = src[yHi];\n> > +             double const *rowAbove = src.data() + yLo * 16;\n> > +             double const *rowBelow = src.data() + yHi * 16;\n>\n> As well as assumed here. Also the previous index was yLo and the new\n> one (yLo * 16). Is it ok ?\n>\n\nYes this is fine.  The platform independent algorithm code writes out a\nflat 1D\narray of 16*12 values.  This bit of code here interprets that as a 2D table\nagain.\n\nRegards,\nNaush\n\n\n>\n> >               for (int i = 0; i < destW; i++) {\n> >                       double above = rowAbove[xLo[i]] * (1 - xf[i]) +\n> rowAbove[xHi[i]] * xf[i];\n> >                       double below = rowBelow[xLo[i]] * (1 - xf[i]) +\n> rowBelow[xHi[i]] * xf[i];\n> > --\n> > 2.34.1\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 957D6BD160\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 27 Mar 2023 10:42:37 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id E0A9A62722;\n\tMon, 27 Mar 2023 12:42:36 +0200 (CEST)","from mail-yb1-xb32.google.com (mail-yb1-xb32.google.com\n\t[IPv6:2607:f8b0:4864:20::b32])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id C0308626D7\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 27 Mar 2023 12:42:34 +0200 (CEST)","by mail-yb1-xb32.google.com with SMTP id b18so9928941ybp.1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 27 Mar 2023 03:42:34 -0700 (PDT)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1679913757;\n\tbh=QTdkLvDkq14j6JywC/okWxX8VxZblHAOh5FSPNfonqg=;\n\th=References:In-Reply-To:Date:To:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc:\n\tFrom;\n\tb=UmmPonIo+nC0oNh1mmRvZgNpWzh0wb8x3ZCAvXWrkTHhaEF2IGz+2iKw5q2AsihO0\n\ta9WMSAcLpkPmCWbmHAsysuh/4covwhPnwsspKe18mBToiRpkmXtXIWPQNrtBWN5ieX\n\tTUyoIeaRWrTEXHY96pvYgt7dgzsjzRhy0Ut02u/AtUXLawdolFzf7AM2cYbCexAhf/\n\tEoNjYvAj5kXVP1ZsM2BTsk7ZmYpnVHQjP3qb1uHRUyz8Vzlosj1IjxU+Rv6R+x8LIj\n\tz7gd7rI/0fIETdnKe7EsSoAx5aA/swYEI+wXLr3fJsHx9kmI8SJJwuoFf4msh88qpf\n\t8x7lLU4auOe5A==","v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google; t=1679913753;\n\th=cc:to:subject:message-id:date:from:in-reply-to:references\n\t:mime-version:from:to:cc:subject:date:message-id:reply-to;\n\tbh=ARhxX8Ml1ZRHOuktuV5z/f0YZ6mBzSSfBZq4xAliY5A=;\n\tb=mBVTv4c+rdIAYm1kkOKhaFAsp18vde7OSj4oMPxAWBASZW5vVSBX++5CoO01Lx8eEK\n\t4h2bwnOQ9TxGCWPAWbEtezUynhq0t/nqxXVKz0RnV2oGIstskLk3eBGX7N6hAIiEtsDV\n\t3R0BbU8CaHKj5YHv1hIc7JHmziJPVmZhoKi61I28FgYaMNTA78rmcVzeFDGl1EmujVgu\n\ti3TUf6IcFYKrUeg9uQZ9gkIMwuCMixCtzQP8kD6Df91zzP1LngGtdciVWdAvQFZw8rFR\n\t//HDHzhmdyJJ3KSBiAGnvuuDhsf81li9vEfvYAVgv+mNC1WaBoE55s5se4VX/lhurCpj\n\tJviA=="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key; \n\tunprotected) header.d=raspberrypi.com\n\theader.i=@raspberrypi.com\n\theader.b=\"mBVTv4c+\"; dkim-atps=neutral","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20210112; t=1679913753;\n\th=cc:to:subject:message-id:date:from:in-reply-to:references\n\t:mime-version:x-gm-message-state:from:to:cc:subject:date:message-id\n\t:reply-to;\n\tbh=ARhxX8Ml1ZRHOuktuV5z/f0YZ6mBzSSfBZq4xAliY5A=;\n\tb=H/NUU1gdb3svdjsvt9dZ04JMkEAcJUZi/Fo5YmORzH+kjGlUFw1tRxddVNHcTGQ3eK\n\tzJlP7XMQsnrSdmocT32X/Vy7jrym8hT2QYtPJgm2otuiqLJafHSYlRubfZ1lMEd20OGw\n\tmalhSvYiYvxOrHPUFWGnbL5YMKgNF3mi4vik50Jh8+0NptIhXLU9kYMjrAj5mxRt/FKz\n\tACjGSpR1b/6tYB8U1bzyCIAn7mSBUgWmVtJ5fpkDERc5xfW1uyhyKuvYox9pYlx2yCOJ\n\tJ33oBWse0u5eF6/9E1bsCcBAZftzP+kq+41YfSVBcuxwBuF/Or5NoBKpHZJ1DJRKPjMc\n\txThQ==","X-Gm-Message-State":"AAQBX9d4djI06/e3elBoxUC/ZGwxc9JEeM5K+48RV05sFuIbo/KAbHRy\n\tPHsJBk0L+jnCF6OLHbi7MYWuHBtffAzkTRSQs7K4XlspEviG53P7BxnqQw==","X-Google-Smtp-Source":"AKy350bSIqC2vl0HEoVwTXiLNqL48R6RRGbId8PZGtJXEezRVQLY1tyO43SDRlmynDSPMh7vEKBFhIIcdOFf6qPPTX4=","X-Received":"by 2002:a05:6902:a8c:b0:b7b:fb15:8685 with SMTP id\n\tcd12-20020a0569020a8c00b00b7bfb158685mr852531ybb.9.1679913753385;\n\tMon, 27 Mar 2023 03:42:33 -0700 (PDT)","MIME-Version":"1.0","References":"<20230322130612.5208-1-naush@raspberrypi.com>\n\t<20230322130612.5208-4-naush@raspberrypi.com>\n\t<20230324083008.untsxdgb63klpere@uno.localdomain>","In-Reply-To":"<20230324083008.untsxdgb63klpere@uno.localdomain>","Date":"Mon, 27 Mar 2023 11:42:17 +0100","Message-ID":"<CAEmqJPonGg1-WDNCFY-xnFTC47Ky9y+cPOn4WByseR=7fiuSHA@mail.gmail.com>","To":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","Content-Type":"multipart/alternative; boundary=\"00000000000086355f05f7df66ca\"","Subject":"Re: [libcamera-devel] [PATCH v1 03/10] ipa: raspberrypi: Generalise\n\tthe ALSC 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>","From":"Naushir Patuck via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>","Reply-To":"Naushir Patuck <naush@raspberrypi.com>","Cc":"libcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]