Show a patch.

GET /api/1.1/patches/18143/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 18143,
    "url": "https://patchwork.libcamera.org/api/1.1/patches/18143/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/18143/",
    "project": {
        "id": 1,
        "url": "https://patchwork.libcamera.org/api/1.1/projects/1/?format=api",
        "name": "libcamera",
        "link_name": "libcamera",
        "list_id": "libcamera_core",
        "list_email": "libcamera-devel@lists.libcamera.org",
        "web_url": "",
        "scm_url": "",
        "webscm_url": ""
    },
    "msgid": "<20230119084112.20564-5-dse@thaumatec.com>",
    "date": "2023-01-19T08:41:08",
    "name": "[libcamera-devel,v3,4/8] ipa: Add class that implements base AF control algorithm",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": false,
    "hash": "f9f126c25d24a0f929c3260e2610a1ddbe6ae249",
    "submitter": {
        "id": 126,
        "url": "https://patchwork.libcamera.org/api/1.1/people/126/?format=api",
        "name": "Daniel Semkowicz",
        "email": "dse@thaumatec.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/18143/mbox/",
    "series": [
        {
            "id": 3714,
            "url": "https://patchwork.libcamera.org/api/1.1/series/3714/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=3714",
            "date": "2023-01-19T08:41:04",
            "name": "ipa: rkisp1: Add autofocus algorithm",
            "version": 3,
            "mbox": "https://patchwork.libcamera.org/series/3714/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/18143/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/18143/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 9F966C3294\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 19 Jan 2023 08:42:14 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 3B9D2625EF;\n\tThu, 19 Jan 2023 09:42:14 +0100 (CET)",
            "from mail-wm1-x333.google.com (mail-wm1-x333.google.com\n\t[IPv6:2a00:1450:4864:20::333])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id E86AE625E4\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 19 Jan 2023 09:42:10 +0100 (CET)",
            "by mail-wm1-x333.google.com with SMTP id\n\tfl11-20020a05600c0b8b00b003daf72fc844so3065416wmb.0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 19 Jan 2023 00:42:10 -0800 (PST)",
            "from localhost.localdomain (ip092042140082.rev.nessus.at.\n\t[92.42.140.82]) by smtp.gmail.com with ESMTPSA id\n\tj10-20020a05600c074a00b003db0ee277b2sm3935754wmn.5.2023.01.19.00.42.09\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tThu, 19 Jan 2023 00:42:10 -0800 (PST)"
        ],
        "DKIM-Signature": [
            "v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1674117734;\n\tbh=cdDw3wLjtAxgG2wNWJVhKXvwAFh7apoEwpxuDYly+Ho=;\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=0pwEyM0xqnLIvNjInm5+JqyhWuu80Ew5XiEPOPmgwZSgN/4tMGE2SHgYsX6VOD/rc\n\tt3QhEpeMJlN3fqzIrry5keo0ckcb60MHoteSxf2la7RV37OdvUHiUaF7EQKtlmYnK/\n\ta26RELrfqp6WJ+aOLfnJWTqDcz81v1taTmC2IT/4bGRh+kjvW0Kw2yHoNpSyxTA2dn\n\tjnRtTYyVrYNwzB0T37qGMugRvYw790UfxjuXLADYjnFmQfbBanE7nITqgQXWIS+15v\n\t9LUrLBrfw2WuXeKjZqm8/TgFLc37gIMEBTSMUpR0B9sJvPPKu7D4zNTO1QXzBguAz/\n\tkwq4xkvMKUl6g==",
            "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=thaumatec-com.20210112.gappssmtp.com; s=20210112;\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=itD8A+wtqRccuaPMhFqApXoteZFJJSNNTdE3362xvIY=;\n\tb=drqI85D8+0Ndms2FhdgjVhGTC1JLruAHV370O5eoAtPqqGBaCeLU7wMESP6o7OMoAt\n\tCIV52wsrtJJWhgTz8FcUGZj7k4RXw89FSE+VpbyGq1makA9eA6SW1rrVPjKueCp2GJ0z\n\tHQdrqnKQrNEMPFLtv6ZcAN6Dkq2ofzUgwHCYm+y2iUyqNXlJC3Q3KUFXdmEDZNRvXARa\n\tWUAUYJNGdHNCGxBLbweVrZa6y6blpshyftzSDrlSrDEgBfEQQKcnF4vjEgPJs2motj8R\n\tQcAGyKcpTrkUdwpT5KU8XwTmdr2sWOPBQ8JJgsoZie//7OTwDBSdSJ1wUIzourZ/065m\n\tpFRw=="
        ],
        "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (2048-bit key; \n\tunprotected)\n\theader.d=thaumatec-com.20210112.gappssmtp.com\n\theader.i=@thaumatec-com.20210112.gappssmtp.com header.b=\"drqI85D8\"; \n\tdkim-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=itD8A+wtqRccuaPMhFqApXoteZFJJSNNTdE3362xvIY=;\n\tb=YLVp5ZqMaiGSg3TdsMV+FZ9cA1kZCVx+ATl3Kyf6qmvkT+zxpgqcx64L6Uc1Bf1pNQ\n\tQI152Q92eXLe/pKxLmvsdyMJE+zDmse7VKwZhcsQH8a9sGSuh0ITscCeAEijx/fhNPsr\n\tvhinl/t6kkoH5QKfWp7bswkb8SjTw2HtcVjEzYkj8Y7Sn5sWSb0bNiY0stkgGwewxuKa\n\tMFhn2PbdZwSx6RPI+3/zeNM/5Fcu+PFhC3z1kNsHI/YJNVaqKF8OAk1fieokqBYKqK6n\n\tAZP1IDUKMsuYYe/dWl+GVxTFhjShfToO0kTV1MbJHHu7nokCJQ1dCgaaDc+H8twh0boL\n\ttkbg==",
        "X-Gm-Message-State": "AFqh2krHjtwtA0Dqpea1o7PKzLRsFCPNH7QEDLBx9WypECoTQawXtWBf\n\t/jr9MkjATLHeLsV/H+w/I/WFsqJtJKvrDQFl60Q=",
        "X-Google-Smtp-Source": "AMrXdXsg3AZCh8+rDuRnuQYUXxFMs5GDOPHUJpRRb63sLPA5ORDZTfQy/oh29kZCT1Y65hJooYqPXg==",
        "X-Received": "by 2002:a05:600c:2d4c:b0:3da:fada:e38a with SMTP id\n\ta12-20020a05600c2d4c00b003dafadae38amr9114296wmg.24.1674117730648; \n\tThu, 19 Jan 2023 00:42:10 -0800 (PST)",
        "To": "libcamera-devel@lists.libcamera.org",
        "Date": "Thu, 19 Jan 2023 09:41:08 +0100",
        "Message-Id": "<20230119084112.20564-5-dse@thaumatec.com>",
        "X-Mailer": "git-send-email 2.39.0",
        "In-Reply-To": "<20230119084112.20564-1-dse@thaumatec.com>",
        "References": "<20230119084112.20564-1-dse@thaumatec.com>",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit",
        "Subject": "[libcamera-devel] [PATCH v3 4/8] ipa: Add class that implements\n\tbase AF control 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": "Daniel Semkowicz via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>",
        "Reply-To": "Daniel Semkowicz <dse@thaumatec.com>",
        "Errors-To": "libcamera-devel-bounces@lists.libcamera.org",
        "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"
    },
    "content": "Move the code that was common for IPU3 and RPi AF algorithms to\na separate class independent of platform specific code.\nThis way each platform can just implement contrast calculation and\nrun the AF control loop basing on this class.\n\nSigned-off-by: Daniel Semkowicz <dse@thaumatec.com>\n---\n .../libipa/algorithms/af_hill_climbing.cpp    | 374 ++++++++++++++++++\n src/ipa/libipa/algorithms/af_hill_climbing.h  | 102 +++++\n src/ipa/libipa/algorithms/meson.build         |   2 +\n 3 files changed, 478 insertions(+)\n create mode 100644 src/ipa/libipa/algorithms/af_hill_climbing.cpp\n create mode 100644 src/ipa/libipa/algorithms/af_hill_climbing.h",
    "diff": "diff --git a/src/ipa/libipa/algorithms/af_hill_climbing.cpp b/src/ipa/libipa/algorithms/af_hill_climbing.cpp\nnew file mode 100644\nindex 00000000..e2eaf3d4\n--- /dev/null\n+++ b/src/ipa/libipa/algorithms/af_hill_climbing.cpp\n@@ -0,0 +1,374 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2021, Red Hat\n+ * Copyright (C) 2022, Ideas On Board\n+ * Copyright (C) 2022, Theobroma Systems\n+ *\n+ * af_hill_climbing.cpp - AF Hill Climbing common algorithm\n+ */\n+\n+#include \"af_hill_climbing.h\"\n+\n+#include \"libcamera/internal/yaml_parser.h\"\n+\n+/**\n+ * \\file af_hill_climbing.h\n+ * \\brief AF Hill Climbing common algorithm\n+ */\n+\n+namespace libcamera::ipa::common::algorithms {\n+\n+LOG_DEFINE_CATEGORY(Af)\n+\n+/**\n+ * \\class AfHillClimbing\n+ * \\brief The base class implementing hill climbing AF control algorithm\n+ * \\tparam Module The IPA module type for this class of algorithms\n+ *\n+ * Control part of auto focus algorithm. It calculates the lens position basing\n+ * on contrast measure supplied by the higher level. This way it is independent\n+ * from the platform.\n+ *\n+ * Derived class should call processAutofocus() for each measured contrast value\n+ * and set the lens to the calculated position.\n+ */\n+\n+/**\n+ * \\brief Initialize the Algorithm with tuning data\n+ * \\param[in] tuningData The tuning data for the algorithm\n+ *\n+ * This function should be called in the init() function of the derived class.\n+ * See alse: libcamera::ipa::Algorithm::init()\n+ *\n+ * \\return 0 if successful, an error code otherwise\n+ */\n+int AfHillClimbing::initBase(const YamlObject &tuningData)\n+{\n+\tminVcmPosition_ = tuningData[\"min-vcm-position\"].get<uint32_t>(0);\n+\tmaxVcmPosition_ = tuningData[\"max-vcm-position\"].get<uint32_t>(100);\n+\tcoarseSearchStep_ = tuningData[\"coarse-search-step\"].get<uint32_t>(30);\n+\tfineSearchStep_ = tuningData[\"fine-search-step\"].get<uint32_t>(1);\n+\tfineRange_ = tuningData[\"fine-scan-range\"].get<double>(0.05);\n+\tmaxChange_ = tuningData[\"max-variance-change\"].get<double>(0.5);\n+\n+\tLOG(Af, Debug) << \"minVcmPosition_: \" << minVcmPosition_\n+\t\t       << \", maxVcmPosition_: \" << maxVcmPosition_\n+\t\t       << \", coarseSearchStep_: \" << coarseSearchStep_\n+\t\t       << \", fineSearchStep_: \" << fineSearchStep_\n+\t\t       << \", fineRange_: \" << fineRange_\n+\t\t       << \", maxChange_: \" << maxChange_;\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * \\brief Provide control values to the algorithm\n+ * \\param[in] frame The frame number to apply the control values\n+ * \\param[in] controls The list of user controls\n+ *\n+ * This function should be called in the queueRequest() function of the derived class.\n+ * See alse: libcamera::ipa::Algorithm::queueRequest()\n+ */\n+void AfHillClimbing::queueRequestBase([[maybe_unused]] const uint32_t frame, const ControlList &controls)\n+{\n+\tfor (auto const &[id, value] : controls) {\n+\t\tswitch (id) {\n+\t\tcase controls::AF_MODE: {\n+\t\t\tsetMode(static_cast<controls::AfModeEnum>(value.get<int32_t>()));\n+\t\t\tbreak;\n+\t\t}\n+\t\tcase controls::AF_TRIGGER: {\n+\t\t\tsetTrigger(static_cast<controls::AfTriggerEnum>(value.get<int32_t>()));\n+\t\t\tbreak;\n+\t\t}\n+\t\tcase controls::AF_PAUSE: {\n+\t\t\tsetPause(static_cast<controls::AfPauseEnum>(value.get<int32_t>()));\n+\t\t\tbreak;\n+\t\t}\n+\t\tcase controls::LENS_POSITION: {\n+\t\t\tsetLensPosition(value.get<float>());\n+\t\t\tbreak;\n+\t\t}\n+\t\tdefault:\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+}\n+\n+/**\n+ * \\brief Run the auto focus algorithm loop\n+ * \\param[in] currentContrast New value of contrast measured for current frame\n+ *\n+ * This method should be called for each new contrast value that was measured,\n+ * usually in the process() method.\n+ *\n+ * \\return New lens position calculated by AF algorithm\n+ */\n+uint32_t AfHillClimbing::processAutofocus(double currentContrast)\n+{\n+\tcurrentContrast_ = currentContrast;\n+\n+\tif (shouldSkipFrame())\n+\t\treturn lensPosition_;\n+\n+\tswitch (mode_) {\n+\tcase controls::AfModeManual:\n+\t\t/* Nothing to process. */\n+\t\tbreak;\n+\tcase controls::AfModeAuto:\n+\t\tprocessAutoMode();\n+\t\tbreak;\n+\tcase controls::AfModeContinuous:\n+\t\tprocessContinousMode();\n+\t\tbreak;\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn lensPosition_;\n+}\n+\n+void AfHillClimbing::processAutoMode()\n+{\n+\tif (state_ == controls::AfStateScanning) {\n+\t\tafCoarseScan();\n+\t\tafFineScan();\n+\t}\n+}\n+\n+void AfHillClimbing::processContinousMode()\n+{\n+\t/* If we are in a paused state, we won't process the stats */\n+\tif (pauseState_ == controls::AfPauseStatePaused)\n+\t\treturn;\n+\n+\tif (state_ == controls::AfStateScanning) {\n+\t\tafCoarseScan();\n+\t\tafFineScan();\n+\t\treturn;\n+\t}\n+\n+\t/* We can re-start the scan at any moment in AfModeContinuous */\n+\tif (afIsOutOfFocus()) {\n+\t\tafReset();\n+\t}\n+}\n+\n+/**\n+ * \\brief Request AF to skip n frames\n+ * \\param[in] n Number of frames to be skipped\n+ *\n+ * Requested number of frames will not be used for AF calculation.\n+ */\n+void AfHillClimbing::setFramesToSkip(uint32_t n)\n+{\n+\tif (n > framesToSkip_)\n+\t\tframesToSkip_ = n;\n+}\n+\n+/**\n+ * \\copydoc libcamera::ipa::common::algorithms::AfInterface::setMode\n+ */\n+void AfHillClimbing::setMode(controls::AfModeEnum mode)\n+{\n+\tif (mode == mode_)\n+\t\treturn;\n+\n+\tLOG(Af, Debug) << \"Switched AF mode from \" << mode_ << \" to \" << mode;\n+\tmode_ = mode;\n+\n+\tstate_ = controls::AfStateIdle;\n+\tpauseState_ = controls::AfPauseStateRunning;\n+\n+\tif (mode_ == controls::AfModeContinuous)\n+\t\tafReset();\n+}\n+\n+/**\n+ * \\copydoc libcamera::ipa::common::algorithms::AfInterface::setRange\n+ */\n+void AfHillClimbing::setRange([[maybe_unused]] controls::AfRangeEnum range)\n+{\n+\tLOG(Af, Error) << __FUNCTION__ << \" not implemented!\";\n+}\n+\n+/**\n+ * \\copydoc libcamera::ipa::common::algorithms::AfInterface::setSpeed\n+ */\n+void AfHillClimbing::setSpeed([[maybe_unused]] controls::AfSpeedEnum speed)\n+{\n+\tLOG(Af, Error) << __FUNCTION__ << \" not implemented!\";\n+}\n+\n+/**\n+ * \\copydoc libcamera::ipa::common::algorithms::AfInterface::setTrigger\n+ */\n+void AfHillClimbing::setTrigger(controls::AfTriggerEnum trigger)\n+{\n+\tif (mode_ != controls::AfModeAuto) {\n+\t\tLOG(Af, Warning) << __FUNCTION__ << \" not possible in mode \" << mode_;\n+\t\treturn;\n+\t}\n+\n+\tLOG(Af, Debug) << \"Trigger called with \" << trigger;\n+\n+\tif (trigger == controls::AfTriggerStart)\n+\t\tafReset();\n+\telse\n+\t\tstate_ = controls::AfStateIdle;\n+}\n+\n+/**\n+ * \\copydoc libcamera::ipa::common::algorithms::AfInterface::setPause\n+ */\n+void AfHillClimbing::setPause(controls::AfPauseEnum pause)\n+{\n+\tif (mode_ != controls::AfModeContinuous) {\n+\t\tLOG(Af, Warning) << __FUNCTION__ << \" not possible in mode \" << mode_;\n+\t\treturn;\n+\t}\n+\n+\tswitch (pause) {\n+\tcase controls::AfPauseImmediate:\n+\t\tpauseState_ = controls::AfPauseStatePaused;\n+\t\tbreak;\n+\tcase controls::AfPauseDeferred:\n+\t\t/* \\todo: add the AfPauseDeferred mode */\n+\t\tLOG(Af, Warning) << \"AfPauseDeferred is not supported!\";\n+\t\tbreak;\n+\tcase controls::AfPauseResume:\n+\t\tpauseState_ = controls::AfPauseStateRunning;\n+\t\tbreak;\n+\tdefault:\n+\t\tbreak;\n+\t}\n+}\n+\n+/**\n+ * \\copydoc libcamera::ipa::common::algorithms::AfInterface::setLensPosition\n+ */\n+void AfHillClimbing::setLensPosition(float lensPosition)\n+{\n+\tif (mode_ != controls::AfModeManual) {\n+\t\tLOG(Af, Warning) << __FUNCTION__ << \" not possible in mode \" << mode_;\n+\t\treturn;\n+\t}\n+\n+\tlensPosition_ = static_cast<uint32_t>(lensPosition);\n+\n+\tLOG(Af, Debug) << \"Requesting lens position \" << lensPosition_;\n+}\n+\n+/**\n+ * \\fn AfHillClimbing::setMeteringMode()\n+ * \\copydoc libcamera::ipa::common::algorithms::AfInterface::setMeteringMode\n+ */\n+\n+/**\n+ * \\fn AfHillClimbing::setWindows()\n+ * \\copydoc libcamera::ipa::common::algorithms::AfInterface::setWindows\n+ */\n+\n+void AfHillClimbing::afCoarseScan()\n+{\n+\tif (coarseCompleted_)\n+\t\treturn;\n+\n+\tif (afScan(coarseSearchStep_)) {\n+\t\tcoarseCompleted_ = true;\n+\t\tmaxContrast_ = 0;\n+\t\tlensPosition_ = lensPosition_ - (lensPosition_ * fineRange_);\n+\t\tpreviousContrast_ = 0;\n+\t\tmaxStep_ = std::clamp(lensPosition_ + static_cast<uint32_t>((lensPosition_ * fineRange_)),\n+\t\t\t\t      0U, maxVcmPosition_);\n+\t}\n+}\n+\n+void AfHillClimbing::afFineScan()\n+{\n+\tif (!coarseCompleted_)\n+\t\treturn;\n+\n+\tif (afScan(fineSearchStep_)) {\n+\t\tLOG(Af, Debug) << \"AF found the best focus position!\";\n+\t\tstate_ = controls::AfStateFocused;\n+\t\tfineCompleted_ = true;\n+\t}\n+}\n+\n+bool AfHillClimbing::afScan(uint32_t minSteps)\n+{\n+\tif (lensPosition_ + minSteps > maxStep_) {\n+\t\t/* If the max step is reached, move lens to the position. */\n+\t\tlensPosition_ = bestPosition_;\n+\t\treturn true;\n+\t} else {\n+\t\t/*\n+\t\t* Find the maximum of the variance by estimating its\n+\t\t* derivative. If the direction changes, it means we have passed\n+\t\t* a maximum one step before.\n+\t\t*/\n+\t\tif ((currentContrast_ - maxContrast_) >= -(maxContrast_ * 0.1)) {\n+\t\t\t/*\n+\t\t\t* Positive and zero derivative:\n+\t\t\t* The variance is still increasing. The focus could be\n+\t\t\t* increased for the next comparison. Also, the max\n+\t\t\t* variance and previous focus value are updated.\n+\t\t\t*/\n+\t\t\tbestPosition_ = lensPosition_;\n+\t\t\tlensPosition_ += minSteps;\n+\t\t\tmaxContrast_ = currentContrast_;\n+\t\t} else {\n+\t\t\t/*\n+\t\t\t* Negative derivative:\n+\t\t\t* The variance starts to decrease which means the maximum\n+\t\t\t* variance is found. Set focus step to previous good one\n+\t\t\t* then return immediately.\n+\t\t\t*/\n+\t\t\tlensPosition_ = bestPosition_;\n+\t\t\treturn true;\n+\t\t}\n+\t}\n+\n+\tpreviousContrast_ = currentContrast_;\n+\tLOG(Af, Debug) << \"Previous step is \" << bestPosition_\n+\t\t       << \", Current step is \" << lensPosition_;\n+\treturn false;\n+}\n+\n+void AfHillClimbing::afReset()\n+{\n+\tLOG(Af, Debug) << \"Reset AF parameters\";\n+\tlensPosition_ = minVcmPosition_;\n+\tmaxStep_ = maxVcmPosition_;\n+\tstate_ = controls::AfStateScanning;\n+\tpreviousContrast_ = 0.0;\n+\tcoarseCompleted_ = false;\n+\tfineCompleted_ = false;\n+\tmaxContrast_ = 0.0;\n+\tsetFramesToSkip(1);\n+}\n+\n+bool AfHillClimbing::afIsOutOfFocus()\n+{\n+\tconst uint32_t diff_var = std::abs(currentContrast_ - maxContrast_);\n+\tconst double var_ratio = diff_var / maxContrast_;\n+\tLOG(Af, Debug) << \"Variance change rate: \" << var_ratio\n+\t\t       << \", Current VCM step: \" << lensPosition_;\n+\tif (var_ratio > maxChange_)\n+\t\treturn true;\n+\telse\n+\t\treturn false;\n+}\n+\n+bool AfHillClimbing::shouldSkipFrame()\n+{\n+\tif (framesToSkip_ > 0) {\n+\t\tframesToSkip_--;\n+\t\treturn true;\n+\t}\n+\n+\treturn false;\n+}\n+\n+} /* namespace libcamera::ipa::common::algorithms */\ndiff --git a/src/ipa/libipa/algorithms/af_hill_climbing.h b/src/ipa/libipa/algorithms/af_hill_climbing.h\nnew file mode 100644\nindex 00000000..6ce95884\n--- /dev/null\n+++ b/src/ipa/libipa/algorithms/af_hill_climbing.h\n@@ -0,0 +1,102 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2021, Red Hat\n+ * Copyright (C) 2022, Ideas On Board\n+ * Copyright (C) 2022, Theobroma Systems\n+ *\n+ * af_hill_climbing.h - AF Hill Climbing common algorithm\n+ */\n+\n+#pragma once\n+\n+#include <libcamera/base/log.h>\n+\n+#include \"af_interface.h\"\n+\n+namespace libcamera {\n+\n+class YamlObject;\n+\n+namespace ipa::common::algorithms {\n+\n+LOG_DECLARE_CATEGORY(Af)\n+\n+class AfHillClimbing : public AfInterface\n+{\n+public:\n+\tAfHillClimbing() = default;\n+\tvirtual ~AfHillClimbing() {}\n+\n+\tcontrols::AfStateEnum getState() final { return state_; }\n+\tcontrols::AfPauseStateEnum getPauseState() final { return pauseState_; }\n+\n+\t/* These methods should be implemented by the derived class: */\n+\tvirtual void setMeteringMode(controls::AfMeteringEnum metering) = 0;\n+\tvirtual void setWindows(Span<const Rectangle> windows) = 0;\n+\n+protected:\n+\tint initBase(const YamlObject &tuningData);\n+\tvoid queueRequestBase(const uint32_t frame, const ControlList &controls);\n+\tuint32_t processAutofocus(double currentContrast);\n+\tvoid setFramesToSkip(uint32_t n);\n+\n+private:\n+\tvoid setMode(controls::AfModeEnum mode) final;\n+\tvoid setRange(controls::AfRangeEnum range) final;\n+\tvoid setSpeed(controls::AfSpeedEnum speed) final;\n+\tvoid setTrigger(controls::AfTriggerEnum trigger) final;\n+\tvoid setPause(controls::AfPauseEnum pause) final;\n+\tvoid setLensPosition(float lensPosition) final;\n+\n+\tvoid processAutoMode();\n+\tvoid processContinousMode();\n+\tvoid afCoarseScan();\n+\tvoid afFineScan();\n+\tbool afScan(uint32_t minSteps);\n+\tvoid afReset();\n+\tbool afIsOutOfFocus();\n+\tbool shouldSkipFrame();\n+\n+\tcontrols::AfModeEnum mode_ = controls::AfModeManual;\n+\tcontrols::AfStateEnum state_ = controls::AfStateIdle;\n+\tcontrols::AfPauseStateEnum pauseState_ = controls::AfPauseStateRunning;\n+\n+\t/* VCM step configuration. It is the current setting of the VCM step. */\n+\tuint32_t lensPosition_ = 0;\n+\t/* The best VCM step. It is a local optimum VCM step during scanning. */\n+\tuint32_t bestPosition_ = 0;\n+\n+\t/* Current AF statistic contrast. */\n+\tdouble currentContrast_ = 0;\n+\t/* It is used to determine the derivative during scanning */\n+\tdouble previousContrast_ = 0;\n+\tdouble maxContrast_ = 0;\n+\t/* The designated maximum range of focus scanning. */\n+\tuint32_t maxStep_ = 0;\n+\t/* If the coarse scan completes, it is set to true. */\n+\tbool coarseCompleted_ = false;\n+\t/* If the fine scan completes, it is set to true. */\n+\tbool fineCompleted_ = false;\n+\n+\tuint32_t framesToSkip_ = 0;\n+\n+\t/*\n+\t* Focus steps range of the VCM control\n+\t* \\todo should be obtained from the VCM driver\n+\t*/\n+\tuint32_t minVcmPosition_;\n+\tuint32_t maxVcmPosition_;\n+\n+\t/* Minimum focus step for searching appropriate focus */\n+\tuint32_t coarseSearchStep_;\n+\tuint32_t fineSearchStep_;\n+\n+\t/* Fine scan range 0 < fineRange_ < 1 */\n+\tdouble fineRange_;\n+\n+\t/* Max ratio of variance change, 0.0 < maxChange_ < 1.0 */\n+\tdouble maxChange_;\n+};\n+\n+} /* namespace ipa::common::algorithms */\n+} /* namespace libcamera */\ndiff --git a/src/ipa/libipa/algorithms/meson.build b/src/ipa/libipa/algorithms/meson.build\nindex 0a1f18fa..a8d94056 100644\n--- a/src/ipa/libipa/algorithms/meson.build\n+++ b/src/ipa/libipa/algorithms/meson.build\n@@ -2,8 +2,10 @@\n \n common_ipa_algorithms_headers = files([\n     'af_interface.h',\n+    'af_hill_climbing.h',\n ])\n \n common_ipa_algorithms_sources = files([\n     'af_interface.cpp',\n+    'af_hill_climbing.cpp',\n ])\n",
    "prefixes": [
        "libcamera-devel",
        "v3",
        "4/8"
    ]
}