{"id":18478,"url":"https://patchwork.libcamera.org/api/1.1/patches/18478/?format=json","web_url":"https://patchwork.libcamera.org/patch/18478/","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":"<20230327122030.11756-10-naush@raspberrypi.com>","date":"2023-03-27T12:20:29","name":"[libcamera-devel,v2,09/10] ipa: raspberrypi: Generalise the autofocus algorithm","commit_ref":null,"pull_url":null,"state":"superseded","archived":false,"hash":"7e196b6c0dbf2922218177e7ba2b93f9de55a2dc","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/18478/mbox/","series":[{"id":3819,"url":"https://patchwork.libcamera.org/api/1.1/series/3819/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=3819","date":"2023-03-27T12:20:20","name":"Raspberry Pi: Generalised algorithms","version":2,"mbox":"https://patchwork.libcamera.org/series/3819/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/18478/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/18478/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 8FCF3C329F\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 27 Mar 2023 12:20:55 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 4783D62752;\n\tMon, 27 Mar 2023 14:20:55 +0200 (CEST)","from mail-io1-xd30.google.com (mail-io1-xd30.google.com\n\t[IPv6:2607:f8b0:4864:20::d30])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 5F92762763\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 27 Mar 2023 14:20:51 +0200 (CEST)","by mail-io1-xd30.google.com with SMTP id d14so3784981ion.9\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 27 Mar 2023 05:20:51 -0700 (PDT)","from localhost.localdomain ([93.93.133.154])\n\tby smtp.gmail.com with ESMTPSA id\n\tr7-20020a5edb47000000b00758ff97aaedsm3239026iop.16.2023.03.27.05.20.49\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tMon, 27 Mar 2023 05:20:50 -0700 (PDT)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1679919655;\n\tbh=yr0ktkwDyN3/Tu/8D/ANe+ofHGDWo4sO1gP6XkDzX0Q=;\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=r4Irz/ox6EDLKqNFZC3M9napDfYeuCBgqR7Zp006bTk8s+T4B6UgbAMTl5Au9eI3X\n\t23SKBujuqwwU24jDX2MoC/VVAVxwJK0SkNGd8osEkrbCIN6RE0KSM7mjmFCzlxIPgT\n\tAft3NeaEe8ZCxGbfPDA5yGYctrhx0sjymfMF6ImS3HwMcTPStvisN+0L5M5yO7UBsG\n\tVnIC7GBKO5/JcJethMzekMeAAVD9loeMF2gmI+GQhTS8CnYeCmWLc6HA6fKtBW8X9h\n\tcBlIqfNeI5Z9BwJWlkr3Kp9D98b0PnwbcswBmvUaOEVx5Xee9rCClFglO+a/XQXs+Y\n\tqsUOu7aUlyopg==","v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google; t=1679919651;\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=F00/QMQd7UwuEW037ja5uCEEW7iXoeIOE6r3lrYMnjg=;\n\tb=qJAYLiUtzuKwbluPYRZ2Q51iLVRwnh6ejp9r6En1jTwdRxUDXz7vj4isz/qsjnJNBM\n\t3Zt8G9t5vm0qvqsAS9IdbTVN0LxkTVFBxJzOoqkBGLqOSQVHekWVcxZTZp/jw+iqI+cw\n\t8stpDZvUdEcERWHIRc6/qoYGyqJZNiNrjpJPTHZblVnWwgEXiN+i/++2Ev6eOuL+YMMV\n\tOtjYDYWZyj6yTt24Jj2uPQkhMAUrtk1DqqcB1tH7EtMjemNdRIfaBLaCRIQpf6mnpf8d\n\tFG/u4u63IvvFFzz6Z83j/mtN/++2Nty5GyjZxpfeFEv3gQ6qTsQJ9pcfkO+Xom4l74jV\n\tNo4A=="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key; \n\tunprotected) header.d=raspberrypi.com\n\theader.i=@raspberrypi.com\n\theader.b=\"qJAYLiUt\"; dkim-atps=neutral","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20210112; t=1679919651;\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=F00/QMQd7UwuEW037ja5uCEEW7iXoeIOE6r3lrYMnjg=;\n\tb=KMFyVTX+Mh9eGER0g7JRwlFqNrcD8traPnxQWIj6w+nWYkE3HTmSA7v1oZR6H42xRh\n\t9/QTxmhEfSU9cIysZOHVpLGVk+N6is3zToRmw2cmeVINdMg1lTyDkL36ztMGIEdtW03X\n\t9XzT37dPCKePIc6UAC9sh8GZd84UWC/ELyy8yyeKNO1/YT/mPvWElFwPqjv1s0n0e5S/\n\teYtrMU8pTx8Ej5Qw7gBEoKzudOu0GOPXCght+u9XoVNsHugI3J7DMcX/tKpKRi7iU058\n\tr12XvyIdim9FKzVEXA34UiSd8DxQpXaPoJltS2qYWOSWnvTyny4UtSASmP/9oS1szqEh\n\tV0hQ==","X-Gm-Message-State":"AO0yUKWDNsVOlqjB+wKZTWcA2AMgXtJCkk53esWAFV2glp6OfSvbPan6\n\t/kq8uNBmtn36vnOKaw5XypNIcF8TyWGw8VoRKGiUrg==","X-Google-Smtp-Source":"AK7set8m2QwvI0YscrB6EHp+2DHTC+xNtDUfgLp69wMcVEcFCO/xsKsdclTNfNEB6skv4DM8k199eg==","X-Received":"by 2002:a6b:f117:0:b0:758:dba1:471c with SMTP id\n\te23-20020a6bf117000000b00758dba1471cmr8876800iog.16.1679919650581; \n\tMon, 27 Mar 2023 05:20:50 -0700 (PDT)","To":"libcamera-devel@lists.libcamera.org","Date":"Mon, 27 Mar 2023 13:20:29 +0100","Message-Id":"<20230327122030.11756-10-naush@raspberrypi.com>","X-Mailer":"git-send-email 2.34.1","In-Reply-To":"<20230327122030.11756-1-naush@raspberrypi.com>","References":"<20230327122030.11756-1-naush@raspberrypi.com>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","Subject":"[libcamera-devel] [PATCH v2 09/10] ipa: raspberrypi: Generalise the\n\tautofocus 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":"Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"},"content":"From: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>\n\nRemove any hard-coded assumptions about the target hardware platform\nfrom the autofocus algorithm. Instead, use the \"target\" string provided\nby the camera tuning config and generalised statistics structures to\ndeterming parameters such as grid and region sizes.\n\nAdditionally, PDAF statistics are represented by a generalised region\nstatistics structure to be device agnostic.\n\nThese changes also require the autofocus algorithm to initialise\nregion weights on the first frame's prepare()/process() call rather\nthan during initialisation.\n\nSigned-off-by: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>\nSigned-off-by: Naushir Patuck <naush@raspberrypi.com>\nTested-by: Naushir Patuck <naush@raspberrypi.com>\n---\n src/ipa/raspberrypi/cam_helper_imx708.cpp  |  23 ++-\n src/ipa/raspberrypi/controller/pdaf_data.h |  21 +--\n src/ipa/raspberrypi/controller/rpi/af.cpp  | 176 +++++++++++----------\n src/ipa/raspberrypi/controller/rpi/af.h    |  28 ++--\n 4 files changed, 133 insertions(+), 115 deletions(-)","diff":"diff --git a/src/ipa/raspberrypi/cam_helper_imx708.cpp b/src/ipa/raspberrypi/cam_helper_imx708.cpp\nindex 1f213d3c0833..641ba18f4b9d 100644\n--- a/src/ipa/raspberrypi/cam_helper_imx708.cpp\n+++ b/src/ipa/raspberrypi/cam_helper_imx708.cpp\n@@ -69,11 +69,14 @@ private:\n \t/* Largest long exposure scale factor given as a left shift on the frame length. */\n \tstatic constexpr int longExposureShiftMax = 7;\n \n+\tstatic constexpr int pdafStatsRows = 12;\n+\tstatic constexpr int pdafStatsCols = 16;\n+\n \tvoid populateMetadata(const MdParser::RegisterMap &registers,\n \t\t\t      Metadata &metadata) const override;\n \n \tstatic bool parsePdafData(const uint8_t *ptr, size_t len, unsigned bpp,\n-\t\t\t\t  PdafData &pdaf);\n+\t\t\t\t  PdafRegions &pdaf);\n \n \tbool parseAEHist(const uint8_t *ptr, size_t len, unsigned bpp);\n \tvoid putAGCStatistics(StatisticsPtr stats);\n@@ -120,11 +123,11 @@ void CamHelperImx708::prepare(libcamera::Span<const uint8_t> buffer, Metadata &m\n \tsize_t bytesPerLine = (mode_.width * mode_.bitdepth) >> 3;\n \n \tif (buffer.size() > 2 * bytesPerLine) {\n-\t\tPdafData pdaf;\n+\t\tPdafRegions pdaf;\n \t\tif (parsePdafData(&buffer[2 * bytesPerLine],\n \t\t\t\t  buffer.size() - 2 * bytesPerLine,\n \t\t\t\t  mode_.bitdepth, pdaf))\n-\t\t\tmetadata.set(\"pdaf.data\", pdaf);\n+\t\t\tmetadata.set(\"pdaf.regions\", pdaf);\n \t}\n \n \t/* Parse AE-HIST data where present */\n@@ -239,7 +242,7 @@ void CamHelperImx708::populateMetadata(const MdParser::RegisterMap &registers,\n }\n \n bool CamHelperImx708::parsePdafData(const uint8_t *ptr, size_t len,\n-\t\t\t\t    unsigned bpp, PdafData &pdaf)\n+\t\t\t\t    unsigned bpp, PdafRegions &pdaf)\n {\n \tsize_t step = bpp >> 1; /* bytes per PDAF grid entry */\n \n@@ -248,13 +251,17 @@ bool CamHelperImx708::parsePdafData(const uint8_t *ptr, size_t len,\n \t\treturn false;\n \t}\n \n+\tpdaf.init({ pdafStatsCols, pdafStatsRows });\n+\n \tptr += 2 * step;\n-\tfor (unsigned i = 0; i < PDAF_DATA_ROWS; ++i) {\n-\t\tfor (unsigned j = 0; j < PDAF_DATA_COLS; ++j) {\n+\tfor (unsigned i = 0; i < pdafStatsRows; ++i) {\n+\t\tfor (unsigned j = 0; j < pdafStatsCols; ++j) {\n \t\t\tunsigned c = (ptr[0] << 3) | (ptr[1] >> 5);\n \t\t\tint p = (((ptr[1] & 0x0F) - (ptr[1] & 0x10)) << 6) | (ptr[2] >> 2);\n-\t\t\tpdaf.conf[i][j] = c;\n-\t\t\tpdaf.phase[i][j] = c ? p : 0;\n+\t\t\tPdafData pdafData;\n+\t\t\tpdafData.conf = c;\n+\t\t\tpdafData.phase = c ? p : 0;\n+\t\t\tpdaf.set(libcamera::Point(j, i), { pdafData, 1, 0 });\n \t\t\tptr += step;\n \t\t}\n \t}\ndiff --git a/src/ipa/raspberrypi/controller/pdaf_data.h b/src/ipa/raspberrypi/controller/pdaf_data.h\nindex 03c00d72c0e8..ae6ab996ded0 100644\n--- a/src/ipa/raspberrypi/controller/pdaf_data.h\n+++ b/src/ipa/raspberrypi/controller/pdaf_data.h\n@@ -2,20 +2,23 @@\n /*\n  * Copyright (C) 2022, Raspberry Pi Ltd\n  *\n- * pdaf_data.h - PDAF Metadata; for now this is\n- * largely based on IMX708's PDAF \"Type 1\" output.\n+ * pdaf_data.h - PDAF Metadata\n  */\n #pragma once\n \n #include <stdint.h>\n \n-#define PDAF_DATA_ROWS 12\n-#define PDAF_DATA_COLS 16\n+#include \"region_stats.h\"\n \n-struct PdafData {\n-\t/* Confidence values, in raster order, in arbitrary units */\n-\tuint16_t conf[PDAF_DATA_ROWS][PDAF_DATA_COLS];\n+namespace RPiController {\n \n-\t/* Phase error, in raster order, in s11 Q4 format (S.6.4) */\n-\tint16_t phase[PDAF_DATA_ROWS][PDAF_DATA_COLS];\n+struct PdafData {\n+\t/* Confidence, in arbitrary units */\n+\tuint16_t conf;\n+\t/* Phase error, in s16 Q4 format (S.11.4) */\n+\tint16_t phase;\n };\n+\n+using PdafRegions = RegionStats<PdafData>;\n+\n+}; // namespace RPiController\ndiff --git a/src/ipa/raspberrypi/controller/rpi/af.cpp b/src/ipa/raspberrypi/controller/rpi/af.cpp\nindex a623651875f2..ed0c8a94d064 100644\n--- a/src/ipa/raspberrypi/controller/rpi/af.cpp\n+++ b/src/ipa/raspberrypi/controller/rpi/af.cpp\n@@ -174,9 +174,8 @@ Af::Af(Controller *controller)\n \t  statsRegion_(0, 0, 0, 0),\n \t  windows_(),\n \t  useWindows_(false),\n-\t  phaseWeights_{},\n-\t  contrastWeights_{},\n-\t  sumWeights_(0),\n+\t  phaseWeights_(),\n+\t  contrastWeights_(),\n \t  scanState_(ScanState::Idle),\n \t  initted_(false),\n \t  ftarget_(-1.0),\n@@ -190,7 +189,15 @@ Af::Af(Controller *controller)\n \t  scanData_(),\n \t  reportState_(AfState::Idle)\n {\n-\tscanData_.reserve(24);\n+\t/*\n+\t * Reserve space for data, to reduce memory fragmentation. It's too early\n+\t * to query the size of the PDAF (from camera) and Contrast (from ISP)\n+\t * statistics, but these are plausible upper bounds.\n+\t */\n+\tphaseWeights_.w.reserve(16 * 12);\n+\tcontrastWeights_.w.reserve(getHardwareConfig().focusRegions.width *\n+\t\t\t\t   getHardwareConfig().focusRegions.height);\n+\tscanData_.reserve(32);\n }\n \n Af::~Af()\n@@ -226,7 +233,7 @@ void Af::switchMode(CameraMode const &cameraMode, [[maybe_unused]] Metadata *met\n \t\t\t  << statsRegion_.y << ','\n \t\t\t  << statsRegion_.width << ','\n \t\t\t  << statsRegion_.height;\n-\tcomputeWeights();\n+\tinvalidateWeights();\n \n \tif (scanState_ >= ScanState::Coarse && scanState_ < ScanState::Settle) {\n \t\t/*\n@@ -239,111 +246,99 @@ void Af::switchMode(CameraMode const &cameraMode, [[maybe_unused]] Metadata *met\n \tskipCount_ = cfg_.skipFrames;\n }\n \n-void Af::computeWeights()\n+void Af::computeWeights(RegionWeights *wgts, unsigned rows, unsigned cols)\n {\n-\tconstexpr int MaxCellWeight = 240 / (int)MaxWindows;\n+\twgts->rows = rows;\n+\twgts->cols = cols;\n+\twgts->sum = 0;\n+\twgts->w.resize(rows * cols);\n+\tstd::fill(wgts->w.begin(), wgts->w.end(), 0);\n \n-\tsumWeights_ = 0;\n-\tfor (int i = 0; i < PDAF_DATA_ROWS; ++i)\n-\t\tstd::fill(phaseWeights_[i], phaseWeights_[i] + PDAF_DATA_COLS, 0);\n-\n-\tif (useWindows_ &&\n-\t    statsRegion_.width >= PDAF_DATA_COLS && statsRegion_.height >= PDAF_DATA_ROWS) {\n+\tif (rows > 0 && cols > 0 && useWindows_ &&\n+\t    statsRegion_.height >= rows && statsRegion_.width >= cols) {\n \t\t/*\n \t\t * Here we just merge all of the given windows, weighted by area.\n \t\t * \\todo Perhaps a better approach might be to find the phase in each\n \t\t * window and choose either the closest or the highest-confidence one?\n-\t\t *\n-\t\t * Using mostly \"int\" arithmetic, because Rectangle has signed x, y\n+\t\t * Ensure weights sum to less than (1<<16). 46080 is a \"round number\"\n+\t\t * below 65536, for better rounding when window size is a simple\n+\t\t * fraction of image dimensions.\n \t\t */\n-\t\tint cellH = (int)(statsRegion_.height / PDAF_DATA_ROWS);\n-\t\tint cellW = (int)(statsRegion_.width / PDAF_DATA_COLS);\n-\t\tint cellA = cellH * cellW;\n+\t\tconst unsigned maxCellWeight = 46080u / (MaxWindows * rows * cols);\n+\t\tconst unsigned cellH = statsRegion_.height / rows;\n+\t\tconst unsigned cellW = statsRegion_.width / cols;\n+\t\tconst unsigned cellA = cellH * cellW;\n \n \t\tfor (auto &w : windows_) {\n-\t\t\tfor (int i = 0; i < PDAF_DATA_ROWS; ++i) {\n-\t\t\t\tint y0 = std::max(statsRegion_.y + cellH * i, w.y);\n-\t\t\t\tint y1 = std::min(statsRegion_.y + cellH * (i + 1), w.y + (int)(w.height));\n+\t\t\tfor (unsigned r = 0; r < rows; ++r) {\n+\t\t\t\tint y0 = std::max(statsRegion_.y + (int)(cellH * r), w.y);\n+\t\t\t\tint y1 = std::min(statsRegion_.y + (int)(cellH * (r + 1)),\n+\t\t\t\t\t\t  w.y + (int)(w.height));\n \t\t\t\tif (y0 >= y1)\n \t\t\t\t\tcontinue;\n \t\t\t\ty1 -= y0;\n-\t\t\t\tfor (int j = 0; j < PDAF_DATA_COLS; ++j) {\n-\t\t\t\t\tint x0 = std::max(statsRegion_.x + cellW * j, w.x);\n-\t\t\t\t\tint x1 = std::min(statsRegion_.x + cellW * (j + 1), w.x + (int)(w.width));\n+\t\t\t\tfor (unsigned c = 0; c < cols; ++c) {\n+\t\t\t\t\tint x0 = std::max(statsRegion_.x + (int)(cellW * c), w.x);\n+\t\t\t\t\tint x1 = std::min(statsRegion_.x + (int)(cellW * (c + 1)),\n+\t\t\t\t\t\t\t  w.x + (int)(w.width));\n \t\t\t\t\tif (x0 >= x1)\n \t\t\t\t\t\tcontinue;\n-\t\t\t\t\tint a = y1 * (x1 - x0);\n-\t\t\t\t\ta = (MaxCellWeight * a + cellA - 1) / cellA;\n-\t\t\t\t\tphaseWeights_[i][j] += a;\n-\t\t\t\t\tsumWeights_ += a;\n+\t\t\t\t\tunsigned a = y1 * (x1 - x0);\n+\t\t\t\t\ta = (maxCellWeight * a + cellA - 1) / cellA;\n+\t\t\t\t\twgts->w[r * cols + c] += a;\n+\t\t\t\t\twgts->sum += a;\n \t\t\t\t}\n \t\t\t}\n \t\t}\n \t}\n \n-\tif (sumWeights_ == 0) {\n-\t\t/*\n-\t\t * Default AF window is the middle 1/2 width of the middle 1/3 height\n-\t\t * since this maps nicely to both PDAF (16x12) and Focus (4x3) grids.\n-\t\t */\n-\t\tfor (int i = PDAF_DATA_ROWS / 3; i < 2 * PDAF_DATA_ROWS / 3; ++i) {\n-\t\t\tfor (int j = PDAF_DATA_COLS / 4; j < 3 * PDAF_DATA_COLS / 4; ++j) {\n-\t\t\t\tphaseWeights_[i][j] = MaxCellWeight;\n-\t\t\t\tsumWeights_ += MaxCellWeight;\n+\tif (wgts->sum == 0) {\n+\t\t/* Default AF window is the middle 1/2 width of the middle 1/3 height */\n+\t\tfor (unsigned r = rows / 3; r < rows - rows / 3; ++r) {\n+\t\t\tfor (unsigned c = cols / 4; c < cols - cols / 4; ++c) {\n+\t\t\t\twgts->w[r * cols + c] = 1;\n+\t\t\t\twgts->sum += 1;\n \t\t\t}\n \t\t}\n \t}\n+}\n \n-\t/* Scale from PDAF to Focus Statistics grid (which has fixed size 4x3) */\n-\tconstexpr int FocusStatsRows = 3;\n-\tconstexpr int FocusStatsCols = 4;\n-\tstatic_assert(FOCUS_REGIONS == FocusStatsRows * FocusStatsCols);\n-\tstatic_assert(PDAF_DATA_ROWS % FocusStatsRows == 0);\n-\tstatic_assert(PDAF_DATA_COLS % FocusStatsCols == 0);\n-\tconstexpr int YFactor = PDAF_DATA_ROWS / FocusStatsRows;\n-\tconstexpr int XFactor = PDAF_DATA_COLS / FocusStatsCols;\n-\n-\tLOG(RPiAf, Debug) << \"Recomputed weights:\";\n-\tfor (int i = 0; i < FocusStatsRows; ++i) {\n-\t\tfor (int j = 0; j < FocusStatsCols; ++j) {\n-\t\t\tunsigned w = 0;\n-\t\t\tfor (int y = 0; y < YFactor; ++y)\n-\t\t\t\tfor (int x = 0; x < XFactor; ++x)\n-\t\t\t\t\tw += phaseWeights_[YFactor * i + y][XFactor * j + x];\n-\t\t\tcontrastWeights_[FocusStatsCols * i + j] = w;\n-\t\t}\n-\t\tLOG(RPiAf, Debug) << \"   \"\n-\t\t\t\t  << contrastWeights_[FocusStatsCols * i + 0] << \" \"\n-\t\t\t\t  << contrastWeights_[FocusStatsCols * i + 1] << \" \"\n-\t\t\t\t  << contrastWeights_[FocusStatsCols * i + 2] << \" \"\n-\t\t\t\t  << contrastWeights_[FocusStatsCols * i + 3];\n-\t}\n+void Af::invalidateWeights()\n+{\n+\tphaseWeights_.sum = 0;\n+\tcontrastWeights_.sum = 0;\n }\n \n-bool Af::getPhase(PdafData const &data, double &phase, double &conf) const\n+bool Af::getPhase(PdafRegions const &regions, double &phase, double &conf)\n {\n+\tlibcamera::Size size = regions.size();\n+\tif (size.height != phaseWeights_.rows || size.width != phaseWeights_.cols ||\n+\t    phaseWeights_.sum == 0) {\n+\t\tLOG(RPiAf, Debug) << \"Recompute Phase weights \" << size.width << 'x' << size.height;\n+\t\tcomputeWeights(&phaseWeights_, size.height, size.width);\n+\t}\n+\n \tuint32_t sumWc = 0;\n \tint64_t sumWcp = 0;\n-\n-\tfor (unsigned i = 0; i < PDAF_DATA_ROWS; ++i) {\n-\t\tfor (unsigned j = 0; j < PDAF_DATA_COLS; ++j) {\n-\t\t\tif (phaseWeights_[i][j]) {\n-\t\t\t\tuint32_t c = data.conf[i][j];\n-\t\t\t\tif (c >= cfg_.confThresh) {\n-\t\t\t\t\tif (c > cfg_.confClip)\n-\t\t\t\t\t\tc = cfg_.confClip;\n-\t\t\t\t\tc -= (cfg_.confThresh >> 2);\n-\t\t\t\t\tsumWc += phaseWeights_[i][j] * c;\n-\t\t\t\t\tc -= (cfg_.confThresh >> 2);\n-\t\t\t\t\tsumWcp += phaseWeights_[i][j] * data.phase[i][j] * (int64_t)c;\n-\t\t\t\t}\n+\tfor (unsigned i = 0; i < regions.numRegions(); ++i) {\n+\t\tunsigned w = phaseWeights_.w[i];\n+\t\tif (w) {\n+\t\t\tconst PdafData &data = regions.get(i).val;\n+\t\t\tunsigned c = data.conf;\n+\t\t\tif (c >= cfg_.confThresh) {\n+\t\t\t\tif (c > cfg_.confClip)\n+\t\t\t\t\tc = cfg_.confClip;\n+\t\t\t\tc -= (cfg_.confThresh >> 2);\n+\t\t\t\tsumWc += w * c;\n+\t\t\t\tc -= (cfg_.confThresh >> 2);\n+\t\t\t\tsumWcp += (int64_t)(w * c) * (int64_t)data.phase;\n \t\t\t}\n \t\t}\n \t}\n \n-\tif (0 < sumWeights_ && sumWeights_ <= sumWc) {\n+\tif (0 < phaseWeights_.sum && phaseWeights_.sum <= sumWc) {\n \t\tphase = (double)sumWcp / (double)sumWc;\n-\t\tconf = (double)sumWc / (double)sumWeights_;\n+\t\tconf = (double)sumWc / (double)phaseWeights_.sum;\n \t\treturn true;\n \t} else {\n \t\tphase = 0.0;\n@@ -352,14 +347,21 @@ bool Af::getPhase(PdafData const &data, double &phase, double &conf) const\n \t}\n }\n \n-double Af::getContrast(const FocusRegions &focusStats) const\n+double Af::getContrast(const FocusRegions &focusStats)\n {\n-\tuint32_t sumWc = 0;\n+\tlibcamera::Size size = focusStats.size();\n+\tif (size.height != contrastWeights_.rows ||\n+\t    size.width != contrastWeights_.cols || contrastWeights_.sum == 0) {\n+\t\tLOG(RPiAf, Debug) << \"Recompute Contrast weights \"\n+\t\t\t\t  << size.width << 'x' << size.height;\n+\t\tcomputeWeights(&contrastWeights_, size.height, size.width);\n+\t}\n \n+\tuint64_t sumWc = 0;\n \tfor (unsigned i = 0; i < focusStats.numRegions(); ++i)\n-\t\tsumWc += contrastWeights_[i] * focusStats.get(i).val;\n+\t\tsumWc += contrastWeights_.w[i] * focusStats.get(i).val;\n \n-\treturn (sumWeights_ == 0) ? 0.0 : (double)sumWc / (double)sumWeights_;\n+\treturn (contrastWeights_.sum > 0) ? ((double)sumWc / (double)contrastWeights_.sum) : 0.0;\n }\n \n void Af::doPDAF(double phase, double conf)\n@@ -623,14 +625,14 @@ void Af::prepare(Metadata *imageMetadata)\n \n \tif (initted_) {\n \t\t/* Get PDAF from the embedded metadata, and run AF algorithm core */\n-\t\tPdafData data;\n+\t\tPdafRegions regions;\n \t\tdouble phase = 0.0, conf = 0.0;\n \t\tdouble oldFt = ftarget_;\n \t\tdouble oldFs = fsmooth_;\n \t\tScanState oldSs = scanState_;\n \t\tuint32_t oldSt = stepCount_;\n-\t\tif (imageMetadata->get(\"pdaf.data\", data) == 0)\n-\t\t\tgetPhase(data, phase, conf);\n+\t\tif (imageMetadata->get(\"pdaf.regions\", regions) == 0)\n+\t\t\tgetPhase(regions, phase, conf);\n \t\tdoAF(prevContrast_, phase, conf);\n \t\tupdateLensPosition();\n \t\tLOG(RPiAf, Debug) << std::fixed << std::setprecision(2)\n@@ -691,7 +693,7 @@ void Af::setMetering(bool mode)\n {\n \tif (useWindows_ != mode) {\n \t\tuseWindows_ = mode;\n-\t\tcomputeWeights();\n+\t\tinvalidateWeights();\n \t}\n }\n \n@@ -708,7 +710,9 @@ void Af::setWindows(libcamera::Span<libcamera::Rectangle const> const &wins)\n \t\tif (windows_.size() >= MaxWindows)\n \t\t\tbreak;\n \t}\n-\tcomputeWeights();\n+\n+\tif (useWindows_)\n+\t\tinvalidateWeights();\n }\n \n bool Af::setLensPosition(double dioptres, int *hwpos)\ndiff --git a/src/ipa/raspberrypi/controller/rpi/af.h b/src/ipa/raspberrypi/controller/rpi/af.h\nindex 7959371baf64..b479feb88c39 100644\n--- a/src/ipa/raspberrypi/controller/rpi/af.h\n+++ b/src/ipa/raspberrypi/controller/rpi/af.h\n@@ -11,12 +11,6 @@\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@@ -121,9 +115,20 @@ private:\n \t\tdouble conf;\n \t};\n \n-\tvoid computeWeights();\n-\tbool getPhase(PdafData const &data, double &phase, double &conf) const;\n-\tdouble getContrast(const FocusRegions &focusStats) const;\n+\tstruct RegionWeights {\n+\t\tunsigned rows;\n+\t\tunsigned cols;\n+\t\tuint32_t sum;\n+\t\tstd::vector<uint16_t> w;\n+\n+\t\tRegionWeights()\n+\t\t\t: rows(0), cols(0), sum(0), w() {}\n+\t};\n+\n+\tvoid computeWeights(RegionWeights *wgts, unsigned rows, unsigned cols);\n+\tvoid invalidateWeights();\n+\tbool getPhase(PdafRegions const &regions, double &phase, double &conf);\n+\tdouble getContrast(const FocusRegions &focusStats);\n \tvoid doPDAF(double phase, double conf);\n \tbool earlyTerminationByPhase(double phase);\n \tdouble findPeak(unsigned index) const;\n@@ -143,9 +148,8 @@ private:\n \tlibcamera::Rectangle statsRegion_;\n \tstd::vector<libcamera::Rectangle> windows_;\n \tbool useWindows_;\n-\tuint8_t phaseWeights_[PDAF_DATA_ROWS][PDAF_DATA_COLS];\n-\tuint16_t contrastWeights_[FOCUS_REGIONS];\n-\tuint32_t sumWeights_;\n+\tRegionWeights phaseWeights_;\n+\tRegionWeights contrastWeights_;\n \n \t/* Working state. */\n \tScanState scanState_;\n","prefixes":["libcamera-devel","v2","09/10"]}