[PATCH v7 1/4] ipa: libipa: Add Vector class
Laurent Pinchart
laurent.pinchart at ideasonboard.com
Tue Jun 11 01:29:21 CEST 2024
Hi Paul,
Thank you for the patch.
On Mon, Jun 10, 2024 at 04:10:31PM +0100, Kieran Bingham wrote:
> Quoting Paul Elder (2024-06-10 15:19:38)
> > Add a vector class to libipa. The original purpose of this is to replace
> > the floating-point Point class that Raspberry Pi used in their Pwl, as
> > that implementation of Point seemed more akin to a Vector than a Point.
> >
> > This is added to libipa instead of to geometry.h to avoid public API
> > issues, plus this is not expected to be needed by applications.
> >
> > Signed-off-by: Paul Elder <paul.elder at ideasonboard.com>
> > Reviewed-by: Stefan Klug <stefan.klug at ideasonboard.com>
> >
> > ---
> > Changes in v7:
> > - fix documentation
> > - fix license and copyright
> > - remove toString()
> >
> > No change in v6
> >
> > New in v5
> > ---
> > src/ipa/libipa/vector.cpp | 145 +++++++++++++++++++++++++++
> > src/ipa/libipa/vector.h | 199 ++++++++++++++++++++++++++++++++++++++
> > 2 files changed, 344 insertions(+)
> > create mode 100644 src/ipa/libipa/vector.cpp
> > create mode 100644 src/ipa/libipa/vector.h
> >
> > diff --git a/src/ipa/libipa/vector.cpp b/src/ipa/libipa/vector.cpp
> > new file mode 100644
> > index 000000000000..e7d0be95812c
> > --- /dev/null
> > +++ b/src/ipa/libipa/vector.cpp
> > @@ -0,0 +1,145 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2024, Paul Elder <paul.elder at ideasonboard.com>
> > + *
> > + * Vector and related operations
> > + */
> > +
> > +#include "vector.h"
> > +
> > +#include <libcamera/base/log.h>
> > +
> > +/**
> > + * \file vector.h
> > + * \brief Vector class
> > + */
> > +
> > +namespace libcamera {
> > +
> > +LOG_DEFINE_CATEGORY(Vector)
> > +
> > +namespace ipa {
> > +
> > +/**
> > + * \class Vector
> > + * \brief Vector class
> > + * \tparam T Type of numerical values to be stored in the vector
> > + * \tparam D Number of dimension of the vector (= number of elements)
> > + */
> > +
> > +/**
> > + * \fn Vector::Vector()
> > + * \brief Construct a zero vector
> > + */
> > +
> > +/**
> > + * \fn Vector::Vector(const std::array<T, D> &data)
> > + * \brief Construct vector from supplied data
> > + * \param data Data from which to construct a vector
> > + *
> > + * The size of \a data must be equal to the dimension size D of the vector.
> > + */
> > +
> > +/**
> > + * \fn Vector::readYaml
> > + * \brief Populate the vector with yaml data
> > + * \param yaml Yaml data to populate the vector with
> > + *
> > + * Any existing data in the vector will be overwritten. The size of the data
> > + * read from \a yaml must be equal to the dimension size D of the vector.
> > + *
> > + * The yaml data is expected to be a list with elements of type T.
> > + *
> > + * \return 0 on success, negative error code otherwise
> > + */
> > +
> > +/**
> > + * \fn T Vector::operator[](size_t i) const
> > + * \brief Index to an element in the vector
> > + * \param i Index of element to retrieve
> > + * \return Element at index \a i from the vector
> > + */
> > +
> > +/**
> > + * \fn T &Vector::operator[](size_t i)
> > + * \copydoc Vector::operator[](size_t i) const
> > + */
> > +
> > +/**
> > + * \fn Vector::x()
> > + * \brief Convenience function to access the first element of the vector
> > + */
> > +
> > +/**
> > + * \fn Vector::y()
> > + * \brief Convenience function to access the second element of the vector
> > + */
> > +
> > +/**
> > + * \fn Vector::operator-() const
> > + * \brief Negate a Vector by negating both all of its coordinates
> > + * \return The negated vector
> > + */
> > +
> > +/**
> > + * \fn Vector::operator-(Vector const &other) const
> > + * \brief Subtract one vector from another
> > + * \param[in] other The other vector
> > + * \return The difference of \a other from this vector
> > + */
> > +
> > +/**
> > + * \fn Vector::operator+()
> > + * \brief Add two vectors together
> > + * \param[in] other The other vector
> > + * \return The sum of the two vectors
> > + */
> > +
> > +/**
> > + * \fn Vector::operator*(const Vector<T, D> &other) const
> > + * \brief Compute the dot product
> > + * \param[in] other The other vector
> > + * \return The dot product of the two vectors
> > + */
> > +
> > +/**
> > + * \fn Vector::operator*(T factor) const
> > + * \brief Multiply the vector by a scalar
> > + * \param[in] factor The factor
> > + * \return The vector multiplied by \a factor
> > + */
> > +
> > +/**
> > + * \fn Vector::operator/()
> > + * \brief Divide the vector by a scalar
> > + * \param[in] factor The factor
> > + * \return The vector divided by \a factor
> > + */
> > +
> > +/**
> > + * \fn Vector::len2()
> > + * \brief Get the squared length of the vector
> > + * \return The squared length of the vector
> > + */
> > +
> > +/**
> > + * \fn Vector::len()
> > + * \brief Get the length of the vector
> > + * \return The length of the vector
> > + */
> > +
> > +/**
> > + * \fn bool operator==(const Vector<T, D> &lhs, const Vector<T, D> &rhs)
> > + * \brief Compare vectors for equality
> > + * \return True if the two vectors are equal, false otherwise
> > + */
> > +
> > +/**
> > + * \fn bool operator!=(const Vector<T, D> &lhs, const Vector<T, D> &rhs)
> > + * \brief Compare vectors for inequality
> > + * \return True if the two vectors are not equal, false otherwise
> > + */
> > +
> > +} /* namespace ipa */
> > +
> > +} /* namespace libcamera */
> > diff --git a/src/ipa/libipa/vector.h b/src/ipa/libipa/vector.h
> > new file mode 100644
> > index 000000000000..b5a6a0e021e0
> > --- /dev/null
> > +++ b/src/ipa/libipa/vector.h
> > @@ -0,0 +1,199 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2024, Paul Elder <paul.elder at ideasonboard.com>
> > + *
> > + * Vector and related operations
> > + */
> > +#pragma once
> > +
> > +#include <algorithm>
> > +#include <array>
> > +#include <cmath>
> > +#include <sstream>
> > +
> > +#include <libcamera/base/log.h>
> > +#include <libcamera/base/span.h>
> > +
> > +#include "libcamera/internal/yaml_parser.h"
> > +
> > +namespace libcamera {
> > +
> > +LOG_DECLARE_CATEGORY(Vector)
> > +
> > +namespace ipa {
> > +
> > +#ifndef __DOXYGEN__
> > +template<typename T, unsigned int D,
> > + std::enable_if_t<std::is_arithmetic_v<T> && D >= 2> * = nullptr>
Why 2 ? Is that because of the x() and y() functions ? You can handle
that in those functions directly:
template<bool Dependent = false, typename = std::enable_if_t<Dependent || D == 2>>
T &x()
{
return data_[0];
}
template<bool Dependent = false, typename = std::enable_if_t<Dependent || D == 2>>
T &y()
{
return data_[1];
}
and drop the constraint here.
> > +#else
> > +template<typename T, unsigned int D>
> > +#endif /* __DOXYGEN__ */
> > +class Vector
What would you think about naming the template parameters Scalar and
Rows instead of T and D ? That would be more explicit.
> > +{
> > +public:
> > + Vector() = default;
> > +
> > + Vector(const std::array<T, D> &data)
Please make the constructors constexpr if possible.
> > + {
> > + ASSERT(data.size() == D);
That's not needed, by definition std::array<T, D>::size() == D.
> > +
> > + for (unsigned int i = 0; i < D; i++)
> > + data_[i] = data[i];
> > + }
> > +
> > + ~Vector() = default;
You can drop this, it's literally the default :-)
> > +
> > + int readYaml(const libcamera::YamlObject &yaml)
> > + {
> > + if (yaml.size() != D) {
> > + LOG(Vector, Error)
> > + << "Wrong number of values in vector: expected "
> > + << D << ", got " << yaml.size();
> > + return -EINVAL;
> > + }
> > +
> > + unsigned int i = 0;
> > + for (const auto &x : yaml.asList()) {
> > + auto value = x.get<T>();
> > + if (!value) {
> > + LOG(Vector, Error) << "Failed to read vector value";
> > + return -EINVAL;
> > + }
> > +
> > + data_[i++] = *value;
> > + }
> > +
> > + return 0;
> > + }
This is the only part that really bothers me. This tight coupling
between the Vector class, which is a very generic math object, and
YamlObject, isn't nice.
One possible option would be to implement this as a specialization of
YamlObject::get(), in vector.cpp. I haven't tested it though.
> > +
> > + const T operator[](size_t i) const
Why do you return a copy instead of a const reference ?
> > + {
> > + ASSERT(0 < i && i < data_.size());
>
> Why 0 < i ? What's wrong with data_[0] ?
On a side note, it should be written 'i > 0', that's the order we use
everywhere. I understand that the above syntex is meant to check
0 < i < data_.size()
and I'm fine with that as a mathematical notation, but code-wise,
'0 < i' takes more mental energy to read.
> > + return data_[i];
> > + }
> > +
> > + T &operator[](size_t i)
> > + {
> > + ASSERT(0 < i && i < data_.size());
>
> Same...
>
> With those checked/fixed if needed.
>
> Reviewed-by: Kieran Bingham <kieran.bingham at ideasonboard.com>
>
> > + return data_[i];
> > + }
> > +
> > + const T x() const
> > + {
> > + return data_[0];
> > + }
> > +
> > + const T y() const
> > + {
> > + return data_[1];
> > + }
> > +
> > + constexpr Vector<T, D> operator-() const
> > + {
> > + Vector<T, D> ret;
> > + for (unsigned int i = 0; i < D; i++)
> > + ret[i] = -data_[i];
> > + return ret;
> > + }
> > +
> > + constexpr Vector<T, D> operator-(const Vector<T, D> &other) const
> > + {
> > + Vector<T, D> ret;
> > + for (unsigned int i = 0; i < D; i++)
> > + ret[i] = data_[i] - other[i];
> > + return ret;
> > + }
> > +
> > + constexpr Vector<T, D> operator+(const Vector<T, D> &other) const
> > + {
> > + Vector<T, D> ret;
> > + for (unsigned int i = 0; i < D; i++)
> > + ret[i] = data_[i] + other[i];
> > + return ret;
> > + }
> > +
> > + constexpr T operator*(const Vector<T, D> &other) const
> > + {
> > + T ret = 0;
> > + for (unsigned int i = 0; i < D; i++)
> > + ret += data_[i] * other[i];
> > + return ret;
> > + }
> > +
> > + constexpr Vector<T, D> operator*(T factor) const
> > + {
> > + Vector<T, D> ret;
> > + for (unsigned int i = 0; i < D; i++)
> > + ret[i] = data_[i] * factor;
> > + return ret;
> > + }
> > +
> > + constexpr Vector<T, D> operator/(T factor) const
> > + {
> > + Vector<T, D> ret;
> > + for (unsigned int i = 0; i < D; i++)
> > + ret[i] = data_[i] / factor;
> > + return ret;
> > + }
> > +
> > + constexpr T len2() const
length2() ? And length() below.
> > + {
> > + T ret = 0;
> > + for (unsigned int i = 0; i < D; i++)
> > + ret += data_[i] * data_[i];
If T is an integer short integer type (8 or 16 bits) there's a high
chance this will overflow silently. Is that an issue ? Should the
function return a double ?
> > + return ret;
> > + }
> > +
> > + constexpr double len() const
> > + {
> > + return std::sqrt(len2());
> > + }
> > +
> > +private:
> > + std::array<T, D> data_;
> > +};
> > +
> > +#ifndef __DOXYGEN__
> > +template<typename T, unsigned int D,
> > + std::enable_if_t<std::is_arithmetic_v<T>> * = nullptr>
Is this needed, given that the Vector class checks that already ? Same
below.
I wrote a long time ago an RGB class which morphed into a Vector, before
I dropped it because I thought I was reinventing the wheel. As it seems
we haven't found a better wheel, I'll probably post a patch on top of
this as it extends your implementation with quite a few functions.
> > +#endif /* __DOXYGEN__ */
> > +bool operator==(const Vector<T, D> &lhs, const Vector<T, D> &rhs)
> > +{
> > + for (unsigned int i = 0; i < D; i++)
> > + if (lhs[i] != rhs[i])
> > + return false;
> > +
> > + return true;
> > +}
> > +
> > +#ifndef __DOXYGEN__
> > +template<typename T, unsigned int D,
> > + std::enable_if_t<std::is_arithmetic_v<T>> * = nullptr>
> > +#endif /* __DOXYGEN__ */
> > +bool operator!=(const Vector<T, D> &lhs, const Vector<T, D> &rhs)
> > +{
> > + for (unsigned int i = 0; i < D; i++)
> > + if (lhs[i] != rhs[i])
> > + return true;
> > +
> > + return false;
> > +}
> > +
> > +} /* namespace ipa */
> > +
> > +#ifndef __DOXYGEN__
> > +template<typename T, unsigned int D>
> > +std::ostream &operator<<(std::ostream &out, const ipa::Vector<T, D> &v)
> > +{
> > + out << "Vector { ";
> > + for (unsigned int i = 0; i < D; i++) {
> > + out << v[i];
> > + out << ((i + 1 < D) ? ", " : " ");
> > + }
> > + out << " }";
> > +
> > + return out;
> > +}
> > +#endif /* __DOXYGEN__ */
> > +
> > +} /* namespace libcamera */
--
Regards,
Laurent Pinchart
More information about the libcamera-devel
mailing list