[{"id":29749,"web_url":"https://patchwork.libcamera.org/comment/29749/","msgid":"<171741576377.205609.3863541201137378795@ping.linuxembedded.co.uk>","date":"2024-06-03T11:56:03","subject":"Re: [PATCH v4 4/4] ipa: rpi: controller: Use libipa's Pwl class","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"Quoting Paul Elder (2024-05-31 15:43:01)\n> To reduce code duplication, use the Pwl class from libipa. This also\n> removes the Pwl class from the Raspberry Pi IPA.\n> \n> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n> Reviewed-by: Stefan Klug <stefan.klug@ideasonboard.com>\n> Acked-by: David Plowman <david.plowman@raspberrypi.com>\n\nI got to the bottom with no comments so on the assumption that the\npreceeding patches are resolved (I think there's some discusion to\nfigure out 1/4 in this series still)\n\nReviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n\n> \n> ---\n> No change in v4\n> \n> No change in v3\n> \n> Changes in v2:\n> - s/FPoint/PointF/g\n> ---\n>  src/ipa/rpi/controller/cac_status.h        |   2 -\n>  src/ipa/rpi/controller/contrast_status.h   |   4 +-\n>  src/ipa/rpi/controller/meson.build         |   2 +-\n>  src/ipa/rpi/controller/pwl.cpp             | 269 ---------------------\n>  src/ipa/rpi/controller/pwl.h               | 127 ----------\n>  src/ipa/rpi/controller/rpi/af.cpp          |   4 +-\n>  src/ipa/rpi/controller/rpi/af.h            |   5 +-\n>  src/ipa/rpi/controller/rpi/agc_channel.cpp |   8 +-\n>  src/ipa/rpi/controller/rpi/agc_channel.h   |   7 +-\n>  src/ipa/rpi/controller/rpi/awb.cpp         |  40 +--\n>  src/ipa/rpi/controller/rpi/awb.h           |  23 +-\n>  src/ipa/rpi/controller/rpi/ccm.cpp         |   4 +-\n>  src/ipa/rpi/controller/rpi/ccm.h           |   5 +-\n>  src/ipa/rpi/controller/rpi/contrast.cpp    |  14 +-\n>  src/ipa/rpi/controller/rpi/contrast.h      |   5 +-\n>  src/ipa/rpi/controller/rpi/geq.cpp         |   5 +-\n>  src/ipa/rpi/controller/rpi/geq.h           |   4 +-\n>  src/ipa/rpi/controller/rpi/hdr.cpp         |   8 +-\n>  src/ipa/rpi/controller/rpi/hdr.h           |   9 +-\n>  src/ipa/rpi/controller/rpi/tonemap.cpp     |   2 +-\n>  src/ipa/rpi/controller/rpi/tonemap.h       |   5 +-\n>  src/ipa/rpi/controller/tonemap_status.h    |   4 +-\n>  22 files changed, 84 insertions(+), 472 deletions(-)\n>  delete mode 100644 src/ipa/rpi/controller/pwl.cpp\n>  delete mode 100644 src/ipa/rpi/controller/pwl.h\n> \n> diff --git a/src/ipa/rpi/controller/cac_status.h b/src/ipa/rpi/controller/cac_status.h\n> index 475d4c5cc..adffce411 100644\n> --- a/src/ipa/rpi/controller/cac_status.h\n> +++ b/src/ipa/rpi/controller/cac_status.h\n> @@ -6,8 +6,6 @@\n>   */\n>  #pragma once\n>  \n> -#include \"pwl.h\"\n> -\n>  struct CacStatus {\n>         std::vector<double> lutRx;\n>         std::vector<double> lutRy;\n> diff --git a/src/ipa/rpi/controller/contrast_status.h b/src/ipa/rpi/controller/contrast_status.h\n> index 7c67f0547..1f175872a 100644\n> --- a/src/ipa/rpi/controller/contrast_status.h\n> +++ b/src/ipa/rpi/controller/contrast_status.h\n> @@ -6,7 +6,7 @@\n>   */\n>  #pragma once\n>  \n> -#include \"pwl.h\"\n> +#include \"libipa/pwl.h\"\n>  \n>  /*\n>   * The \"contrast\" algorithm creates a gamma curve, optionally doing a little bit\n> @@ -14,7 +14,7 @@\n>   */\n>  \n>  struct ContrastStatus {\n> -       RPiController::Pwl gammaCurve;\n> +       libcamera::ipa::Pwl gammaCurve;\n>         double brightness;\n>         double contrast;\n>  };\n> diff --git a/src/ipa/rpi/controller/meson.build b/src/ipa/rpi/controller/meson.build\n> index 32a4d31cf..74b74888b 100644\n> --- a/src/ipa/rpi/controller/meson.build\n> +++ b/src/ipa/rpi/controller/meson.build\n> @@ -5,7 +5,6 @@ rpi_ipa_controller_sources = files([\n>      'controller.cpp',\n>      'device_status.cpp',\n>      'histogram.cpp',\n> -    'pwl.cpp',\n>      'rpi/af.cpp',\n>      'rpi/agc.cpp',\n>      'rpi/agc_channel.cpp',\n> @@ -32,4 +31,5 @@ rpi_ipa_controller_deps = [\n>  ]\n>  \n>  rpi_ipa_controller_lib = static_library('rpi_ipa_controller', rpi_ipa_controller_sources,\n> +                                        include_directories : libipa_includes,\n>                                          dependencies : rpi_ipa_controller_deps)\n> diff --git a/src/ipa/rpi/controller/pwl.cpp b/src/ipa/rpi/controller/pwl.cpp\n> deleted file mode 100644\n> index e39123767..000000000\n> --- a/src/ipa/rpi/controller/pwl.cpp\n> +++ /dev/null\n> @@ -1,269 +0,0 @@\n> -/* SPDX-License-Identifier: BSD-2-Clause */\n> -/*\n> - * Copyright (C) 2019, Raspberry Pi Ltd\n> - *\n> - * piecewise linear functions\n> - */\n> -\n> -#include <cassert>\n> -#include <cmath>\n> -#include <stdexcept>\n> -\n> -#include \"pwl.h\"\n> -\n> -using namespace RPiController;\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/rpi/controller/pwl.h b/src/ipa/rpi/controller/pwl.h\n> deleted file mode 100644\n> index 7d5e7e4d3..000000000\n> --- a/src/ipa/rpi/controller/pwl.h\n> +++ /dev/null\n> @@ -1,127 +0,0 @@\n> -/* SPDX-License-Identifier: BSD-2-Clause */\n> -/*\n> - * Copyright (C) 2019, Raspberry Pi Ltd\n> - *\n> - * 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> -namespace RPiController {\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> -} /* namespace RPiController */\n> diff --git a/src/ipa/rpi/controller/rpi/af.cpp b/src/ipa/rpi/controller/rpi/af.cpp\n> index c5fd84826..304629d6d 100644\n> --- a/src/ipa/rpi/controller/rpi/af.cpp\n> +++ b/src/ipa/rpi/controller/rpi/af.cpp\n> @@ -139,7 +139,7 @@ int Af::CfgParams::read(const libcamera::YamlObject &params)\n>         readNumber<uint32_t>(skipFrames, params, \"skip_frames\");\n>  \n>         if (params.contains(\"map\"))\n> -               map.read(params[\"map\"]);\n> +               map.readYaml(params[\"map\"]);\n>         else\n>                 LOG(RPiAf, Warning) << \"No map defined\";\n>  \n> @@ -721,7 +721,7 @@ bool Af::setLensPosition(double dioptres, int *hwpos)\n>  \n>         if (mode_ == AfModeManual) {\n>                 LOG(RPiAf, Debug) << \"setLensPosition: \" << dioptres;\n> -               ftarget_ = cfg_.map.domain().clip(dioptres);\n> +               ftarget_ = cfg_.map.domain().clamp(dioptres);\n>                 changed = !(initted_ && fsmooth_ == ftarget_);\n>                 updateLensPosition();\n>         }\n> diff --git a/src/ipa/rpi/controller/rpi/af.h b/src/ipa/rpi/controller/rpi/af.h\n> index 2617e2ace..317a51f3e 100644\n> --- a/src/ipa/rpi/controller/rpi/af.h\n> +++ b/src/ipa/rpi/controller/rpi/af.h\n> @@ -9,7 +9,8 @@\n>  #include \"../af_algorithm.h\"\n>  #include \"../af_status.h\"\n>  #include \"../pdaf_data.h\"\n> -#include \"../pwl.h\"\n> +\n> +#include \"libipa/pwl.h\"\n>  \n>  /*\n>   * This algorithm implements a hybrid of CDAF and PDAF, favouring PDAF.\n> @@ -100,7 +101,7 @@ private:\n>                 uint32_t confThresh;            /* PDAF confidence cell min (sensor-specific) */\n>                 uint32_t confClip;              /* PDAF confidence cell max (sensor-specific) */\n>                 uint32_t skipFrames;            /* frames to skip at start or modeswitch */\n> -               Pwl map;                        /* converts dioptres -> lens driver position */\n> +               libcamera::ipa::Pwl map;        /* converts dioptres -> lens driver position */\n>  \n>                 CfgParams();\n>                 int read(const libcamera::YamlObject &params);\n> diff --git a/src/ipa/rpi/controller/rpi/agc_channel.cpp b/src/ipa/rpi/controller/rpi/agc_channel.cpp\n> index a77ccec36..a381dd972 100644\n> --- a/src/ipa/rpi/controller/rpi/agc_channel.cpp\n> +++ b/src/ipa/rpi/controller/rpi/agc_channel.cpp\n> @@ -130,7 +130,7 @@ int AgcConstraint::read(const libcamera::YamlObject &params)\n>                 return -EINVAL;\n>         qHi = *value;\n>  \n> -       return yTarget.read(params[\"y_target\"]);\n> +       return yTarget.readYaml(params[\"y_target\"]);\n>  }\n>  \n>  static std::tuple<int, AgcConstraintMode>\n> @@ -237,7 +237,7 @@ int AgcConfig::read(const libcamera::YamlObject &params)\n>                         return ret;\n>         }\n>  \n> -       ret = yTarget.read(params[\"y_target\"]);\n> +       ret = yTarget.readYaml(params[\"y_target\"]);\n>         if (ret)\n>                 return ret;\n>  \n> @@ -715,7 +715,7 @@ static constexpr double EvGainYTargetLimit = 0.9;\n>  static double constraintComputeGain(AgcConstraint &c, const Histogram &h, double lux,\n>                                     double evGain, double &targetY)\n>  {\n> -       targetY = c.yTarget.eval(c.yTarget.domain().clip(lux));\n> +       targetY = c.yTarget.eval(c.yTarget.domain().clamp(lux));\n>         targetY = std::min(EvGainYTargetLimit, targetY * evGain);\n>         double iqm = h.interQuantileMean(c.qLo, c.qHi);\n>         return (targetY * h.bins()) / iqm;\n> @@ -734,7 +734,7 @@ void AgcChannel::computeGain(StatisticsPtr &statistics, Metadata *imageMetadata,\n>          * The initial gain and target_Y come from some of the regions. After\n>          * that we consider the histogram constraints.\n>          */\n> -       targetY = config_.yTarget.eval(config_.yTarget.domain().clip(lux.lux));\n> +       targetY = config_.yTarget.eval(config_.yTarget.domain().clamp(lux.lux));\n>         targetY = std::min(EvGainYTargetLimit, targetY * evGain);\n>  \n>         /*\n> diff --git a/src/ipa/rpi/controller/rpi/agc_channel.h b/src/ipa/rpi/controller/rpi/agc_channel.h\n> index 99033e23e..58368889e 100644\n> --- a/src/ipa/rpi/controller/rpi/agc_channel.h\n> +++ b/src/ipa/rpi/controller/rpi/agc_channel.h\n> @@ -12,10 +12,11 @@\n>  \n>  #include <libcamera/base/utils.h>\n>  \n> +#include <libipa/pwl.h>\n> +\n>  #include \"../agc_status.h\"\n>  #include \"../awb_status.h\"\n>  #include \"../controller.h\"\n> -#include \"../pwl.h\"\n>  \n>  /* This is our implementation of AGC. */\n>  \n> @@ -40,7 +41,7 @@ struct AgcConstraint {\n>         Bound bound;\n>         double qLo;\n>         double qHi;\n> -       Pwl yTarget;\n> +       libcamera::ipa::Pwl yTarget;\n>         int read(const libcamera::YamlObject &params);\n>  };\n>  \n> @@ -61,7 +62,7 @@ struct AgcConfig {\n>         std::map<std::string, AgcExposureMode> exposureModes;\n>         std::map<std::string, AgcConstraintMode> constraintModes;\n>         std::vector<AgcChannelConstraint> channelConstraints;\n> -       Pwl yTarget;\n> +       libcamera::ipa::Pwl yTarget;\n>         double speed;\n>         uint16_t startupFrames;\n>         unsigned int convergenceFrames;\n> diff --git a/src/ipa/rpi/controller/rpi/awb.cpp b/src/ipa/rpi/controller/rpi/awb.cpp\n> index abe5906e9..db9937cd4 100644\n> --- a/src/ipa/rpi/controller/rpi/awb.cpp\n> +++ b/src/ipa/rpi/controller/rpi/awb.cpp\n> @@ -49,10 +49,10 @@ int AwbPrior::read(const libcamera::YamlObject &params)\n>                 return -EINVAL;\n>         lux = *value;\n>  \n> -       return prior.read(params[\"prior\"]);\n> +       return prior.readYaml(params[\"prior\"]);\n>  }\n>  \n> -static int readCtCurve(Pwl &ctR, Pwl &ctB, const libcamera::YamlObject &params)\n> +static int readCtCurve(ipa::Pwl &ctR, ipa::Pwl &ctB, const libcamera::YamlObject &params)\n>  {\n>         if (params.size() % 3) {\n>                 LOG(RPiAwb, Error) << \"AwbConfig: incomplete CT curve entry\";\n> @@ -207,7 +207,7 @@ void Awb::initialise()\n>          * them.\n>          */\n>         if (!config_.ctR.empty() && !config_.ctB.empty()) {\n> -               syncResults_.temperatureK = config_.ctR.domain().clip(4000);\n> +               syncResults_.temperatureK = config_.ctR.domain().clamp(4000);\n>                 syncResults_.gainR = 1.0 / config_.ctR.eval(syncResults_.temperatureK);\n>                 syncResults_.gainG = 1.0;\n>                 syncResults_.gainB = 1.0 / config_.ctB.eval(syncResults_.temperatureK);\n> @@ -273,8 +273,8 @@ void Awb::setManualGains(double manualR, double manualB)\n>                 syncResults_.gainB = prevSyncResults_.gainB = manualB_;\n>                 if (config_.bayes) {\n>                         /* Also estimate the best corresponding colour temperature from the curves. */\n> -                       double ctR = config_.ctRInverse.eval(config_.ctRInverse.domain().clip(1 / manualR_));\n> -                       double ctB = config_.ctBInverse.eval(config_.ctBInverse.domain().clip(1 / manualB_));\n> +                       double ctR = config_.ctRInverse.eval(config_.ctRInverse.domain().clamp(1 / manualR_));\n> +                       double ctB = config_.ctBInverse.eval(config_.ctBInverse.domain().clamp(1 / manualB_));\n>                         prevSyncResults_.temperatureK = (ctR + ctB) / 2;\n>                         syncResults_.temperatureK = prevSyncResults_.temperatureK;\n>                 }\n> @@ -468,7 +468,7 @@ double Awb::computeDelta2Sum(double gainR, double gainB)\n>         return delta2Sum;\n>  }\n>  \n> -Pwl Awb::interpolatePrior()\n> +ipa::Pwl Awb::interpolatePrior()\n>  {\n>         /*\n>          * Interpolate the prior log likelihood function for our current lux\n> @@ -485,7 +485,7 @@ Pwl Awb::interpolatePrior()\n>                         idx++;\n>                 double lux0 = config_.priors[idx].lux,\n>                        lux1 = config_.priors[idx + 1].lux;\n> -               return Pwl::combine(config_.priors[idx].prior,\n> +               return ipa::Pwl::combine(config_.priors[idx].prior,\n>                                     config_.priors[idx + 1].prior,\n>                                     [&](double /*x*/, double y0, double y1) {\n>                                             return y0 + (y1 - y0) *\n> @@ -494,15 +494,15 @@ Pwl Awb::interpolatePrior()\n>         }\n>  }\n>  \n> -static double interpolateQuadatric(Pwl::Point const &a, Pwl::Point const &b,\n> -                                  Pwl::Point const &c)\n> +static double interpolateQuadatric(PointF const &a, PointF const &b,\n> +                                  PointF const &c)\n>  {\n>         /*\n>          * Given 3 points on a curve, find the extremum of the function in that\n>          * interval by fitting a quadratic.\n>          */\n>         const double eps = 1e-3;\n> -       Pwl::Point ca = c - a, ba = b - a;\n> +       PointF ca = c - a, ba = b - a;\n>         double denominator = 2 * (ba.y * ca.x - ca.y * ba.x);\n>         if (abs(denominator) > eps) {\n>                 double numerator = ba.y * ca.x * ca.x - ca.y * ba.x * ba.x;\n> @@ -513,7 +513,7 @@ static double interpolateQuadatric(Pwl::Point const &a, Pwl::Point const &b,\n>         return a.y < c.y - eps ? a.x : (c.y < a.y - eps ? c.x : b.x);\n>  }\n>  \n> -double Awb::coarseSearch(Pwl const &prior)\n> +double Awb::coarseSearch(ipa::Pwl const &prior)\n>  {\n>         points_.clear(); /* assume doesn't deallocate memory */\n>         size_t bestPoint = 0;\n> @@ -525,14 +525,14 @@ double Awb::coarseSearch(Pwl const &prior)\n>                 double b = config_.ctB.eval(t, &spanB);\n>                 double gainR = 1 / r, gainB = 1 / b;\n>                 double delta2Sum = computeDelta2Sum(gainR, gainB);\n> -               double priorLogLikelihood = prior.eval(prior.domain().clip(t));\n> +               double priorLogLikelihood = prior.eval(prior.domain().clamp(t));\n>                 double finalLogLikelihood = delta2Sum - priorLogLikelihood;\n>                 LOG(RPiAwb, Debug)\n>                         << \"t: \" << t << \" gain R \" << gainR << \" gain B \"\n>                         << gainB << \" delta2_sum \" << delta2Sum\n>                         << \" prior \" << priorLogLikelihood << \" final \"\n>                         << finalLogLikelihood;\n> -               points_.push_back(Pwl::Point(t, finalLogLikelihood));\n> +               points_.push_back(PointF(t, finalLogLikelihood));\n>                 if (points_.back().y < points_[bestPoint].y)\n>                         bestPoint = points_.size() - 1;\n>                 if (t == mode_->ctHi)\n> @@ -559,7 +559,7 @@ double Awb::coarseSearch(Pwl const &prior)\n>         return t;\n>  }\n>  \n> -void Awb::fineSearch(double &t, double &r, double &b, Pwl const &prior)\n> +void Awb::fineSearch(double &t, double &r, double &b, ipa::Pwl const &prior)\n>  {\n>         int spanR = -1, spanB = -1;\n>         config_.ctR.eval(t, &spanR);\n> @@ -570,7 +570,7 @@ void Awb::fineSearch(double &t, double &r, double &b, Pwl const &prior)\n>                        config_.ctR.eval(t - nsteps * step, &spanR);\n>         double bDiff = config_.ctB.eval(t + nsteps * step, &spanB) -\n>                        config_.ctB.eval(t - nsteps * step, &spanB);\n> -       Pwl::Point transverse(bDiff, -rDiff);\n> +       PointF transverse(bDiff, -rDiff);\n>         if (transverse.len2() < 1e-6)\n>                 return;\n>         /*\n> @@ -592,17 +592,17 @@ void Awb::fineSearch(double &t, double &r, double &b, Pwl const &prior)\n>         for (int i = -nsteps; i <= nsteps; i++) {\n>                 double tTest = t + i * step;\n>                 double priorLogLikelihood =\n> -                       prior.eval(prior.domain().clip(tTest));\n> +                       prior.eval(prior.domain().clamp(tTest));\n>                 double rCurve = config_.ctR.eval(tTest, &spanR);\n>                 double bCurve = config_.ctB.eval(tTest, &spanB);\n>                 /* x will be distance off the curve, y the log likelihood there */\n> -               Pwl::Point points[maxNumDeltas];\n> +               PointF points[maxNumDeltas];\n>                 int bestPoint = 0;\n>                 /* Take some measurements transversely *off* the CT curve. */\n>                 for (int j = 0; j < numDeltas; j++) {\n>                         points[j].x = -config_.transverseNeg +\n>                                       (transverseRange * j) / (numDeltas - 1);\n> -                       Pwl::Point rbTest = Pwl::Point(rCurve, bCurve) +\n> +                       PointF rbTest = PointF(rCurve, bCurve) +\n>                                             transverse * points[j].x;\n>                         double rTest = rbTest.x, bTest = rbTest.y;\n>                         double gainR = 1 / rTest, gainB = 1 / bTest;\n> @@ -619,7 +619,7 @@ void Awb::fineSearch(double &t, double &r, double &b, Pwl const &prior)\n>                  * now let's do a quadratic interpolation for the best result.\n>                  */\n>                 bestPoint = std::max(1, std::min(bestPoint, numDeltas - 2));\n> -               Pwl::Point rbTest = Pwl::Point(rCurve, bCurve) +\n> +               PointF rbTest = PointF(rCurve, bCurve) +\n>                                         transverse * interpolateQuadatric(points[bestPoint - 1],\n>                                                                         points[bestPoint],\n>                                                                         points[bestPoint + 1]);\n> @@ -653,7 +653,7 @@ void Awb::awbBayes()\n>          * Get the current prior, and scale according to how many zones are\n>          * valid... not entirely sure about this.\n>          */\n> -       Pwl prior = interpolatePrior();\n> +       ipa::Pwl prior = interpolatePrior();\n>         prior *= zones_.size() / (double)(statistics_->awbRegions.numRegions());\n>         prior.map([](double x, double y) {\n>                 LOG(RPiAwb, Debug) << \"(\" << x << \",\" << y << \")\";\n> diff --git a/src/ipa/rpi/controller/rpi/awb.h b/src/ipa/rpi/controller/rpi/awb.h\n> index 499b4519c..7869d630a 100644\n> --- a/src/ipa/rpi/controller/rpi/awb.h\n> +++ b/src/ipa/rpi/controller/rpi/awb.h\n> @@ -10,11 +10,14 @@\n>  #include <condition_variable>\n>  #include <thread>\n>  \n> +#include <libcamera/geometry.h>\n> +\n>  #include \"../awb_algorithm.h\"\n> -#include \"../pwl.h\"\n>  #include \"../awb_status.h\"\n>  #include \"../statistics.h\"\n>  \n> +#include \"libipa/pwl.h\"\n> +\n>  namespace RPiController {\n>  \n>  /* Control algorithm to perform AWB calculations. */\n> @@ -28,7 +31,7 @@ struct AwbMode {\n>  struct AwbPrior {\n>         int read(const libcamera::YamlObject &params);\n>         double lux; /* lux level */\n> -       Pwl prior; /* maps CT to prior log likelihood for this lux level */\n> +       libcamera::ipa::Pwl prior; /* maps CT to prior log likelihood for this lux level */\n>  };\n>  \n>  struct AwbConfig {\n> @@ -41,10 +44,10 @@ struct AwbConfig {\n>         unsigned int convergenceFrames; /* approx number of frames to converge */\n>         double speed; /* IIR filter speed applied to algorithm results */\n>         bool fast; /* \"fast\" mode uses a 16x16 rather than 32x32 grid */\n> -       Pwl ctR; /* function maps CT to r (= R/G) */\n> -       Pwl ctB; /* function maps CT to b (= B/G) */\n> -       Pwl ctRInverse; /* inverse of ctR */\n> -       Pwl ctBInverse; /* inverse of ctB */\n> +       libcamera::ipa::Pwl ctR; /* function maps CT to r (= R/G) */\n> +       libcamera::ipa::Pwl ctB; /* function maps CT to b (= B/G) */\n> +       libcamera::ipa::Pwl ctRInverse; /* inverse of ctR */\n> +       libcamera::ipa::Pwl ctBInverse; /* inverse of ctB */\n>         /* table of illuminant priors at different lux levels */\n>         std::vector<AwbPrior> priors;\n>         /* AWB \"modes\" (determines the search range) */\n> @@ -161,11 +164,11 @@ private:\n>         void awbGrey();\n>         void prepareStats();\n>         double computeDelta2Sum(double gainR, double gainB);\n> -       Pwl interpolatePrior();\n> -       double coarseSearch(Pwl const &prior);\n> -       void fineSearch(double &t, double &r, double &b, Pwl const &prior);\n> +       libcamera::ipa::Pwl interpolatePrior();\n> +       double coarseSearch(libcamera::ipa::Pwl const &prior);\n> +       void fineSearch(double &t, double &r, double &b, libcamera::ipa::Pwl const &prior);\n>         std::vector<RGB> zones_;\n> -       std::vector<Pwl::Point> points_;\n> +       std::vector<libcamera::PointF> points_;\n>         /* manual r setting */\n>         double manualR_;\n>         /* manual b setting */\n> diff --git a/src/ipa/rpi/controller/rpi/ccm.cpp b/src/ipa/rpi/controller/rpi/ccm.cpp\n> index c55880296..3272a1416 100644\n> --- a/src/ipa/rpi/controller/rpi/ccm.cpp\n> +++ b/src/ipa/rpi/controller/rpi/ccm.cpp\n> @@ -71,7 +71,7 @@ int Ccm::read(const libcamera::YamlObject &params)\n>         int ret;\n>  \n>         if (params.contains(\"saturation\")) {\n> -               ret = config_.saturation.read(params[\"saturation\"]);\n> +               ret = config_.saturation.readYaml(params[\"saturation\"]);\n>                 if (ret)\n>                         return ret;\n>         }\n> @@ -172,7 +172,7 @@ void Ccm::prepare(Metadata *imageMetadata)\n>         ccmStatus.saturation = saturation;\n>         if (!config_.saturation.empty())\n>                 saturation *= config_.saturation.eval(\n> -                       config_.saturation.domain().clip(lux.lux));\n> +                       config_.saturation.domain().clamp(lux.lux));\n>         ccm = applySaturation(ccm, saturation);\n>         for (int j = 0; j < 3; j++)\n>                 for (int i = 0; i < 3; i++)\n> diff --git a/src/ipa/rpi/controller/rpi/ccm.h b/src/ipa/rpi/controller/rpi/ccm.h\n> index b3abeddf6..4e5b33fef 100644\n> --- a/src/ipa/rpi/controller/rpi/ccm.h\n> +++ b/src/ipa/rpi/controller/rpi/ccm.h\n> @@ -8,8 +8,9 @@\n>  \n>  #include <vector>\n>  \n> +#include <libipa/pwl.h>\n> +\n>  #include \"../ccm_algorithm.h\"\n> -#include \"../pwl.h\"\n>  \n>  namespace RPiController {\n>  \n> @@ -54,7 +55,7 @@ struct CtCcm {\n>  \n>  struct CcmConfig {\n>         std::vector<CtCcm> ccms;\n> -       Pwl saturation;\n> +       libcamera::ipa::Pwl saturation;\n>  };\n>  \n>  class Ccm : public CcmAlgorithm\n> diff --git a/src/ipa/rpi/controller/rpi/contrast.cpp b/src/ipa/rpi/controller/rpi/contrast.cpp\n> index 9eef792d3..66871a61e 100644\n> --- a/src/ipa/rpi/controller/rpi/contrast.cpp\n> +++ b/src/ipa/rpi/controller/rpi/contrast.cpp\n> @@ -53,7 +53,7 @@ int Contrast::read(const libcamera::YamlObject &params)\n>         config_.hiHistogram = params[\"hi_histogram\"].get<double>(0.95);\n>         config_.hiLevel = params[\"hi_level\"].get<double>(0.95);\n>         config_.hiMax = params[\"hi_max\"].get<double>(2000);\n> -       return config_.gammaCurve.read(params[\"gamma_curve\"]);\n> +       return config_.gammaCurve.readYaml(params[\"gamma_curve\"]);\n>  }\n>  \n>  void Contrast::setBrightness(double brightness)\n> @@ -92,10 +92,10 @@ void Contrast::prepare(Metadata *imageMetadata)\n>         imageMetadata->set(\"contrast.status\", status_);\n>  }\n>  \n> -Pwl computeStretchCurve(Histogram const &histogram,\n> +ipa::Pwl computeStretchCurve(Histogram const &histogram,\n>                         ContrastConfig const &config)\n>  {\n> -       Pwl enhance;\n> +       ipa::Pwl enhance;\n>         enhance.append(0, 0);\n>         /*\n>          * If the start of the histogram is rather empty, try to pull it down a\n> @@ -136,10 +136,10 @@ Pwl computeStretchCurve(Histogram const &histogram,\n>         return enhance;\n>  }\n>  \n> -Pwl applyManualContrast(Pwl const &gammaCurve, double brightness,\n> -                       double contrast)\n> +ipa::Pwl applyManualContrast(ipa::Pwl const &gammaCurve, double brightness,\n> +                            double contrast)\n>  {\n> -       Pwl newGammaCurve;\n> +       ipa::Pwl newGammaCurve;\n>         LOG(RPiContrast, Debug)\n>                 << \"Manual brightness \" << brightness << \" contrast \" << contrast;\n>         gammaCurve.map([&](double x, double y) {\n> @@ -160,7 +160,7 @@ void Contrast::process(StatisticsPtr &stats,\n>          * ways: 1. Adjust the gamma curve so as to pull the start of the\n>          * histogram down, and possibly push the end up.\n>          */\n> -       Pwl gammaCurve = config_.gammaCurve;\n> +       ipa::Pwl gammaCurve = config_.gammaCurve;\n>         if (ceEnable_) {\n>                 if (config_.loMax != 0 || config_.hiMax != 0)\n>                         gammaCurve = computeStretchCurve(histogram, config_).compose(gammaCurve);\n> diff --git a/src/ipa/rpi/controller/rpi/contrast.h b/src/ipa/rpi/controller/rpi/contrast.h\n> index a9d9bbc99..c0f7db981 100644\n> --- a/src/ipa/rpi/controller/rpi/contrast.h\n> +++ b/src/ipa/rpi/controller/rpi/contrast.h\n> @@ -8,8 +8,9 @@\n>  \n>  #include <mutex>\n>  \n> +#include <libipa/pwl.h>\n> +\n>  #include \"../contrast_algorithm.h\"\n> -#include \"../pwl.h\"\n>  \n>  namespace RPiController {\n>  \n> @@ -26,7 +27,7 @@ struct ContrastConfig {\n>         double hiHistogram;\n>         double hiLevel;\n>         double hiMax;\n> -       Pwl gammaCurve;\n> +       libcamera::ipa::Pwl gammaCurve;\n>  };\n>  \n>  class Contrast : public ContrastAlgorithm\n> diff --git a/src/ipa/rpi/controller/rpi/geq.cpp b/src/ipa/rpi/controller/rpi/geq.cpp\n> index fb539d1f2..c9c38ebff 100644\n> --- a/src/ipa/rpi/controller/rpi/geq.cpp\n> +++ b/src/ipa/rpi/controller/rpi/geq.cpp\n> @@ -9,7 +9,6 @@\n>  \n>  #include \"../device_status.h\"\n>  #include \"../lux_status.h\"\n> -#include \"../pwl.h\"\n>  \n>  #include \"geq.h\"\n>  \n> @@ -45,7 +44,7 @@ int Geq::read(const libcamera::YamlObject &params)\n>         }\n>  \n>         if (params.contains(\"strength\")) {\n> -               int ret = config_.strength.read(params[\"strength\"]);\n> +               int ret = config_.strength.readYaml(params[\"strength\"]);\n>                 if (ret)\n>                         return ret;\n>         }\n> @@ -67,7 +66,7 @@ void Geq::prepare(Metadata *imageMetadata)\n>         GeqStatus geqStatus = {};\n>         double strength = config_.strength.empty()\n>                         ? 1.0\n> -                       : config_.strength.eval(config_.strength.domain().clip(luxStatus.lux));\n> +                       : config_.strength.eval(config_.strength.domain().clamp(luxStatus.lux));\n>         strength *= deviceStatus.analogueGain;\n>         double offset = config_.offset * strength;\n>         double slope = config_.slope * strength;\n> diff --git a/src/ipa/rpi/controller/rpi/geq.h b/src/ipa/rpi/controller/rpi/geq.h\n> index 2c8400c2f..e8b9f4270 100644\n> --- a/src/ipa/rpi/controller/rpi/geq.h\n> +++ b/src/ipa/rpi/controller/rpi/geq.h\n> @@ -6,6 +6,8 @@\n>   */\n>  #pragma once\n>  \n> +#include <libipa/pwl.h>\n> +\n>  #include \"../algorithm.h\"\n>  #include \"../geq_status.h\"\n>  \n> @@ -16,7 +18,7 @@ namespace RPiController {\n>  struct GeqConfig {\n>         uint16_t offset;\n>         double slope;\n> -       Pwl strength; /* lux to strength factor */\n> +       libcamera::ipa::Pwl strength; /* lux to strength factor */\n>  };\n>  \n>  class Geq : public Algorithm\n> diff --git a/src/ipa/rpi/controller/rpi/hdr.cpp b/src/ipa/rpi/controller/rpi/hdr.cpp\n> index 34cf360e9..d533a4ea4 100644\n> --- a/src/ipa/rpi/controller/rpi/hdr.cpp\n> +++ b/src/ipa/rpi/controller/rpi/hdr.cpp\n> @@ -42,7 +42,7 @@ void HdrConfig::read(const libcamera::YamlObject &params, const std::string &mod\n>  \n>         /* Lens shading related parameters. */\n>         if (params.contains(\"spatial_gain_curve\")) {\n> -               spatialGainCurve.read(params[\"spatial_gain_curve\"]);\n> +               spatialGainCurve.readYaml(params[\"spatial_gain_curve\"]);\n>         } else if (params.contains(\"spatial_gain\")) {\n>                 double spatialGain = params[\"spatial_gain\"].get<double>(2.0);\n>                 spatialGainCurve.append(0.0, spatialGain);\n> @@ -66,7 +66,7 @@ void HdrConfig::read(const libcamera::YamlObject &params, const std::string &mod\n>         iirStrength = params[\"iir_strength\"].get<double>(8.0);\n>         strength = params[\"strength\"].get<double>(1.5);\n>         if (tonemapEnable)\n> -               tonemap.read(params[\"tonemap\"]);\n> +               tonemap.readYaml(params[\"tonemap\"]);\n>         speed = params[\"speed\"].get<double>(1.0);\n>         if (params.contains(\"hi_quantile_targets\")) {\n>                 hiQuantileTargets = params[\"hi_quantile_targets\"].getList<double>().value();\n> @@ -212,7 +212,7 @@ bool Hdr::updateTonemap([[maybe_unused]] StatisticsPtr &stats, HdrConfig &config\n>         /* When there's a change of HDR mode we start over with a new tonemap curve. */\n>         if (delayedStatus_.mode != previousMode_) {\n>                 previousMode_ = delayedStatus_.mode;\n> -               tonemap_ = Pwl();\n> +               tonemap_ = ipa::Pwl();\n>         }\n>  \n>         /* No tonemapping. No need to output a tonemap.status. */\n> @@ -275,7 +275,7 @@ bool Hdr::updateTonemap([[maybe_unused]] StatisticsPtr &stats, HdrConfig &config\n>         double power = std::clamp(min_power, config.powerMin, config.powerMax);\n>  \n>         /* Generate the tonemap, including the contrast adjustment factors. */\n> -       Pwl tonemap;\n> +       libcamera::ipa::Pwl tonemap;\n>         tonemap.append(0, 0);\n>         for (unsigned int i = 0; i <= 6; i++) {\n>                 double x = 1 << (i + 9); /* x loops from 512 to 32768 inclusive */\n> diff --git a/src/ipa/rpi/controller/rpi/hdr.h b/src/ipa/rpi/controller/rpi/hdr.h\n> index 9b7327f8b..5c2f3988d 100644\n> --- a/src/ipa/rpi/controller/rpi/hdr.h\n> +++ b/src/ipa/rpi/controller/rpi/hdr.h\n> @@ -12,9 +12,10 @@\n>  \n>  #include <libcamera/geometry.h>\n>  \n> +#include <libipa/pwl.h>\n> +\n>  #include \"../hdr_algorithm.h\"\n>  #include \"../hdr_status.h\"\n> -#include \"../pwl.h\"\n>  \n>  /* This is our implementation of an HDR algorithm. */\n>  \n> @@ -26,7 +27,7 @@ struct HdrConfig {\n>         std::map<unsigned int, std::string> channelMap;\n>  \n>         /* Lens shading related parameters. */\n> -       Pwl spatialGainCurve; /* Brightness to gain curve for different image regions. */\n> +       libcamera::ipa::Pwl spatialGainCurve; /* Brightness to gain curve for different image regions. */\n>         unsigned int diffusion; /* How much to diffuse the gain spatially. */\n>  \n>         /* Tonemap related parameters. */\n> @@ -35,7 +36,7 @@ struct HdrConfig {\n>         double detailSlope;\n>         double iirStrength;\n>         double strength;\n> -       Pwl tonemap;\n> +       libcamera::ipa::Pwl tonemap;\n>         /* These relate to adaptive tonemap calculation. */\n>         double speed;\n>         std::vector<double> hiQuantileTargets; /* quantiles to check for unsaturated images */\n> @@ -75,7 +76,7 @@ private:\n>         HdrStatus status_; /* track the current HDR mode and channel */\n>         HdrStatus delayedStatus_; /* track the delayed HDR mode and channel */\n>         std::string previousMode_;\n> -       Pwl tonemap_;\n> +       libcamera::ipa::Pwl tonemap_;\n>         libcamera::Size regions_; /* stats regions */\n>         unsigned int numRegions_; /* total number of stats regions */\n>         std::vector<double> gains_[2];\n> diff --git a/src/ipa/rpi/controller/rpi/tonemap.cpp b/src/ipa/rpi/controller/rpi/tonemap.cpp\n> index 0426e9722..2dc50dfc8 100644\n> --- a/src/ipa/rpi/controller/rpi/tonemap.cpp\n> +++ b/src/ipa/rpi/controller/rpi/tonemap.cpp\n> @@ -33,7 +33,7 @@ int Tonemap::read(const libcamera::YamlObject &params)\n>         config_.detailSlope = params[\"detail_slope\"].get<double>(0.1);\n>         config_.iirStrength = params[\"iir_strength\"].get<double>(1.0);\n>         config_.strength = params[\"strength\"].get<double>(1.0);\n> -       config_.tonemap.read(params[\"tone_curve\"]);\n> +       config_.tonemap.readYaml(params[\"tone_curve\"]);\n>         return 0;\n>  }\n>  \n> diff --git a/src/ipa/rpi/controller/rpi/tonemap.h b/src/ipa/rpi/controller/rpi/tonemap.h\n> index f25aa47f8..ba0cf5c40 100644\n> --- a/src/ipa/rpi/controller/rpi/tonemap.h\n> +++ b/src/ipa/rpi/controller/rpi/tonemap.h\n> @@ -6,8 +6,9 @@\n>   */\n>  #pragma once\n>  \n> +#include <libipa/pwl.h>\n> +\n>  #include \"algorithm.h\"\n> -#include \"pwl.h\"\n>  \n>  namespace RPiController {\n>  \n> @@ -16,7 +17,7 @@ struct TonemapConfig {\n>         double detailSlope;\n>         double iirStrength;\n>         double strength;\n> -       Pwl tonemap;\n> +       libcamera::ipa::Pwl tonemap;\n>  };\n>  \n>  class Tonemap : public Algorithm\n> diff --git a/src/ipa/rpi/controller/tonemap_status.h b/src/ipa/rpi/controller/tonemap_status.h\n> index 41a7bf2ff..0364ff66f 100644\n> --- a/src/ipa/rpi/controller/tonemap_status.h\n> +++ b/src/ipa/rpi/controller/tonemap_status.h\n> @@ -6,12 +6,12 @@\n>   */\n>  #pragma once\n>  \n> -#include \"pwl.h\"\n> +#include <libipa/pwl.h>\n>  \n>  struct TonemapStatus {\n>         uint16_t detailConstant;\n>         double detailSlope;\n>         double iirStrength;\n>         double strength;\n> -       RPiController::Pwl tonemap;\n> +       libcamera::ipa::Pwl tonemap;\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 B4FC7BD87C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon,  3 Jun 2024 11:56:09 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id AB5E5634CA;\n\tMon,  3 Jun 2024 13:56:08 +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 4653061A3B\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon,  3 Jun 2024 13:56:07 +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 04BC1908;\n\tMon,  3 Jun 2024 13:55:59 +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=\"GditF1zD\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1717415760;\n\tbh=7fKCylho1zWzQBFIC7Y1B+fmv4o5QccwHty5N3gChYM=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=GditF1zDgvaiQmAYuHWhgg4eNszYHySWQ3QZ0UB1R2CllQDQM45QA+9cfKNT9DQpM\n\thlMlzJsmWSiTJfC8gJxIP1blmfCjlhEnC4NJXklboaSairI3cdqeT8VpNh2eAJI+VR\n\tvcXQnY6XtheN1DNM8/goLJ7r5ZGCg7ZIayUlp3Gs=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<20240531144301.3950115-5-paul.elder@ideasonboard.com>","References":"<20240531144301.3950115-1-paul.elder@ideasonboard.com>\n\t<20240531144301.3950115-5-paul.elder@ideasonboard.com>","Subject":"Re: [PATCH v4 4/4] ipa: rpi: controller: Use libipa's Pwl class","From":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Cc":"Paul Elder <paul.elder@ideasonboard.com>,\n\tStefan Klug <stefan.klug@ideasonboard.com>,\n\tDavid Plowman <david.plowman@raspberrypi.com>","To":"Paul Elder <paul.elder@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","Date":"Mon, 03 Jun 2024 12:56:03 +0100","Message-ID":"<171741576377.205609.3863541201137378795@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>"}}]