{"id":18430,"url":"https://patchwork.libcamera.org/api/patches/18430/?format=json","web_url":"https://patchwork.libcamera.org/patch/18430/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20230322130612.5208-4-naush@raspberrypi.com>","date":"2023-03-22T13:06:05","name":"[libcamera-devel,v1,03/10] ipa: raspberrypi: Generalise the ALSC algorithm","commit_ref":null,"pull_url":null,"state":"superseded","archived":false,"hash":"c64f41ad6eb108e8b8e60f7e34e0ef954a77f1f0","submitter":{"id":34,"url":"https://patchwork.libcamera.org/api/people/34/?format=json","name":"Naushir Patuck","email":"naush@raspberrypi.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/18430/mbox/","series":[{"id":3810,"url":"https://patchwork.libcamera.org/api/series/3810/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=3810","date":"2023-03-22T13:06:02","name":"Raspberry Pi: Generalised algorithms","version":1,"mbox":"https://patchwork.libcamera.org/series/3810/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/18430/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/18430/checks/","tags":{},"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 A892FC329D\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 22 Mar 2023 13:06:24 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 4553862719;\n\tWed, 22 Mar 2023 14:06:24 +0100 (CET)","from mail-io1-xd2b.google.com (mail-io1-xd2b.google.com\n\t[IPv6:2607:f8b0:4864:20::d2b])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 4E7B7626E6\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 22 Mar 2023 14:06:21 +0100 (CET)","by mail-io1-xd2b.google.com with SMTP id d14so5520308ion.9\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 22 Mar 2023 06:06:21 -0700 (PDT)","from localhost.localdomain ([93.93.133.154])\n\tby smtp.gmail.com with ESMTPSA id\n\tl6-20020a026646000000b003c488204c6fsm4927960jaf.76.2023.03.22.06.06.19\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tWed, 22 Mar 2023 06:06:19 -0700 (PDT)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1679490384;\n\tbh=mPek/zScnAmKzPCIA0OSpmdJ46HnVIBuWjdDOsuGbWY=;\n\th=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:\n\tFrom;\n\tb=mf89I1zF3PZEBdJk5RLYgkuEoRLn9HNzadm2zrRfFYlyARyH3y2Z4+xw8lG3XXfyW\n\t4Dm5kbpDwZy9dXnr1dLJ75B86P8KGezPQCg0JJ2YS2szekxMidpzPLN5CKicJyuc6J\n\tnpQsmZyvxqFEN8gKbV9GXt0Lqx/LBRxmZ3FP6QvhVRE8PeRVuCsODihPPegRgukSG0\n\t38W2du6hm+ESeGn7ffZInT/9M+wBUymDev47G4xFCMf7PXlVeI6iyrnIr62bWtam/t\n\twD8h6Oi3pctwKBEOeCyxJnPAC2wM11lhh0+ak086/O7qdaUNwtvJj7UM8DBNxvpTHo\n\tDVuE6ZjGEnScQ==","v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google; t=1679490380;\n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=9gtWlJjnVtYPCba1bV1a8ZlAMg1AKXhIDUnb9+Ws+Y8=;\n\tb=gfLu5rUKUbXi9PFKbYCEKUlf9R3HPpcqVwmrlvQyDiZuetSwnfaNBVoWWrGC+XDXah\n\tjM8yGJkqvYfLAPx+K63MoMme0SmKeo+Efxg/xkEvNtkgcPq/e0aU0Uduug3uN8l8bGPQ\n\t7zP2pQDIJxzATSluODUBMhdqm9NWsiCA9EGEsf9uP9UwC6HtgNFaSZ47CGwYamUBNHgt\n\tui7UAid77whMFiAlcJKaxpveIj0SWf9soBOmSz6ko4eqff+JFx8JRjuoRXbDc5ru4ACl\n\tBMlduv94iDMhhe1Ra0sssuEt1DoTlz/Vbw2HmuiN0dHiTSthi02kXVKea+gzTSwQUphb\n\tmxVQ=="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key; \n\tunprotected) header.d=raspberrypi.com\n\theader.i=@raspberrypi.com\n\theader.b=\"gfLu5rUK\"; dkim-atps=neutral","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20210112; t=1679490381;\n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc\n\t:subject:date:message-id:reply-to;\n\tbh=9gtWlJjnVtYPCba1bV1a8ZlAMg1AKXhIDUnb9+Ws+Y8=;\n\tb=M9TV8Su9Mh8M3KY1a53o3MHJzSponl3tutKRbMhF5WqaN5nN8efpk5oWAY1BVJuub5\n\tyaR5o3h8faFyoyp4Ob2hBXZ/Uz73Ixka6baHn1b6lY/u6n+a6bjkgS9P2eMl+1uPXEC2\n\t7Dyp8OJbeAXQ61oGqoSQjLi/zk9u+MoyrJ7aDect8apR73q/wTzxRa3aZvXpLVYcDBBg\n\t/qyUM4jteUhGrcFu2PaciSPKeRZllldD9ZR99C2AZp8FWLDWfsdHlaHohyUzRLIxdKyn\n\tbqSBcOshicMgHHnWcNs7dwxMWx+qPEBaVXz7IVllWGQLgQkwaXPxrCCNTqqD3sPseasZ\n\tBY0g==","X-Gm-Message-State":"AO0yUKWgajvrOIbQIlvYvTDRLjboNEH2SJcVLbRU9+qHBacXTyO9OF2P\n\ttjNpSEvQYL2zg5eg4JMfNculbtzAc6pRZ2ouMx9R0g==","X-Google-Smtp-Source":"AK7set+kYSzpatqQKSSb1bf+hFJdId2tEFqpsNryf690vvdUjU5d8DgLGHb4EvCWQRCvWtCSiOT7Rw==","X-Received":"by 2002:a5e:de45:0:b0:753:2862:a2c0 with SMTP id\n\te5-20020a5ede45000000b007532862a2c0mr4090564ioq.21.1679490380094; \n\tWed, 22 Mar 2023 06:06:20 -0700 (PDT)","To":"libcamera-devel@lists.libcamera.org","Date":"Wed, 22 Mar 2023 13:06:05 +0000","Message-Id":"<20230322130612.5208-4-naush@raspberrypi.com>","X-Mailer":"git-send-email 2.34.1","In-Reply-To":"<20230322130612.5208-1-naush@raspberrypi.com>","References":"<20230322130612.5208-1-naush@raspberrypi.com>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","Subject":"[libcamera-devel] [PATCH v1 03/10] ipa: raspberrypi: Generalise the\n\tALSC 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>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"},"content":"Remove any hard-coded assumptions about the target hardware platform\nfrom the ALSC algorithm. Instead, use the \"target\" string provided by\nthe camera tuning config and generalised statistics structures to\ndeterming parameters such as grid and region sizes.\n\nThe ALSC calculations use run-time allocated arrays/vectors on every\nframe. Allocating these might add a non-trivial run-time penalty.\nReplace these dynamic allocations with a set of reusable pre-allocated\nvectors during the init phase.\n\nSigned-off-by: Naushir Patuck <naush@raspberrypi.com>\nSigned-off-by: David Plowman <david.plowman@raspberrypi.com>\n---\n src/ipa/raspberrypi/controller/alsc_status.h |  13 +-\n src/ipa/raspberrypi/controller/rpi/alsc.cpp  | 341 +++++++++++--------\n src/ipa/raspberrypi/controller/rpi/alsc.h    |  29 +-\n src/ipa/raspberrypi/raspberrypi.cpp          |   9 +-\n 4 files changed, 224 insertions(+), 168 deletions(-)","diff":"diff --git a/src/ipa/raspberrypi/controller/alsc_status.h b/src/ipa/raspberrypi/controller/alsc_status.h\nindex e5aa7e37c330..49a9f4a0cb5a 100644\n--- a/src/ipa/raspberrypi/controller/alsc_status.h\n+++ b/src/ipa/raspberrypi/controller/alsc_status.h\n@@ -6,16 +6,17 @@\n  */\n #pragma once\n \n+#include <vector>\n+\n /*\n  * The ALSC algorithm should post the following structure into the image's\n  * \"alsc.status\" metadata.\n  */\n \n-constexpr unsigned int AlscCellsX = 16;\n-constexpr unsigned int AlscCellsY = 12;\n-\n struct AlscStatus {\n-\tdouble r[AlscCellsY][AlscCellsX];\n-\tdouble g[AlscCellsY][AlscCellsX];\n-\tdouble b[AlscCellsY][AlscCellsX];\n+\tstd::vector<double> r;\n+\tstd::vector<double> g;\n+\tstd::vector<double> b;\n+\tunsigned int rows;\n+\tunsigned int cols;\n };\ndiff --git a/src/ipa/raspberrypi/controller/rpi/alsc.cpp b/src/ipa/raspberrypi/controller/rpi/alsc.cpp\nindex eb4e2f9496e1..51fe5d73f52d 100644\n--- a/src/ipa/raspberrypi/controller/rpi/alsc.cpp\n+++ b/src/ipa/raspberrypi/controller/rpi/alsc.cpp\n@@ -5,6 +5,7 @@\n  * alsc.cpp - ALSC (auto lens shading correction) control algorithm\n  */\n \n+#include <algorithm>\n #include <functional>\n #include <math.h>\n #include <numeric>\n@@ -24,9 +25,6 @@ LOG_DEFINE_CATEGORY(RPiAlsc)\n \n #define NAME \"rpi.alsc\"\n \n-static const int X = AlscCellsX;\n-static const int Y = AlscCellsY;\n-static const int XY = X * Y;\n static const double InsufficientData = -1.0;\n \n Alsc::Alsc(Controller *controller)\n@@ -51,8 +49,11 @@ char const *Alsc::name() const\n \treturn NAME;\n }\n \n-static int generateLut(double *lut, const libcamera::YamlObject &params)\n+static int generateLut(std::vector<double> &lut, const libcamera::YamlObject &params,\n+\t\t       const Size &size)\n {\n+\t/* These must be signed ints for the co-ordinate calculations below. */\n+\tint X = size.width, Y = size.height;\n \tdouble cstrength = params[\"corner_strength\"].get<double>(2.0);\n \tif (cstrength <= 1.0) {\n \t\tLOG(RPiAlsc, Error) << \"corner_strength must be > 1.0\";\n@@ -81,9 +82,9 @@ static int generateLut(double *lut, const libcamera::YamlObject &params)\n \treturn 0;\n }\n \n-static int readLut(double *lut, const libcamera::YamlObject &params)\n+static int readLut(std::vector<double> &lut, const libcamera::YamlObject &params, const Size &size)\n {\n-\tif (params.size() != XY) {\n+\tif (params.size() != size.width * size.height) {\n \t\tLOG(RPiAlsc, Error) << \"Invalid number of entries in LSC table\";\n \t\treturn -EINVAL;\n \t}\n@@ -101,7 +102,7 @@ static int readLut(double *lut, const libcamera::YamlObject &params)\n \n static int readCalibrations(std::vector<AlscCalibration> &calibrations,\n \t\t\t    const libcamera::YamlObject &params,\n-\t\t\t    std::string const &name)\n+\t\t\t    std::string const &name, const Size &size)\n {\n \tif (params.contains(name)) {\n \t\tdouble lastCt = 0;\n@@ -119,7 +120,7 @@ static int readCalibrations(std::vector<AlscCalibration> &calibrations,\n \t\t\tcalibration.ct = lastCt = ct;\n \n \t\t\tconst libcamera::YamlObject &table = p[\"table\"];\n-\t\t\tif (table.size() != XY) {\n+\t\t\tif (table.size() != size.width * size.height) {\n \t\t\t\tLOG(RPiAlsc, Error)\n \t\t\t\t\t<< \"Incorrect number of values for ct \"\n \t\t\t\t\t<< ct << \" in \" << name;\n@@ -127,6 +128,7 @@ static int readCalibrations(std::vector<AlscCalibration> &calibrations,\n \t\t\t}\n \n \t\t\tint num = 0;\n+\t\t\tcalibration.table.resize(size.width * size.height);\n \t\t\tfor (const auto &elem : table.asList()) {\n \t\t\t\tvalue = elem.get<double>();\n \t\t\t\tif (!value)\n@@ -134,7 +136,7 @@ static int readCalibrations(std::vector<AlscCalibration> &calibrations,\n \t\t\t\tcalibration.table[num++] = *value;\n \t\t\t}\n \n-\t\t\tcalibrations.push_back(calibration);\n+\t\t\tcalibrations.push_back(std::move(calibration));\n \t\t\tLOG(RPiAlsc, Debug)\n \t\t\t\t<< \"Read \" << name << \" calibration for ct \" << ct;\n \t\t}\n@@ -144,6 +146,7 @@ static int readCalibrations(std::vector<AlscCalibration> &calibrations,\n \n int Alsc::read(const libcamera::YamlObject &params)\n {\n+\tconfig_.tableSize = getHardwareConfig().awbRegions;\n \tconfig_.framePeriod = params[\"frame_period\"].get<uint16_t>(12);\n \tconfig_.startupFrames = params[\"startup_frames\"].get<uint16_t>(10);\n \tconfig_.speed = params[\"speed\"].get<double>(0.05);\n@@ -153,28 +156,29 @@ int Alsc::read(const libcamera::YamlObject &params)\n \tconfig_.minCount = params[\"min_count\"].get<double>(10.0);\n \tconfig_.minG = params[\"min_G\"].get<uint16_t>(50);\n \tconfig_.omega = params[\"omega\"].get<double>(1.3);\n-\tconfig_.nIter = params[\"n_iter\"].get<uint32_t>(X + Y);\n+\tconfig_.nIter = params[\"n_iter\"].get<uint32_t>(config_.tableSize.width + config_.tableSize.height);\n \tconfig_.luminanceStrength =\n \t\tparams[\"luminance_strength\"].get<double>(1.0);\n-\tfor (int i = 0; i < XY; i++)\n-\t\tconfig_.luminanceLut[i] = 1.0;\n \n+\tconfig_.luminanceLut.resize(config_.tableSize.width * config_.tableSize.height, 1.0);\n \tint ret = 0;\n \n \tif (params.contains(\"corner_strength\"))\n-\t\tret = generateLut(config_.luminanceLut, params);\n+\t\tret = generateLut(config_.luminanceLut, params, config_.tableSize);\n \telse if (params.contains(\"luminance_lut\"))\n-\t\tret = readLut(config_.luminanceLut, params[\"luminance_lut\"]);\n+\t\tret = readLut(config_.luminanceLut, params[\"luminance_lut\"], config_.tableSize);\n \telse\n \t\tLOG(RPiAlsc, Warning)\n \t\t\t<< \"no luminance table - assume unity everywhere\";\n \tif (ret)\n \t\treturn ret;\n \n-\tret = readCalibrations(config_.calibrationsCr, params, \"calibrations_Cr\");\n+\tret = readCalibrations(config_.calibrationsCr, params, \"calibrations_Cr\",\n+\t\t\t       config_.tableSize);\n \tif (ret)\n \t\treturn ret;\n-\tret = readCalibrations(config_.calibrationsCb, params, \"calibrations_Cb\");\n+\tret = readCalibrations(config_.calibrationsCb, params, \"calibrations_Cb\",\n+\t\t\t       config_.tableSize);\n \tif (ret)\n \t\treturn ret;\n \n@@ -187,13 +191,16 @@ int Alsc::read(const libcamera::YamlObject &params)\n \n static double getCt(Metadata *metadata, double defaultCt);\n static void getCalTable(double ct, std::vector<AlscCalibration> const &calibrations,\n-\t\t\tdouble calTable[XY]);\n-static void resampleCalTable(double const calTableIn[XY], CameraMode const &cameraMode,\n-\t\t\t     double calTableOut[XY]);\n-static void compensateLambdasForCal(double const calTable[XY], double const oldLambdas[XY],\n-\t\t\t\t    double newLambdas[XY]);\n-static void addLuminanceToTables(double results[3][Y][X], double const lambdaR[XY], double lambdaG,\n-\t\t\t\t double const lambdaB[XY], double const luminanceLut[XY],\n+\t\t\tstd::vector<double> &calTable);\n+static void resampleCalTable(const std::vector<double> &calTableIn, CameraMode const &cameraMode,\n+\t\t\t     const Size &size, std::vector<double> &calTableOut);\n+static void compensateLambdasForCal(const std::vector<double> &calTable,\n+\t\t\t\t    const std::vector<double> &oldLambdas,\n+\t\t\t\t    std::vector<double> &newLambdas);\n+static void addLuminanceToTables(std::array<std::vector<double>, 3> &results,\n+\t\t\t\t const std::vector<double> &lambdaR, double lambdaG,\n+\t\t\t\t const std::vector<double> &lambdaB,\n+\t\t\t\t const std::vector<double> &luminanceLut,\n \t\t\t\t double luminanceStrength);\n \n void Alsc::initialise()\n@@ -201,7 +208,28 @@ void Alsc::initialise()\n \tframeCount2_ = frameCount_ = framePhase_ = 0;\n \tfirstTime_ = true;\n \tct_ = config_.defaultCt;\n+\n+\tconst size_t XY = config_.tableSize.width * config_.tableSize.height;\n+\n+\tfor (auto &r : syncResults_)\n+\t\tr.resize(XY);\n+\tfor (auto &r : prevSyncResults_)\n+\t\tr.resize(XY);\n+\tfor (auto &r : asyncResults_)\n+\t\tr.resize(XY);\n+\n+\tluminanceTable_.resize(XY);\n+\tasyncLambdaR_.resize(XY);\n+\tasyncLambdaB_.resize(XY);\n \t/* The lambdas are initialised in the SwitchMode. */\n+\tlambdaR_.resize(XY);\n+\tlambdaB_.resize(XY);\n+\n+\t/* Temporaries for the computations, but sensible to allocate this up-front! */\n+\tfor (auto &c : tmpC_)\n+\t\tc.resize(XY);\n+\tfor (auto &m : tmpM_)\n+\t\tm.resize(XY);\n }\n \n void Alsc::waitForAysncThread()\n@@ -262,7 +290,7 @@ void Alsc::switchMode(CameraMode const &cameraMode,\n \t * We must resample the luminance table like we do the others, but it's\n \t * fixed so we can simply do it up front here.\n \t */\n-\tresampleCalTable(config_.luminanceLut, cameraMode_, luminanceTable_);\n+\tresampleCalTable(config_.luminanceLut, cameraMode_, config_.tableSize, luminanceTable_);\n \n \tif (resetTables) {\n \t\t/*\n@@ -272,18 +300,18 @@ void Alsc::switchMode(CameraMode const &cameraMode,\n \t\t * the lambdas, but the rest of this code then echoes the code in\n \t\t * doAlsc, without the adaptive algorithm.\n \t\t */\n-\t\tfor (int i = 0; i < XY; i++)\n-\t\t\tlambdaR_[i] = lambdaB_[i] = 1.0;\n-\t\tdouble calTableR[XY], calTableB[XY], calTableTmp[XY];\n+\t\tstd::fill(lambdaR_.begin(), lambdaR_.end(), 1.0);\n+\t\tstd::fill(lambdaB_.begin(), lambdaB_.end(), 1.0);\n+\t\tstd::vector<double> &calTableR = tmpC_[0], &calTableB = tmpC_[1], &calTableTmp = tmpC_[2];\n \t\tgetCalTable(ct_, config_.calibrationsCr, calTableTmp);\n-\t\tresampleCalTable(calTableTmp, cameraMode_, calTableR);\n+\t\tresampleCalTable(calTableTmp, cameraMode_, config_.tableSize, calTableR);\n \t\tgetCalTable(ct_, config_.calibrationsCb, calTableTmp);\n-\t\tresampleCalTable(calTableTmp, cameraMode_, calTableB);\n+\t\tresampleCalTable(calTableTmp, cameraMode_, config_.tableSize, calTableB);\n \t\tcompensateLambdasForCal(calTableR, lambdaR_, asyncLambdaR_);\n \t\tcompensateLambdasForCal(calTableB, lambdaB_, asyncLambdaB_);\n \t\taddLuminanceToTables(syncResults_, asyncLambdaR_, 1.0, asyncLambdaB_,\n \t\t\t\t     luminanceTable_, config_.luminanceStrength);\n-\t\tmemcpy(prevSyncResults_, syncResults_, sizeof(prevSyncResults_));\n+\t\tprevSyncResults_ = syncResults_;\n \t\tframePhase_ = config_.framePeriod; /* run the algo again asap */\n \t\tfirstTime_ = false;\n \t}\n@@ -294,7 +322,7 @@ void Alsc::fetchAsyncResults()\n \tLOG(RPiAlsc, Debug) << \"Fetch ALSC results\";\n \tasyncFinished_ = false;\n \tasyncStarted_ = false;\n-\tmemcpy(syncResults_, asyncResults_, sizeof(syncResults_));\n+\tsyncResults_ = asyncResults_;\n }\n \n double getCt(Metadata *metadata, double defaultCt)\n@@ -316,9 +344,9 @@ static void copyStats(RgbyRegions &regions, StatisticsPtr &stats,\n \tif (!regions.numRegions())\n \t\tregions.init(stats->awbRegions.size());\n \n-\tdouble *rTable = (double *)status.r;\n-\tdouble *gTable = (double *)status.g;\n-\tdouble *bTable = (double *)status.b;\n+\tconst std::vector<double> &rTable = status.r;\n+\tconst std::vector<double> &gTable = status.g;\n+\tconst std::vector<double> &bTable = status.b;\n \tfor (unsigned int i = 0; i < stats->awbRegions.numRegions(); i++) {\n \t\tauto r = stats->awbRegions.get(i);\n \t\tr.val.rSum = static_cast<uint64_t>(r.val.rSum / rTable[i]);\n@@ -344,12 +372,9 @@ void Alsc::restartAsync(StatisticsPtr &stats, Metadata *imageMetadata)\n \tif (imageMetadata->get(\"alsc.status\", alscStatus) != 0) {\n \t\tLOG(RPiAlsc, Warning)\n \t\t\t<< \"No ALSC status found for applied gains!\";\n-\t\tfor (int y = 0; y < Y; y++)\n-\t\t\tfor (int x = 0; x < X; x++) {\n-\t\t\t\talscStatus.r[y][x] = 1.0;\n-\t\t\t\talscStatus.g[y][x] = 1.0;\n-\t\t\t\talscStatus.b[y][x] = 1.0;\n-\t\t\t}\n+\t\talscStatus.r.resize(config_.tableSize.width * config_.tableSize.height, 1.0);\n+\t\talscStatus.g.resize(config_.tableSize.width * config_.tableSize.height, 1.0);\n+\t\talscStatus.b.resize(config_.tableSize.width * config_.tableSize.height, 1.0);\n \t}\n \tcopyStats(statistics_, stats, alscStatus);\n \tframePhase_ = 0;\n@@ -380,15 +405,15 @@ void Alsc::prepare(Metadata *imageMetadata)\n \t\t\tfetchAsyncResults();\n \t}\n \t/* Apply IIR filter to results and program into the pipeline. */\n-\tdouble *ptr = (double *)syncResults_,\n-\t       *pptr = (double *)prevSyncResults_;\n-\tfor (unsigned int i = 0; i < sizeof(syncResults_) / sizeof(double); i++)\n-\t\tpptr[i] = speed * ptr[i] + (1.0 - speed) * pptr[i];\n+\tfor (unsigned int j = 0; j < syncResults_.size(); j++) {\n+\t\tfor (unsigned int i = 0; i < syncResults_[j].size(); i++)\n+\t\t\tprevSyncResults_[j][i] = speed * syncResults_[j][i] + (1.0 - speed) * prevSyncResults_[j][i];\n+\t}\n \t/* Put output values into status metadata. */\n \tAlscStatus status;\n-\tmemcpy(status.r, prevSyncResults_[0], sizeof(status.r));\n-\tmemcpy(status.g, prevSyncResults_[1], sizeof(status.g));\n-\tmemcpy(status.b, prevSyncResults_[2], sizeof(status.b));\n+\tstatus.r = prevSyncResults_[0];\n+\tstatus.g = prevSyncResults_[1];\n+\tstatus.b = prevSyncResults_[2];\n \timageMetadata->set(\"alsc.status\", status);\n }\n \n@@ -432,18 +457,17 @@ void Alsc::asyncFunc()\n }\n \n void getCalTable(double ct, std::vector<AlscCalibration> const &calibrations,\n-\t\t double calTable[XY])\n+\t\t std::vector<double> &calTable)\n {\n \tif (calibrations.empty()) {\n-\t\tfor (int i = 0; i < XY; i++)\n-\t\t\tcalTable[i] = 1.0;\n+\t\tstd::fill(calTable.begin(), calTable.end(), 1.0);\n \t\tLOG(RPiAlsc, Debug) << \"no calibrations found\";\n \t} else if (ct <= calibrations.front().ct) {\n-\t\tmemcpy(calTable, calibrations.front().table, XY * sizeof(double));\n+\t\tcalTable = calibrations.front().table;\n \t\tLOG(RPiAlsc, Debug) << \"using calibration for \"\n \t\t\t\t    << calibrations.front().ct;\n \t} else if (ct >= calibrations.back().ct) {\n-\t\tmemcpy(calTable, calibrations.back().table, XY * sizeof(double));\n+\t\tcalTable = calibrations.back().table;\n \t\tLOG(RPiAlsc, Debug) << \"using calibration for \"\n \t\t\t\t    << calibrations.back().ct;\n \t} else {\n@@ -454,7 +478,7 @@ void getCalTable(double ct, std::vector<AlscCalibration> const &calibrations,\n \t\tLOG(RPiAlsc, Debug)\n \t\t\t<< \"ct is \" << ct << \", interpolating between \"\n \t\t\t<< ct0 << \" and \" << ct1;\n-\t\tfor (int i = 0; i < XY; i++)\n+\t\tfor (unsigned int i = 0; i < calTable.size(); i++)\n \t\t\tcalTable[i] =\n \t\t\t\t(calibrations[idx].table[i] * (ct1 - ct) +\n \t\t\t\t calibrations[idx + 1].table[i] * (ct - ct0)) /\n@@ -462,9 +486,13 @@ void getCalTable(double ct, std::vector<AlscCalibration> const &calibrations,\n \t}\n }\n \n-void resampleCalTable(double const calTableIn[XY],\n-\t\t      CameraMode const &cameraMode, double calTableOut[XY])\n+void resampleCalTable(const std::vector<double> &calTableIn,\n+\t\t      CameraMode const &cameraMode, const Size &size,\n+\t\t      std::vector<double> &calTableOut)\n {\n+\tint X = size.width;\n+\tint Y = size.height;\n+\n \t/*\n \t * Precalculate and cache the x sampling locations and phases to save\n \t * recomputing them on every row.\n@@ -501,23 +529,24 @@ void resampleCalTable(double const calTableIn[XY],\n \t\t\tyLo = Y - 1 - yLo;\n \t\t\tyHi = Y - 1 - yHi;\n \t\t}\n-\t\tdouble const *rowAbove = calTableIn + X * yLo;\n-\t\tdouble const *rowBelow = calTableIn + X * yHi;\n+\t\tdouble const *rowAbove = calTableIn.data() + X * yLo;\n+\t\tdouble const *rowBelow = calTableIn.data() + X * yHi;\n+\t\tdouble *out = calTableOut.data() + X * j;\n \t\tfor (int i = 0; i < X; i++) {\n \t\t\tdouble above = rowAbove[xLo[i]] * (1 - xf[i]) +\n \t\t\t\t       rowAbove[xHi[i]] * xf[i];\n \t\t\tdouble below = rowBelow[xLo[i]] * (1 - xf[i]) +\n \t\t\t\t       rowBelow[xHi[i]] * xf[i];\n-\t\t\t*(calTableOut++) = above * (1 - yf) + below * yf;\n+\t\t\t*(out++) = above * (1 - yf) + below * yf;\n \t\t}\n \t}\n }\n \n /* Calculate chrominance statistics (R/G and B/G) for each region. */\n-static void calculateCrCb(const RgbyRegions &awbRegion, double cr[XY],\n-\t\t\t  double cb[XY], uint32_t minCount, uint16_t minG)\n+static void calculateCrCb(const RgbyRegions &awbRegion, std::vector<double> &cr,\n+\t\t\t  std::vector<double> &cb, uint32_t minCount, uint16_t minG)\n {\n-\tfor (int i = 0; i < XY; i++) {\n+\tfor (unsigned int i = 0; i < cr.size(); i++) {\n \t\tauto s = awbRegion.get(i);\n \n \t\tif (s.counted <= minCount || s.val.gSum / s.counted <= minG) {\n@@ -530,33 +559,34 @@ static void calculateCrCb(const RgbyRegions &awbRegion, double cr[XY],\n \t}\n }\n \n-static void applyCalTable(double const calTable[XY], double C[XY])\n+static void applyCalTable(const std::vector<double> &calTable, std::vector<double> &C)\n {\n-\tfor (int i = 0; i < XY; i++)\n+\tfor (unsigned int i = 0; i < C.size(); i++)\n \t\tif (C[i] != InsufficientData)\n \t\t\tC[i] *= calTable[i];\n }\n \n-void compensateLambdasForCal(double const calTable[XY],\n-\t\t\t     double const oldLambdas[XY],\n-\t\t\t     double newLambdas[XY])\n+void compensateLambdasForCal(const std::vector<double> &calTable,\n+\t\t\t     const std::vector<double> &oldLambdas,\n+\t\t\t     std::vector<double> &newLambdas)\n {\n \tdouble minNewLambda = std::numeric_limits<double>::max();\n-\tfor (int i = 0; i < XY; i++) {\n+\tfor (unsigned int i = 0; i < newLambdas.size(); i++) {\n \t\tnewLambdas[i] = oldLambdas[i] * calTable[i];\n \t\tminNewLambda = std::min(minNewLambda, newLambdas[i]);\n \t}\n-\tfor (int i = 0; i < XY; i++)\n+\tfor (unsigned int i = 0; i < newLambdas.size(); i++)\n \t\tnewLambdas[i] /= minNewLambda;\n }\n \n-[[maybe_unused]] static void printCalTable(double const C[XY])\n+[[maybe_unused]] static void printCalTable(const std::vector<double> &C,\n+\t\t\t\t\t   const Size &size)\n {\n \tprintf(\"table: [\\n\");\n-\tfor (int j = 0; j < Y; j++) {\n-\t\tfor (int i = 0; i < X; i++) {\n-\t\t\tprintf(\"%5.3f\", 1.0 / C[j * X + i]);\n-\t\t\tif (i != X - 1 || j != Y - 1)\n+\tfor (unsigned int j = 0; j < size.height; j++) {\n+\t\tfor (unsigned int i = 0; i < size.width; i++) {\n+\t\t\tprintf(\"%5.3f\", 1.0 / C[j * size.width + i]);\n+\t\t\tif (i != size.width - 1 || j != size.height - 1)\n \t\t\t\tprintf(\",\");\n \t\t}\n \t\tprintf(\"\\n\");\n@@ -577,9 +607,13 @@ static double computeWeight(double Ci, double Cj, double sigma)\n }\n \n /* Compute all weights. */\n-static void computeW(double const C[XY], double sigma, double W[XY][4])\n+static void computeW(const std::vector<double> &C, double sigma,\n+\t\t     std::vector<std::array<double, 4>> &W, const Size &size)\n {\n-\tfor (int i = 0; i < XY; i++) {\n+\tsize_t XY = size.width * size.height;\n+\tsize_t X = size.width;\n+\n+\tfor (unsigned int i = 0; i < XY; i++) {\n \t\t/* Start with neighbour above and go clockwise. */\n \t\tW[i][0] = i >= X ? computeWeight(C[i], C[i - X], sigma) : 0;\n \t\tW[i][1] = i % X < X - 1 ? computeWeight(C[i], C[i + 1], sigma) : 0;\n@@ -589,11 +623,16 @@ static void computeW(double const C[XY], double sigma, double W[XY][4])\n }\n \n /* Compute M, the large but sparse matrix such that M * lambdas = 0. */\n-static void constructM(double const C[XY], double const W[XY][4],\n-\t\t       double M[XY][4])\n+static void constructM(const std::vector<double> &C,\n+\t\t       const std::vector<std::array<double, 4>> &W,\n+\t\t       std::vector<std::array<double, 4>> &M,\n+\t\t       const Size &size)\n {\n+\tsize_t XY = size.width * size.height;\n+\tsize_t X = size.width;\n+\n \tdouble epsilon = 0.001;\n-\tfor (int i = 0; i < XY; i++) {\n+\tfor (unsigned int i = 0; i < XY; i++) {\n \t\t/*\n \t\t * Note how, if C[i] == INSUFFICIENT_DATA, the weights will all\n \t\t * be zero so the equation is still set up correctly.\n@@ -614,79 +653,80 @@ static void constructM(double const C[XY], double const W[XY][4],\n  * left/right neighbours are zero down the left/right edges, so we don't need\n  * need to test the i value to exclude them.\n  */\n-static double computeLambdaBottom(int i, double const M[XY][4],\n-\t\t\t\t  double lambda[XY])\n+static double computeLambdaBottom(int i, const std::vector<std::array<double, 4>> &M,\n+\t\t\t\t  std::vector<double> &lambda, const Size &size)\n {\n-\treturn M[i][1] * lambda[i + 1] + M[i][2] * lambda[i + X] +\n+\treturn M[i][1] * lambda[i + 1] + M[i][2] * lambda[i + size.width] +\n \t       M[i][3] * lambda[i - 1];\n }\n-static double computeLambdaBottomStart(int i, double const M[XY][4],\n-\t\t\t\t       double lambda[XY])\n+static double computeLambdaBottomStart(int i, const std::vector<std::array<double, 4>> &M,\n+\t\t\t\t       std::vector<double> &lambda, const Size &size)\n {\n-\treturn M[i][1] * lambda[i + 1] + M[i][2] * lambda[i + X];\n+\treturn M[i][1] * lambda[i + 1] + M[i][2] * lambda[i + size.width];\n }\n-static double computeLambdaInterior(int i, double const M[XY][4],\n-\t\t\t\t    double lambda[XY])\n+static double computeLambdaInterior(int i, const std::vector<std::array<double, 4>> &M,\n+\t\t\t\t    std::vector<double> &lambda, const Size &size)\n {\n-\treturn M[i][0] * lambda[i - X] + M[i][1] * lambda[i + 1] +\n-\t       M[i][2] * lambda[i + X] + M[i][3] * lambda[i - 1];\n+\treturn M[i][0] * lambda[i - size.width] + M[i][1] * lambda[i + 1] +\n+\t       M[i][2] * lambda[i + size.width] + M[i][3] * lambda[i - 1];\n }\n-static double computeLambdaTop(int i, double const M[XY][4],\n-\t\t\t       double lambda[XY])\n+static double computeLambdaTop(int i, const std::vector<std::array<double, 4>> &M,\n+\t\t\t       std::vector<double> &lambda, const Size &size)\n {\n-\treturn M[i][0] * lambda[i - X] + M[i][1] * lambda[i + 1] +\n+\treturn M[i][0] * lambda[i - size.width] + M[i][1] * lambda[i + 1] +\n \t       M[i][3] * lambda[i - 1];\n }\n-static double computeLambdaTopEnd(int i, double const M[XY][4],\n-\t\t\t\t  double lambda[XY])\n+static double computeLambdaTopEnd(int i, const std::vector<std::array<double, 4>> &M,\n+\t\t\t\t  std::vector<double> &lambda, const Size &size)\n {\n-\treturn M[i][0] * lambda[i - X] + M[i][3] * lambda[i - 1];\n+\treturn M[i][0] * lambda[i - size.width] + M[i][3] * lambda[i - 1];\n }\n \n /* Gauss-Seidel iteration with over-relaxation. */\n-static double gaussSeidel2Sor(double const M[XY][4], double omega,\n-\t\t\t      double lambda[XY], double lambdaBound)\n+static double gaussSeidel2Sor(const std::vector<std::array<double, 4>> &M, double omega,\n+\t\t\t      std::vector<double> &lambda, double lambdaBound,\n+\t\t\t      const Size &size)\n {\n+\tint XY = size.width * size.height;\n+\tint X = size.width;\n \tconst double min = 1 - lambdaBound, max = 1 + lambdaBound;\n-\tdouble oldLambda[XY];\n+\tstd::vector<double> oldLambda = lambda;\n \tint i;\n-\tfor (i = 0; i < XY; i++)\n-\t\toldLambda[i] = lambda[i];\n-\tlambda[0] = computeLambdaBottomStart(0, M, lambda);\n+\tlambda[0] = computeLambdaBottomStart(0, M, lambda, size);\n \tlambda[0] = std::clamp(lambda[0], min, max);\n \tfor (i = 1; i < X; i++) {\n-\t\tlambda[i] = computeLambdaBottom(i, M, lambda);\n+\t\tlambda[i] = computeLambdaBottom(i, M, lambda, size);\n \t\tlambda[i] = std::clamp(lambda[i], min, max);\n \t}\n \tfor (; i < XY - X; i++) {\n-\t\tlambda[i] = computeLambdaInterior(i, M, lambda);\n+\t\tlambda[i] = computeLambdaInterior(i, M, lambda, size);\n \t\tlambda[i] = std::clamp(lambda[i], min, max);\n \t}\n \tfor (; i < XY - 1; i++) {\n-\t\tlambda[i] = computeLambdaTop(i, M, lambda);\n+\t\tlambda[i] = computeLambdaTop(i, M, lambda, size);\n \t\tlambda[i] = std::clamp(lambda[i], min, max);\n \t}\n-\tlambda[i] = computeLambdaTopEnd(i, M, lambda);\n+\tlambda[i] = computeLambdaTopEnd(i, M, lambda, size);\n \tlambda[i] = std::clamp(lambda[i], min, max);\n \t/*\n \t * Also solve the system from bottom to top, to help spread the updates\n \t * better.\n \t */\n-\tlambda[i] = computeLambdaTopEnd(i, M, lambda);\n+\tlambda[i] = computeLambdaTopEnd(i, M, lambda, size);\n \tlambda[i] = std::clamp(lambda[i], min, max);\n \tfor (i = XY - 2; i >= XY - X; i--) {\n-\t\tlambda[i] = computeLambdaTop(i, M, lambda);\n+\t\tlambda[i] = computeLambdaTop(i, M, lambda, size);\n \t\tlambda[i] = std::clamp(lambda[i], min, max);\n \t}\n \tfor (; i >= X; i--) {\n-\t\tlambda[i] = computeLambdaInterior(i, M, lambda);\n+\t\tlambda[i] = computeLambdaInterior(i, M, lambda, size);\n \t\tlambda[i] = std::clamp(lambda[i], min, max);\n \t}\n \tfor (; i >= 1; i--) {\n-\t\tlambda[i] = computeLambdaBottom(i, M, lambda);\n+\t\tlambda[i] = computeLambdaBottom(i, M, lambda, size);\n \t\tlambda[i] = std::clamp(lambda[i], min, max);\n \t}\n-\tlambda[0] = computeLambdaBottomStart(0, M, lambda);\n+\tlambda[0] = computeLambdaBottomStart(0, M, lambda, size);\n \tlambda[0] = std::clamp(lambda[0], min, max);\n \tdouble maxDiff = 0;\n \tfor (i = 0; i < XY; i++) {\n@@ -698,33 +738,33 @@ static double gaussSeidel2Sor(double const M[XY][4], double omega,\n }\n \n /* Normalise the values so that the smallest value is 1. */\n-static void normalise(double *ptr, size_t n)\n+static void normalise(std::vector<double> &results)\n {\n-\tdouble minval = ptr[0];\n-\tfor (size_t i = 1; i < n; i++)\n-\t\tminval = std::min(minval, ptr[i]);\n-\tfor (size_t i = 0; i < n; i++)\n-\t\tptr[i] /= minval;\n+\tdouble minval = *std::min_element(results.begin(), results.end());\n+\tstd::for_each(results.begin(), results.end(),\n+\t\t      [minval](double val) { return val / minval; });\n }\n \n /* Rescale the values so that the average value is 1. */\n-static void reaverage(Span<double> data)\n+static void reaverage(std::vector<double> &data)\n {\n \tdouble sum = std::accumulate(data.begin(), data.end(), 0.0);\n \tdouble ratio = 1 / (sum / data.size());\n-\tfor (double &d : data)\n-\t\td *= ratio;\n+\tstd::for_each(data.begin(), data.end(),\n+\t\t      [ratio](double val) { return val * ratio; });\n }\n \n-static void runMatrixIterations(double const C[XY], double lambda[XY],\n-\t\t\t\tdouble const W[XY][4], double omega,\n-\t\t\t\tint nIter, double threshold, double lambdaBound)\n+static void runMatrixIterations(const std::vector<double> &C,\n+\t\t\t\tstd::vector<double> &lambda,\n+\t\t\t\tconst std::vector<std::array<double, 4>> &W,\n+\t\t\t\tstd::vector<std::array<double, 4>> &M, double omega,\n+\t\t\t\tunsigned int nIter, double threshold, double lambdaBound,\n+\t\t\t\tconst Size &size)\n {\n-\tdouble M[XY][4];\n-\tconstructM(C, W, M);\n+\tconstructM(C, W, M, size);\n \tdouble lastMaxDiff = std::numeric_limits<double>::max();\n-\tfor (int i = 0; i < nIter; i++) {\n-\t\tdouble maxDiff = fabs(gaussSeidel2Sor(M, omega, lambda, lambdaBound));\n+\tfor (unsigned int i = 0; i < nIter; i++) {\n+\t\tdouble maxDiff = fabs(gaussSeidel2Sor(M, omega, lambda, lambdaBound, size));\n \t\tif (maxDiff < threshold) {\n \t\t\tLOG(RPiAlsc, Debug)\n \t\t\t\t<< \"Stop after \" << i + 1 << \" iterations\";\n@@ -741,39 +781,44 @@ static void runMatrixIterations(double const C[XY], double lambda[XY],\n \t\tlastMaxDiff = maxDiff;\n \t}\n \t/* We're going to normalise the lambdas so the total average is 1. */\n-\treaverage({ lambda, XY });\n+\treaverage(lambda);\n }\n \n-static void addLuminanceRb(double result[XY], double const lambda[XY],\n-\t\t\t   double const luminanceLut[XY],\n+static void addLuminanceRb(std::vector<double> &result, const std::vector<double> &lambda,\n+\t\t\t   const std::vector<double> &luminanceLut,\n \t\t\t   double luminanceStrength)\n {\n-\tfor (int i = 0; i < XY; i++)\n+\tfor (unsigned int i = 0; i < result.size(); i++)\n \t\tresult[i] = lambda[i] * ((luminanceLut[i] - 1) * luminanceStrength + 1);\n }\n \n-static void addLuminanceG(double result[XY], double lambda,\n-\t\t\t  double const luminanceLut[XY],\n+static void addLuminanceG(std::vector<double> &result, double lambda,\n+\t\t\t  const std::vector<double> &luminanceLut,\n \t\t\t  double luminanceStrength)\n {\n-\tfor (int i = 0; i < XY; i++)\n+\tfor (unsigned int i = 0; i < result.size(); i++)\n \t\tresult[i] = lambda * ((luminanceLut[i] - 1) * luminanceStrength + 1);\n }\n \n-void addLuminanceToTables(double results[3][Y][X], double const lambdaR[XY],\n-\t\t\t  double lambdaG, double const lambdaB[XY],\n-\t\t\t  double const luminanceLut[XY],\n+void addLuminanceToTables(std::array<std::vector<double>, 3> &results,\n+\t\t\t  const std::vector<double> &lambdaR,\n+\t\t\t  double lambdaG, const std::vector<double> &lambdaB,\n+\t\t\t  const std::vector<double> &luminanceLut,\n \t\t\t  double luminanceStrength)\n {\n-\taddLuminanceRb((double *)results[0], lambdaR, luminanceLut, luminanceStrength);\n-\taddLuminanceG((double *)results[1], lambdaG, luminanceLut, luminanceStrength);\n-\taddLuminanceRb((double *)results[2], lambdaB, luminanceLut, luminanceStrength);\n-\tnormalise((double *)results, 3 * XY);\n+\taddLuminanceRb(results[0], lambdaR, luminanceLut, luminanceStrength);\n+\taddLuminanceG(results[1], lambdaG, luminanceLut, luminanceStrength);\n+\taddLuminanceRb(results[2], lambdaB, luminanceLut, luminanceStrength);\n+\tfor (auto &r : results)\n+\t\tnormalise(r);\n }\n \n void Alsc::doAlsc()\n {\n-\tdouble cr[XY], cb[XY], wr[XY][4], wb[XY][4], calTableR[XY], calTableB[XY], calTableTmp[XY];\n+\tstd::vector<double> &cr = tmpC_[0], &cb = tmpC_[1], &calTableR = tmpC_[2],\n+\t\t\t    &calTableB = tmpC_[3], &calTableTmp = tmpC_[4];\n+\tstd::vector<std::array<double, 4>> &wr = tmpM_[0], &wb = tmpM_[1], &M = tmpM_[2];\n+\n \t/*\n \t * Calculate our R/B (\"Cr\"/\"Cb\") colour statistics, and assess which are\n \t * usable.\n@@ -784,9 +829,9 @@ void Alsc::doAlsc()\n \t * case the camera mode is not full-frame.\n \t */\n \tgetCalTable(ct_, config_.calibrationsCr, calTableTmp);\n-\tresampleCalTable(calTableTmp, cameraMode_, calTableR);\n+\tresampleCalTable(calTableTmp, cameraMode_, config_.tableSize, calTableR);\n \tgetCalTable(ct_, config_.calibrationsCb, calTableTmp);\n-\tresampleCalTable(calTableTmp, cameraMode_, calTableB);\n+\tresampleCalTable(calTableTmp, cameraMode_, config_.tableSize, calTableB);\n \t/*\n \t * You could print out the cal tables for this image here, if you're\n \t * tuning the algorithm...\n@@ -796,13 +841,13 @@ void Alsc::doAlsc()\n \tapplyCalTable(calTableR, cr);\n \tapplyCalTable(calTableB, cb);\n \t/* Compute weights between zones. */\n-\tcomputeW(cr, config_.sigmaCr, wr);\n-\tcomputeW(cb, config_.sigmaCb, wb);\n+\tcomputeW(cr, config_.sigmaCr, wr, config_.tableSize);\n+\tcomputeW(cb, config_.sigmaCb, wb, config_.tableSize);\n \t/* Run Gauss-Seidel iterations over the resulting matrix, for R and B. */\n-\trunMatrixIterations(cr, lambdaR_, wr, config_.omega, config_.nIter,\n-\t\t\t    config_.threshold, config_.lambdaBound);\n-\trunMatrixIterations(cb, lambdaB_, wb, config_.omega, config_.nIter,\n-\t\t\t    config_.threshold, config_.lambdaBound);\n+\trunMatrixIterations(cr, lambdaR_, wr, M, config_.omega, config_.nIter,\n+\t\t\t    config_.threshold, config_.lambdaBound, config_.tableSize);\n+\trunMatrixIterations(cb, lambdaB_, wb, M, config_.omega, config_.nIter,\n+\t\t\t    config_.threshold, config_.lambdaBound, config_.tableSize);\n \t/*\n \t * Fold the calibrated gains into our final lambda values. (Note that on\n \t * the next run, we re-start with the lambda values that don't have the\ndiff --git a/src/ipa/raspberrypi/controller/rpi/alsc.h b/src/ipa/raspberrypi/controller/rpi/alsc.h\nindex 9167c9ffa2e3..85e998db40e9 100644\n--- a/src/ipa/raspberrypi/controller/rpi/alsc.h\n+++ b/src/ipa/raspberrypi/controller/rpi/alsc.h\n@@ -6,9 +6,13 @@\n  */\n #pragma once\n \n+#include <array>\n #include <mutex>\n #include <condition_variable>\n #include <thread>\n+#include <vector>\n+\n+#include <libcamera/geometry.h>\n \n #include \"../algorithm.h\"\n #include \"../alsc_status.h\"\n@@ -20,7 +24,7 @@ namespace RPiController {\n \n struct AlscCalibration {\n \tdouble ct;\n-\tdouble table[AlscCellsX * AlscCellsY];\n+\tstd::vector<double> table;\n };\n \n struct AlscConfig {\n@@ -36,13 +40,14 @@ struct AlscConfig {\n \tuint16_t minG;\n \tdouble omega;\n \tuint32_t nIter;\n-\tdouble luminanceLut[AlscCellsX * AlscCellsY];\n+\tstd::vector<double> luminanceLut;\n \tdouble luminanceStrength;\n \tstd::vector<AlscCalibration> calibrationsCr;\n \tstd::vector<AlscCalibration> calibrationsCb;\n \tdouble defaultCt; /* colour temperature if no metadata found */\n \tdouble threshold; /* iteration termination threshold */\n \tdouble lambdaBound; /* upper/lower bound for lambda from a value of 1 */\n+\tlibcamera::Size tableSize;\n };\n \n class Alsc : public Algorithm\n@@ -62,7 +67,7 @@ private:\n \tAlscConfig config_;\n \tbool firstTime_;\n \tCameraMode cameraMode_;\n-\tdouble luminanceTable_[AlscCellsX * AlscCellsY];\n+\tstd::vector<double> luminanceTable_;\n \tstd::thread asyncThread_;\n \tvoid asyncFunc(); /* asynchronous thread function */\n \tstd::mutex mutex_;\n@@ -88,8 +93,8 @@ private:\n \tint frameCount_;\n \t/* counts up to startupFrames for Process function */\n \tint frameCount2_;\n-\tdouble syncResults_[3][AlscCellsY][AlscCellsX];\n-\tdouble prevSyncResults_[3][AlscCellsY][AlscCellsX];\n+\tstd::array<std::vector<double>, 3> syncResults_;\n+\tstd::array<std::vector<double>, 3> prevSyncResults_;\n \tvoid waitForAysncThread();\n \t/*\n \t * The following are for the asynchronous thread to use, though the main\n@@ -100,12 +105,16 @@ private:\n \tvoid fetchAsyncResults();\n \tdouble ct_;\n \tRgbyRegions statistics_;\n-\tdouble asyncResults_[3][AlscCellsY][AlscCellsX];\n-\tdouble asyncLambdaR_[AlscCellsX * AlscCellsY];\n-\tdouble asyncLambdaB_[AlscCellsX * AlscCellsY];\n+\tstd::array<std::vector<double>, 3> asyncResults_;\n+\tstd::vector<double> asyncLambdaR_;\n+\tstd::vector<double> asyncLambdaB_;\n \tvoid doAlsc();\n-\tdouble lambdaR_[AlscCellsX * AlscCellsY];\n-\tdouble lambdaB_[AlscCellsX * AlscCellsY];\n+\tstd::vector<double> lambdaR_;\n+\tstd::vector<double> lambdaB_;\n+\n+\t/* Temporaries for the computations */\n+\tstd::array<std::vector<double>, 5> tmpC_;\n+\tstd::array<std::vector<std::array<double, 4>>, 3> tmpM_;\n };\n \n } /* namespace RPiController */\ndiff --git a/src/ipa/raspberrypi/raspberrypi.cpp b/src/ipa/raspberrypi/raspberrypi.cpp\nindex b64cb96e2dde..0fa79bb4af41 100644\n--- a/src/ipa/raspberrypi/raspberrypi.cpp\n+++ b/src/ipa/raspberrypi/raspberrypi.cpp\n@@ -13,6 +13,7 @@\n #include <math.h>\n #include <stdint.h>\n #include <string.h>\n+#include <vector>\n #include <sys/mman.h>\n \n #include <linux/bcm2835-isp.h>\n@@ -174,7 +175,7 @@ private:\n \tvoid applyDPC(const struct DpcStatus *dpcStatus, ControlList &ctrls);\n \tvoid applyLS(const struct AlscStatus *lsStatus, ControlList &ctrls);\n \tvoid applyAF(const struct AfStatus *afStatus, ControlList &lensCtrls);\n-\tvoid resampleTable(uint16_t dest[], double const src[12][16], int destW, int destH);\n+\tvoid resampleTable(uint16_t dest[], const std::vector<double> &src, int destW, int destH);\n \n \tstd::map<unsigned int, MappedFrameBuffer> buffers_;\n \n@@ -1768,7 +1769,7 @@ void IPARPi::applyAF(const struct AfStatus *afStatus, ControlList &lensCtrls)\n  * Resamples a 16x12 table with central sampling to destW x destH with corner\n  * sampling.\n  */\n-void IPARPi::resampleTable(uint16_t dest[], double const src[12][16],\n+void IPARPi::resampleTable(uint16_t dest[], const std::vector<double> &src,\n \t\t\t   int destW, int destH)\n {\n \t/*\n@@ -1793,8 +1794,8 @@ void IPARPi::resampleTable(uint16_t dest[], double const src[12][16],\n \t\tdouble yf = y - yLo;\n \t\tint yHi = yLo < 11 ? yLo + 1 : 11;\n \t\tyLo = yLo > 0 ? yLo : 0;\n-\t\tdouble const *rowAbove = src[yLo];\n-\t\tdouble const *rowBelow = src[yHi];\n+\t\tdouble const *rowAbove = src.data() + yLo * 16;\n+\t\tdouble const *rowBelow = src.data() + yHi * 16;\n \t\tfor (int i = 0; i < destW; i++) {\n \t\t\tdouble above = rowAbove[xLo[i]] * (1 - xf[i]) + rowAbove[xHi[i]] * xf[i];\n \t\t\tdouble below = rowBelow[xLo[i]] * (1 - xf[i]) + rowBelow[xHi[i]] * xf[i];\n","prefixes":["libcamera-devel","v1","03/10"]}