[{"id":26793,"web_url":"https://patchwork.libcamera.org/comment/26793/","msgid":"<168004569475.3713142.14150628823010794462@Monstersaurus>","date":"2023-03-28T23:21:34","subject":"Re: [libcamera-devel] [PATCH v2 03/10] ipa: raspberrypi: Generalise\n\tthe ALSC algorithm","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"Quoting Naushir Patuck via libcamera-devel (2023-03-27 13:20:23)\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> Reviewed-off-by: David Plowman <david.plowman@raspberrypi.com>\n\nWell this has taken me at least 2 or 3 reads to parse. And I haven't\nseen anything specific that worries me overall.\n\nThe only thing to check below is\n - Alsc::read using awbRegions\n\nAnd I almost tripped up on the IIR filter - but I think it's fine, it\nwas just me mis-reading it.\n\nIf the awb region is fine:\n\nReviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n\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> -       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 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 libcamera::YamlObject &params,\n> +                      const Size &size)\n>  {\n> +       /* These must be signed ints for the co-ordinate calculations 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 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 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 table\";\n>                 return -EINVAL;\n>         }\n> @@ -101,7 +102,7 @@ static int readLut(double *lut, const 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 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 ct \"\n>                                         << ct << \" in \" << name;\n> @@ -127,6 +128,7 @@ static int 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 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 \" << ct;\n>                 }\n> @@ -144,6 +146,7 @@ static int readCalibrations(std::vector<AlscCalibration> &calibrations,\n>  \n>  int Alsc::read(const libcamera::YamlObject &params)\n>  {\n> +       config_.tableSize = getHardwareConfig().awbRegions;\n\nI suspect this is fine, but is the ALSC always based on the same\nregions as the AWB ?\n\n\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 = params[\"n_iter\"].get<uint32_t>(config_.tableSize.width + 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 * 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, config_.tableSize);\n>         else if (params.contains(\"luminance_lut\"))\n> -               ret = readLut(config_.luminanceLut, params[\"luminance_lut\"]);\n> +               ret = readLut(config_.luminanceLut, 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, \"calibrations_Cr\");\n> +       ret = readCalibrations(config_.calibrationsCr, params, \"calibrations_Cr\",\n> +                              config_.tableSize);\n>         if (ret)\n>                 return ret;\n> -       ret = readCalibrations(config_.calibrationsCb, params, \"calibrations_Cb\");\n> +       ret = readCalibrations(config_.calibrationsCb, params, \"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 &calibrations,\n> -                       double calTable[XY]);\n> -static void resampleCalTable(double const calTableIn[XY], CameraMode const &cameraMode,\n> -                            double calTableOut[XY]);\n> -static void compensateLambdasForCal(double const calTable[XY], double const oldLambdas[XY],\n> -                                   double newLambdas[XY]);\n> -static void addLuminanceToTables(double results[3][Y][X], double const lambdaR[XY], double lambdaG,\n> -                                double const lambdaB[XY], double const luminanceLut[XY],\n> +                       std::vector<double> &calTable);\n> +static void resampleCalTable(const std::vector<double> &calTableIn, CameraMode const &cameraMode,\n> +                            const Size &size, std::vector<double> &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> &results,\n> +                                const std::vector<double> &lambdaR, 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 * 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 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 it's\n>          * fixed so we can simply do it up front here.\n>          */\n> -       resampleCalTable(config_.luminanceLut, cameraMode_, luminanceTable_);\n> +       resampleCalTable(config_.luminanceLut, cameraMode_, 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 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 = tmpC_[1], &calTableTmp = tmpC_[2];\n>                 getCalTable(ct_, config_.calibrationsCr, calTableTmp);\n> -               resampleCalTable(calTableTmp, cameraMode_, calTableR);\n> +               resampleCalTable(calTableTmp, cameraMode_, config_.tableSize, calTableR);\n>                 getCalTable(ct_, config_.calibrationsCb, calTableTmp);\n> -               resampleCalTable(calTableTmp, cameraMode_, calTableB);\n> +               resampleCalTable(calTableTmp, cameraMode_, config_.tableSize, calTableB);\n>                 compensateLambdasForCal(calTableR, lambdaR_, asyncLambdaR_);\n>                 compensateLambdasForCal(calTableB, lambdaB_, asyncLambdaB_);\n>                 addLuminanceToTables(syncResults_, asyncLambdaR_, 1.0, asyncLambdaB_,\n>                                      luminanceTable_, config_.luminanceStrength);\n> -               memcpy(prevSyncResults_, syncResults_, sizeof(prevSyncResults_));\n> +               prevSyncResults_ = syncResults_;\n>                 framePhase_ = config_.framePeriod; /* run the algo again 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, 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, 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 * config_.tableSize.height, 1.0);\n> +               alscStatus.g.resize(config_.tableSize.width * config_.tableSize.height, 1.0);\n> +               alscStatus.b.resize(config_.tableSize.width * 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_) / 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 * syncResults_[j][i] + (1.0 - speed) * prevSyncResults_[j][i];\n\nI almost slipped up here thinking this read as \"X = Y\" ... but I\nmisread. Definitely using both 'syncResults_' 'prevSyncResults_' so I\nthink this is fine.\n\n\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 &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 * 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 * 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, 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 - ct0)) /\n> @@ -462,9 +486,13 @@ void getCalTable(double ct, 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 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, std::vector<double> &cr,\n> +                         std::vector<double> &cb, uint32_t minCount, 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 <= minG) {\n> @@ -530,33 +559,34 @@ static void calculateCrCb(const RgbyRegions &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, 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, 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 &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) : 0;\n>                 W[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> -                      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 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 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> -                                 double lambda[XY])\n> +static double computeLambdaBottom(int i, const std::vector<std::array<double, 4>> &M,\n> +                                 std::vector<double> &lambda, const 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>                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 std::vector<std::array<double, 4>> &M,\n> +                                      std::vector<double> &lambda, const 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 std::vector<std::array<double, 4>> &M,\n> +                                   std::vector<double> &lambda, const 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 std::vector<std::array<double, 4>> &M,\n> +                              std::vector<double> &lambda, const Size &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 std::vector<std::array<double, 4>> &M,\n> +                                 std::vector<double> &lambda, const Size &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>> &M, double omega,\n> +                             std::vector<double> &lambda, double 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 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 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 lambdaBound)\n> +static void runMatrixIterations(const std::vector<double> &C,\n> +                               std::vector<double> &lambda,\n> +                               const std::vector<std::array<double, 4>> &W,\n> +                               std::vector<std::array<double, 4>> &M, double omega,\n> +                               unsigned int nIter, double threshold, 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, lambdaBound));\n> +       for (unsigned int i = 0; i < nIter; i++) {\n> +               double maxDiff = fabs(gaussSeidel2Sor(M, omega, lambda, 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 C[XY], double lambda[XY],\n>                 lastMaxDiff = maxDiff;\n>         }\n>         /* We're going to normalise the lambdas so the total average is 1. */\n> -       reaverage({ lambda, XY });\n> +       reaverage(lambda);\n\nSomehow I thought lambda was a reserved keyword, but it doesn't seem to\nbe.\n\n\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 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) * 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) * luminanceStrength + 1);\n>  }\n>  \n> -void addLuminanceToTables(double results[3][Y][X], double const 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> &lambdaB,\n> +                         const std::vector<double> &luminanceLut,\n>                           double luminanceStrength)\n>  {\n> -       addLuminanceRb((double *)results[0], lambdaR, luminanceLut, luminanceStrength);\n> -       addLuminanceG((double *)results[1], lambdaG, luminanceLut, luminanceStrength);\n> -       addLuminanceRb((double *)results[2], lambdaB, luminanceLut, luminanceStrength);\n> -       normalise((double *)results, 3 * XY);\n> +       addLuminanceRb(results[0], lambdaR, luminanceLut, luminanceStrength);\n> +       addLuminanceG(results[1], lambdaG, luminanceLut, luminanceStrength);\n> +       addLuminanceRb(results[2], lambdaB, luminanceLut, 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], calTableB[XY], calTableTmp[XY];\n> +       std::vector<double> &cr = tmpC_[0], &cb = tmpC_[1], &calTableR = tmpC_[2],\n> +                           &calTableB = tmpC_[3], &calTableTmp = tmpC_[4];\n> +       std::vector<std::array<double, 4>> &wr = tmpM_[0], &wb = tmpM_[1], &M = tmpM_[2];\n> +\n>         /*\n>          * Calculate our R/B (\"Cr\"/\"Cb\") colour statistics, and assess 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, calTableR);\n>         getCalTable(ct_, config_.calibrationsCb, calTableTmp);\n> -       resampleCalTable(calTableTmp, cameraMode_, calTableB);\n> +       resampleCalTable(calTableTmp, cameraMode_, config_.tableSize, calTableB);\n>         /*\n>          * You could print out the cal tables for this image here, if 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 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, config_.nIter,\n> +                           config_.threshold, config_.lambdaBound, config_.tableSize);\n> +       runMatrixIterations(cb, lambdaB_, wb, M, config_.omega, config_.nIter,\n> +                           config_.threshold, config_.lambdaBound, config_.tableSize);\n>         /*\n>          * Fold the calibrated gains into our final lambda values. (Note that on\n>          * 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>         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 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 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 b/src/ipa/raspberrypi/raspberrypi.cpp\n> index b64cb96e2dde..257d862e07b6 100644\n> --- a/src/ipa/raspberrypi/raspberrypi.cpp\n> +++ b/src/ipa/raspberrypi/raspberrypi.cpp\n> @@ -14,6 +14,7 @@\n>  #include <stdint.h>\n>  #include <string.h>\n>  #include <sys/mman.h>\n> +#include <vector>\n>  \n>  #include <linux/bcm2835-isp.h>\n>  \n> @@ -174,7 +175,7 @@ private:\n>         void applyDPC(const struct DpcStatus *dpcStatus, ControlList &ctrls);\n>         void applyLS(const struct AlscStatus *lsStatus, ControlList &ctrls);\n>         void applyAF(const struct AfStatus *afStatus, ControlList &lensCtrls);\n> -       void resampleTable(uint16_t dest[], double const src[12][16], int destW, int destH);\n> +       void resampleTable(uint16_t dest[], const std::vector<double> &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 *afStatus, ControlList &lensCtrls)\n>   * Resamples a 16x12 table with central sampling to destW x destH with corner\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>                            int destW, int destH)\n>  {\n>         /*\n> @@ -1793,8 +1794,8 @@ void IPARPi::resampleTable(uint16_t dest[], double 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\nIs this 'stride' always going to be 16?\n\n>                 for (int i = 0; i < destW; i++) {\n>                         double above = rowAbove[xLo[i]] * (1 - xf[i]) + rowAbove[xHi[i]] * xf[i];\n>                         double 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 719A7BD160\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 28 Mar 2023 23:21:40 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id A82E062740;\n\tWed, 29 Mar 2023 01:21:39 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id E62DA626D7\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 29 Mar 2023 01:21:37 +0200 (CEST)","from pendragon.ideasonboard.com\n\t(aztw-30-b2-v4wan-166917-cust845.vm26.cable.virginm.net\n\t[82.37.23.78])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 5810F6E1;\n\tWed, 29 Mar 2023 01:21:37 +0200 (CEST)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1680045699;\n\tbh=wk4W5YCSyIWnZ3Hh3b8KHtYV7hjjuvv2e3JZUsgXzCg=;\n\th=In-Reply-To:References:To:Date:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:\n\tFrom;\n\tb=1/ak0eARxjCOcGlQi0ZcCA5w9fSbqvOWibOuKSfwXtU6lVeOCO8Y6mXolsP2P+hhw\n\t0dp6m/TKJ//voyyx1Nf5htaIxNXA2WKYtBblZ9WOwk2jhQEKWADwUhis7fKSZEfJpi\n\t+YT40OJZZFQlyQlgp1TNjVGG7uo1vxC17RF08VB6RARIi30t5I6TLllLc4Y3dPpqCe\n\tWJba2AN3pab1inPWesuqx9+1UjMOKVaQ2BaDHJlj+T/1+WLOQ6SJS66J/eM/i2t0gi\n\tKzY8zh/NfNL/EoXt6NhzsbQkZuQrhgUlVbcJVOGjSEA7qjRFiTLVAAsg0+HpeD9hAI\n\ttLzU3G/0x/bbA==","v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1680045697;\n\tbh=wk4W5YCSyIWnZ3Hh3b8KHtYV7hjjuvv2e3JZUsgXzCg=;\n\th=In-Reply-To:References:Subject:From:To:Date:From;\n\tb=tkUmIBFtBzU6HzzHX6MIEc3ANReBlhLOK5+Yney+vyT5LXU0014KDxk3qMkECttYR\n\tlsD6sCqok/XalJC3NuFzdi0CWfKHZq3ILrjfNwmsh7G+kffd4ebeXQoXkCrLGMHXK4\n\t3gQVAQbnfksldTNs1iwiLygLSBO2i9ic6qv8SZ2A="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"tkUmIBFt\"; dkim-atps=neutral","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<20230327122030.11756-4-naush@raspberrypi.com>","References":"<20230327122030.11756-1-naush@raspberrypi.com>\n\t<20230327122030.11756-4-naush@raspberrypi.com>","To":"Naushir Patuck <naush@raspberrypi.com>,\n\tlibcamera-devel@lists.libcamera.org","Date":"Wed, 29 Mar 2023 00:21:34 +0100","Message-ID":"<168004569475.3713142.14150628823010794462@Monstersaurus>","User-Agent":"alot/0.10","Subject":"Re: [libcamera-devel] [PATCH v2 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":"Kieran Bingham via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>","Reply-To":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":26794,"web_url":"https://patchwork.libcamera.org/comment/26794/","msgid":"<CAEmqJPp6cUBfm0b4xRMHv=MZJG2c1hn5UUfq1so-ehBHHJ+X2w@mail.gmail.com>","date":"2023-03-29T07:42:54","subject":"Re: [libcamera-devel] [PATCH v2 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 Kieran,\n\nThanks for the late night feedback! :-)\n\nOn Wed, 29 Mar 2023 at 00:21, Kieran Bingham\n<kieran.bingham@ideasonboard.com> wrote:\n>\n> Quoting Naushir Patuck via libcamera-devel (2023-03-27 13:20:23)\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> > Reviewed-off-by: David Plowman <david.plowman@raspberrypi.com>\n>\n> Well this has taken me at least 2 or 3 reads to parse. And I haven't\n> seen anything specific that worries me overall.\n>\n> The only thing to check below is\n>  - Alsc::read using awbRegions\n\nThis is correct, ALSC works on AWB region stats.  Hence the usage here.\n\nRegards,\nNaush\n\n\n>\n> And I almost tripped up on the IIR filter - but I think it's fine, it\n> was just me mis-reading it.\n>\n> If the awb region is fine:\n>\n> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n>\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> > -       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 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 libcamera::YamlObject &params,\n> > +                      const Size &size)\n> >  {\n> > +       /* These must be signed ints for the co-ordinate calculations 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 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 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 table\";\n> >                 return -EINVAL;\n> >         }\n> > @@ -101,7 +102,7 @@ static int readLut(double *lut, const 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 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 ct \"\n> >                                         << ct << \" in \" << name;\n> > @@ -127,6 +128,7 @@ static int 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 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 \" << ct;\n> >                 }\n> > @@ -144,6 +146,7 @@ static int readCalibrations(std::vector<AlscCalibration> &calibrations,\n> >\n> >  int Alsc::read(const libcamera::YamlObject &params)\n> >  {\n> > +       config_.tableSize = getHardwareConfig().awbRegions;\n>\n> I suspect this is fine, but is the ALSC always based on the same\n> regions as the AWB ?\n>\n>\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 = params[\"n_iter\"].get<uint32_t>(config_.tableSize.width + 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 * 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, config_.tableSize);\n> >         else if (params.contains(\"luminance_lut\"))\n> > -               ret = readLut(config_.luminanceLut, params[\"luminance_lut\"]);\n> > +               ret = readLut(config_.luminanceLut, 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, \"calibrations_Cr\");\n> > +       ret = readCalibrations(config_.calibrationsCr, params, \"calibrations_Cr\",\n> > +                              config_.tableSize);\n> >         if (ret)\n> >                 return ret;\n> > -       ret = readCalibrations(config_.calibrationsCb, params, \"calibrations_Cb\");\n> > +       ret = readCalibrations(config_.calibrationsCb, params, \"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 &calibrations,\n> > -                       double calTable[XY]);\n> > -static void resampleCalTable(double const calTableIn[XY], CameraMode const &cameraMode,\n> > -                            double calTableOut[XY]);\n> > -static void compensateLambdasForCal(double const calTable[XY], double const oldLambdas[XY],\n> > -                                   double newLambdas[XY]);\n> > -static void addLuminanceToTables(double results[3][Y][X], double const lambdaR[XY], double lambdaG,\n> > -                                double const lambdaB[XY], double const luminanceLut[XY],\n> > +                       std::vector<double> &calTable);\n> > +static void resampleCalTable(const std::vector<double> &calTableIn, CameraMode const &cameraMode,\n> > +                            const Size &size, std::vector<double> &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> &results,\n> > +                                const std::vector<double> &lambdaR, 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 * 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 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 it's\n> >          * fixed so we can simply do it up front here.\n> >          */\n> > -       resampleCalTable(config_.luminanceLut, cameraMode_, luminanceTable_);\n> > +       resampleCalTable(config_.luminanceLut, cameraMode_, 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 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 = tmpC_[1], &calTableTmp = tmpC_[2];\n> >                 getCalTable(ct_, config_.calibrationsCr, calTableTmp);\n> > -               resampleCalTable(calTableTmp, cameraMode_, calTableR);\n> > +               resampleCalTable(calTableTmp, cameraMode_, config_.tableSize, calTableR);\n> >                 getCalTable(ct_, config_.calibrationsCb, calTableTmp);\n> > -               resampleCalTable(calTableTmp, cameraMode_, calTableB);\n> > +               resampleCalTable(calTableTmp, cameraMode_, config_.tableSize, calTableB);\n> >                 compensateLambdasForCal(calTableR, lambdaR_, asyncLambdaR_);\n> >                 compensateLambdasForCal(calTableB, lambdaB_, asyncLambdaB_);\n> >                 addLuminanceToTables(syncResults_, asyncLambdaR_, 1.0, asyncLambdaB_,\n> >                                      luminanceTable_, config_.luminanceStrength);\n> > -               memcpy(prevSyncResults_, syncResults_, sizeof(prevSyncResults_));\n> > +               prevSyncResults_ = syncResults_;\n> >                 framePhase_ = config_.framePeriod; /* run the algo again 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, 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, 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 * config_.tableSize.height, 1.0);\n> > +               alscStatus.g.resize(config_.tableSize.width * config_.tableSize.height, 1.0);\n> > +               alscStatus.b.resize(config_.tableSize.width * 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_) / 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 * syncResults_[j][i] + (1.0 - speed) * prevSyncResults_[j][i];\n>\n> I almost slipped up here thinking this read as \"X = Y\" ... but I\n> misread. Definitely using both 'syncResults_' 'prevSyncResults_' so I\n> think this is fine.\n>\n>\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 &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 * 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 * 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, 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 - ct0)) /\n> > @@ -462,9 +486,13 @@ void getCalTable(double ct, 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 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, std::vector<double> &cr,\n> > +                         std::vector<double> &cb, uint32_t minCount, 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 <= minG) {\n> > @@ -530,33 +559,34 @@ static void calculateCrCb(const RgbyRegions &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, 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, 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 &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) : 0;\n> >                 W[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> > -                      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 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 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> > -                                 double lambda[XY])\n> > +static double computeLambdaBottom(int i, const std::vector<std::array<double, 4>> &M,\n> > +                                 std::vector<double> &lambda, const 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> >                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 std::vector<std::array<double, 4>> &M,\n> > +                                      std::vector<double> &lambda, const 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 std::vector<std::array<double, 4>> &M,\n> > +                                   std::vector<double> &lambda, const 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 std::vector<std::array<double, 4>> &M,\n> > +                              std::vector<double> &lambda, const Size &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 std::vector<std::array<double, 4>> &M,\n> > +                                 std::vector<double> &lambda, const Size &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>> &M, double omega,\n> > +                             std::vector<double> &lambda, double 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 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 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 lambdaBound)\n> > +static void runMatrixIterations(const std::vector<double> &C,\n> > +                               std::vector<double> &lambda,\n> > +                               const std::vector<std::array<double, 4>> &W,\n> > +                               std::vector<std::array<double, 4>> &M, double omega,\n> > +                               unsigned int nIter, double threshold, 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, lambdaBound));\n> > +       for (unsigned int i = 0; i < nIter; i++) {\n> > +               double maxDiff = fabs(gaussSeidel2Sor(M, omega, lambda, 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 C[XY], double lambda[XY],\n> >                 lastMaxDiff = maxDiff;\n> >         }\n> >         /* We're going to normalise the lambdas so the total average is 1. */\n> > -       reaverage({ lambda, XY });\n> > +       reaverage(lambda);\n>\n> Somehow I thought lambda was a reserved keyword, but it doesn't seem to\n> be.\n>\n>\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 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) * 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) * luminanceStrength + 1);\n> >  }\n> >\n> > -void addLuminanceToTables(double results[3][Y][X], double const 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> &lambdaB,\n> > +                         const std::vector<double> &luminanceLut,\n> >                           double luminanceStrength)\n> >  {\n> > -       addLuminanceRb((double *)results[0], lambdaR, luminanceLut, luminanceStrength);\n> > -       addLuminanceG((double *)results[1], lambdaG, luminanceLut, luminanceStrength);\n> > -       addLuminanceRb((double *)results[2], lambdaB, luminanceLut, luminanceStrength);\n> > -       normalise((double *)results, 3 * XY);\n> > +       addLuminanceRb(results[0], lambdaR, luminanceLut, luminanceStrength);\n> > +       addLuminanceG(results[1], lambdaG, luminanceLut, luminanceStrength);\n> > +       addLuminanceRb(results[2], lambdaB, luminanceLut, 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], calTableB[XY], calTableTmp[XY];\n> > +       std::vector<double> &cr = tmpC_[0], &cb = tmpC_[1], &calTableR = tmpC_[2],\n> > +                           &calTableB = tmpC_[3], &calTableTmp = tmpC_[4];\n> > +       std::vector<std::array<double, 4>> &wr = tmpM_[0], &wb = tmpM_[1], &M = tmpM_[2];\n> > +\n> >         /*\n> >          * Calculate our R/B (\"Cr\"/\"Cb\") colour statistics, and assess 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, calTableR);\n> >         getCalTable(ct_, config_.calibrationsCb, calTableTmp);\n> > -       resampleCalTable(calTableTmp, cameraMode_, calTableB);\n> > +       resampleCalTable(calTableTmp, cameraMode_, config_.tableSize, calTableB);\n> >         /*\n> >          * You could print out the cal tables for this image here, if 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 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, config_.nIter,\n> > +                           config_.threshold, config_.lambdaBound, config_.tableSize);\n> > +       runMatrixIterations(cb, lambdaB_, wb, M, config_.omega, config_.nIter,\n> > +                           config_.threshold, config_.lambdaBound, config_.tableSize);\n> >         /*\n> >          * Fold the calibrated gains into our final lambda values. (Note that on\n> >          * 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> >         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 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 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 b/src/ipa/raspberrypi/raspberrypi.cpp\n> > index b64cb96e2dde..257d862e07b6 100644\n> > --- a/src/ipa/raspberrypi/raspberrypi.cpp\n> > +++ b/src/ipa/raspberrypi/raspberrypi.cpp\n> > @@ -14,6 +14,7 @@\n> >  #include <stdint.h>\n> >  #include <string.h>\n> >  #include <sys/mman.h>\n> > +#include <vector>\n> >\n> >  #include <linux/bcm2835-isp.h>\n> >\n> > @@ -174,7 +175,7 @@ private:\n> >         void applyDPC(const struct DpcStatus *dpcStatus, ControlList &ctrls);\n> >         void applyLS(const struct AlscStatus *lsStatus, ControlList &ctrls);\n> >         void applyAF(const struct AfStatus *afStatus, ControlList &lensCtrls);\n> > -       void resampleTable(uint16_t dest[], double const src[12][16], int destW, int destH);\n> > +       void resampleTable(uint16_t dest[], const std::vector<double> &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 *afStatus, ControlList &lensCtrls)\n> >   * Resamples a 16x12 table with central sampling to destW x destH with corner\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> >                            int destW, int destH)\n> >  {\n> >         /*\n> > @@ -1793,8 +1794,8 @@ void IPARPi::resampleTable(uint16_t dest[], double 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> Is this 'stride' always going to be 16?\n>\n> >                 for (int i = 0; i < destW; i++) {\n> >                         double above = rowAbove[xLo[i]] * (1 - xf[i]) + rowAbove[xHi[i]] * xf[i];\n> >                         double 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 79F16BD160\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 29 Mar 2023 07:43:12 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 823CF6274B;\n\tWed, 29 Mar 2023 09:43:11 +0200 (CEST)","from mail-yw1-x1136.google.com (mail-yw1-x1136.google.com\n\t[IPv6:2607:f8b0:4864:20::1136])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 9A8CD603A7\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 29 Mar 2023 09:43:09 +0200 (CEST)","by mail-yw1-x1136.google.com with SMTP id\n\t00721157ae682-536af432ee5so278940167b3.0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 29 Mar 2023 00:43:09 -0700 (PDT)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1680075791;\n\tbh=aUQcqMB5BZtW0ktUsCF/Quc/vYRijy8fBgIWbRwOyyg=;\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=xtvi5hOwyZjT2QjbY17IPW05Ol/sE+QejmveyzVFqHU7owxKlb2ixKEhhwlz6evUR\n\t6+skS1X4NrXnEKqc07Rn5FUJaYMa9bzWvMRbWXfp8nyDLAQ2eZP1EwMTra4FmBRExB\n\tVGwuiHmq2AuzefNh8Pl9vmZWPcLpwM3INp4V2Tdidig34j2+JafzwEQa/m0LWUyoej\n\t4zY6ZZnVorMcDyaP2UaVO8DNJhxmzKg08y8l6JGlKgwoV55DU5ucA+JTGi5JGSbxlV\n\tpMkY1cEnw0G1E0e1aC3ERXtf4pNy/wuJydxKArPs1/U+NbHCGB3I73pxi8HiJysNzm\n\tM/7OxVjilryqg==","v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google; t=1680075788;\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=x+U/iVLZZF1YOSBj3uU8et4jZGClu6qEDkMUWoN2TUM=;\n\tb=V5sShtCFZSdPSfOqSgPkVYumhLa/8HtgQF3WIWUfhgWbDB0LQTu4wpAr0mbz/YTpgg\n\tjgXBkDxyBPWS5FXZ3A1bYekWnGpmmbPkEPMYJROTeBEPoLEbpcOKjiu/g0h/Joq6m1Bs\n\tYMkRZBfqWtupFbeQuTyPgWb+iD/LJijBcY+KAXNfnfw9+o4QLKsF6zEE4pd0eq/J6g8r\n\t395GbX1FNZ+RHQ8ayBUa6Fe3KftZ4Q6kwHWd486m42pOfZwHz86y3Qw/B+ydRaM/+qQT\n\t8J+H6ATQRbVWkr2vmuJWqiij2Tr9spRdTO9QcFWdJHZj8Xat2mbTqgoIakNGhN2MgoBk\n\tsSIg=="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key; \n\tunprotected) header.d=raspberrypi.com\n\theader.i=@raspberrypi.com\n\theader.b=\"V5sShtCF\"; dkim-atps=neutral","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20210112; t=1680075788;\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=x+U/iVLZZF1YOSBj3uU8et4jZGClu6qEDkMUWoN2TUM=;\n\tb=pvqsHXAePMZODVdBqfy+6y43qs7IS6iN+zx13QoxkfmrYZzqdzdY5VKJna/1hitNQF\n\tghYDZo6UtDfYDf6hb5xs091sGToOqku0Sj3CrQs9NH8Qw1Y/xQyUql6YXlPyUGrTKert\n\tCIk4KB0N1bLXqz19bEhgr7hGgxBr+d5Ox89ZQcY151hPdIeGfsEgzlb8cQz2o5kB7oxw\n\tEYUfCZWjS+NmemgVhIAxIw/UMjukAL6E0oTHUy7kvEzVDNhCaurKm7bbw2vNQdR37mkU\n\t+7pARYM7NGmsNGpROP0oG2d17vDOq1oHZXR07U4qjmfZ5NPlF8Ntt+4WIfXN2H6hgrha\n\tPCFw==","X-Gm-Message-State":"AAQBX9eLEDlo6Go22jm78PfFkB5Mggw1c3IDNrJk+/pMbaptu8ye6bYa\n\tl1ujCqHuAnXctVJQ9XQce/vZM9xj7UUdqd/9GuQBYQ==","X-Google-Smtp-Source":"AKy350Zz6Ih1nEUqN2hnS1kJ105lEoFm9eZtJLI1PYsWUkh4PzQ9LE9GwqzA8+IxG68jSfGmZMnUyi2pYY3uEYuxIA8=","X-Received":"by 2002:a81:ac64:0:b0:544:b9b2:5c32 with SMTP id\n\tz36-20020a81ac64000000b00544b9b25c32mr8738756ywj.7.1680075787394;\n\tWed, 29 Mar 2023 00:43:07 -0700 (PDT)","MIME-Version":"1.0","References":"<20230327122030.11756-1-naush@raspberrypi.com>\n\t<20230327122030.11756-4-naush@raspberrypi.com>\n\t<168004569475.3713142.14150628823010794462@Monstersaurus>","In-Reply-To":"<168004569475.3713142.14150628823010794462@Monstersaurus>","Date":"Wed, 29 Mar 2023 08:42:54 +0100","Message-ID":"<CAEmqJPp6cUBfm0b4xRMHv=MZJG2c1hn5UUfq1so-ehBHHJ+X2w@mail.gmail.com>","To":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Content-Type":"text/plain; charset=\"UTF-8\"","Subject":"Re: [libcamera-devel] [PATCH v2 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>"}}]