[{"id":29176,"web_url":"https://patchwork.libcamera.org/comment/29176/","msgid":"<CAEmqJPqh=yhZZGpuU8wfrSnXrr5bY8LBgvyJpcpfBQZudjtdHw@mail.gmail.com>","date":"2024-04-08T08:14:41","subject":"Re: [PATCH 2/4] ipa: libipa: Copy pwl from rpi","submitter":{"id":34,"url":"https://patchwork.libcamera.org/api/people/34/","name":"Naushir Patuck","email":"naush@raspberrypi.com"},"content":"Hi Paul,\n\nOn Fri, 5 Apr 2024 at 09:03, Paul Elder <paul.elder@ideasonboard.com> wrote:\n>\n> Copy the piecewise linear function code from Raspberry Pi.\n>\n\nThis seems like a good time to mention something I was intending on\ndoing for a while...\n\nOur PWL library is used internally in a few places\n(libcamera/libpisp/rpicam-apps/some internal tools).  Right now each\nof these libraries has a copy of pretty much the same pwl.c/pwl.h\nfile.  I was intending to create a stand alone Raspberry Pi PWL\nlibrary that could be linked with these so we only have one canonical\ncopy of the source code.  This could then be included into\nlibcamera/libpipa as a subproject if needed.  Do you think this would\nbe helpful and suitable for you?  We will need to look at possibly\ntemplating the Point type if needed as well to be more appropriate for\nlibcamera's use.\n\nRegards,\nNaush\n\n> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n> ---\n>  src/ipa/libipa/meson.build |   2 +\n>  src/ipa/libipa/pwl.cpp     | 267 +++++++++++++++++++++++++++++++++++++\n>  src/ipa/libipa/pwl.h       | 123 +++++++++++++++++\n>  3 files changed, 392 insertions(+)\n>  create mode 100644 src/ipa/libipa/pwl.cpp\n>  create mode 100644 src/ipa/libipa/pwl.h\n>\n> diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build\n> index 31cc8d70..8fba16e1 100644\n> --- a/src/ipa/libipa/meson.build\n> +++ b/src/ipa/libipa/meson.build\n> @@ -8,6 +8,7 @@ libipa_headers = files([\n>      'fc_queue.h',\n>      'histogram.h',\n>      'module.h',\n> +    'pwl.h',\n>  ])\n>\n>  libipa_sources = files([\n> @@ -18,6 +19,7 @@ libipa_sources = files([\n>      'fc_queue.cpp',\n>      'histogram.cpp',\n>      'module.cpp',\n> +    'pwl.cpp'\n>  ])\n>\n>  libipa_includes = include_directories('..')\n> diff --git a/src/ipa/libipa/pwl.cpp b/src/ipa/libipa/pwl.cpp\n> new file mode 100644\n> index 00000000..09f5d65c\n> --- /dev/null\n> +++ b/src/ipa/libipa/pwl.cpp\n> @@ -0,0 +1,267 @@\n> +/* SPDX-License-Identifier: BSD-2-Clause */\n> +/*\n> + * Copyright (C) 2019, Raspberry Pi Ltd\n> + *\n> + * pwl.cpp - piecewise linear functions\n> + */\n> +\n> +#include <cassert>\n> +#include <cmath>\n> +#include <stdexcept>\n> +\n> +#include \"pwl.h\"\n> +\n> +int Pwl::read(const libcamera::YamlObject &params)\n> +{\n> +       if (!params.size() || params.size() % 2)\n> +               return -EINVAL;\n> +\n> +       const auto &list = params.asList();\n> +\n> +       for (auto it = list.begin(); it != list.end(); it++) {\n> +               auto x = it->get<double>();\n> +               if (!x)\n> +                       return -EINVAL;\n> +               if (it != list.begin() && *x <= points_.back().x)\n> +                       return -EINVAL;\n> +\n> +               auto y = (++it)->get<double>();\n> +               if (!y)\n> +                       return -EINVAL;\n> +\n> +               points_.push_back(Point(*x, *y));\n> +       }\n> +\n> +       return 0;\n> +}\n> +\n> +void Pwl::append(double x, double y, const double eps)\n> +{\n> +       if (points_.empty() || points_.back().x + eps < x)\n> +               points_.push_back(Point(x, y));\n> +}\n> +\n> +void Pwl::prepend(double x, double y, const double eps)\n> +{\n> +       if (points_.empty() || points_.front().x - eps > x)\n> +               points_.insert(points_.begin(), Point(x, y));\n> +}\n> +\n> +Pwl::Interval Pwl::domain() const\n> +{\n> +       return Interval(points_[0].x, points_[points_.size() - 1].x);\n> +}\n> +\n> +Pwl::Interval Pwl::range() const\n> +{\n> +       double lo = points_[0].y, hi = lo;\n> +       for (auto &p : points_)\n> +               lo = std::min(lo, p.y), hi = std::max(hi, p.y);\n> +       return Interval(lo, hi);\n> +}\n> +\n> +bool Pwl::empty() const\n> +{\n> +       return points_.empty();\n> +}\n> +\n> +double Pwl::eval(double x, int *spanPtr, bool updateSpan) const\n> +{\n> +       int span = findSpan(x, spanPtr && *spanPtr != -1 ? *spanPtr : points_.size() / 2 - 1);\n> +       if (spanPtr && updateSpan)\n> +               *spanPtr = span;\n> +       return points_[span].y +\n> +              (x - points_[span].x) * (points_[span + 1].y - points_[span].y) /\n> +                      (points_[span + 1].x - points_[span].x);\n> +}\n> +\n> +int Pwl::findSpan(double x, int span) const\n> +{\n> +       /*\n> +        * Pwls are generally small, so linear search may well be faster than\n> +        * binary, though could review this if large PWls start turning up.\n> +        */\n> +       int lastSpan = points_.size() - 2;\n> +       /*\n> +        * some algorithms may call us with span pointing directly at the last\n> +        * control point\n> +        */\n> +       span = std::max(0, std::min(lastSpan, span));\n> +       while (span < lastSpan && x >= points_[span + 1].x)\n> +               span++;\n> +       while (span && x < points_[span].x)\n> +               span--;\n> +       return span;\n> +}\n> +\n> +Pwl::PerpType Pwl::invert(Point const &xy, Point &perp, int &span,\n> +                         const double eps) const\n> +{\n> +       assert(span >= -1);\n> +       bool prevOffEnd = false;\n> +       for (span = span + 1; span < (int)points_.size() - 1; span++) {\n> +               Point spanVec = points_[span + 1] - points_[span];\n> +               double t = ((xy - points_[span]) % spanVec) / spanVec.len2();\n> +               if (t < -eps) /* off the start of this span */\n> +               {\n> +                       if (span == 0) {\n> +                               perp = points_[span];\n> +                               return PerpType::Start;\n> +                       } else if (prevOffEnd) {\n> +                               perp = points_[span];\n> +                               return PerpType::Vertex;\n> +                       }\n> +               } else if (t > 1 + eps) /* off the end of this span */\n> +               {\n> +                       if (span == (int)points_.size() - 2) {\n> +                               perp = points_[span + 1];\n> +                               return PerpType::End;\n> +                       }\n> +                       prevOffEnd = true;\n> +               } else /* a true perpendicular */\n> +               {\n> +                       perp = points_[span] + spanVec * t;\n> +                       return PerpType::Perpendicular;\n> +               }\n> +       }\n> +       return PerpType::None;\n> +}\n> +\n> +Pwl Pwl::inverse(bool *trueInverse, const double eps) const\n> +{\n> +       bool appended = false, prepended = false, neither = false;\n> +       Pwl inverse;\n> +\n> +       for (Point const &p : points_) {\n> +               if (inverse.empty())\n> +                       inverse.append(p.y, p.x, eps);\n> +               else if (std::abs(inverse.points_.back().x - p.y) <= eps ||\n> +                        std::abs(inverse.points_.front().x - p.y) <= eps)\n> +                       /* do nothing */;\n> +               else if (p.y > inverse.points_.back().x) {\n> +                       inverse.append(p.y, p.x, eps);\n> +                       appended = true;\n> +               } else if (p.y < inverse.points_.front().x) {\n> +                       inverse.prepend(p.y, p.x, eps);\n> +                       prepended = true;\n> +               } else\n> +                       neither = true;\n> +       }\n> +\n> +       /*\n> +        * This is not a proper inverse if we found ourselves putting points\n> +        * onto both ends of the inverse, or if there were points that couldn't\n> +        * go on either.\n> +        */\n> +       if (trueInverse)\n> +               *trueInverse = !(neither || (appended && prepended));\n> +\n> +       return inverse;\n> +}\n> +\n> +Pwl Pwl::compose(Pwl const &other, const double eps) const\n> +{\n> +       double thisX = points_[0].x, thisY = points_[0].y;\n> +       int thisSpan = 0, otherSpan = other.findSpan(thisY, 0);\n> +       Pwl result({ { thisX, other.eval(thisY, &otherSpan, false) } });\n> +       while (thisSpan != (int)points_.size() - 1) {\n> +               double dx = points_[thisSpan + 1].x - points_[thisSpan].x,\n> +                      dy = points_[thisSpan + 1].y - points_[thisSpan].y;\n> +               if (std::abs(dy) > eps &&\n> +                   otherSpan + 1 < (int)other.points_.size() &&\n> +                   points_[thisSpan + 1].y >=\n> +                           other.points_[otherSpan + 1].x + eps) {\n> +                       /*\n> +                        * next control point in result will be where this\n> +                        * function's y reaches the next span in other\n> +                        */\n> +                       thisX = points_[thisSpan].x +\n> +                               (other.points_[otherSpan + 1].x -\n> +                                points_[thisSpan].y) *\n> +                                       dx / dy;\n> +                       thisY = other.points_[++otherSpan].x;\n> +               } else if (std::abs(dy) > eps && otherSpan > 0 &&\n> +                          points_[thisSpan + 1].y <=\n> +                                  other.points_[otherSpan - 1].x - eps) {\n> +                       /*\n> +                        * next control point in result will be where this\n> +                        * function's y reaches the previous span in other\n> +                        */\n> +                       thisX = points_[thisSpan].x +\n> +                               (other.points_[otherSpan + 1].x -\n> +                                points_[thisSpan].y) *\n> +                                       dx / dy;\n> +                       thisY = other.points_[--otherSpan].x;\n> +               } else {\n> +                       /* we stay in the same span in other */\n> +                       thisSpan++;\n> +                       thisX = points_[thisSpan].x,\n> +                       thisY = points_[thisSpan].y;\n> +               }\n> +               result.append(thisX, other.eval(thisY, &otherSpan, false),\n> +                             eps);\n> +       }\n> +       return result;\n> +}\n> +\n> +void Pwl::map(std::function<void(double x, double y)> f) const\n> +{\n> +       for (auto &pt : points_)\n> +               f(pt.x, pt.y);\n> +}\n> +\n> +void Pwl::map2(Pwl const &pwl0, Pwl const &pwl1,\n> +              std::function<void(double x, double y0, double y1)> f)\n> +{\n> +       int span0 = 0, span1 = 0;\n> +       double x = std::min(pwl0.points_[0].x, pwl1.points_[0].x);\n> +       f(x, pwl0.eval(x, &span0, false), pwl1.eval(x, &span1, false));\n> +       while (span0 < (int)pwl0.points_.size() - 1 ||\n> +              span1 < (int)pwl1.points_.size() - 1) {\n> +               if (span0 == (int)pwl0.points_.size() - 1)\n> +                       x = pwl1.points_[++span1].x;\n> +               else if (span1 == (int)pwl1.points_.size() - 1)\n> +                       x = pwl0.points_[++span0].x;\n> +               else if (pwl0.points_[span0 + 1].x > pwl1.points_[span1 + 1].x)\n> +                       x = pwl1.points_[++span1].x;\n> +               else\n> +                       x = pwl0.points_[++span0].x;\n> +               f(x, pwl0.eval(x, &span0, false), pwl1.eval(x, &span1, false));\n> +       }\n> +}\n> +\n> +Pwl Pwl::combine(Pwl const &pwl0, Pwl const &pwl1,\n> +                std::function<double(double x, double y0, double y1)> f,\n> +                const double eps)\n> +{\n> +       Pwl result;\n> +       map2(pwl0, pwl1, [&](double x, double y0, double y1) {\n> +               result.append(x, f(x, y0, y1), eps);\n> +       });\n> +       return result;\n> +}\n> +\n> +void Pwl::matchDomain(Interval const &domain, bool clip, const double eps)\n> +{\n> +       int span = 0;\n> +       prepend(domain.start, eval(clip ? points_[0].x : domain.start, &span),\n> +               eps);\n> +       span = points_.size() - 2;\n> +       append(domain.end, eval(clip ? points_.back().x : domain.end, &span),\n> +              eps);\n> +}\n> +\n> +Pwl &Pwl::operator*=(double d)\n> +{\n> +       for (auto &pt : points_)\n> +               pt.y *= d;\n> +       return *this;\n> +}\n> +\n> +void Pwl::debug(FILE *fp) const\n> +{\n> +       fprintf(fp, \"Pwl {\\n\");\n> +       for (auto &p : points_)\n> +               fprintf(fp, \"\\t(%g, %g)\\n\", p.x, p.y);\n> +       fprintf(fp, \"}\\n\");\n> +}\n> diff --git a/src/ipa/libipa/pwl.h b/src/ipa/libipa/pwl.h\n> new file mode 100644\n> index 00000000..7a6a6452\n> --- /dev/null\n> +++ b/src/ipa/libipa/pwl.h\n> @@ -0,0 +1,123 @@\n> +/* SPDX-License-Identifier: BSD-2-Clause */\n> +/*\n> + * Copyright (C) 2019, Raspberry Pi Ltd\n> + *\n> + * pwl.h - piecewise linear functions interface\n> + */\n> +#pragma once\n> +\n> +#include <functional>\n> +#include <math.h>\n> +#include <vector>\n> +\n> +#include \"libcamera/internal/yaml_parser.h\"\n> +\n> +class Pwl\n> +{\n> +public:\n> +       struct Interval {\n> +               Interval(double _start, double _end)\n> +                       : start(_start), end(_end)\n> +               {\n> +               }\n> +               double start, end;\n> +               bool contains(double value)\n> +               {\n> +                       return value >= start && value <= end;\n> +               }\n> +               double clip(double value)\n> +               {\n> +                       return value < start ? start\n> +                                            : (value > end ? end : value);\n> +               }\n> +               double len() const { return end - start; }\n> +       };\n> +       struct Point {\n> +               Point() : x(0), y(0) {}\n> +               Point(double _x, double _y)\n> +                       : x(_x), y(_y) {}\n> +               double x, y;\n> +               Point operator-(Point const &p) const\n> +               {\n> +                       return Point(x - p.x, y - p.y);\n> +               }\n> +               Point operator+(Point const &p) const\n> +               {\n> +                       return Point(x + p.x, y + p.y);\n> +               }\n> +               double operator%(Point const &p) const\n> +               {\n> +                       return x * p.x + y * p.y;\n> +               }\n> +               Point operator*(double f) const { return Point(x * f, y * f); }\n> +               Point operator/(double f) const { return Point(x / f, y / f); }\n> +               double len2() const { return x * x + y * y; }\n> +               double len() const { return sqrt(len2()); }\n> +       };\n> +       Pwl() {}\n> +       Pwl(std::vector<Point> const &points) : points_(points) {}\n> +       int read(const libcamera::YamlObject &params);\n> +       void append(double x, double y, const double eps = 1e-6);\n> +       void prepend(double x, double y, const double eps = 1e-6);\n> +       Interval domain() const;\n> +       Interval range() const;\n> +       bool empty() const;\n> +       /*\n> +        * Evaluate Pwl, optionally supplying an initial guess for the\n> +        * \"span\". The \"span\" may be optionally be updated.  If you want to know\n> +        * the \"span\" value but don't have an initial guess you can set it to\n> +        * -1.\n> +        */\n> +       double eval(double x, int *spanPtr = nullptr,\n> +                   bool updateSpan = true) const;\n> +       /*\n> +        * Find perpendicular closest to xy, starting from span+1 so you can\n> +        * call it repeatedly to check for multiple closest points (set span to\n> +        * -1 on the first call). Also returns \"pseudo\" perpendiculars; see\n> +        * PerpType enum.\n> +        */\n> +       enum class PerpType {\n> +               None, /* no perpendicular found */\n> +               Start, /* start of Pwl is closest point */\n> +               End, /* end of Pwl is closest point */\n> +               Vertex, /* vertex of Pwl is closest point */\n> +               Perpendicular /* true perpendicular found */\n> +       };\n> +       PerpType invert(Point const &xy, Point &perp, int &span,\n> +                       const double eps = 1e-6) const;\n> +       /*\n> +        * Compute the inverse function. Indicate if it is a proper (true)\n> +        * inverse, or only a best effort (e.g. input was non-monotonic).\n> +        */\n> +       Pwl inverse(bool *trueInverse = nullptr, const double eps = 1e-6) const;\n> +       /* Compose two Pwls together, doing \"this\" first and \"other\" after. */\n> +       Pwl compose(Pwl const &other, const double eps = 1e-6) const;\n> +       /* Apply function to (x,y) values at every control point. */\n> +       void map(std::function<void(double x, double y)> f) const;\n> +       /*\n> +        * Apply function to (x, y0, y1) values wherever either Pwl has a\n> +        * control point.\n> +        */\n> +       static void map2(Pwl const &pwl0, Pwl const &pwl1,\n> +                        std::function<void(double x, double y0, double y1)> f);\n> +       /*\n> +        * Combine two Pwls, meaning we create a new Pwl where the y values are\n> +        * given by running f wherever either has a knot.\n> +        */\n> +       static Pwl\n> +       combine(Pwl const &pwl0, Pwl const &pwl1,\n> +               std::function<double(double x, double y0, double y1)> f,\n> +               const double eps = 1e-6);\n> +       /*\n> +        * Make \"this\" match (at least) the given domain. Any extension my be\n> +        * clipped or linear.\n> +        */\n> +       void matchDomain(Interval const &domain, bool clip = true,\n> +                        const double eps = 1e-6);\n> +       Pwl &operator*=(double d);\n> +       void debug(FILE *fp = stdout) const;\n> +\n> +private:\n> +       int findSpan(double x, int span) const;\n> +       std::vector<Point> points_;\n> +};\n> --\n> 2.39.2\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 162B4BE08B\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon,  8 Apr 2024 08:14:53 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 497B463339;\n\tMon,  8 Apr 2024 10:14:52 +0200 (CEST)","from mail-yw1-x112c.google.com (mail-yw1-x112c.google.com\n\t[IPv6:2607:f8b0:4864:20::112c])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 700B063339\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon,  8 Apr 2024 10:14:49 +0200 (CEST)","by mail-yw1-x112c.google.com with SMTP id\n\t00721157ae682-6154a1812ffso43576717b3.1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 08 Apr 2024 01:14:49 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=raspberrypi.com header.i=@raspberrypi.com\n\theader.b=\"OZhmTkP9\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google; t=1712564088; x=1713168888;\n\tdarn=lists.libcamera.org; \n\th=cc:to:subject:message-id:date:from:in-reply-to:references\n\t:mime-version:from:to:cc:subject:date:message-id:reply-to;\n\tbh=EDskLF68P+n1OB9VmpRtayODttaOAa71S3sOpOe59FM=;\n\tb=OZhmTkP9xqU0qIAbQbO5R8brhTi8QxnHlHcMctJ6txzIGwtiZh1PTsb2++iJJSvOpn\n\tknGF+ZW39lZKlabohcnn5cy+kpg7w3quIRMdpyJSjMWwrWFPiOogkdaAN0ZgQnlRXjf+\n\tofF0b6dCwDcVY4DCkpFRMh3/1EWEfXLDr7l6x3pltfZ9ci2Mgm4pRadSfpBhs3E1K6zI\n\tttD/LNltMcn/nJuEvERr4/35sXydd6NmPjwLTmW7NwVfYKMXFFgoXMbYkEhu1sDaWp3V\n\tFAk2KrGnqss9O0/hOh9Y8z13ty+yIw/VnHuj4PpVSNyazFnL4zc1kOfEJiGhhj3p1Z35\n\txtFQ==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1712564088; x=1713168888;\n\th=cc:to:subject:message-id:date:from:in-reply-to:references\n\t:mime-version:x-gm-message-state:from:to:cc:subject:date:message-id\n\t:reply-to;\n\tbh=EDskLF68P+n1OB9VmpRtayODttaOAa71S3sOpOe59FM=;\n\tb=b2rqIfpDtWqzi6A50kQw5aSP1n3jrtQJsRPP394tsghmAntRu/2bDuREkfqv34Jr43\n\tHpkB+7/5sg6UVztgscRmoVtJ7ZA8tD2ArxMm+OfJHqFNJTS/B69j5CjxCmgiO5AmQZqo\n\tqUaHSxq/MpfgOjWqgY79/SZXXH1FK06vLccwmyJp+hQ+prBKsWHUegjFIPHiRGB4k5SS\n\tI1YlbMWnDByMwjye2AKeZF3KIWfDhxCKsyMtdIB4CnieaxQKJvlSMgFtd9pNmy2F9Cgt\n\tS+q/JL4PJ5TtOmXXlYNoGUjLG5lyJ8Ggl1XubY13sNA31LmOz6MMQS+gwtGA5LuER+b1\n\ttR5Q==","X-Gm-Message-State":"AOJu0YyCfkc/gAbbtLvgmwwZzQE360SiDgXdPkqNok2xsdIzxsRo8oFI\n\t6O/+0QeIfgk0Ut0zWNAidmZ2ZjUCJrwPj0GKdxtG8iGkPkqdAyLZtzyPdA297nn8GKveWjwAR6Y\n\tuD/KhNr3qnMTE8LvTjT8RlWNfby/aaPoqATMgbfxLRPkg2GOj","X-Google-Smtp-Source":"AGHT+IF1kZcGnwTek+gvI68lQjvpiruSQOKEpRAwPLpWaosISIW6VVxxuxwDobHXeISj9emUHBepAl10c9H5palLQnI=","X-Received":"by 2002:a25:7086:0:b0:dcc:1449:71ea with SMTP id\n\tl128-20020a257086000000b00dcc144971eamr6405504ybc.50.1712564088110;\n\tMon, 08 Apr 2024 01:14:48 -0700 (PDT)","MIME-Version":"1.0","References":"<20240405080259.1806453-1-paul.elder@ideasonboard.com>\n\t<20240405080259.1806453-3-paul.elder@ideasonboard.com>","In-Reply-To":"<20240405080259.1806453-3-paul.elder@ideasonboard.com>","From":"Naushir Patuck <naush@raspberrypi.com>","Date":"Mon, 8 Apr 2024 09:14:41 +0100","Message-ID":"<CAEmqJPqh=yhZZGpuU8wfrSnXrr5bY8LBgvyJpcpfBQZudjtdHw@mail.gmail.com>","Subject":"Re: [PATCH 2/4] ipa: libipa: Copy pwl from rpi","To":"Paul Elder <paul.elder@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Content-Type":"text/plain; charset=\"UTF-8\"","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>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":29224,"web_url":"https://patchwork.libcamera.org/comment/29224/","msgid":"<20240415120717.ci3qpaddurlk3flx@jasper>","date":"2024-04-15T12:07:17","subject":"Re: [PATCH 2/4] ipa: libipa: Copy pwl from rpi","submitter":{"id":184,"url":"https://patchwork.libcamera.org/api/people/184/","name":"Stefan Klug","email":"stefan.klug@ideasonboard.com"},"content":"Hi Paul,\n\nthanks for the patch.\n\nAs this is only a copy of existing files, there is not much to review\nhere.\n\nReviewed-by: Stefan Klug <stefan.klug@ideasonboard.com> \n\nCheers,\nStefan\n\nOn Fri, Apr 05, 2024 at 05:02:57PM +0900, Paul Elder wrote:\n> Copy the piecewise linear function code from Raspberry Pi.\n> \n> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n> ---\n>  src/ipa/libipa/meson.build |   2 +\n>  src/ipa/libipa/pwl.cpp     | 267 +++++++++++++++++++++++++++++++++++++\n>  src/ipa/libipa/pwl.h       | 123 +++++++++++++++++\n>  3 files changed, 392 insertions(+)\n>  create mode 100644 src/ipa/libipa/pwl.cpp\n>  create mode 100644 src/ipa/libipa/pwl.h\n> \n> diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build\n> index 31cc8d70..8fba16e1 100644\n> --- a/src/ipa/libipa/meson.build\n> +++ b/src/ipa/libipa/meson.build\n> @@ -8,6 +8,7 @@ libipa_headers = files([\n>      'fc_queue.h',\n>      'histogram.h',\n>      'module.h',\n> +    'pwl.h',\n>  ])\n>  \n>  libipa_sources = files([\n> @@ -18,6 +19,7 @@ libipa_sources = files([\n>      'fc_queue.cpp',\n>      'histogram.cpp',\n>      'module.cpp',\n> +    'pwl.cpp'\n>  ])\n>  \n>  libipa_includes = include_directories('..')\n> diff --git a/src/ipa/libipa/pwl.cpp b/src/ipa/libipa/pwl.cpp\n> new file mode 100644\n> index 00000000..09f5d65c\n> --- /dev/null\n> +++ b/src/ipa/libipa/pwl.cpp\n> @@ -0,0 +1,267 @@\n> +/* SPDX-License-Identifier: BSD-2-Clause */\n> +/*\n> + * Copyright (C) 2019, Raspberry Pi Ltd\n> + *\n> + * pwl.cpp - piecewise linear functions\n> + */\n> +\n> +#include <cassert>\n> +#include <cmath>\n> +#include <stdexcept>\n> +\n> +#include \"pwl.h\"\n> +\n> +int Pwl::read(const libcamera::YamlObject &params)\n> +{\n> +\tif (!params.size() || params.size() % 2)\n> +\t\treturn -EINVAL;\n> +\n> +\tconst auto &list = params.asList();\n> +\n> +\tfor (auto it = list.begin(); it != list.end(); it++) {\n> +\t\tauto x = it->get<double>();\n> +\t\tif (!x)\n> +\t\t\treturn -EINVAL;\n> +\t\tif (it != list.begin() && *x <= points_.back().x)\n> +\t\t\treturn -EINVAL;\n> +\n> +\t\tauto y = (++it)->get<double>();\n> +\t\tif (!y)\n> +\t\t\treturn -EINVAL;\n> +\n> +\t\tpoints_.push_back(Point(*x, *y));\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +void Pwl::append(double x, double y, const double eps)\n> +{\n> +\tif (points_.empty() || points_.back().x + eps < x)\n> +\t\tpoints_.push_back(Point(x, y));\n> +}\n> +\n> +void Pwl::prepend(double x, double y, const double eps)\n> +{\n> +\tif (points_.empty() || points_.front().x - eps > x)\n> +\t\tpoints_.insert(points_.begin(), Point(x, y));\n> +}\n> +\n> +Pwl::Interval Pwl::domain() const\n> +{\n> +\treturn Interval(points_[0].x, points_[points_.size() - 1].x);\n> +}\n> +\n> +Pwl::Interval Pwl::range() const\n> +{\n> +\tdouble lo = points_[0].y, hi = lo;\n> +\tfor (auto &p : points_)\n> +\t\tlo = std::min(lo, p.y), hi = std::max(hi, p.y);\n> +\treturn Interval(lo, hi);\n> +}\n> +\n> +bool Pwl::empty() const\n> +{\n> +\treturn points_.empty();\n> +}\n> +\n> +double Pwl::eval(double x, int *spanPtr, bool updateSpan) const\n> +{\n> +\tint span = findSpan(x, spanPtr && *spanPtr != -1 ? *spanPtr : points_.size() / 2 - 1);\n> +\tif (spanPtr && updateSpan)\n> +\t\t*spanPtr = span;\n> +\treturn points_[span].y +\n> +\t       (x - points_[span].x) * (points_[span + 1].y - points_[span].y) /\n> +\t\t       (points_[span + 1].x - points_[span].x);\n> +}\n> +\n> +int Pwl::findSpan(double x, int span) const\n> +{\n> +\t/*\n> +\t * Pwls are generally small, so linear search may well be faster than\n> +\t * binary, though could review this if large PWls start turning up.\n> +\t */\n> +\tint lastSpan = points_.size() - 2;\n> +\t/*\n> +\t * some algorithms may call us with span pointing directly at the last\n> +\t * control point\n> +\t */\n> +\tspan = std::max(0, std::min(lastSpan, span));\n> +\twhile (span < lastSpan && x >= points_[span + 1].x)\n> +\t\tspan++;\n> +\twhile (span && x < points_[span].x)\n> +\t\tspan--;\n> +\treturn span;\n> +}\n> +\n> +Pwl::PerpType Pwl::invert(Point const &xy, Point &perp, int &span,\n> +\t\t\t  const double eps) const\n> +{\n> +\tassert(span >= -1);\n> +\tbool prevOffEnd = false;\n> +\tfor (span = span + 1; span < (int)points_.size() - 1; span++) {\n> +\t\tPoint spanVec = points_[span + 1] - points_[span];\n> +\t\tdouble t = ((xy - points_[span]) % spanVec) / spanVec.len2();\n> +\t\tif (t < -eps) /* off the start of this span */\n> +\t\t{\n> +\t\t\tif (span == 0) {\n> +\t\t\t\tperp = points_[span];\n> +\t\t\t\treturn PerpType::Start;\n> +\t\t\t} else if (prevOffEnd) {\n> +\t\t\t\tperp = points_[span];\n> +\t\t\t\treturn PerpType::Vertex;\n> +\t\t\t}\n> +\t\t} else if (t > 1 + eps) /* off the end of this span */\n> +\t\t{\n> +\t\t\tif (span == (int)points_.size() - 2) {\n> +\t\t\t\tperp = points_[span + 1];\n> +\t\t\t\treturn PerpType::End;\n> +\t\t\t}\n> +\t\t\tprevOffEnd = true;\n> +\t\t} else /* a true perpendicular */\n> +\t\t{\n> +\t\t\tperp = points_[span] + spanVec * t;\n> +\t\t\treturn PerpType::Perpendicular;\n> +\t\t}\n> +\t}\n> +\treturn PerpType::None;\n> +}\n> +\n> +Pwl Pwl::inverse(bool *trueInverse, const double eps) const\n> +{\n> +\tbool appended = false, prepended = false, neither = false;\n> +\tPwl inverse;\n> +\n> +\tfor (Point const &p : points_) {\n> +\t\tif (inverse.empty())\n> +\t\t\tinverse.append(p.y, p.x, eps);\n> +\t\telse if (std::abs(inverse.points_.back().x - p.y) <= eps ||\n> +\t\t\t std::abs(inverse.points_.front().x - p.y) <= eps)\n> +\t\t\t/* do nothing */;\n> +\t\telse if (p.y > inverse.points_.back().x) {\n> +\t\t\tinverse.append(p.y, p.x, eps);\n> +\t\t\tappended = true;\n> +\t\t} else if (p.y < inverse.points_.front().x) {\n> +\t\t\tinverse.prepend(p.y, p.x, eps);\n> +\t\t\tprepended = true;\n> +\t\t} else\n> +\t\t\tneither = true;\n> +\t}\n> +\n> +\t/*\n> +\t * This is not a proper inverse if we found ourselves putting points\n> +\t * onto both ends of the inverse, or if there were points that couldn't\n> +\t * go on either.\n> +\t */\n> +\tif (trueInverse)\n> +\t\t*trueInverse = !(neither || (appended && prepended));\n> +\n> +\treturn inverse;\n> +}\n> +\n> +Pwl Pwl::compose(Pwl const &other, const double eps) const\n> +{\n> +\tdouble thisX = points_[0].x, thisY = points_[0].y;\n> +\tint thisSpan = 0, otherSpan = other.findSpan(thisY, 0);\n> +\tPwl result({ { thisX, other.eval(thisY, &otherSpan, false) } });\n> +\twhile (thisSpan != (int)points_.size() - 1) {\n> +\t\tdouble dx = points_[thisSpan + 1].x - points_[thisSpan].x,\n> +\t\t       dy = points_[thisSpan + 1].y - points_[thisSpan].y;\n> +\t\tif (std::abs(dy) > eps &&\n> +\t\t    otherSpan + 1 < (int)other.points_.size() &&\n> +\t\t    points_[thisSpan + 1].y >=\n> +\t\t\t    other.points_[otherSpan + 1].x + eps) {\n> +\t\t\t/*\n> +\t\t\t * next control point in result will be where this\n> +\t\t\t * function's y reaches the next span in other\n> +\t\t\t */\n> +\t\t\tthisX = points_[thisSpan].x +\n> +\t\t\t\t(other.points_[otherSpan + 1].x -\n> +\t\t\t\t points_[thisSpan].y) *\n> +\t\t\t\t\tdx / dy;\n> +\t\t\tthisY = other.points_[++otherSpan].x;\n> +\t\t} else if (std::abs(dy) > eps && otherSpan > 0 &&\n> +\t\t\t   points_[thisSpan + 1].y <=\n> +\t\t\t\t   other.points_[otherSpan - 1].x - eps) {\n> +\t\t\t/*\n> +\t\t\t * next control point in result will be where this\n> +\t\t\t * function's y reaches the previous span in other\n> +\t\t\t */\n> +\t\t\tthisX = points_[thisSpan].x +\n> +\t\t\t\t(other.points_[otherSpan + 1].x -\n> +\t\t\t\t points_[thisSpan].y) *\n> +\t\t\t\t\tdx / dy;\n> +\t\t\tthisY = other.points_[--otherSpan].x;\n> +\t\t} else {\n> +\t\t\t/* we stay in the same span in other */\n> +\t\t\tthisSpan++;\n> +\t\t\tthisX = points_[thisSpan].x,\n> +\t\t\tthisY = points_[thisSpan].y;\n> +\t\t}\n> +\t\tresult.append(thisX, other.eval(thisY, &otherSpan, false),\n> +\t\t\t      eps);\n> +\t}\n> +\treturn result;\n> +}\n> +\n> +void Pwl::map(std::function<void(double x, double y)> f) const\n> +{\n> +\tfor (auto &pt : points_)\n> +\t\tf(pt.x, pt.y);\n> +}\n> +\n> +void Pwl::map2(Pwl const &pwl0, Pwl const &pwl1,\n> +\t       std::function<void(double x, double y0, double y1)> f)\n> +{\n> +\tint span0 = 0, span1 = 0;\n> +\tdouble x = std::min(pwl0.points_[0].x, pwl1.points_[0].x);\n> +\tf(x, pwl0.eval(x, &span0, false), pwl1.eval(x, &span1, false));\n> +\twhile (span0 < (int)pwl0.points_.size() - 1 ||\n> +\t       span1 < (int)pwl1.points_.size() - 1) {\n> +\t\tif (span0 == (int)pwl0.points_.size() - 1)\n> +\t\t\tx = pwl1.points_[++span1].x;\n> +\t\telse if (span1 == (int)pwl1.points_.size() - 1)\n> +\t\t\tx = pwl0.points_[++span0].x;\n> +\t\telse if (pwl0.points_[span0 + 1].x > pwl1.points_[span1 + 1].x)\n> +\t\t\tx = pwl1.points_[++span1].x;\n> +\t\telse\n> +\t\t\tx = pwl0.points_[++span0].x;\n> +\t\tf(x, pwl0.eval(x, &span0, false), pwl1.eval(x, &span1, false));\n> +\t}\n> +}\n> +\n> +Pwl Pwl::combine(Pwl const &pwl0, Pwl const &pwl1,\n> +\t\t std::function<double(double x, double y0, double y1)> f,\n> +\t\t const double eps)\n> +{\n> +\tPwl result;\n> +\tmap2(pwl0, pwl1, [&](double x, double y0, double y1) {\n> +\t\tresult.append(x, f(x, y0, y1), eps);\n> +\t});\n> +\treturn result;\n> +}\n> +\n> +void Pwl::matchDomain(Interval const &domain, bool clip, const double eps)\n> +{\n> +\tint span = 0;\n> +\tprepend(domain.start, eval(clip ? points_[0].x : domain.start, &span),\n> +\t\teps);\n> +\tspan = points_.size() - 2;\n> +\tappend(domain.end, eval(clip ? points_.back().x : domain.end, &span),\n> +\t       eps);\n> +}\n> +\n> +Pwl &Pwl::operator*=(double d)\n> +{\n> +\tfor (auto &pt : points_)\n> +\t\tpt.y *= d;\n> +\treturn *this;\n> +}\n> +\n> +void Pwl::debug(FILE *fp) const\n> +{\n> +\tfprintf(fp, \"Pwl {\\n\");\n> +\tfor (auto &p : points_)\n> +\t\tfprintf(fp, \"\\t(%g, %g)\\n\", p.x, p.y);\n> +\tfprintf(fp, \"}\\n\");\n> +}\n> diff --git a/src/ipa/libipa/pwl.h b/src/ipa/libipa/pwl.h\n> new file mode 100644\n> index 00000000..7a6a6452\n> --- /dev/null\n> +++ b/src/ipa/libipa/pwl.h\n> @@ -0,0 +1,123 @@\n> +/* SPDX-License-Identifier: BSD-2-Clause */\n> +/*\n> + * Copyright (C) 2019, Raspberry Pi Ltd\n> + *\n> + * pwl.h - piecewise linear functions interface\n> + */\n> +#pragma once\n> +\n> +#include <functional>\n> +#include <math.h>\n> +#include <vector>\n> +\n> +#include \"libcamera/internal/yaml_parser.h\"\n> +\n> +class Pwl\n> +{\n> +public:\n> +\tstruct Interval {\n> +\t\tInterval(double _start, double _end)\n> +\t\t\t: start(_start), end(_end)\n> +\t\t{\n> +\t\t}\n> +\t\tdouble start, end;\n> +\t\tbool contains(double value)\n> +\t\t{\n> +\t\t\treturn value >= start && value <= end;\n> +\t\t}\n> +\t\tdouble clip(double value)\n> +\t\t{\n> +\t\t\treturn value < start ? start\n> +\t\t\t\t\t     : (value > end ? end : value);\n> +\t\t}\n> +\t\tdouble len() const { return end - start; }\n> +\t};\n> +\tstruct Point {\n> +\t\tPoint() : x(0), y(0) {}\n> +\t\tPoint(double _x, double _y)\n> +\t\t\t: x(_x), y(_y) {}\n> +\t\tdouble x, y;\n> +\t\tPoint operator-(Point const &p) const\n> +\t\t{\n> +\t\t\treturn Point(x - p.x, y - p.y);\n> +\t\t}\n> +\t\tPoint operator+(Point const &p) const\n> +\t\t{\n> +\t\t\treturn Point(x + p.x, y + p.y);\n> +\t\t}\n> +\t\tdouble operator%(Point const &p) const\n> +\t\t{\n> +\t\t\treturn x * p.x + y * p.y;\n> +\t\t}\n> +\t\tPoint operator*(double f) const { return Point(x * f, y * f); }\n> +\t\tPoint operator/(double f) const { return Point(x / f, y / f); }\n> +\t\tdouble len2() const { return x * x + y * y; }\n> +\t\tdouble len() const { return sqrt(len2()); }\n> +\t};\n> +\tPwl() {}\n> +\tPwl(std::vector<Point> const &points) : points_(points) {}\n> +\tint read(const libcamera::YamlObject &params);\n> +\tvoid append(double x, double y, const double eps = 1e-6);\n> +\tvoid prepend(double x, double y, const double eps = 1e-6);\n> +\tInterval domain() const;\n> +\tInterval range() const;\n> +\tbool empty() const;\n> +\t/*\n> +\t * Evaluate Pwl, optionally supplying an initial guess for the\n> +\t * \"span\". The \"span\" may be optionally be updated.  If you want to know\n> +\t * the \"span\" value but don't have an initial guess you can set it to\n> +\t * -1.\n> +\t */\n> +\tdouble eval(double x, int *spanPtr = nullptr,\n> +\t\t    bool updateSpan = true) const;\n> +\t/*\n> +\t * Find perpendicular closest to xy, starting from span+1 so you can\n> +\t * call it repeatedly to check for multiple closest points (set span to\n> +\t * -1 on the first call). Also returns \"pseudo\" perpendiculars; see\n> +\t * PerpType enum.\n> +\t */\n> +\tenum class PerpType {\n> +\t\tNone, /* no perpendicular found */\n> +\t\tStart, /* start of Pwl is closest point */\n> +\t\tEnd, /* end of Pwl is closest point */\n> +\t\tVertex, /* vertex of Pwl is closest point */\n> +\t\tPerpendicular /* true perpendicular found */\n> +\t};\n> +\tPerpType invert(Point const &xy, Point &perp, int &span,\n> +\t\t\tconst double eps = 1e-6) const;\n> +\t/*\n> +\t * Compute the inverse function. Indicate if it is a proper (true)\n> +\t * inverse, or only a best effort (e.g. input was non-monotonic).\n> +\t */\n> +\tPwl inverse(bool *trueInverse = nullptr, const double eps = 1e-6) const;\n> +\t/* Compose two Pwls together, doing \"this\" first and \"other\" after. */\n> +\tPwl compose(Pwl const &other, const double eps = 1e-6) const;\n> +\t/* Apply function to (x,y) values at every control point. */\n> +\tvoid map(std::function<void(double x, double y)> f) const;\n> +\t/*\n> +\t * Apply function to (x, y0, y1) values wherever either Pwl has a\n> +\t * control point.\n> +\t */\n> +\tstatic void map2(Pwl const &pwl0, Pwl const &pwl1,\n> +\t\t\t std::function<void(double x, double y0, double y1)> f);\n> +\t/*\n> +\t * Combine two Pwls, meaning we create a new Pwl where the y values are\n> +\t * given by running f wherever either has a knot.\n> +\t */\n> +\tstatic Pwl\n> +\tcombine(Pwl const &pwl0, Pwl const &pwl1,\n> +\t\tstd::function<double(double x, double y0, double y1)> f,\n> +\t\tconst double eps = 1e-6);\n> +\t/*\n> +\t * Make \"this\" match (at least) the given domain. Any extension my be\n> +\t * clipped or linear.\n> +\t */\n> +\tvoid matchDomain(Interval const &domain, bool clip = true,\n> +\t\t\t const double eps = 1e-6);\n> +\tPwl &operator*=(double d);\n> +\tvoid debug(FILE *fp = stdout) const;\n> +\n> +private:\n> +\tint findSpan(double x, int span) const;\n> +\tstd::vector<Point> points_;\n> +};\n> -- \n> 2.39.2\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 DA23FBE08B\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 15 Apr 2024 12:07:22 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id D8B6D63352;\n\tMon, 15 Apr 2024 14:07:21 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 88B2763339\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 15 Apr 2024 14:07:20 +0200 (CEST)","from ideasonboard.com (unknown\n\t[IPv6:2a00:6020:448c:6c00:7a0d:dd2e:881a:db83])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id C93C5735;\n\tMon, 15 Apr 2024 14:06:34 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"DuAXyfKf\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1713182794;\n\tbh=Pmh27LUyu1kroNnpuzUqc10VIQtxjatLUBsZSyd0JcA=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=DuAXyfKfBEWDlENUbdVAKC6FqMtjZg212d/LeUTOTGK5GH4i1zgEWvtRmnE/TzvlE\n\twKelk+jmMVre+YxlLI/ACxB5fPgGgR7MEMmvCNaYy8hFZ0EpCqWstM0KDR6knKCjqu\n\t7IN9zoeGYL99Z1zfRGWSEpxirXIJStaIu9FnNSM0=","Date":"Mon, 15 Apr 2024 14:07:17 +0200","From":"Stefan Klug <stefan.klug@ideasonboard.com>","To":"Paul Elder <paul.elder@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Subject":"Re: [PATCH 2/4] ipa: libipa: Copy pwl from rpi","Message-ID":"<20240415120717.ci3qpaddurlk3flx@jasper>","References":"<20240405080259.1806453-1-paul.elder@ideasonboard.com>\n\t<20240405080259.1806453-3-paul.elder@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20240405080259.1806453-3-paul.elder@ideasonboard.com>","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>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":29346,"web_url":"https://patchwork.libcamera.org/comment/29346/","msgid":"<ZitaO-M-I1zXfjlB@pyrite.rasen.tech>","date":"2024-04-26T07:39:39","subject":"Re: [PATCH 2/4] ipa: libipa: Copy pwl from rpi","submitter":{"id":17,"url":"https://patchwork.libcamera.org/api/people/17/","name":"Paul Elder","email":"paul.elder@ideasonboard.com"},"content":"Hi Naush,\n\nOn Mon, Apr 08, 2024 at 09:14:41AM +0100, Naushir Patuck wrote:\n> Hi Paul,\n> \n> On Fri, 5 Apr 2024 at 09:03, Paul Elder <paul.elder@ideasonboard.com> wrote:\n> >\n> > Copy the piecewise linear function code from Raspberry Pi.\n> >\n> \n> This seems like a good time to mention something I was intending on\n> doing for a while...\n> \n> Our PWL library is used internally in a few places\n> (libcamera/libpisp/rpicam-apps/some internal tools).  Right now each\n> of these libraries has a copy of pretty much the same pwl.c/pwl.h\n> file.  I was intending to create a stand alone Raspberry Pi PWL\n> library that could be linked with these so we only have one canonical\n> copy of the source code.  This could then be included into\n> libcamera/libpipa as a subproject if needed.  Do you think this would\n> be helpful and suitable for you?  We will need to look at possibly\n\nYeah that might be a good idea. (although I'm not in the position to\nmake these decisions)\n\nIn any case we need this right now so for now we'll go with this :)\n\n\nThanks,\n\nPaul\n\n> templating the Point type if needed as well to be more appropriate for\n> libcamera's use.\n> \n> Regards,\n> Naush\n> \n> > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n> > ---\n> >  src/ipa/libipa/meson.build |   2 +\n> >  src/ipa/libipa/pwl.cpp     | 267 +++++++++++++++++++++++++++++++++++++\n> >  src/ipa/libipa/pwl.h       | 123 +++++++++++++++++\n> >  3 files changed, 392 insertions(+)\n> >  create mode 100644 src/ipa/libipa/pwl.cpp\n> >  create mode 100644 src/ipa/libipa/pwl.h\n> >\n> > diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build\n> > index 31cc8d70..8fba16e1 100644\n> > --- a/src/ipa/libipa/meson.build\n> > +++ b/src/ipa/libipa/meson.build\n> > @@ -8,6 +8,7 @@ libipa_headers = files([\n> >      'fc_queue.h',\n> >      'histogram.h',\n> >      'module.h',\n> > +    'pwl.h',\n> >  ])\n> >\n> >  libipa_sources = files([\n> > @@ -18,6 +19,7 @@ libipa_sources = files([\n> >      'fc_queue.cpp',\n> >      'histogram.cpp',\n> >      'module.cpp',\n> > +    'pwl.cpp'\n> >  ])\n> >\n> >  libipa_includes = include_directories('..')\n> > diff --git a/src/ipa/libipa/pwl.cpp b/src/ipa/libipa/pwl.cpp\n> > new file mode 100644\n> > index 00000000..09f5d65c\n> > --- /dev/null\n> > +++ b/src/ipa/libipa/pwl.cpp\n> > @@ -0,0 +1,267 @@\n> > +/* SPDX-License-Identifier: BSD-2-Clause */\n> > +/*\n> > + * Copyright (C) 2019, Raspberry Pi Ltd\n> > + *\n> > + * pwl.cpp - piecewise linear functions\n> > + */\n> > +\n> > +#include <cassert>\n> > +#include <cmath>\n> > +#include <stdexcept>\n> > +\n> > +#include \"pwl.h\"\n> > +\n> > +int Pwl::read(const libcamera::YamlObject &params)\n> > +{\n> > +       if (!params.size() || params.size() % 2)\n> > +               return -EINVAL;\n> > +\n> > +       const auto &list = params.asList();\n> > +\n> > +       for (auto it = list.begin(); it != list.end(); it++) {\n> > +               auto x = it->get<double>();\n> > +               if (!x)\n> > +                       return -EINVAL;\n> > +               if (it != list.begin() && *x <= points_.back().x)\n> > +                       return -EINVAL;\n> > +\n> > +               auto y = (++it)->get<double>();\n> > +               if (!y)\n> > +                       return -EINVAL;\n> > +\n> > +               points_.push_back(Point(*x, *y));\n> > +       }\n> > +\n> > +       return 0;\n> > +}\n> > +\n> > +void Pwl::append(double x, double y, const double eps)\n> > +{\n> > +       if (points_.empty() || points_.back().x + eps < x)\n> > +               points_.push_back(Point(x, y));\n> > +}\n> > +\n> > +void Pwl::prepend(double x, double y, const double eps)\n> > +{\n> > +       if (points_.empty() || points_.front().x - eps > x)\n> > +               points_.insert(points_.begin(), Point(x, y));\n> > +}\n> > +\n> > +Pwl::Interval Pwl::domain() const\n> > +{\n> > +       return Interval(points_[0].x, points_[points_.size() - 1].x);\n> > +}\n> > +\n> > +Pwl::Interval Pwl::range() const\n> > +{\n> > +       double lo = points_[0].y, hi = lo;\n> > +       for (auto &p : points_)\n> > +               lo = std::min(lo, p.y), hi = std::max(hi, p.y);\n> > +       return Interval(lo, hi);\n> > +}\n> > +\n> > +bool Pwl::empty() const\n> > +{\n> > +       return points_.empty();\n> > +}\n> > +\n> > +double Pwl::eval(double x, int *spanPtr, bool updateSpan) const\n> > +{\n> > +       int span = findSpan(x, spanPtr && *spanPtr != -1 ? *spanPtr : points_.size() / 2 - 1);\n> > +       if (spanPtr && updateSpan)\n> > +               *spanPtr = span;\n> > +       return points_[span].y +\n> > +              (x - points_[span].x) * (points_[span + 1].y - points_[span].y) /\n> > +                      (points_[span + 1].x - points_[span].x);\n> > +}\n> > +\n> > +int Pwl::findSpan(double x, int span) const\n> > +{\n> > +       /*\n> > +        * Pwls are generally small, so linear search may well be faster than\n> > +        * binary, though could review this if large PWls start turning up.\n> > +        */\n> > +       int lastSpan = points_.size() - 2;\n> > +       /*\n> > +        * some algorithms may call us with span pointing directly at the last\n> > +        * control point\n> > +        */\n> > +       span = std::max(0, std::min(lastSpan, span));\n> > +       while (span < lastSpan && x >= points_[span + 1].x)\n> > +               span++;\n> > +       while (span && x < points_[span].x)\n> > +               span--;\n> > +       return span;\n> > +}\n> > +\n> > +Pwl::PerpType Pwl::invert(Point const &xy, Point &perp, int &span,\n> > +                         const double eps) const\n> > +{\n> > +       assert(span >= -1);\n> > +       bool prevOffEnd = false;\n> > +       for (span = span + 1; span < (int)points_.size() - 1; span++) {\n> > +               Point spanVec = points_[span + 1] - points_[span];\n> > +               double t = ((xy - points_[span]) % spanVec) / spanVec.len2();\n> > +               if (t < -eps) /* off the start of this span */\n> > +               {\n> > +                       if (span == 0) {\n> > +                               perp = points_[span];\n> > +                               return PerpType::Start;\n> > +                       } else if (prevOffEnd) {\n> > +                               perp = points_[span];\n> > +                               return PerpType::Vertex;\n> > +                       }\n> > +               } else if (t > 1 + eps) /* off the end of this span */\n> > +               {\n> > +                       if (span == (int)points_.size() - 2) {\n> > +                               perp = points_[span + 1];\n> > +                               return PerpType::End;\n> > +                       }\n> > +                       prevOffEnd = true;\n> > +               } else /* a true perpendicular */\n> > +               {\n> > +                       perp = points_[span] + spanVec * t;\n> > +                       return PerpType::Perpendicular;\n> > +               }\n> > +       }\n> > +       return PerpType::None;\n> > +}\n> > +\n> > +Pwl Pwl::inverse(bool *trueInverse, const double eps) const\n> > +{\n> > +       bool appended = false, prepended = false, neither = false;\n> > +       Pwl inverse;\n> > +\n> > +       for (Point const &p : points_) {\n> > +               if (inverse.empty())\n> > +                       inverse.append(p.y, p.x, eps);\n> > +               else if (std::abs(inverse.points_.back().x - p.y) <= eps ||\n> > +                        std::abs(inverse.points_.front().x - p.y) <= eps)\n> > +                       /* do nothing */;\n> > +               else if (p.y > inverse.points_.back().x) {\n> > +                       inverse.append(p.y, p.x, eps);\n> > +                       appended = true;\n> > +               } else if (p.y < inverse.points_.front().x) {\n> > +                       inverse.prepend(p.y, p.x, eps);\n> > +                       prepended = true;\n> > +               } else\n> > +                       neither = true;\n> > +       }\n> > +\n> > +       /*\n> > +        * This is not a proper inverse if we found ourselves putting points\n> > +        * onto both ends of the inverse, or if there were points that couldn't\n> > +        * go on either.\n> > +        */\n> > +       if (trueInverse)\n> > +               *trueInverse = !(neither || (appended && prepended));\n> > +\n> > +       return inverse;\n> > +}\n> > +\n> > +Pwl Pwl::compose(Pwl const &other, const double eps) const\n> > +{\n> > +       double thisX = points_[0].x, thisY = points_[0].y;\n> > +       int thisSpan = 0, otherSpan = other.findSpan(thisY, 0);\n> > +       Pwl result({ { thisX, other.eval(thisY, &otherSpan, false) } });\n> > +       while (thisSpan != (int)points_.size() - 1) {\n> > +               double dx = points_[thisSpan + 1].x - points_[thisSpan].x,\n> > +                      dy = points_[thisSpan + 1].y - points_[thisSpan].y;\n> > +               if (std::abs(dy) > eps &&\n> > +                   otherSpan + 1 < (int)other.points_.size() &&\n> > +                   points_[thisSpan + 1].y >=\n> > +                           other.points_[otherSpan + 1].x + eps) {\n> > +                       /*\n> > +                        * next control point in result will be where this\n> > +                        * function's y reaches the next span in other\n> > +                        */\n> > +                       thisX = points_[thisSpan].x +\n> > +                               (other.points_[otherSpan + 1].x -\n> > +                                points_[thisSpan].y) *\n> > +                                       dx / dy;\n> > +                       thisY = other.points_[++otherSpan].x;\n> > +               } else if (std::abs(dy) > eps && otherSpan > 0 &&\n> > +                          points_[thisSpan + 1].y <=\n> > +                                  other.points_[otherSpan - 1].x - eps) {\n> > +                       /*\n> > +                        * next control point in result will be where this\n> > +                        * function's y reaches the previous span in other\n> > +                        */\n> > +                       thisX = points_[thisSpan].x +\n> > +                               (other.points_[otherSpan + 1].x -\n> > +                                points_[thisSpan].y) *\n> > +                                       dx / dy;\n> > +                       thisY = other.points_[--otherSpan].x;\n> > +               } else {\n> > +                       /* we stay in the same span in other */\n> > +                       thisSpan++;\n> > +                       thisX = points_[thisSpan].x,\n> > +                       thisY = points_[thisSpan].y;\n> > +               }\n> > +               result.append(thisX, other.eval(thisY, &otherSpan, false),\n> > +                             eps);\n> > +       }\n> > +       return result;\n> > +}\n> > +\n> > +void Pwl::map(std::function<void(double x, double y)> f) const\n> > +{\n> > +       for (auto &pt : points_)\n> > +               f(pt.x, pt.y);\n> > +}\n> > +\n> > +void Pwl::map2(Pwl const &pwl0, Pwl const &pwl1,\n> > +              std::function<void(double x, double y0, double y1)> f)\n> > +{\n> > +       int span0 = 0, span1 = 0;\n> > +       double x = std::min(pwl0.points_[0].x, pwl1.points_[0].x);\n> > +       f(x, pwl0.eval(x, &span0, false), pwl1.eval(x, &span1, false));\n> > +       while (span0 < (int)pwl0.points_.size() - 1 ||\n> > +              span1 < (int)pwl1.points_.size() - 1) {\n> > +               if (span0 == (int)pwl0.points_.size() - 1)\n> > +                       x = pwl1.points_[++span1].x;\n> > +               else if (span1 == (int)pwl1.points_.size() - 1)\n> > +                       x = pwl0.points_[++span0].x;\n> > +               else if (pwl0.points_[span0 + 1].x > pwl1.points_[span1 + 1].x)\n> > +                       x = pwl1.points_[++span1].x;\n> > +               else\n> > +                       x = pwl0.points_[++span0].x;\n> > +               f(x, pwl0.eval(x, &span0, false), pwl1.eval(x, &span1, false));\n> > +       }\n> > +}\n> > +\n> > +Pwl Pwl::combine(Pwl const &pwl0, Pwl const &pwl1,\n> > +                std::function<double(double x, double y0, double y1)> f,\n> > +                const double eps)\n> > +{\n> > +       Pwl result;\n> > +       map2(pwl0, pwl1, [&](double x, double y0, double y1) {\n> > +               result.append(x, f(x, y0, y1), eps);\n> > +       });\n> > +       return result;\n> > +}\n> > +\n> > +void Pwl::matchDomain(Interval const &domain, bool clip, const double eps)\n> > +{\n> > +       int span = 0;\n> > +       prepend(domain.start, eval(clip ? points_[0].x : domain.start, &span),\n> > +               eps);\n> > +       span = points_.size() - 2;\n> > +       append(domain.end, eval(clip ? points_.back().x : domain.end, &span),\n> > +              eps);\n> > +}\n> > +\n> > +Pwl &Pwl::operator*=(double d)\n> > +{\n> > +       for (auto &pt : points_)\n> > +               pt.y *= d;\n> > +       return *this;\n> > +}\n> > +\n> > +void Pwl::debug(FILE *fp) const\n> > +{\n> > +       fprintf(fp, \"Pwl {\\n\");\n> > +       for (auto &p : points_)\n> > +               fprintf(fp, \"\\t(%g, %g)\\n\", p.x, p.y);\n> > +       fprintf(fp, \"}\\n\");\n> > +}\n> > diff --git a/src/ipa/libipa/pwl.h b/src/ipa/libipa/pwl.h\n> > new file mode 100644\n> > index 00000000..7a6a6452\n> > --- /dev/null\n> > +++ b/src/ipa/libipa/pwl.h\n> > @@ -0,0 +1,123 @@\n> > +/* SPDX-License-Identifier: BSD-2-Clause */\n> > +/*\n> > + * Copyright (C) 2019, Raspberry Pi Ltd\n> > + *\n> > + * pwl.h - piecewise linear functions interface\n> > + */\n> > +#pragma once\n> > +\n> > +#include <functional>\n> > +#include <math.h>\n> > +#include <vector>\n> > +\n> > +#include \"libcamera/internal/yaml_parser.h\"\n> > +\n> > +class Pwl\n> > +{\n> > +public:\n> > +       struct Interval {\n> > +               Interval(double _start, double _end)\n> > +                       : start(_start), end(_end)\n> > +               {\n> > +               }\n> > +               double start, end;\n> > +               bool contains(double value)\n> > +               {\n> > +                       return value >= start && value <= end;\n> > +               }\n> > +               double clip(double value)\n> > +               {\n> > +                       return value < start ? start\n> > +                                            : (value > end ? end : value);\n> > +               }\n> > +               double len() const { return end - start; }\n> > +       };\n> > +       struct Point {\n> > +               Point() : x(0), y(0) {}\n> > +               Point(double _x, double _y)\n> > +                       : x(_x), y(_y) {}\n> > +               double x, y;\n> > +               Point operator-(Point const &p) const\n> > +               {\n> > +                       return Point(x - p.x, y - p.y);\n> > +               }\n> > +               Point operator+(Point const &p) const\n> > +               {\n> > +                       return Point(x + p.x, y + p.y);\n> > +               }\n> > +               double operator%(Point const &p) const\n> > +               {\n> > +                       return x * p.x + y * p.y;\n> > +               }\n> > +               Point operator*(double f) const { return Point(x * f, y * f); }\n> > +               Point operator/(double f) const { return Point(x / f, y / f); }\n> > +               double len2() const { return x * x + y * y; }\n> > +               double len() const { return sqrt(len2()); }\n> > +       };\n> > +       Pwl() {}\n> > +       Pwl(std::vector<Point> const &points) : points_(points) {}\n> > +       int read(const libcamera::YamlObject &params);\n> > +       void append(double x, double y, const double eps = 1e-6);\n> > +       void prepend(double x, double y, const double eps = 1e-6);\n> > +       Interval domain() const;\n> > +       Interval range() const;\n> > +       bool empty() const;\n> > +       /*\n> > +        * Evaluate Pwl, optionally supplying an initial guess for the\n> > +        * \"span\". The \"span\" may be optionally be updated.  If you want to know\n> > +        * the \"span\" value but don't have an initial guess you can set it to\n> > +        * -1.\n> > +        */\n> > +       double eval(double x, int *spanPtr = nullptr,\n> > +                   bool updateSpan = true) const;\n> > +       /*\n> > +        * Find perpendicular closest to xy, starting from span+1 so you can\n> > +        * call it repeatedly to check for multiple closest points (set span to\n> > +        * -1 on the first call). Also returns \"pseudo\" perpendiculars; see\n> > +        * PerpType enum.\n> > +        */\n> > +       enum class PerpType {\n> > +               None, /* no perpendicular found */\n> > +               Start, /* start of Pwl is closest point */\n> > +               End, /* end of Pwl is closest point */\n> > +               Vertex, /* vertex of Pwl is closest point */\n> > +               Perpendicular /* true perpendicular found */\n> > +       };\n> > +       PerpType invert(Point const &xy, Point &perp, int &span,\n> > +                       const double eps = 1e-6) const;\n> > +       /*\n> > +        * Compute the inverse function. Indicate if it is a proper (true)\n> > +        * inverse, or only a best effort (e.g. input was non-monotonic).\n> > +        */\n> > +       Pwl inverse(bool *trueInverse = nullptr, const double eps = 1e-6) const;\n> > +       /* Compose two Pwls together, doing \"this\" first and \"other\" after. */\n> > +       Pwl compose(Pwl const &other, const double eps = 1e-6) const;\n> > +       /* Apply function to (x,y) values at every control point. */\n> > +       void map(std::function<void(double x, double y)> f) const;\n> > +       /*\n> > +        * Apply function to (x, y0, y1) values wherever either Pwl has a\n> > +        * control point.\n> > +        */\n> > +       static void map2(Pwl const &pwl0, Pwl const &pwl1,\n> > +                        std::function<void(double x, double y0, double y1)> f);\n> > +       /*\n> > +        * Combine two Pwls, meaning we create a new Pwl where the y values are\n> > +        * given by running f wherever either has a knot.\n> > +        */\n> > +       static Pwl\n> > +       combine(Pwl const &pwl0, Pwl const &pwl1,\n> > +               std::function<double(double x, double y0, double y1)> f,\n> > +               const double eps = 1e-6);\n> > +       /*\n> > +        * Make \"this\" match (at least) the given domain. Any extension my be\n> > +        * clipped or linear.\n> > +        */\n> > +       void matchDomain(Interval const &domain, bool clip = true,\n> > +                        const double eps = 1e-6);\n> > +       Pwl &operator*=(double d);\n> > +       void debug(FILE *fp = stdout) const;\n> > +\n> > +private:\n> > +       int findSpan(double x, int span) const;\n> > +       std::vector<Point> points_;\n> > +};\n> > --\n> > 2.39.2\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 0BA13BE08B\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 26 Apr 2024 07:39:51 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 66C806340A;\n\tFri, 26 Apr 2024 09:39:49 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 559FD633F4\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 26 Apr 2024 09:39:47 +0200 (CEST)","from pyrite.rasen.tech (h175-177-049-156.catv02.itscom.jp\n\t[175.177.49.156])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id DFEF4D7E;\n\tFri, 26 Apr 2024 09:38:52 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"Tb5PDrsK\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1714117134;\n\tbh=qWbeKXn81M0GgMC90eQLwlcjoiDwpU/vTOvEYuUlBmA=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=Tb5PDrsKPZDOgProt/vot9y5MCc+K2YmrTZN3I+bPN4otBD8nnr3VO5cTrSwhhUyH\n\tS2L2Bd5rp8S7ugTOyBLAezzJ9DfggaTniW4R7Ez+4CxBlPPUAjIzn6Dnn7N3WLVdho\n\tGcxn38DkH07wCo+1bX4vUx/T+e4sguAZGwzBkSQo=","Date":"Fri, 26 Apr 2024 16:39:39 +0900","From":"Paul Elder <paul.elder@ideasonboard.com>","To":"Naushir Patuck <naush@raspberrypi.com>","Cc":"libcamera-devel@lists.libcamera.org","Subject":"Re: [PATCH 2/4] ipa: libipa: Copy pwl from rpi","Message-ID":"<ZitaO-M-I1zXfjlB@pyrite.rasen.tech>","References":"<20240405080259.1806453-1-paul.elder@ideasonboard.com>\n\t<20240405080259.1806453-3-paul.elder@ideasonboard.com>\n\t<CAEmqJPqh=yhZZGpuU8wfrSnXrr5bY8LBgvyJpcpfBQZudjtdHw@mail.gmail.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=us-ascii","Content-Disposition":"inline","In-Reply-To":"<CAEmqJPqh=yhZZGpuU8wfrSnXrr5bY8LBgvyJpcpfBQZudjtdHw@mail.gmail.com>","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>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":29691,"web_url":"https://patchwork.libcamera.org/comment/29691/","msgid":"<171715165579.1000377.17196450495128663341@ping.linuxembedded.co.uk>","date":"2024-05-31T10:34:15","subject":"Re: [PATCH 2/4] ipa: libipa: Copy pwl from rpi","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"Quoting Paul Elder (2024-04-26 08:39:39)\n> Hi Naush,\n> \n> On Mon, Apr 08, 2024 at 09:14:41AM +0100, Naushir Patuck wrote:\n> > Hi Paul,\n> > \n> > On Fri, 5 Apr 2024 at 09:03, Paul Elder <paul.elder@ideasonboard.com> wrote:\n> > >\n> > > Copy the piecewise linear function code from Raspberry Pi.\n> > >\n> > \n> > This seems like a good time to mention something I was intending on\n> > doing for a while...\n> > \n> > Our PWL library is used internally in a few places\n> > (libcamera/libpisp/rpicam-apps/some internal tools).  Right now each\n> > of these libraries has a copy of pretty much the same pwl.c/pwl.h\n> > file.  I was intending to create a stand alone Raspberry Pi PWL\n> > library that could be linked with these so we only have one canonical\n> > copy of the source code.  This could then be included into\n> > libcamera/libpipa as a subproject if needed.  Do you think this would\n> > be helpful and suitable for you?  We will need to look at possibly\n> \n> Yeah that might be a good idea. (although I'm not in the position to\n> make these decisions)\n\nI don't think core libcamera can rely on a 'subproject' (in meson terms)\nas packagers/distributions will not accept a build that has to go and\ndownload more code to perform the build.\n\nBut we should certainly find a way to reduce having 2, 3, 4 copies of\nthe same code in different places...\n\nI think the Pwl is small enough (and stable enough) that it's not too\npainful for now. I wonder if we do need to break out more like we have\nwith libcamera-base ... but I don't know where the bar is for that yet.\n\n> In any case we need this right now so for now we'll go with this :)\n> \n> \n> Thanks,\n> \n> Paul\n\n--\nKieran","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 936E6BD87C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 31 May 2024 10:34:21 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 9047A634B6;\n\tFri, 31 May 2024 12:34:20 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id D2A7861A46\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 31 May 2024 12:34:18 +0200 (CEST)","from pendragon.ideasonboard.com\n\t(cpc89244-aztw30-2-0-cust6594.18-1.cable.virginm.net [86.31.185.195])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id CA824B53;\n\tFri, 31 May 2024 12:34:13 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"aczKgWKd\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1717151653;\n\tbh=/ENOTKrf26wM6Cxe7Yuq9AUuE8xZFsjzuR2kmFASRAs=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=aczKgWKdyqXeTtQetdoAL9VrffHFjl+Jzue1fPy1CbW0NXkeSrGGfce2Ztf576MiW\n\taaVk6Lh4ICu5OJgEgQYKtNVTGowCBsMzqxbkeK6l/e7i0boaxliB8x54ruzYT9ETD7\n\tiC5rQlBsHRxsx5dR5F/sp/bw8hO3LbjjxjK7/wgU=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<ZitaO-M-I1zXfjlB@pyrite.rasen.tech>","References":"<20240405080259.1806453-1-paul.elder@ideasonboard.com>\n\t<20240405080259.1806453-3-paul.elder@ideasonboard.com>\n\t<CAEmqJPqh=yhZZGpuU8wfrSnXrr5bY8LBgvyJpcpfBQZudjtdHw@mail.gmail.com>\n\t<ZitaO-M-I1zXfjlB@pyrite.rasen.tech>","Subject":"Re: [PATCH 2/4] ipa: libipa: Copy pwl from rpi","From":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","To":"Naushir Patuck <naush@raspberrypi.com>,\n\tPaul Elder <paul.elder@ideasonboard.com>","Date":"Fri, 31 May 2024 11:34:15 +0100","Message-ID":"<171715165579.1000377.17196450495128663341@ping.linuxembedded.co.uk>","User-Agent":"alot/0.10","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>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":29692,"web_url":"https://patchwork.libcamera.org/comment/29692/","msgid":"<20240531104945.GC23937@pendragon.ideasonboard.com>","date":"2024-05-31T10:49:45","subject":"Re: [PATCH 2/4] ipa: libipa: Copy pwl from rpi","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"On Fri, May 31, 2024 at 11:34:15AM +0100, Kieran Bingham wrote:\n> Quoting Paul Elder (2024-04-26 08:39:39)\n> > Hi Naush,\n> > \n> > On Mon, Apr 08, 2024 at 09:14:41AM +0100, Naushir Patuck wrote:\n> > > Hi Paul,\n> > > \n> > > On Fri, 5 Apr 2024 at 09:03, Paul Elder <paul.elder@ideasonboard.com> wrote:\n> > > >\n> > > > Copy the piecewise linear function code from Raspberry Pi.\n> > > >\n> > > \n> > > This seems like a good time to mention something I was intending on\n> > > doing for a while...\n> > > \n> > > Our PWL library is used internally in a few places\n> > > (libcamera/libpisp/rpicam-apps/some internal tools).  Right now each\n> > > of these libraries has a copy of pretty much the same pwl.c/pwl.h\n> > > file.  I was intending to create a stand alone Raspberry Pi PWL\n> > > library that could be linked with these so we only have one canonical\n> > > copy of the source code.  This could then be included into\n> > > libcamera/libpipa as a subproject if needed.  Do you think this would\n> > > be helpful and suitable for you?  We will need to look at possibly\n> > \n> > Yeah that might be a good idea. (although I'm not in the position to\n> > make these decisions)\n> \n> I don't think core libcamera can rely on a 'subproject' (in meson terms)\n> as packagers/distributions will not accept a build that has to go and\n> download more code to perform the build.\n> \n> But we should certainly find a way to reduce having 2, 3, 4 copies of\n> the same code in different places...\n> \n> I think the Pwl is small enough (and stable enough) that it's not too\n> painful for now. I wonder if we do need to break out more like we have\n> with libcamera-base ... but I don't know where the bar is for that yet.\n\nI would vote for a standalone math library that would be optimized for\ncamera usage, but I fear we would be reinventing the wheel :-)\n\n> > In any case we need this right now so for now we'll go with this :)","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 87D95BDE6B\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 31 May 2024 10:50:01 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id A463A634B6;\n\tFri, 31 May 2024 12:50:00 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 3855B61A46\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 31 May 2024 12:49:59 +0200 (CEST)","from pendragon.ideasonboard.com (81-175-209-231.bb.dnainternet.fi\n\t[81.175.209.231])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 14C37B53;\n\tFri, 31 May 2024 12:49:54 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"GUR+M2mT\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1717152594;\n\tbh=RqcMM/5HS8WBiCwB++tqUuWtadudVGtnoADTner3JMM=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=GUR+M2mTmE5wgwCeCsVpNjju+B7DpDSnbB4gT+4mCDuCQWFrQ9q2KSH4wW04LKxD2\n\twnQjwhibB7MzfayA9iGcake5Bydh5+LZVRsFjJ67qm0QotR8EBMCCCecKIga7ofvjw\n\tqiYRT5Nk5INV4/M/O2Fo0214d2YMAe2oL86C80Ec=","Date":"Fri, 31 May 2024 13:49:45 +0300","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Cc":"Naushir Patuck <naush@raspberrypi.com>,\n\tPaul Elder <paul.elder@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","Subject":"Re: [PATCH 2/4] ipa: libipa: Copy pwl from rpi","Message-ID":"<20240531104945.GC23937@pendragon.ideasonboard.com>","References":"<20240405080259.1806453-1-paul.elder@ideasonboard.com>\n\t<20240405080259.1806453-3-paul.elder@ideasonboard.com>\n\t<CAEmqJPqh=yhZZGpuU8wfrSnXrr5bY8LBgvyJpcpfBQZudjtdHw@mail.gmail.com>\n\t<ZitaO-M-I1zXfjlB@pyrite.rasen.tech>\n\t<171715165579.1000377.17196450495128663341@ping.linuxembedded.co.uk>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<171715165579.1000377.17196450495128663341@ping.linuxembedded.co.uk>","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>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]