[{"id":22561,"web_url":"https://patchwork.libcamera.org/comment/22561/","msgid":"<CAHW6GYKieuAcOZWycapETN3waqoTgJwJLzVkKNiJ3NbGc-w_oQ@mail.gmail.com>","date":"2022-04-04T15:42:56","subject":"Re: [libcamera-devel] [PATCH] ipa: raspberrypi: alcs: Limit the\n\tcalculated lambda values","submitter":{"id":42,"url":"https://patchwork.libcamera.org/api/people/42/","name":"David Plowman","email":"david.plowman@raspberrypi.com"},"content":"Hi Naush\n\nThanks for working on this!\n\nOn Mon, 4 Apr 2022 at 16:21, Naushir Patuck <naush@raspberrypi.com> wrote:\n>\n> Under the right circumstances, the alsc calculations could spread the colour\n> errors across the entire image as lambda remains unbound. This would cause the\n> corrected image chroma values to slowly drift to incorrect values.\n>\n> This change adds a config parameter (alcs.lambda_bound) that provides an upper\n\nStrictly, I suppose that should be \"alsc\" rather than \"alcs\"...\n\nHowever everything else looks good, so:\n\nReviewed-by: David Plowman <david.plowman@raspberrypi.com>\n\nThanks!\nDavid\n\n> and lower bound to the lambda value at every stage of the calculation. With this\n> change, we now adjust the lambda values so that the average across the entire\n> grid is 1 instead of normalising to the minimum value.\n>\n> Signed-off-by: Naushir Patuck <naush@raspberrypi.com>\n> ---\n>  src/ipa/raspberrypi/controller/rpi/alsc.cpp | 57 +++++++++++++++------\n>  src/ipa/raspberrypi/controller/rpi/alsc.hpp |  1 +\n>  2 files changed, 43 insertions(+), 15 deletions(-)\n>\n> diff --git a/src/ipa/raspberrypi/controller/rpi/alsc.cpp b/src/ipa/raspberrypi/controller/rpi/alsc.cpp\n> index be3d1ae476cd..a88fee9f6d94 100644\n> --- a/src/ipa/raspberrypi/controller/rpi/alsc.cpp\n> +++ b/src/ipa/raspberrypi/controller/rpi/alsc.cpp\n> @@ -149,6 +149,7 @@ void Alsc::Read(boost::property_tree::ptree const &params)\n>         read_calibrations(config_.calibrations_Cb, params, \"calibrations_Cb\");\n>         config_.default_ct = params.get<double>(\"default_ct\", 4500.0);\n>         config_.threshold = params.get<double>(\"threshold\", 1e-3);\n> +       config_.lambda_bound = params.get<double>(\"lambda_bound\", 0.05);\n>  }\n>\n>  static double get_ct(Metadata *metadata, double default_ct);\n> @@ -610,30 +611,47 @@ static double compute_lambda_top_end(int i, double const M[XY][4],\n>\n>  // Gauss-Seidel iteration with over-relaxation.\n>  static double gauss_seidel2_SOR(double const M[XY][4], double omega,\n> -                               double lambda[XY])\n> +                               double lambda[XY], double lambda_bound)\n>  {\n> +       const double min = 1 - lambda_bound, max = 1 + lambda_bound;\n>         double old_lambda[XY];\n>         int i;\n>         for (i = 0; i < XY; i++)\n>                 old_lambda[i] = lambda[i];\n>         lambda[0] = compute_lambda_bottom_start(0, M, lambda);\n> -       for (i = 1; i < X; i++)\n> +       lambda[0] = std::clamp(lambda[0], min, max);\n> +       for (i = 1; i < X; i++) {\n>                 lambda[i] = compute_lambda_bottom(i, M, lambda);\n> -       for (; i < XY - X; i++)\n> +               lambda[i] = std::clamp(lambda[i], min, max);\n> +       }\n> +       for (; i < XY - X; i++) {\n>                 lambda[i] = compute_lambda_interior(i, M, lambda);\n> -       for (; i < XY - 1; i++)\n> +               lambda[i] = std::clamp(lambda[i], min, max);\n> +       }\n> +       for (; i < XY - 1; i++) {\n>                 lambda[i] = compute_lambda_top(i, M, lambda);\n> +               lambda[i] = std::clamp(lambda[i], min, max);\n> +       }\n>         lambda[i] = compute_lambda_top_end(i, M, lambda);\n> +       lambda[i] = std::clamp(lambda[i], min, max);\n>         // Also solve the system from bottom to top, to help spread the updates\n>         // better.\n>         lambda[i] = compute_lambda_top_end(i, M, lambda);\n> -       for (i = XY - 2; i >= XY - X; i--)\n> +       lambda[i] = std::clamp(lambda[i], min, max);\n> +       for (i = XY - 2; i >= XY - X; i--) {\n>                 lambda[i] = compute_lambda_top(i, M, lambda);\n> -       for (; i >= X; i--)\n> +               lambda[i] = std::clamp(lambda[i], min, max);\n> +       }\n> +       for (; i >= X; i--) {\n>                 lambda[i] = compute_lambda_interior(i, M, lambda);\n> -       for (; i >= 1; i--)\n> +               lambda[i] = std::clamp(lambda[i], min, max);\n> +       }\n> +       for (; i >= 1; i--) {\n>                 lambda[i] = compute_lambda_bottom(i, M, lambda);\n> +               lambda[i] = std::clamp(lambda[i], min, max);\n> +       }\n>         lambda[0] = compute_lambda_bottom_start(0, M, lambda);\n> +       lambda[0] = std::clamp(lambda[0], min, max);\n>         double max_diff = 0;\n>         for (i = 0; i < XY; i++) {\n>                 lambda[i] = old_lambda[i] + (lambda[i] - old_lambda[i]) * omega;\n> @@ -653,15 +671,26 @@ static void normalise(double *ptr, size_t n)\n>                 ptr[i] /= minval;\n>  }\n>\n> +// Rescale the values so that the avarage value is 1.\n> +static void reaverage(double *ptr, size_t n)\n> +{\n> +       double sum = 0;\n> +       for (size_t i = 0; i < n; i++)\n> +               sum += ptr[i];\n> +       double ratio = 1 / (sum / n);\n> +       for (size_t i = 0; i < n; i++)\n> +               ptr[i] *= ratio;\n> +}\n> +\n>  static void run_matrix_iterations(double const C[XY], double lambda[XY],\n>                                   double const W[XY][4], double omega,\n> -                                 int n_iter, double threshold)\n> +                                 int n_iter, double threshold, double lambda_bound)\n>  {\n>         double M[XY][4];\n>         construct_M(C, W, M);\n>         double last_max_diff = std::numeric_limits<double>::max();\n>         for (int i = 0; i < n_iter; i++) {\n> -               double max_diff = fabs(gauss_seidel2_SOR(M, omega, lambda));\n> +               double max_diff = fabs(gauss_seidel2_SOR(M, omega, lambda, lambda_bound));\n>                 if (max_diff < threshold) {\n>                         LOG(RPiAlsc, Debug)\n>                                 << \"Stop after \" << i + 1 << \" iterations\";\n> @@ -675,10 +704,8 @@ static void run_matrix_iterations(double const C[XY], double lambda[XY],\n>                                 << last_max_diff << \" to \" << max_diff;\n>                 last_max_diff = max_diff;\n>         }\n> -       // We're going to normalise the lambdas so the smallest is 1. Not sure\n> -       // this is really necessary as they get renormalised later, but I\n> -       // suppose it does stop these quantities from wandering off...\n> -       normalise(lambda, XY);\n> +       // We're going to normalise the lambdas so the total average is 1.\n> +       reaverage(lambda, XY);\n>  }\n>\n>  static void add_luminance_rb(double result[XY], double const lambda[XY],\n> @@ -737,9 +764,9 @@ void Alsc::doAlsc()\n>         compute_W(Cb, config_.sigma_Cb, Wb);\n>         // Run Gauss-Seidel iterations over the resulting matrix, for R and B.\n>         run_matrix_iterations(Cr, lambda_r_, Wr, config_.omega, config_.n_iter,\n> -                             config_.threshold);\n> +                             config_.threshold, config_.lambda_bound);\n>         run_matrix_iterations(Cb, lambda_b_, Wb, config_.omega, config_.n_iter,\n> -                             config_.threshold);\n> +                             config_.threshold, config_.lambda_bound);\n>         // Fold the calibrated gains into our final lambda values. (Note that on\n>         // the next run, we re-start with the lambda values that don't have the\n>         // calibration gains included.)\n> diff --git a/src/ipa/raspberrypi/controller/rpi/alsc.hpp b/src/ipa/raspberrypi/controller/rpi/alsc.hpp\n> index 9616b99ea7ca..d1dbe0d1d22d 100644\n> --- a/src/ipa/raspberrypi/controller/rpi/alsc.hpp\n> +++ b/src/ipa/raspberrypi/controller/rpi/alsc.hpp\n> @@ -41,6 +41,7 @@ struct AlscConfig {\n>         std::vector<AlscCalibration> calibrations_Cb;\n>         double default_ct; // colour temperature if no metadata found\n>         double threshold; // iteration termination threshold\n> +       double lambda_bound; // upper/lower bound for lambda from a value of 1\n>  };\n>\n>  class Alsc : public Algorithm\n> --\n> 2.25.1\n>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id BD415C3256\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon,  4 Apr 2022 15:43:10 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 145BA65640;\n\tMon,  4 Apr 2022 17:43:10 +0200 (CEST)","from mail-wm1-x332.google.com (mail-wm1-x332.google.com\n\t[IPv6:2a00:1450:4864:20::332])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 198F0633A5\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon,  4 Apr 2022 17:43:08 +0200 (CEST)","by mail-wm1-x332.google.com with SMTP id\n\tp26-20020a05600c1d9a00b0038ccbff1951so194412wms.1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 04 Apr 2022 08:43:08 -0700 (PDT)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1649086990;\n\tbh=UkcEvDk2X6tHSfTA6P7Q9X0hdzISHKrqkzISCZ0pyFw=;\n\th=References:In-Reply-To:Date:To:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc:\n\tFrom;\n\tb=AwYFa7kXGMlMvl5r5wLTcESO7964QDfGcCbO/dtS5DrPk8UxPTIOKuQ7PamVHZ4Yq\n\tsgWwIdT0a+m1zxLm60BNt7ineqPXdOaD6AgMnHmd9H9+hd2OFlKwR9UycyeEO5+z47\n\tSsU+pCpnP8vpVQ5PDmhwk3aoZjfwGi22e1Yk9ozzjuvCG0mvXpOG39SuahTAU0Udrm\n\tAsrxJ2ca3NHgnNakM2c8SjxHGtq4f6dWPkb6fy5uOTSyxzYulWjHs2ByuT/cQFDyoA\n\tRapQwSkrt6nxjRvqSNqk/pFqzVZ/DQqBXz//zJdHXWBL6PuhXYrUh8S37W/gXzU9cE\n\t9L0ev2+bqgFGA==","v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google;\n\th=mime-version:references:in-reply-to:from:date:message-id:subject:to\n\t:cc; bh=qU8XsoRn8ZwBo4Tx69AxcPNSnW410OzM/Rf3uVLF/u4=;\n\tb=WymyT4GNdugtLicRPyoVPcpUoytM+T0Yp2nmtoiiyr79GP3RiXitfUvF1H1Dh450p5\n\tnQecVgrt62nMzMM7ce0ZxHST6LpBePXGUNzvjpVotvHyamZzjvY9kv3EPvmFdt2Q7UV0\n\tPLYWCrpmvOtTm9g2wiEbL4d1E85fcHzxrR5uW8EIMIuDZkGgQNuWs2PwPRdN8YzUJDGp\n\tRyE9BRcf3CAXRc/lM6fjjiNtInCSHwzB6pw5w+fmIY1l6ezFYrnqnVbWAOBiKSaUEv70\n\ttyffjQ+lsR6yQsGRD1d0N7uhZqf259vX2UTd2tjnNsiLdDcTA8WESba1J+Aie9N7dMir\n\tVmmA=="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key; \n\tunprotected) header.d=raspberrypi.com\n\theader.i=@raspberrypi.com\n\theader.b=\"WymyT4GN\"; dkim-atps=neutral","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20210112;\n\th=x-gm-message-state:mime-version:references:in-reply-to:from:date\n\t:message-id:subject:to:cc;\n\tbh=qU8XsoRn8ZwBo4Tx69AxcPNSnW410OzM/Rf3uVLF/u4=;\n\tb=08d7MqU0IC3s38hkthjXq6YngWIVbTVG0Y4QEtThf+iPjc58wNJ/MR9GegGzF+ZlUb\n\t8E1SX7JT0mXb3ykwRbx9tX63ar04AdqcBZ5scpQ7JUHkvTE1ZNq6TkXOoSy3KirNjOsJ\n\tDJzwOrFUpIGC2slITHO1WgCRhI1Xn3XfynWsFDdu9W3nZEW9z2KYoxnpgs5b4//pB39S\n\tzcGQ+AgYX5DGO2AAUELmKs7FOze0tiWpE+pJszhzg04hbAK/VQC8fxSOWs1s4/HFb7N7\n\t+60s0M91BZhXpRyGPIzydwfMmD/2abcQqH/74LXqwA5MrZSFzNM8b9RgwUsEH7NOgO/f\n\t1VTg==","X-Gm-Message-State":"AOAM5323JN44pLsnHPbZLJmpY/qITWddpbo1vq90nn9V/JtBkLYoUpHG\n\thoMbhfp3H4ZiTPg3Zym1dvld1qU6IdG3rDxPlw/p1g==","X-Google-Smtp-Source":"ABdhPJxJqWD+3dHpanLurhtjO8r1HeTJ3kbbumVI4swRxjzftyMCclO2WAy8lD0/oim3HKLlc4vyYFzA0DQmss9i22g=","X-Received":"by 2002:a05:600c:2185:b0:38c:9091:892 with SMTP id\n\te5-20020a05600c218500b0038c90910892mr19946054wme.14.1649086987583;\n\tMon, 04 Apr 2022 08:43:07 -0700 (PDT)","MIME-Version":"1.0","References":"<20220404152152.1779553-1-naush@raspberrypi.com>","In-Reply-To":"<20220404152152.1779553-1-naush@raspberrypi.com>","Date":"Mon, 4 Apr 2022 16:42:56 +0100","Message-ID":"<CAHW6GYKieuAcOZWycapETN3waqoTgJwJLzVkKNiJ3NbGc-w_oQ@mail.gmail.com>","To":"Naushir Patuck <naush@raspberrypi.com>","Content-Type":"text/plain; charset=\"UTF-8\"","Subject":"Re: [libcamera-devel] [PATCH] ipa: raspberrypi: alcs: Limit the\n\tcalculated lambda values","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":"David Plowman via libcamera-devel <libcamera-devel@lists.libcamera.org>","Reply-To":"David Plowman <david.plowman@raspberrypi.com>","Cc":"libcamera devel <libcamera-devel@lists.libcamera.org>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]