{"id":18243,"url":"https://patchwork.libcamera.org/api/1.1/patches/18243/?format=json","web_url":"https://patchwork.libcamera.org/patch/18243/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/1.1/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":"<20230203091734.22803-5-naush@raspberrypi.com>","date":"2023-02-03T09:17:33","name":"[libcamera-devel,v4,4/5] ipa: raspberrypi: Use the generic statistics structure in the algorithms","commit_ref":null,"pull_url":null,"state":"accepted","archived":false,"hash":"55834a26bb619a419c189445d1f16a9315928625","submitter":{"id":34,"url":"https://patchwork.libcamera.org/api/1.1/people/34/?format=json","name":"Naushir Patuck","email":"naush@raspberrypi.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/18243/mbox/","series":[{"id":3738,"url":"https://patchwork.libcamera.org/api/1.1/series/3738/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=3738","date":"2023-02-03T09:17:29","name":"Raspberry Pi: Generalise statistics","version":4,"mbox":"https://patchwork.libcamera.org/series/3738/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/18243/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/18243/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 62D92C329C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri,  3 Feb 2023 09:17:46 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id AACCB625E6;\n\tFri,  3 Feb 2023 10:17:45 +0100 (CET)","from mail-wm1-x32e.google.com (mail-wm1-x32e.google.com\n\t[IPv6:2a00:1450:4864:20::32e])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id D0F74625E6\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri,  3 Feb 2023 10:17:42 +0100 (CET)","by mail-wm1-x32e.google.com with SMTP id\n\tl37-20020a05600c1d2500b003dfe46a9801so1863765wms.0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 03 Feb 2023 01:17:42 -0800 (PST)","from localhost.localdomain ([93.93.133.154])\n\tby smtp.gmail.com with ESMTPSA id\n\taz24-20020a05600c601800b003dc4baaedd3sm7468864wmb.37.2023.02.03.01.17.41\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tFri, 03 Feb 2023 01:17:41 -0800 (PST)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1675415865;\n\tbh=3fF12qfXaAoV4729g7w1NrymLzTumDyDGI/x10LBEf4=;\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:Cc:\n\tFrom;\n\tb=tMlMEsVEjjP2NFXDZvn/0DCeXzAAu9jadD3dz2N2jb/PlilTZ/xp3/olpJxyRNwZu\n\tb9ICpmAUWPriGERv4TL7bRjQQIiTMKm9zdw3s0lVkc84yWBtlmGZkY25gW3USFEg3C\n\tS2Wb6vbzyldCJaiOoZByVoDCRPp+sWHYMGoiiVrH5aCy8fXJJqY632kXZ3bV5lqu93\n\ts5vPuXIk/jMkNIrw8QztLY86nhIYknKuOYUi9Ep1QSmJNP5CEV0I9l2HXJC+rEygSP\n\tM/jBQUm6pXec03NrvHcG3yhmq6qXFrPmu9oSeB4ySSlT62iyBt08BmWCnW2yCpnlq2\n\tTuvhYPQqZrRWA==","v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google;\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=hcSRKZKVn+WWn/o3x44hx5plyPapJYKIGnhoSAAJTr0=;\n\tb=o+Iq8RALG6uzXGYRNkE+mXuge/QvuD9+gg+mPDeOAW7W1lOFmiU1EPC/yB2v0qyh3r\n\tmmtegU62g4pCSvO5znBj32F0CJyBulJbTTDPp7Nz/ZTGlTzeUlAichzXa+tpo/KWuQSP\n\tkY3EzSqq1v49jTTY/u1otgwKkiq0wKcVtt1BcmPjPVWsmvQIkhvXThX8SJEnQ16d5puH\n\tuo/4NTAPlKCX4H76jLWPUL6e+cJOjg351yLnHGN/GfMyaNfZXXQlSuZuCN2qj/uDJ01t\n\tkfVSnosohrEGu9foIQwYgM2iQbUxZf+vFcveos6vMd6pbnNeQ0VC3FxHAGLMWFyofuAU\n\tcrEw=="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key; \n\tunprotected) header.d=raspberrypi.com\n\theader.i=@raspberrypi.com\n\theader.b=\"o+Iq8RAL\"; dkim-atps=neutral","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20210112;\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=hcSRKZKVn+WWn/o3x44hx5plyPapJYKIGnhoSAAJTr0=;\n\tb=v8zpqPLQFkIsmt03j2c+2M4Fb6oMi88XBeALTWUCHPIpdlbf/9qxXlowTpvknors2x\n\tayAILMklwm2yIgnhCl+sVFmeVzpnY1R2WFIBrLyQxYqpDqe0kTLQY7EDZtTIqCIeh4gf\n\t2H23xK+OONU+hcwdxHCvDi2xOBnIIceGx3y7RKcA5d6Y05vOfVoo5hvTLQ4ReLsYl/Tn\n\trBh1Q+rvY8ktIF6wfgVjlzGCwhI+ltpn8llHtYgE2WPIVyS0trL/AxO0duSHKint2AIA\n\tPgS6Reg2ZuFtMG0QGqA6DQ5LgNW+LKMLJcdJRAAvpLFCy+Xs4JY8Ptc0Jm7g2qPFz/d8\n\teIhg==","X-Gm-Message-State":"AO0yUKV0qmVJ3gtwWEq5KqBCPYVtp2x1Gt1QnUAV5Wwdn20aV+ORnHKl\n\t7nv1HM5QoN1DzIbfAqqHtIuD4OB+TeU5K3b3KceBdA==","X-Google-Smtp-Source":"AK7set/0ENuL23ylyxQsDlBKRx5ffS1YLiHG3k8HsgHHfogw0jZU82jXZBYrjVfrq5I7wMjLucaXIA==","X-Received":"by 2002:a05:600c:4f50:b0:3df:94c3:4725 with SMTP id\n\tm16-20020a05600c4f5000b003df94c34725mr6486266wmq.38.1675415861715; \n\tFri, 03 Feb 2023 01:17:41 -0800 (PST)","To":"libcamera-devel@lists.libcamera.org","Date":"Fri,  3 Feb 2023 09:17:33 +0000","Message-Id":"<20230203091734.22803-5-naush@raspberrypi.com>","X-Mailer":"git-send-email 2.25.1","In-Reply-To":"<20230203091734.22803-1-naush@raspberrypi.com>","References":"<20230203091734.22803-1-naush@raspberrypi.com>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","Subject":"[libcamera-devel] [PATCH v4 4/5] ipa: raspberrypi: Use the generic\n\tstatistics structure in the algorithms","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":"Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"},"content":"Repurpose the StatisticsPtr type from being a shared_ptr<bcm2835_isp_stats> to\nshared_ptr<RPiController::Statistics>. This removes any hardware specific header\nfiles and structures from the algorithms source code.\n\nAdd a new function in the Raspberry Pi IPA to populate the generic statistics\nstructure from the values provided by the hardware in the bcm2835_isp_stats\nstructure.\n\nUpdate the Lux, AWB, AGC, ALSC, Contrast, and Focus algorithms to use the\ngeneric statistics structure appropriately in their calculations. Additionally,\nremove references to any hardware specific headers and defines in these source\nfiles.\n\nSigned-off-by: Naushir Patuck <naush@raspberrypi.com>\nReviewed-by: David Plowman <david.plowman@raspberrypi.com>\nTested-by: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>\n---\n src/ipa/raspberrypi/cam_helper_imx708.cpp     | 26 ++++++-----\n src/ipa/raspberrypi/controller/controller.h   |  4 +-\n src/ipa/raspberrypi/controller/rpi/af.cpp     | 10 ++---\n src/ipa/raspberrypi/controller/rpi/af.h       |  8 +++-\n src/ipa/raspberrypi/controller/rpi/agc.cpp    | 27 +++++-------\n src/ipa/raspberrypi/controller/rpi/agc.h      |  2 +-\n src/ipa/raspberrypi/controller/rpi/alsc.cpp   | 32 +++++++-------\n src/ipa/raspberrypi/controller/rpi/alsc.h     |  3 +-\n src/ipa/raspberrypi/controller/rpi/awb.cpp    | 20 ++++-----\n src/ipa/raspberrypi/controller/rpi/awb.h      |  1 +\n .../raspberrypi/controller/rpi/contrast.cpp   |  8 ++--\n src/ipa/raspberrypi/controller/rpi/focus.cpp  |  7 ++-\n src/ipa/raspberrypi/controller/rpi/lux.cpp    | 14 +-----\n src/ipa/raspberrypi/raspberrypi.cpp           | 44 ++++++++++++++++++-\n src/ipa/raspberrypi/statistics.h              |  2 +\n 15 files changed, 121 insertions(+), 87 deletions(-)","diff":"diff --git a/src/ipa/raspberrypi/cam_helper_imx708.cpp b/src/ipa/raspberrypi/cam_helper_imx708.cpp\nindex 4c43c8e7fb7d..f825a7f178e3 100644\n--- a/src/ipa/raspberrypi/cam_helper_imx708.cpp\n+++ b/src/ipa/raspberrypi/cam_helper_imx708.cpp\n@@ -78,14 +78,14 @@ private:\n \tbool parseAEHist(const uint8_t *ptr, size_t len, unsigned bpp);\n \tvoid putAGCStatistics(StatisticsPtr stats);\n \n-\tuint32_t aeHistLinear_[128];\n+\tHistogram aeHistLinear_;\n \tuint32_t aeHistAverage_;\n \tbool aeHistValid_;\n };\n \n CamHelperImx708::CamHelperImx708()\n \t: CamHelper(std::make_unique<MdParserSmia>(registerList), frameIntegrationDiff),\n-\t  aeHistLinear_{ 0 }, aeHistAverage_(0), aeHistValid_(false)\n+\t  aeHistLinear_{}, aeHistAverage_(0), aeHistValid_(false)\n {\n }\n \n@@ -264,9 +264,11 @@ bool CamHelperImx708::parsePdafData(const uint8_t *ptr, size_t len,\n \n bool CamHelperImx708::parseAEHist(const uint8_t *ptr, size_t len, unsigned bpp)\n {\n-\tstatic const uint32_t ISP_PIPELINE_BITS = 13;\n+\tstatic constexpr unsigned int PipelineBits = Statistics::NormalisationFactorPow2;\n+\n \tuint64_t count = 0, sum = 0;\n \tsize_t step = bpp >> 1; /* bytes per histogram bin */\n+\tuint32_t hist[128];\n \n \tif (len < 144 * step)\n \t\treturn false;\n@@ -280,12 +282,12 @@ bool CamHelperImx708::parseAEHist(const uint8_t *ptr, size_t len, unsigned bpp)\n \t\tif (ptr[3] != 0x55)\n \t\t\treturn false;\n \t\tuint32_t c = (ptr[0] << 14) + (ptr[1] << 6) + (ptr[2] >> 2);\n-\t\taeHistLinear_[i] = c >> 2; /* pixels to quads */\n+\t\thist[i] = c >> 2; /* pixels to quads */\n \t\tif (i != 0) {\n \t\t\tcount += c;\n \t\t\tsum += c *\n-\t\t\t       (i * (1u << (ISP_PIPELINE_BITS - 7)) +\n-\t\t\t\t(1u << (ISP_PIPELINE_BITS - 8)));\n+\t\t\t       (i * (1u << (PipelineBits - 7)) +\n+\t\t\t\t(1u << (PipelineBits - 8)));\n \t\t}\n \t\tptr += step;\n \t}\n@@ -301,15 +303,16 @@ bool CamHelperImx708::parseAEHist(const uint8_t *ptr, size_t len, unsigned bpp)\n \t\tuint32_t c = (ptr[0] << 14) + (ptr[1] << 6) + (ptr[2] >> 2);\n \t\tcount += c;\n \t\tsum += c *\n-\t\t       ((3u << ISP_PIPELINE_BITS) >> (17 - i));\n+\t\t       ((3u << PipelineBits) >> (17 - i));\n \t\tptr += step;\n \t}\n \tif ((unsigned)((ptr[0] << 12) + (ptr[1] << 4) + (ptr[2] >> 4)) !=\n-\t    aeHistLinear_[1]) {\n+\t    hist[1]) {\n \t\tLOG(IPARPI, Error) << \"Lin/Log histogram mismatch\";\n \t\treturn false;\n \t}\n \n+\taeHistLinear_ = std::move(Histogram(hist, 128));\n \taeHistAverage_ = count ? (sum / count) : 0;\n \n \treturn count != 0;\n@@ -329,13 +332,12 @@ void CamHelperImx708::putAGCStatistics(StatisticsPtr stats)\n \t * scaled by a fiddle-factor so that a conventional (non-HDR) y_target\n \t * of e.g. 0.17 will map to a suitable level for HDR.\n \t */\n-\tmemcpy(stats->hist[0].g_hist, aeHistLinear_, sizeof(stats->hist[0].g_hist));\n+\tstats->yHist = aeHistLinear_;\n \n \tconstexpr unsigned int HdrHeadroomFactor = 4;\n \tuint64_t v = HdrHeadroomFactor * aeHistAverage_;\n-\tfor (int i = 0; i < AGC_REGIONS; i++) {\n-\t\tstruct bcm2835_isp_stats_region &r = stats->agc_stats[i];\n-\t\tr.r_sum = r.b_sum = r.g_sum = r.counted * v;\n+\tfor (auto &region : stats->agcRegions) {\n+\t\tregion.val.rSum = region.val.gSum = region.val.bSum = region.counted * v;\n \t}\n }\n \ndiff --git a/src/ipa/raspberrypi/controller/controller.h b/src/ipa/raspberrypi/controller/controller.h\nindex 3e1e051703b3..e6c950c3a509 100644\n--- a/src/ipa/raspberrypi/controller/controller.h\n+++ b/src/ipa/raspberrypi/controller/controller.h\n@@ -15,19 +15,17 @@\n #include <vector>\n #include <string>\n \n-#include <linux/bcm2835-isp.h>\n-\n #include \"libcamera/internal/yaml_parser.h\"\n \n #include \"camera_mode.h\"\n #include \"device_status.h\"\n #include \"metadata.h\"\n+#include \"statistics.h\"\n \n namespace RPiController {\n \n class Algorithm;\n typedef std::unique_ptr<Algorithm> AlgorithmPtr;\n-typedef std::shared_ptr<bcm2835_isp_stats> StatisticsPtr;\n \n /*\n  * The Controller holds a pointer to some global_metadata, which is how\ndiff --git a/src/ipa/raspberrypi/controller/rpi/af.cpp b/src/ipa/raspberrypi/controller/rpi/af.cpp\nindex 2e72f239fb25..a623651875f2 100644\n--- a/src/ipa/raspberrypi/controller/rpi/af.cpp\n+++ b/src/ipa/raspberrypi/controller/rpi/af.cpp\n@@ -352,14 +352,12 @@ bool Af::getPhase(PdafData const &data, double &phase, double &conf) const\n \t}\n }\n \n-double Af::getContrast(struct bcm2835_isp_stats_focus const focus_stats[FOCUS_REGIONS]) const\n+double Af::getContrast(const FocusRegions &focusStats) const\n {\n \tuint32_t sumWc = 0;\n \n-\tfor (unsigned i = 0; i < FOCUS_REGIONS; ++i) {\n-\t\tunsigned w = contrastWeights_[i];\n-\t\tsumWc += w * (focus_stats[i].contrast_val[1][1] >> 10);\n-\t}\n+\tfor (unsigned i = 0; i < focusStats.numRegions(); ++i)\n+\t\tsumWc += contrastWeights_[i] * focusStats.get(i).val;\n \n \treturn (sumWeights_ == 0) ? 0.0 : (double)sumWc / (double)sumWeights_;\n }\n@@ -666,7 +664,7 @@ void Af::prepare(Metadata *imageMetadata)\n void Af::process(StatisticsPtr &stats, [[maybe_unused]] Metadata *imageMetadata)\n {\n \t(void)imageMetadata;\n-\tprevContrast_ = getContrast(stats->focus_stats);\n+\tprevContrast_ = getContrast(stats->focusRegions);\n }\n \n /* Controls */\ndiff --git a/src/ipa/raspberrypi/controller/rpi/af.h b/src/ipa/raspberrypi/controller/rpi/af.h\nindex f7baf8979873..7959371baf64 100644\n--- a/src/ipa/raspberrypi/controller/rpi/af.h\n+++ b/src/ipa/raspberrypi/controller/rpi/af.h\n@@ -11,6 +11,12 @@\n #include \"../pdaf_data.h\"\n #include \"../pwl.h\"\n \n+/*\n+ * \\todo FOCUS_REGIONS is taken from bcm2835-isp.h, but should be made as a\n+ * generic RegionStats structure.\n+ */\n+#define FOCUS_REGIONS 12\n+\n /*\n  * This algorithm implements a hybrid of CDAF and PDAF, favouring PDAF.\n  *\n@@ -117,7 +123,7 @@ private:\n \n \tvoid computeWeights();\n \tbool getPhase(PdafData const &data, double &phase, double &conf) const;\n-\tdouble getContrast(struct bcm2835_isp_stats_focus const focus_stats[FOCUS_REGIONS]) const;\n+\tdouble getContrast(const FocusRegions &focusStats) const;\n \tvoid doPDAF(double phase, double conf);\n \tbool earlyTerminationByPhase(double phase);\n \tdouble findPeak(unsigned index) const;\ndiff --git a/src/ipa/raspberrypi/controller/rpi/agc.cpp b/src/ipa/raspberrypi/controller/rpi/agc.cpp\nindex 46dcc81ae14c..868c30f03d66 100644\n--- a/src/ipa/raspberrypi/controller/rpi/agc.cpp\n+++ b/src/ipa/raspberrypi/controller/rpi/agc.cpp\n@@ -9,8 +9,6 @@\n #include <map>\n #include <tuple>\n \n-#include <linux/bcm2835-isp.h>\n-\n #include <libcamera/base/log.h>\n \n #include \"../awb_status.h\"\n@@ -451,7 +449,7 @@ void Agc::process(StatisticsPtr &stats, Metadata *imageMetadata)\n \tfetchCurrentExposure(imageMetadata);\n \t/* Compute the total gain we require relative to the current exposure. */\n \tdouble gain, targetY;\n-\tcomputeGain(stats.get(), imageMetadata, gain, targetY);\n+\tcomputeGain(stats, imageMetadata, gain, targetY);\n \t/* Now compute the target (final) exposure which we think we want. */\n \tcomputeTargetExposure(gain);\n \t/*\n@@ -585,24 +583,23 @@ void Agc::fetchAwbStatus(Metadata *imageMetadata)\n \t\tLOG(RPiAgc, Debug) << \"No AWB status found\";\n }\n \n-static double computeInitialY(bcm2835_isp_stats *stats, AwbStatus const &awb,\n+static double computeInitialY(StatisticsPtr &stats, AwbStatus const &awb,\n \t\t\t      double weights[], double gain)\n {\n-\tbcm2835_isp_stats_region *regions = stats->agc_stats;\n \t/*\n \t * Note how the calculation below means that equal weights give you\n \t * \"average\" metering (i.e. all pixels equally important).\n \t */\n \tdouble rSum = 0, gSum = 0, bSum = 0, pixelSum = 0;\n-\tfor (unsigned int i = 0; i < AgcStatsSize; i++) {\n-\t\tdouble counted = regions[i].counted;\n-\t\tdouble rAcc = std::min(regions[i].r_sum * gain, ((1 << PipelineBits) - 1) * counted);\n-\t\tdouble gAcc = std::min(regions[i].g_sum * gain, ((1 << PipelineBits) - 1) * counted);\n-\t\tdouble bAcc = std::min(regions[i].b_sum * gain, ((1 << PipelineBits) - 1) * counted);\n+\tfor (unsigned int i = 0; i < stats->agcRegions.numRegions(); i++) {\n+\t\tauto &region = stats->agcRegions.get(i);\n+\t\tdouble rAcc = std::min<double>(region.val.rSum * gain, ((1 << PipelineBits) - 1) * region.counted);\n+\t\tdouble gAcc = std::min<double>(region.val.gSum * gain, ((1 << PipelineBits) - 1) * region.counted);\n+\t\tdouble bAcc = std::min<double>(region.val.bSum * gain, ((1 << PipelineBits) - 1) * region.counted);\n \t\trSum += rAcc * weights[i];\n \t\tgSum += gAcc * weights[i];\n \t\tbSum += bAcc * weights[i];\n-\t\tpixelSum += counted * weights[i];\n+\t\tpixelSum += region.counted * weights[i];\n \t}\n \tif (pixelSum == 0.0) {\n \t\tLOG(RPiAgc, Warning) << \"computeInitialY: pixelSum is zero\";\n@@ -624,23 +621,23 @@ static double computeInitialY(bcm2835_isp_stats *stats, AwbStatus const &awb,\n \n static constexpr double EvGainYTargetLimit = 0.9;\n \n-static double constraintComputeGain(AgcConstraint &c, Histogram &h, double lux,\n+static double constraintComputeGain(AgcConstraint &c, const Histogram &h, double lux,\n \t\t\t\t    double evGain, double &targetY)\n {\n \ttargetY = c.yTarget.eval(c.yTarget.domain().clip(lux));\n \ttargetY = std::min(EvGainYTargetLimit, targetY * evGain);\n \tdouble iqm = h.interQuantileMean(c.qLo, c.qHi);\n-\treturn (targetY * NUM_HISTOGRAM_BINS) / iqm;\n+\treturn (targetY * h.bins()) / iqm;\n }\n \n-void Agc::computeGain(bcm2835_isp_stats *statistics, Metadata *imageMetadata,\n+void Agc::computeGain(StatisticsPtr &statistics, Metadata *imageMetadata,\n \t\t      double &gain, double &targetY)\n {\n \tstruct LuxStatus lux = {};\n \tlux.lux = 400; /* default lux level to 400 in case no metadata found */\n \tif (imageMetadata->get(\"lux.status\", lux) != 0)\n \t\tLOG(RPiAgc, Warning) << \"No lux level found\";\n-\tHistogram h(statistics->hist[0].g_hist, NUM_HISTOGRAM_BINS);\n+\tconst Histogram &h = statistics->yHist;\n \tdouble evGain = status_.ev * config_.baseEv;\n \t/*\n \t * The initial gain and target_Y come from some of the regions. After\ndiff --git a/src/ipa/raspberrypi/controller/rpi/agc.h b/src/ipa/raspberrypi/controller/rpi/agc.h\nindex cf04da1973f1..f04896ca25ad 100644\n--- a/src/ipa/raspberrypi/controller/rpi/agc.h\n+++ b/src/ipa/raspberrypi/controller/rpi/agc.h\n@@ -96,7 +96,7 @@ private:\n \tvoid housekeepConfig();\n \tvoid fetchCurrentExposure(Metadata *imageMetadata);\n \tvoid fetchAwbStatus(Metadata *imageMetadata);\n-\tvoid computeGain(bcm2835_isp_stats *statistics, Metadata *imageMetadata,\n+\tvoid computeGain(StatisticsPtr &statistics, Metadata *imageMetadata,\n \t\t\t double &gain, double &targetY);\n \tvoid computeTargetExposure(double gain);\n \tbool applyDigitalGain(double gain, double targetY);\ndiff --git a/src/ipa/raspberrypi/controller/rpi/alsc.cpp b/src/ipa/raspberrypi/controller/rpi/alsc.cpp\nindex a4afaf841c41..eb4e2f9496e1 100644\n--- a/src/ipa/raspberrypi/controller/rpi/alsc.cpp\n+++ b/src/ipa/raspberrypi/controller/rpi/alsc.cpp\n@@ -310,19 +310,21 @@ double getCt(Metadata *metadata, double defaultCt)\n \treturn awbStatus.temperatureK;\n }\n \n-static void copyStats(bcm2835_isp_stats_region regions[XY], StatisticsPtr &stats,\n+static void copyStats(RgbyRegions &regions, StatisticsPtr &stats,\n \t\t      AlscStatus const &status)\n {\n-\tbcm2835_isp_stats_region *inputRegions = stats->awb_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-\tfor (int i = 0; i < XY; i++) {\n-\t\tregions[i].r_sum = inputRegions[i].r_sum / rTable[i];\n-\t\tregions[i].g_sum = inputRegions[i].g_sum / gTable[i];\n-\t\tregions[i].b_sum = inputRegions[i].b_sum / bTable[i];\n-\t\tregions[i].counted = inputRegions[i].counted;\n-\t\t/* (don't care about the uncounted value) */\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+\t\tr.val.gSum = static_cast<uint64_t>(r.val.gSum / gTable[i]);\n+\t\tr.val.bSum = static_cast<uint64_t>(r.val.bSum / bTable[i]);\n+\t\tregions.set(i, r);\n \t}\n }\n \n@@ -512,19 +514,19 @@ void resampleCalTable(double const calTableIn[XY],\n }\n \n /* Calculate chrominance statistics (R/G and B/G) for each region. */\n-static_assert(XY == AWB_REGIONS, \"ALSC/AWB statistics region mismatch\");\n-static void calculateCrCb(bcm2835_isp_stats_region *awbRegion, double cr[XY],\n+static void calculateCrCb(const RgbyRegions &awbRegion, double cr[XY],\n \t\t\t  double cb[XY], uint32_t minCount, uint16_t minG)\n {\n \tfor (int i = 0; i < XY; i++) {\n-\t\tbcm2835_isp_stats_region &zone = awbRegion[i];\n-\t\tif (zone.counted <= minCount ||\n-\t\t    zone.g_sum / zone.counted <= minG) {\n+\t\tauto s = awbRegion.get(i);\n+\n+\t\tif (s.counted <= minCount || s.val.gSum / s.counted <= minG) {\n \t\t\tcr[i] = cb[i] = InsufficientData;\n \t\t\tcontinue;\n \t\t}\n-\t\tcr[i] = zone.r_sum / (double)zone.g_sum;\n-\t\tcb[i] = zone.b_sum / (double)zone.g_sum;\n+\n+\t\tcr[i] = s.val.rSum / (double)s.val.gSum;\n+\t\tcb[i] = s.val.bSum / (double)s.val.gSum;\n \t}\n }\n \ndiff --git a/src/ipa/raspberrypi/controller/rpi/alsc.h b/src/ipa/raspberrypi/controller/rpi/alsc.h\nindex a858ef5a6551..9167c9ffa2e3 100644\n--- a/src/ipa/raspberrypi/controller/rpi/alsc.h\n+++ b/src/ipa/raspberrypi/controller/rpi/alsc.h\n@@ -12,6 +12,7 @@\n \n #include \"../algorithm.h\"\n #include \"../alsc_status.h\"\n+#include \"../statistics.h\"\n \n namespace RPiController {\n \n@@ -98,7 +99,7 @@ private:\n \t/* copy out the results from the async thread so that it can be restarted */\n \tvoid fetchAsyncResults();\n \tdouble ct_;\n-\tbcm2835_isp_stats_region statistics_[AlscCellsY * AlscCellsX];\n+\tRgbyRegions statistics_;\n \tdouble asyncResults_[3][AlscCellsY][AlscCellsX];\n \tdouble asyncLambdaR_[AlscCellsX * AlscCellsY];\n \tdouble asyncLambdaB_[AlscCellsX * AlscCellsY];\ndiff --git a/src/ipa/raspberrypi/controller/rpi/awb.cpp b/src/ipa/raspberrypi/controller/rpi/awb.cpp\nindex 04d1c8783654..ef3435d66106 100644\n--- a/src/ipa/raspberrypi/controller/rpi/awb.cpp\n+++ b/src/ipa/raspberrypi/controller/rpi/awb.cpp\n@@ -21,9 +21,6 @@ LOG_DEFINE_CATEGORY(RPiAwb)\n \n #define NAME \"rpi.awb\"\n \n-static constexpr unsigned int AwbStatsSizeX = DEFAULT_AWB_REGIONS_X;\n-static constexpr unsigned int AwbStatsSizeY = DEFAULT_AWB_REGIONS_Y;\n-\n /*\n  * todo - the locking in this algorithm needs some tidying up as has been done\n  * elsewhere (ALSC and AGC).\n@@ -401,17 +398,16 @@ void Awb::asyncFunc()\n }\n \n static void generateStats(std::vector<Awb::RGB> &zones,\n-\t\t\t  bcm2835_isp_stats_region *stats, double minPixels,\n+\t\t\t  RgbyRegions &stats, double minPixels,\n \t\t\t  double minG)\n {\n-\tfor (unsigned int i = 0; i < AwbStatsSizeX * AwbStatsSizeY; i++) {\n+\tfor (auto const &region : stats) {\n \t\tAwb::RGB zone;\n-\t\tdouble counted = stats[i].counted;\n-\t\tif (counted >= minPixels) {\n-\t\t\tzone.G = stats[i].g_sum / counted;\n+\t\tif (region.counted >= minPixels) {\n+\t\t\tzone.G = region.val.gSum / region.counted;\n \t\t\tif (zone.G >= minG) {\n-\t\t\t\tzone.R = stats[i].r_sum / counted;\n-\t\t\t\tzone.B = stats[i].b_sum / counted;\n+\t\t\t\tzone.R = region.val.rSum / region.counted;\n+\t\t\t\tzone.B = region.val.bSum / region.counted;\n \t\t\t\tzones.push_back(zone);\n \t\t\t}\n \t\t}\n@@ -425,7 +421,7 @@ void Awb::prepareStats()\n \t * LSC has already been applied to the stats in this pipeline, so stop\n \t * any LSC compensation.  We also ignore config_.fast in this version.\n \t */\n-\tgenerateStats(zones_, statistics_->awb_stats, config_.minPixels,\n+\tgenerateStats(zones_, statistics_->awbRegions, config_.minPixels,\n \t\t      config_.minG);\n \t/*\n \t * apply sensitivities, so values appear to come from our \"canonical\"\n@@ -641,7 +637,7 @@ void Awb::awbBayes()\n \t * valid... not entirely sure about this.\n \t */\n \tPwl prior = interpolatePrior();\n-\tprior *= zones_.size() / (double)(AwbStatsSizeX * AwbStatsSizeY);\n+\tprior *= zones_.size() / (double)(statistics_->awbRegions.numRegions());\n \tprior.map([](double x, double y) {\n \t\tLOG(RPiAwb, Debug) << \"(\" << x << \",\" << y << \")\";\n \t});\ndiff --git a/src/ipa/raspberrypi/controller/rpi/awb.h b/src/ipa/raspberrypi/controller/rpi/awb.h\nindex 2254c3ed2cc4..e7d49cd8036b 100644\n--- a/src/ipa/raspberrypi/controller/rpi/awb.h\n+++ b/src/ipa/raspberrypi/controller/rpi/awb.h\n@@ -13,6 +13,7 @@\n #include \"../awb_algorithm.h\"\n #include \"../pwl.h\"\n #include \"../awb_status.h\"\n+#include \"../statistics.h\"\n \n namespace RPiController {\n \ndiff --git a/src/ipa/raspberrypi/controller/rpi/contrast.cpp b/src/ipa/raspberrypi/controller/rpi/contrast.cpp\nindex 5b37edcbd46a..a4f8c4f04fc4 100644\n--- a/src/ipa/raspberrypi/controller/rpi/contrast.cpp\n+++ b/src/ipa/raspberrypi/controller/rpi/contrast.cpp\n@@ -106,7 +106,7 @@ Pwl computeStretchCurve(Histogram const &histogram,\n \t * bit.\n \t */\n \tdouble histLo = histogram.quantile(config.loHistogram) *\n-\t\t\t(65536 / NUM_HISTOGRAM_BINS);\n+\t\t\t(65536 / histogram.bins());\n \tdouble levelLo = config.loLevel * 65536;\n \tLOG(RPiContrast, Debug)\n \t\t<< \"Move histogram point \" << histLo << \" to \" << levelLo;\n@@ -119,7 +119,7 @@ Pwl computeStretchCurve(Histogram const &histogram,\n \t * Keep the mid-point (median) in the same place, though, to limit the\n \t * apparent amount of global brightness shift.\n \t */\n-\tdouble mid = histogram.quantile(0.5) * (65536 / NUM_HISTOGRAM_BINS);\n+\tdouble mid = histogram.quantile(0.5) * (65536 / histogram.bins());\n \tenhance.append(mid, mid);\n \n \t/*\n@@ -127,7 +127,7 @@ Pwl computeStretchCurve(Histogram const &histogram,\n \t * there up.\n \t */\n \tdouble histHi = histogram.quantile(config.hiHistogram) *\n-\t\t\t(65536 / NUM_HISTOGRAM_BINS);\n+\t\t\t(65536 / histogram.bins());\n \tdouble levelHi = config.hiLevel * 65536;\n \tLOG(RPiContrast, Debug)\n \t\t<< \"Move histogram point \" << histHi << \" to \" << levelHi;\n@@ -158,7 +158,7 @@ Pwl applyManualContrast(Pwl const &gammaCurve, double brightness,\n void Contrast::process(StatisticsPtr &stats,\n \t\t       [[maybe_unused]] Metadata *imageMetadata)\n {\n-\tHistogram histogram(stats->hist[0].g_hist, NUM_HISTOGRAM_BINS);\n+\tHistogram &histogram = stats->yHist;\n \t/*\n \t * We look at the histogram and adjust the gamma curve in the following\n \t * ways: 1. Adjust the gamma curve so as to pull the start of the\ndiff --git a/src/ipa/raspberrypi/controller/rpi/focus.cpp b/src/ipa/raspberrypi/controller/rpi/focus.cpp\nindex 8c5029bd0e95..ea3cc00e42c3 100644\n--- a/src/ipa/raspberrypi/controller/rpi/focus.cpp\n+++ b/src/ipa/raspberrypi/controller/rpi/focus.cpp\n@@ -31,10 +31,9 @@ char const *Focus::name() const\n void Focus::process(StatisticsPtr &stats, Metadata *imageMetadata)\n {\n \tFocusStatus status;\n-\tunsigned int i;\n-\tfor (i = 0; i < FOCUS_REGIONS; i++)\n-\t\tstatus.focusMeasures[i] = stats->focus_stats[i].contrast_val[1][1] / 1000;\n-\tstatus.num = i;\n+\tfor (unsigned int i = 0; i < stats->focusRegions.numRegions(); i++)\n+\t\tstatus.focusMeasures[i] = stats->focusRegions.get(i).val;\n+\tstatus.num = stats->focusRegions.numRegions();\n \timageMetadata->set(\"focus.status\", status);\n \n \tLOG(RPiFocus, Debug)\ndiff --git a/src/ipa/raspberrypi/controller/rpi/lux.cpp b/src/ipa/raspberrypi/controller/rpi/lux.cpp\nindex 9759186afacf..06625f3a5ea3 100644\n--- a/src/ipa/raspberrypi/controller/rpi/lux.cpp\n+++ b/src/ipa/raspberrypi/controller/rpi/lux.cpp\n@@ -6,8 +6,6 @@\n  */\n #include <math.h>\n \n-#include <linux/bcm2835-isp.h>\n-\n #include <libcamera/base/log.h>\n \n #include \"../device_status.h\"\n@@ -83,20 +81,12 @@ void Lux::process(StatisticsPtr &stats, Metadata *imageMetadata)\n \tif (imageMetadata->get(\"device.status\", deviceStatus) == 0) {\n \t\tdouble currentGain = deviceStatus.analogueGain;\n \t\tdouble currentAperture = deviceStatus.aperture.value_or(currentAperture_);\n-\t\tuint64_t sum = 0;\n-\t\tuint32_t num = 0;\n-\t\tuint32_t *bin = stats->hist[0].g_hist;\n-\t\tconst int numBins = sizeof(stats->hist[0].g_hist) /\n-\t\t\t\t    sizeof(stats->hist[0].g_hist[0]);\n-\t\tfor (int i = 0; i < numBins; i++)\n-\t\t\tsum += bin[i] * (uint64_t)i, num += bin[i];\n-\t\t/* add .5 to reflect the mid-points of bins */\n-\t\tdouble currentY = sum / (double)num + .5;\n+\t\tdouble currentY = stats->yHist.interQuantileMean(0, 1);\n \t\tdouble gainRatio = referenceGain_ / currentGain;\n \t\tdouble shutterSpeedRatio =\n \t\t\treferenceShutterSpeed_ / deviceStatus.shutterSpeed;\n \t\tdouble apertureRatio = referenceAperture_ / currentAperture;\n-\t\tdouble yRatio = currentY * (65536 / numBins) / referenceY_;\n+\t\tdouble yRatio = currentY * (65536 / stats->yHist.bins()) / referenceY_;\n \t\tdouble estimatedLux = shutterSpeedRatio * gainRatio *\n \t\t\t\t      apertureRatio * apertureRatio *\n \t\t\t\t      yRatio * referenceLux_;\ndiff --git a/src/ipa/raspberrypi/raspberrypi.cpp b/src/ipa/raspberrypi/raspberrypi.cpp\nindex 5f7397e2ab21..b8cce51522c0 100644\n--- a/src/ipa/raspberrypi/raspberrypi.cpp\n+++ b/src/ipa/raspberrypi/raspberrypi.cpp\n@@ -54,6 +54,7 @@\n #include \"metadata.h\"\n #include \"sharpen_algorithm.h\"\n #include \"sharpen_status.h\"\n+#include \"statistics.h\"\n \n namespace libcamera {\n \n@@ -152,6 +153,7 @@ private:\n \tvoid prepareISP(const ISPConfig &data);\n \tvoid reportMetadata(unsigned int ipaContext);\n \tvoid fillDeviceStatus(const ControlList &sensorControls, unsigned int ipaContext);\n+\tRPiController::StatisticsPtr fillStatistics(bcm2835_isp_stats *stats) const;\n \tvoid processStats(unsigned int bufferId, unsigned int ipaContext);\n \tvoid applyFrameDurations(Duration minFrameDuration, Duration maxFrameDuration);\n \tvoid applyAGC(const struct AgcStatus *agcStatus, ControlList &ctrls);\n@@ -1364,6 +1366,46 @@ void IPARPi::fillDeviceStatus(const ControlList &sensorControls, unsigned int ip\n \trpiMetadata_[ipaContext].set(\"device.status\", deviceStatus);\n }\n \n+RPiController::StatisticsPtr IPARPi::fillStatistics(bcm2835_isp_stats *stats) const\n+{\n+\tusing namespace RPiController;\n+\n+\tunsigned int i;\n+\tStatisticsPtr statistics =\n+\t\tstd::make_unique<Statistics>(Statistics::AgcStatsPos::PreWb, Statistics::ColourStatsPos::PostLsc);\n+\n+\t/* RGB histograms are not used, so do not populate them. */\n+\tstatistics->yHist = std::move(RPiController::Histogram(stats->hist[0].g_hist, NUM_HISTOGRAM_BINS));\n+\n+\tstatistics->awbRegions.init({ DEFAULT_AWB_REGIONS_X, DEFAULT_AWB_REGIONS_Y });\n+\tfor (i = 0; i < statistics->awbRegions.numRegions(); i++)\n+\t\tstatistics->awbRegions.set(i, { { stats->awb_stats[i].r_sum,\n+\t\t\t\t\t\t  stats->awb_stats[i].g_sum,\n+\t\t\t\t\t\t  stats->awb_stats[i].b_sum },\n+\t\t\t\t\t\tstats->awb_stats[i].counted,\n+\t\t\t\t\t\tstats->awb_stats[i].notcounted });\n+\n+\t/*\n+\t * There are only ever 15 regions computed by the firmware due to zoning,\n+\t * but the HW defines AGC_REGIONS == 16!\n+\t */\n+\tstatistics->agcRegions.init(15);\n+\tfor (i = 0; i < statistics->agcRegions.numRegions(); i++)\n+\t\tstatistics->agcRegions.set(i, { { stats->agc_stats[i].r_sum,\n+\t\t\t\t\t\t  stats->agc_stats[i].g_sum,\n+\t\t\t\t\t\t  stats->agc_stats[i].b_sum },\n+\t\t\t\t\t\tstats->agc_stats[i].counted,\n+\t\t\t\t\t\tstats->awb_stats[i].notcounted });\n+\n+\tstatistics->focusRegions.init({ 4, 3 });\n+\tfor (i = 0; i < statistics->focusRegions.numRegions(); i++)\n+\t\tstatistics->focusRegions.set(i, { stats->focus_stats[i].contrast_val[1][1] / 1000,\n+\t\t\t\t\t\t  stats->focus_stats[i].contrast_val_num[1][1],\n+\t\t\t\t\t\t  stats->focus_stats[i].contrast_val_num[1][0] });\n+\n+\treturn statistics;\n+}\n+\n void IPARPi::processStats(unsigned int bufferId, unsigned int ipaContext)\n {\n \tRPiController::Metadata &rpiMetadata = rpiMetadata_[ipaContext];\n@@ -1376,7 +1418,7 @@ void IPARPi::processStats(unsigned int bufferId, unsigned int ipaContext)\n \n \tSpan<uint8_t> mem = it->second.planes()[0];\n \tbcm2835_isp_stats *stats = reinterpret_cast<bcm2835_isp_stats *>(mem.data());\n-\tRPiController::StatisticsPtr statistics = std::make_shared<bcm2835_isp_stats>(*stats);\n+\tRPiController::StatisticsPtr statistics = fillStatistics(stats);\n \thelper_->process(statistics, rpiMetadata);\n \tcontroller_.process(statistics, &rpiMetadata);\n \ndiff --git a/src/ipa/raspberrypi/statistics.h b/src/ipa/raspberrypi/statistics.h\nindex ad486534b216..015d4efc6454 100644\n--- a/src/ipa/raspberrypi/statistics.h\n+++ b/src/ipa/raspberrypi/statistics.h\n@@ -73,4 +73,6 @@ struct Statistics {\n \tFocusRegions focusRegions;\n };\n \n+using StatisticsPtr = std::shared_ptr<Statistics>;\n+\n } /* namespace RPiController */\n","prefixes":["libcamera-devel","v4","4/5"]}