[v6,01/16] ipa: libipa: Provide a Quantized data type support
diff mbox series

Message ID 20260121173737.376113-2-kieran.bingham@ideasonboard.com
State New
Headers show
Series
  • libipa: Introduce a Quantized type
Related show

Commit Message

Kieran Bingham Jan. 21, 2026, 5:37 p.m. UTC
Frequently when handling data in IPA components we must convert and
store user interface values which may be floating point values, and
perform a specific operation or conversion to quantize this to a
hardware value.

This value may be to a fixed point type, or more custom code mappings,
but in either case it is important to contain both the required hardware
value, with its effective quantized value.

Provide a new storage type 'Quantized' which can be defined based on a
set of type specific Traits to perform the conversions between floats
and the underlying hardware type.

Reviewed-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
Reviewed-by: Isaac Scott <isaac.scott@ideasonboard.com>
Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>

---
v3:
 - adapt string format to [0xff:1.99] style instead of Q:0xff V:1.99
 - Clean up comments and copyright
 - Remove private initialisers - already handled by constructors
 - Change quantized_type to QuantizedType

v5:
- introduce operator<<(std::ostream &out, const Quantized<Traits> &q)
- Remove unused iomanip and stdint.h from includes

Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
---
 src/ipa/libipa/meson.build   |   2 +
 src/ipa/libipa/quantized.cpp | 135 +++++++++++++++++++++++++++++++++++
 src/ipa/libipa/quantized.h   |  75 +++++++++++++++++++
 3 files changed, 212 insertions(+)
 create mode 100644 src/ipa/libipa/quantized.cpp
 create mode 100644 src/ipa/libipa/quantized.h

Comments

Laurent Pinchart Jan. 24, 2026, 1:19 a.m. UTC | #1
Hi Kieran,

Thank you for the patch.

On Wed, Jan 21, 2026 at 05:37:20PM +0000, Kieran Bingham wrote:
> Frequently when handling data in IPA components we must convert and
> store user interface values which may be floating point values, and
> perform a specific operation or conversion to quantize this to a
> hardware value.
> 
> This value may be to a fixed point type, or more custom code mappings,
> but in either case it is important to contain both the required hardware
> value, with its effective quantized value.
> 
> Provide a new storage type 'Quantized' which can be defined based on a
> set of type specific Traits to perform the conversions between floats
> and the underlying hardware type.
> 
> Reviewed-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
> Reviewed-by: Isaac Scott <isaac.scott@ideasonboard.com>
> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> 
> ---
> v3:
>  - adapt string format to [0xff:1.99] style instead of Q:0xff V:1.99
>  - Clean up comments and copyright
>  - Remove private initialisers - already handled by constructors
>  - Change quantized_type to QuantizedType
> 
> v5:
> - introduce operator<<(std::ostream &out, const Quantized<Traits> &q)
> - Remove unused iomanip and stdint.h from includes
> 
> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> ---
>  src/ipa/libipa/meson.build   |   2 +
>  src/ipa/libipa/quantized.cpp | 135 +++++++++++++++++++++++++++++++++++
>  src/ipa/libipa/quantized.h   |  75 +++++++++++++++++++
>  3 files changed, 212 insertions(+)
>  create mode 100644 src/ipa/libipa/quantized.cpp
>  create mode 100644 src/ipa/libipa/quantized.h
> 
> diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build
> index 7202df869c2f..963c5ee73063 100644
> --- a/src/ipa/libipa/meson.build
> +++ b/src/ipa/libipa/meson.build
> @@ -17,6 +17,7 @@ libipa_headers = files([
>      'lux.h',
>      'module.h',
>      'pwl.h',
> +    'quantized.h',
>      'v4l2_params.h',
>  ])
>  
> @@ -37,6 +38,7 @@ libipa_sources = files([
>      'lux.cpp',
>      'module.cpp',
>      'pwl.cpp',
> +    'quantized.cpp',
>      'v4l2_params.cpp',
>  ])
>  
> diff --git a/src/ipa/libipa/quantized.cpp b/src/ipa/libipa/quantized.cpp
> new file mode 100644
> index 000000000000..06143a97ab3e
> --- /dev/null
> +++ b/src/ipa/libipa/quantized.cpp
> @@ -0,0 +1,135 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2025, Ideas On Board Oy
> + *
> + * Helper class to manage conversions between floating point types and quantized
> + * storage and representation of those values.
> + */
> +
> +#include "quantized.h"
> +
> +/**
> + * \file quantized.h
> + * \brief Quantized storage and Quantizer representations
> + */
> +
> +namespace libcamera {
> +
> +namespace ipa {
> +
> +/**
> + * \struct libcamera::ipa::Quantized
> + * \brief Wrapper that stores a value in both quantized and floating-point form
> + * \tparam Traits The traits class defining the quantization behaviour
> + *
> + * The Quantized struct template provides a thin wrapper around a quantized
> + * representation of a floating-point value. It uses a traits type \a Traits
> + * to define the conversion policy between the floating-point domain and the
> + * quantized integer domain.
> + *
> + * Each Quantized instance maintains two synchronized members:
> + *  - the quantized integer representation, and
> + *  - the corresponding floating-point value.
> + *
> + * The traits type defines:
> + *  - the integer storage type used for quantization,
> + *  - the static conversion functions \c fromFloat() and \c toFloat(), and
> + *  - optional metadata such as value ranges.
> + *
> + * Quantized provides convenient constructors and assignment operators from
> + * either representation, as well as comparison and string formatting utilities.
> + */
> +
> +/**
> + * \typedef Quantized::TraitsType
> + * \brief The traits policy type defining the quantization behaviour
> + *
> + * Exposes the associated traits type used by this Quantized instance.
> + * This allows external code to refer to constants or metadata defined in
> + * the traits, such as \c TraitsType::min or \c TraitsType::max.
> + */
> +
> +/**
> + * \typedef Quantized::QuantizedType
> + * \brief The integer type used for the quantized representation
> + *
> + * This alias corresponds to \c TraitsType::QuantizedType, as defined by
> + * the traits class.
> + */
> +
> +/**
> + * \fn Quantized::Quantized(float x)
> + * \brief Construct a Quantized value from a floating-point number
> + * \param[in] x The floating-point value to be quantized
> + *
> + * Converts the floating-point input \a x to its quantized integer
> + * representation using the associated traits policy, and initializes
> + * both the quantized and floating-point members.
> + */
> +
> +/**
> + * \fn Quantized::Quantized(QuantizedType x)
> + * \brief Construct a Quantized value from an existing quantized integer
> + * \param[in] x The quantized integer value
> + *
> + * Converts the quantized integer \a x to its corresponding floating-point
> + * value using the traits policy, and initializes both internal members.
> + */
> +
> +/**
> + * \fn Quantized::operator=(float x)
> + * \brief Assign a floating-point value to the Quantized object
> + * \param[in] x The floating-point value to assign
> + * \return A reference to the updated Quantized object
> + *
> + * Converts the floating-point value \a x to its quantized integer
> + * representation using the traits policy and updates both members.
> + */
> +
> +/**
> + * \fn Quantized::operator=(QuantizedType x)
> + * \brief Assign a quantized integer value to the Quantized object
> + * \param[in] x The quantized integer value to assign
> + * \return A reference to the updated Quantized object
> + *
> + * Converts the quantized integer \a x to its corresponding floating-point
> + * value using the traits policy and updates both members.
> + */
> +
> +/**
> + * \fn Quantized::value() const noexcept
> + * \brief Retrieve the floating-point representation
> + * \return The floating-point value corresponding to the quantized value
> + */
> +
> +/**
> + * \fn Quantized::quantized() const noexcept
> + * \brief Retrieve the quantized integer representation
> + * \return The quantized integer value
> + */
> +
> +/**
> + * \fn Quantized::operator==(const Quantized &other) const noexcept
> + * \brief Compare two Quantized objects for equality
> + * \param[in] other The other Quantized object to compare against
> + * \return True if both objects have the same quantized integer value
> + */
> +
> +/**
> + * \fn Quantized::operator!=(const Quantized &other) const noexcept
> + * \brief Compare two Quantized objects for inequality
> + * \param[in] other The other Quantized object to compare against
> + * \return True if the quantized integer values differ
> + */
> +
> +/**
> + * \fn std::ostream &Quantized::operator<<(std::ostream &out, const Quantized<Traits> &q)
> + * \brief Insert a text representation of a Quantized into an output stream
> + * \param[in] out The output stream
> + * \param[in] q The Quantized
> + * \return The output stream \a out
> + */
> +
> +} /* namespace ipa */
> +
> +} /* namespace libcamera */
> diff --git a/src/ipa/libipa/quantized.h b/src/ipa/libipa/quantized.h
> new file mode 100644
> index 000000000000..0b2f7148c821
> --- /dev/null
> +++ b/src/ipa/libipa/quantized.h
> @@ -0,0 +1,75 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2025, Ideas On Board Oy
> + *
> + * Helper class to manage conversions between floating point types and quantized
> + * storage and representation of those values.
> + */
> +
> +#pragma once
> +
> +#include <sstream>
> +#include <type_traits>
> +
> +#include <libcamera/base/utils.h>
> +
> +namespace libcamera {
> +
> +namespace ipa {
> +
> +template<typename Traits>
> +struct Quantized {

Any reason to use a struct and not a class ? We usually use struct only
(or mostly) for plain old C structures with no member functions.

> +	using TraitsType = Traits;
> +	using QuantizedType = typename Traits::QuantizedType;
> +	static_assert(std::is_arithmetic_v<QuantizedType>,
> +		      "Quantized: QuantizedType must be arithmetic");
> +
> +	Quantized()
> +		: Quantized(0.0f) {}
> +	Quantized(float x) { *this = x; }
> +	Quantized(QuantizedType x) { *this = x; }

There's an open question in the v4 thread about whether or not we should
make these constructors explicit. Let's continue the discussion there.

> +
> +	Quantized &operator=(float x)
> +	{
> +		quantized_ = Traits::fromFloat(x);
> +		value_ = Traits::toFloat(quantized_);
> +		return *this;
> +	}
> +
> +	Quantized &operator=(QuantizedType x)
> +	{
> +		value_ = Traits::toFloat(x);
> +		quantized_ = x;
> +		return *this;
> +	}
> +
> +	float value() const noexcept { return value_; }

As we don't use exceptions, is noexcept useful ?

Apart from those comments this looks good.

> +	QuantizedType quantized() const noexcept { return quantized_; }
> +
> +	bool operator==(const Quantized &other) const noexcept
> +	{
> +		return quantized_ == other.quantized_;
> +	}
> +
> +	bool operator!=(const Quantized &other) const noexcept
> +	{
> +		return !(*this == other);
> +	}
> +
> +	friend std::ostream &operator<<(std::ostream &out,
> +					const Quantized<Traits> &q)
> +	{
> +		out << "[" << utils::hex(q.quantized())
> +		    << ":" << q.value() << "]";
> +
> +		return out;
> +	}
> +
> +private:
> +	QuantizedType quantized_;
> +	float value_;
> +};
> +
> +} /* namespace ipa */
> +
> +} /* namespace libcamera */
Laurent Pinchart Jan. 24, 2026, 1:20 a.m. UTC | #2
On Sat, Jan 24, 2026 at 03:19:23AM +0200, Laurent Pinchart wrote:
> On Wed, Jan 21, 2026 at 05:37:20PM +0000, Kieran Bingham wrote:
> > Frequently when handling data in IPA components we must convert and
> > store user interface values which may be floating point values, and
> > perform a specific operation or conversion to quantize this to a
> > hardware value.
> > 
> > This value may be to a fixed point type, or more custom code mappings,
> > but in either case it is important to contain both the required hardware
> > value, with its effective quantized value.
> > 
> > Provide a new storage type 'Quantized' which can be defined based on a
> > set of type specific Traits to perform the conversions between floats
> > and the underlying hardware type.
> > 
> > Reviewed-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
> > Reviewed-by: Isaac Scott <isaac.scott@ideasonboard.com>
> > Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > 
> > ---
> > v3:
> >  - adapt string format to [0xff:1.99] style instead of Q:0xff V:1.99
> >  - Clean up comments and copyright
> >  - Remove private initialisers - already handled by constructors
> >  - Change quantized_type to QuantizedType
> > 
> > v5:
> > - introduce operator<<(std::ostream &out, const Quantized<Traits> &q)
> > - Remove unused iomanip and stdint.h from includes
> > 
> > Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > ---
> >  src/ipa/libipa/meson.build   |   2 +
> >  src/ipa/libipa/quantized.cpp | 135 +++++++++++++++++++++++++++++++++++
> >  src/ipa/libipa/quantized.h   |  75 +++++++++++++++++++
> >  3 files changed, 212 insertions(+)
> >  create mode 100644 src/ipa/libipa/quantized.cpp
> >  create mode 100644 src/ipa/libipa/quantized.h
> > 
> > diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build
> > index 7202df869c2f..963c5ee73063 100644
> > --- a/src/ipa/libipa/meson.build
> > +++ b/src/ipa/libipa/meson.build
> > @@ -17,6 +17,7 @@ libipa_headers = files([
> >      'lux.h',
> >      'module.h',
> >      'pwl.h',
> > +    'quantized.h',
> >      'v4l2_params.h',
> >  ])
> >  
> > @@ -37,6 +38,7 @@ libipa_sources = files([
> >      'lux.cpp',
> >      'module.cpp',
> >      'pwl.cpp',
> > +    'quantized.cpp',
> >      'v4l2_params.cpp',
> >  ])
> >  
> > diff --git a/src/ipa/libipa/quantized.cpp b/src/ipa/libipa/quantized.cpp
> > new file mode 100644
> > index 000000000000..06143a97ab3e
> > --- /dev/null
> > +++ b/src/ipa/libipa/quantized.cpp
> > @@ -0,0 +1,135 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2025, Ideas On Board Oy
> > + *
> > + * Helper class to manage conversions between floating point types and quantized
> > + * storage and representation of those values.
> > + */
> > +
> > +#include "quantized.h"
> > +
> > +/**
> > + * \file quantized.h
> > + * \brief Quantized storage and Quantizer representations
> > + */
> > +
> > +namespace libcamera {
> > +
> > +namespace ipa {
> > +
> > +/**
> > + * \struct libcamera::ipa::Quantized
> > + * \brief Wrapper that stores a value in both quantized and floating-point form
> > + * \tparam Traits The traits class defining the quantization behaviour
> > + *
> > + * The Quantized struct template provides a thin wrapper around a quantized
> > + * representation of a floating-point value. It uses a traits type \a Traits
> > + * to define the conversion policy between the floating-point domain and the
> > + * quantized integer domain.
> > + *
> > + * Each Quantized instance maintains two synchronized members:
> > + *  - the quantized integer representation, and
> > + *  - the corresponding floating-point value.
> > + *
> > + * The traits type defines:
> > + *  - the integer storage type used for quantization,
> > + *  - the static conversion functions \c fromFloat() and \c toFloat(), and
> > + *  - optional metadata such as value ranges.
> > + *
> > + * Quantized provides convenient constructors and assignment operators from
> > + * either representation, as well as comparison and string formatting utilities.
> > + */
> > +
> > +/**
> > + * \typedef Quantized::TraitsType
> > + * \brief The traits policy type defining the quantization behaviour
> > + *
> > + * Exposes the associated traits type used by this Quantized instance.
> > + * This allows external code to refer to constants or metadata defined in
> > + * the traits, such as \c TraitsType::min or \c TraitsType::max.
> > + */
> > +
> > +/**
> > + * \typedef Quantized::QuantizedType
> > + * \brief The integer type used for the quantized representation
> > + *
> > + * This alias corresponds to \c TraitsType::QuantizedType, as defined by
> > + * the traits class.
> > + */
> > +
> > +/**
> > + * \fn Quantized::Quantized(float x)
> > + * \brief Construct a Quantized value from a floating-point number
> > + * \param[in] x The floating-point value to be quantized
> > + *
> > + * Converts the floating-point input \a x to its quantized integer
> > + * representation using the associated traits policy, and initializes
> > + * both the quantized and floating-point members.
> > + */
> > +
> > +/**
> > + * \fn Quantized::Quantized(QuantizedType x)
> > + * \brief Construct a Quantized value from an existing quantized integer
> > + * \param[in] x The quantized integer value
> > + *
> > + * Converts the quantized integer \a x to its corresponding floating-point
> > + * value using the traits policy, and initializes both internal members.
> > + */
> > +
> > +/**
> > + * \fn Quantized::operator=(float x)
> > + * \brief Assign a floating-point value to the Quantized object
> > + * \param[in] x The floating-point value to assign
> > + * \return A reference to the updated Quantized object
> > + *
> > + * Converts the floating-point value \a x to its quantized integer
> > + * representation using the traits policy and updates both members.
> > + */
> > +
> > +/**
> > + * \fn Quantized::operator=(QuantizedType x)
> > + * \brief Assign a quantized integer value to the Quantized object
> > + * \param[in] x The quantized integer value to assign
> > + * \return A reference to the updated Quantized object
> > + *
> > + * Converts the quantized integer \a x to its corresponding floating-point
> > + * value using the traits policy and updates both members.
> > + */
> > +
> > +/**
> > + * \fn Quantized::value() const noexcept
> > + * \brief Retrieve the floating-point representation
> > + * \return The floating-point value corresponding to the quantized value
> > + */
> > +
> > +/**
> > + * \fn Quantized::quantized() const noexcept
> > + * \brief Retrieve the quantized integer representation
> > + * \return The quantized integer value
> > + */
> > +
> > +/**
> > + * \fn Quantized::operator==(const Quantized &other) const noexcept
> > + * \brief Compare two Quantized objects for equality
> > + * \param[in] other The other Quantized object to compare against
> > + * \return True if both objects have the same quantized integer value
> > + */
> > +
> > +/**
> > + * \fn Quantized::operator!=(const Quantized &other) const noexcept
> > + * \brief Compare two Quantized objects for inequality
> > + * \param[in] other The other Quantized object to compare against
> > + * \return True if the quantized integer values differ
> > + */
> > +
> > +/**
> > + * \fn std::ostream &Quantized::operator<<(std::ostream &out, const Quantized<Traits> &q)
> > + * \brief Insert a text representation of a Quantized into an output stream
> > + * \param[in] out The output stream
> > + * \param[in] q The Quantized
> > + * \return The output stream \a out
> > + */
> > +
> > +} /* namespace ipa */
> > +
> > +} /* namespace libcamera */
> > diff --git a/src/ipa/libipa/quantized.h b/src/ipa/libipa/quantized.h
> > new file mode 100644
> > index 000000000000..0b2f7148c821
> > --- /dev/null
> > +++ b/src/ipa/libipa/quantized.h
> > @@ -0,0 +1,75 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2025, Ideas On Board Oy
> > + *
> > + * Helper class to manage conversions between floating point types and quantized
> > + * storage and representation of those values.
> > + */
> > +
> > +#pragma once
> > +
> > +#include <sstream>
> > +#include <type_traits>
> > +
> > +#include <libcamera/base/utils.h>
> > +
> > +namespace libcamera {
> > +
> > +namespace ipa {
> > +
> > +template<typename Traits>
> > +struct Quantized {
> 
> Any reason to use a struct and not a class ? We usually use struct only
> (or mostly) for plain old C structures with no member functions.
> 
> > +	using TraitsType = Traits;
> > +	using QuantizedType = typename Traits::QuantizedType;
> > +	static_assert(std::is_arithmetic_v<QuantizedType>,
> > +		      "Quantized: QuantizedType must be arithmetic");
> > +
> > +	Quantized()
> > +		: Quantized(0.0f) {}
> > +	Quantized(float x) { *this = x; }
> > +	Quantized(QuantizedType x) { *this = x; }
> 
> There's an open question in the v4 thread about whether or not we should
> make these constructors explicit. Let's continue the discussion there.
> 
> > +
> > +	Quantized &operator=(float x)
> > +	{
> > +		quantized_ = Traits::fromFloat(x);
> > +		value_ = Traits::toFloat(quantized_);
> > +		return *this;
> > +	}
> > +
> > +	Quantized &operator=(QuantizedType x)
> > +	{
> > +		value_ = Traits::toFloat(x);
> > +		quantized_ = x;
> > +		return *this;
> > +	}
> > +
> > +	float value() const noexcept { return value_; }
> 
> As we don't use exceptions, is noexcept useful ?
> 
> Apart from those comments this looks good.

Ah I forgot to mention that the operator<<() should not be a friend
member function any more now that Barnabás has fixed the ADL issue.

> > +	QuantizedType quantized() const noexcept { return quantized_; }
> > +
> > +	bool operator==(const Quantized &other) const noexcept
> > +	{
> > +		return quantized_ == other.quantized_;
> > +	}
> > +
> > +	bool operator!=(const Quantized &other) const noexcept
> > +	{
> > +		return !(*this == other);
> > +	}
> > +
> > +	friend std::ostream &operator<<(std::ostream &out,
> > +					const Quantized<Traits> &q)
> > +	{
> > +		out << "[" << utils::hex(q.quantized())
> > +		    << ":" << q.value() << "]";
> > +
> > +		return out;
> > +	}
> > +
> > +private:
> > +	QuantizedType quantized_;
> > +	float value_;
> > +};
> > +
> > +} /* namespace ipa */
> > +
> > +} /* namespace libcamera */
Kieran Bingham Jan. 24, 2026, 12:06 p.m. UTC | #3
Quoting Laurent Pinchart (2026-01-24 01:20:29)
> On Sat, Jan 24, 2026 at 03:19:23AM +0200, Laurent Pinchart wrote:
> > On Wed, Jan 21, 2026 at 05:37:20PM +0000, Kieran Bingham wrote:
> > > Frequently when handling data in IPA components we must convert and
> > > store user interface values which may be floating point values, and
> > > perform a specific operation or conversion to quantize this to a
> > > hardware value.
> > > 
> > > This value may be to a fixed point type, or more custom code mappings,
> > > but in either case it is important to contain both the required hardware
> > > value, with its effective quantized value.
> > > 
> > > Provide a new storage type 'Quantized' which can be defined based on a
> > > set of type specific Traits to perform the conversions between floats
> > > and the underlying hardware type.
> > > 
> > > Reviewed-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
> > > Reviewed-by: Isaac Scott <isaac.scott@ideasonboard.com>
> > > Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > > 
> > > ---
> > > v3:
> > >  - adapt string format to [0xff:1.99] style instead of Q:0xff V:1.99
> > >  - Clean up comments and copyright
> > >  - Remove private initialisers - already handled by constructors
> > >  - Change quantized_type to QuantizedType
> > > 
> > > v5:
> > > - introduce operator<<(std::ostream &out, const Quantized<Traits> &q)
> > > - Remove unused iomanip and stdint.h from includes
> > > 
> > > Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > > ---
> > >  src/ipa/libipa/meson.build   |   2 +
> > >  src/ipa/libipa/quantized.cpp | 135 +++++++++++++++++++++++++++++++++++
> > >  src/ipa/libipa/quantized.h   |  75 +++++++++++++++++++
> > >  3 files changed, 212 insertions(+)
> > >  create mode 100644 src/ipa/libipa/quantized.cpp
> > >  create mode 100644 src/ipa/libipa/quantized.h
> > > 
> > > diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build
> > > index 7202df869c2f..963c5ee73063 100644
> > > --- a/src/ipa/libipa/meson.build
> > > +++ b/src/ipa/libipa/meson.build
> > > @@ -17,6 +17,7 @@ libipa_headers = files([
> > >      'lux.h',
> > >      'module.h',
> > >      'pwl.h',
> > > +    'quantized.h',
> > >      'v4l2_params.h',
> > >  ])
> > >  
> > > @@ -37,6 +38,7 @@ libipa_sources = files([
> > >      'lux.cpp',
> > >      'module.cpp',
> > >      'pwl.cpp',
> > > +    'quantized.cpp',
> > >      'v4l2_params.cpp',
> > >  ])
> > >  
> > > diff --git a/src/ipa/libipa/quantized.cpp b/src/ipa/libipa/quantized.cpp
> > > new file mode 100644
> > > index 000000000000..06143a97ab3e
> > > --- /dev/null
> > > +++ b/src/ipa/libipa/quantized.cpp
> > > @@ -0,0 +1,135 @@
> > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > > +/*
> > > + * Copyright (C) 2025, Ideas On Board Oy
> > > + *
> > > + * Helper class to manage conversions between floating point types and quantized
> > > + * storage and representation of those values.
> > > + */
> > > +
> > > +#include "quantized.h"
> > > +
> > > +/**
> > > + * \file quantized.h
> > > + * \brief Quantized storage and Quantizer representations
> > > + */
> > > +
> > > +namespace libcamera {
> > > +
> > > +namespace ipa {
> > > +
> > > +/**
> > > + * \struct libcamera::ipa::Quantized
> > > + * \brief Wrapper that stores a value in both quantized and floating-point form
> > > + * \tparam Traits The traits class defining the quantization behaviour
> > > + *
> > > + * The Quantized struct template provides a thin wrapper around a quantized
> > > + * representation of a floating-point value. It uses a traits type \a Traits
> > > + * to define the conversion policy between the floating-point domain and the
> > > + * quantized integer domain.
> > > + *
> > > + * Each Quantized instance maintains two synchronized members:
> > > + *  - the quantized integer representation, and
> > > + *  - the corresponding floating-point value.
> > > + *
> > > + * The traits type defines:
> > > + *  - the integer storage type used for quantization,
> > > + *  - the static conversion functions \c fromFloat() and \c toFloat(), and
> > > + *  - optional metadata such as value ranges.
> > > + *
> > > + * Quantized provides convenient constructors and assignment operators from
> > > + * either representation, as well as comparison and string formatting utilities.
> > > + */
> > > +
> > > +/**
> > > + * \typedef Quantized::TraitsType
> > > + * \brief The traits policy type defining the quantization behaviour
> > > + *
> > > + * Exposes the associated traits type used by this Quantized instance.
> > > + * This allows external code to refer to constants or metadata defined in
> > > + * the traits, such as \c TraitsType::min or \c TraitsType::max.
> > > + */
> > > +
> > > +/**
> > > + * \typedef Quantized::QuantizedType
> > > + * \brief The integer type used for the quantized representation
> > > + *
> > > + * This alias corresponds to \c TraitsType::QuantizedType, as defined by
> > > + * the traits class.
> > > + */
> > > +
> > > +/**
> > > + * \fn Quantized::Quantized(float x)
> > > + * \brief Construct a Quantized value from a floating-point number
> > > + * \param[in] x The floating-point value to be quantized
> > > + *
> > > + * Converts the floating-point input \a x to its quantized integer
> > > + * representation using the associated traits policy, and initializes
> > > + * both the quantized and floating-point members.
> > > + */
> > > +
> > > +/**
> > > + * \fn Quantized::Quantized(QuantizedType x)
> > > + * \brief Construct a Quantized value from an existing quantized integer
> > > + * \param[in] x The quantized integer value
> > > + *
> > > + * Converts the quantized integer \a x to its corresponding floating-point
> > > + * value using the traits policy, and initializes both internal members.
> > > + */
> > > +
> > > +/**
> > > + * \fn Quantized::operator=(float x)
> > > + * \brief Assign a floating-point value to the Quantized object
> > > + * \param[in] x The floating-point value to assign
> > > + * \return A reference to the updated Quantized object
> > > + *
> > > + * Converts the floating-point value \a x to its quantized integer
> > > + * representation using the traits policy and updates both members.
> > > + */
> > > +
> > > +/**
> > > + * \fn Quantized::operator=(QuantizedType x)
> > > + * \brief Assign a quantized integer value to the Quantized object
> > > + * \param[in] x The quantized integer value to assign
> > > + * \return A reference to the updated Quantized object
> > > + *
> > > + * Converts the quantized integer \a x to its corresponding floating-point
> > > + * value using the traits policy and updates both members.
> > > + */
> > > +
> > > +/**
> > > + * \fn Quantized::value() const noexcept
> > > + * \brief Retrieve the floating-point representation
> > > + * \return The floating-point value corresponding to the quantized value
> > > + */
> > > +
> > > +/**
> > > + * \fn Quantized::quantized() const noexcept
> > > + * \brief Retrieve the quantized integer representation
> > > + * \return The quantized integer value
> > > + */
> > > +
> > > +/**
> > > + * \fn Quantized::operator==(const Quantized &other) const noexcept
> > > + * \brief Compare two Quantized objects for equality
> > > + * \param[in] other The other Quantized object to compare against
> > > + * \return True if both objects have the same quantized integer value
> > > + */
> > > +
> > > +/**
> > > + * \fn Quantized::operator!=(const Quantized &other) const noexcept
> > > + * \brief Compare two Quantized objects for inequality
> > > + * \param[in] other The other Quantized object to compare against
> > > + * \return True if the quantized integer values differ
> > > + */
> > > +
> > > +/**
> > > + * \fn std::ostream &Quantized::operator<<(std::ostream &out, const Quantized<Traits> &q)
> > > + * \brief Insert a text representation of a Quantized into an output stream
> > > + * \param[in] out The output stream
> > > + * \param[in] q The Quantized
> > > + * \return The output stream \a out
> > > + */
> > > +
> > > +} /* namespace ipa */
> > > +
> > > +} /* namespace libcamera */
> > > diff --git a/src/ipa/libipa/quantized.h b/src/ipa/libipa/quantized.h
> > > new file mode 100644
> > > index 000000000000..0b2f7148c821
> > > --- /dev/null
> > > +++ b/src/ipa/libipa/quantized.h
> > > @@ -0,0 +1,75 @@
> > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > > +/*
> > > + * Copyright (C) 2025, Ideas On Board Oy
> > > + *
> > > + * Helper class to manage conversions between floating point types and quantized
> > > + * storage and representation of those values.
> > > + */
> > > +
> > > +#pragma once
> > > +
> > > +#include <sstream>
> > > +#include <type_traits>
> > > +
> > > +#include <libcamera/base/utils.h>
> > > +
> > > +namespace libcamera {
> > > +
> > > +namespace ipa {
> > > +
> > > +template<typename Traits>
> > > +struct Quantized {
> > 
> > Any reason to use a struct and not a class ? We usually use struct only
> > (or mostly) for plain old C structures with no member functions.
> > 
> > > +   using TraitsType = Traits;
> > > +   using QuantizedType = typename Traits::QuantizedType;
> > > +   static_assert(std::is_arithmetic_v<QuantizedType>,
> > > +                 "Quantized: QuantizedType must be arithmetic");
> > > +
> > > +   Quantized()
> > > +           : Quantized(0.0f) {}
> > > +   Quantized(float x) { *this = x; }
> > > +   Quantized(QuantizedType x) { *this = x; }
> > 
> > There's an open question in the v4 thread about whether or not we should
> > make these constructors explicit. Let's continue the discussion there.


I remember not liking that .. but lets try to close this.

> > > +
> > > +   Quantized &operator=(float x)
> > > +   {
> > > +           quantized_ = Traits::fromFloat(x);
> > > +           value_ = Traits::toFloat(quantized_);
> > > +           return *this;
> > > +   }
> > > +
> > > +   Quantized &operator=(QuantizedType x)
> > > +   {
> > > +           value_ = Traits::toFloat(x);
> > > +           quantized_ = x;
> > > +           return *this;
> > > +   }
> > > +
> > > +   float value() const noexcept { return value_; }
> > 
> > As we don't use exceptions, is noexcept useful ?

At this stage, this was from a long time ago I don't remember why
noexcept is here. Do you want me to just remove it ?


> > 
> > Apart from those comments this looks good.
> 
> Ah I forgot to mention that the operator<<() should not be a friend
> member function any more now that Barnabás has fixed the ADL issue.

Except that's not merged yet - I posted somewhere that a patch is
already available on top, but this series works and passes CI on master,
so it has to use the friend for the moment.
--
Kieran

> 
> > > +   QuantizedType quantized() const noexcept { return quantized_; }
> > > +
> > > +   bool operator==(const Quantized &other) const noexcept
> > > +   {
> > > +           return quantized_ == other.quantized_;
> > > +   }
> > > +
> > > +   bool operator!=(const Quantized &other) const noexcept
> > > +   {
> > > +           return !(*this == other);
> > > +   }
> > > +
> > > +   friend std::ostream &operator<<(std::ostream &out,
> > > +                                   const Quantized<Traits> &q)
> > > +   {
> > > +           out << "[" << utils::hex(q.quantized())
> > > +               << ":" << q.value() << "]";
> > > +
> > > +           return out;
> > > +   }
> > > +
> > > +private:
> > > +   QuantizedType quantized_;
> > > +   float value_;
> > > +};
> > > +
> > > +} /* namespace ipa */
> > > +
> > > +} /* namespace libcamera */
> 
> -- 
> Regards,
> 
> Laurent Pinchart
Laurent Pinchart Jan. 24, 2026, 12:36 p.m. UTC | #4
On Sat, Jan 24, 2026 at 12:06:13PM +0000, Kieran Bingham wrote:
> Quoting Laurent Pinchart (2026-01-24 01:20:29)
> > On Sat, Jan 24, 2026 at 03:19:23AM +0200, Laurent Pinchart wrote:
> > > On Wed, Jan 21, 2026 at 05:37:20PM +0000, Kieran Bingham wrote:
> > > > Frequently when handling data in IPA components we must convert and
> > > > store user interface values which may be floating point values, and
> > > > perform a specific operation or conversion to quantize this to a
> > > > hardware value.
> > > > 
> > > > This value may be to a fixed point type, or more custom code mappings,
> > > > but in either case it is important to contain both the required hardware
> > > > value, with its effective quantized value.
> > > > 
> > > > Provide a new storage type 'Quantized' which can be defined based on a
> > > > set of type specific Traits to perform the conversions between floats
> > > > and the underlying hardware type.
> > > > 
> > > > Reviewed-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
> > > > Reviewed-by: Isaac Scott <isaac.scott@ideasonboard.com>
> > > > Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > > > 
> > > > ---
> > > > v3:
> > > >  - adapt string format to [0xff:1.99] style instead of Q:0xff V:1.99
> > > >  - Clean up comments and copyright
> > > >  - Remove private initialisers - already handled by constructors
> > > >  - Change quantized_type to QuantizedType
> > > > 
> > > > v5:
> > > > - introduce operator<<(std::ostream &out, const Quantized<Traits> &q)
> > > > - Remove unused iomanip and stdint.h from includes
> > > > 
> > > > Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > > > ---
> > > >  src/ipa/libipa/meson.build   |   2 +
> > > >  src/ipa/libipa/quantized.cpp | 135 +++++++++++++++++++++++++++++++++++
> > > >  src/ipa/libipa/quantized.h   |  75 +++++++++++++++++++
> > > >  3 files changed, 212 insertions(+)
> > > >  create mode 100644 src/ipa/libipa/quantized.cpp
> > > >  create mode 100644 src/ipa/libipa/quantized.h
> > > > 
> > > > diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build
> > > > index 7202df869c2f..963c5ee73063 100644
> > > > --- a/src/ipa/libipa/meson.build
> > > > +++ b/src/ipa/libipa/meson.build
> > > > @@ -17,6 +17,7 @@ libipa_headers = files([
> > > >      'lux.h',
> > > >      'module.h',
> > > >      'pwl.h',
> > > > +    'quantized.h',
> > > >      'v4l2_params.h',
> > > >  ])
> > > >  
> > > > @@ -37,6 +38,7 @@ libipa_sources = files([
> > > >      'lux.cpp',
> > > >      'module.cpp',
> > > >      'pwl.cpp',
> > > > +    'quantized.cpp',
> > > >      'v4l2_params.cpp',
> > > >  ])
> > > >  
> > > > diff --git a/src/ipa/libipa/quantized.cpp b/src/ipa/libipa/quantized.cpp
> > > > new file mode 100644
> > > > index 000000000000..06143a97ab3e
> > > > --- /dev/null
> > > > +++ b/src/ipa/libipa/quantized.cpp
> > > > @@ -0,0 +1,135 @@
> > > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > > > +/*
> > > > + * Copyright (C) 2025, Ideas On Board Oy
> > > > + *
> > > > + * Helper class to manage conversions between floating point types and quantized
> > > > + * storage and representation of those values.
> > > > + */
> > > > +
> > > > +#include "quantized.h"
> > > > +
> > > > +/**
> > > > + * \file quantized.h
> > > > + * \brief Quantized storage and Quantizer representations
> > > > + */
> > > > +
> > > > +namespace libcamera {
> > > > +
> > > > +namespace ipa {
> > > > +
> > > > +/**
> > > > + * \struct libcamera::ipa::Quantized
> > > > + * \brief Wrapper that stores a value in both quantized and floating-point form
> > > > + * \tparam Traits The traits class defining the quantization behaviour
> > > > + *
> > > > + * The Quantized struct template provides a thin wrapper around a quantized
> > > > + * representation of a floating-point value. It uses a traits type \a Traits
> > > > + * to define the conversion policy between the floating-point domain and the
> > > > + * quantized integer domain.
> > > > + *
> > > > + * Each Quantized instance maintains two synchronized members:
> > > > + *  - the quantized integer representation, and
> > > > + *  - the corresponding floating-point value.
> > > > + *
> > > > + * The traits type defines:
> > > > + *  - the integer storage type used for quantization,
> > > > + *  - the static conversion functions \c fromFloat() and \c toFloat(), and
> > > > + *  - optional metadata such as value ranges.
> > > > + *
> > > > + * Quantized provides convenient constructors and assignment operators from
> > > > + * either representation, as well as comparison and string formatting utilities.
> > > > + */
> > > > +
> > > > +/**
> > > > + * \typedef Quantized::TraitsType
> > > > + * \brief The traits policy type defining the quantization behaviour
> > > > + *
> > > > + * Exposes the associated traits type used by this Quantized instance.
> > > > + * This allows external code to refer to constants or metadata defined in
> > > > + * the traits, such as \c TraitsType::min or \c TraitsType::max.
> > > > + */
> > > > +
> > > > +/**
> > > > + * \typedef Quantized::QuantizedType
> > > > + * \brief The integer type used for the quantized representation
> > > > + *
> > > > + * This alias corresponds to \c TraitsType::QuantizedType, as defined by
> > > > + * the traits class.
> > > > + */
> > > > +
> > > > +/**
> > > > + * \fn Quantized::Quantized(float x)
> > > > + * \brief Construct a Quantized value from a floating-point number
> > > > + * \param[in] x The floating-point value to be quantized
> > > > + *
> > > > + * Converts the floating-point input \a x to its quantized integer
> > > > + * representation using the associated traits policy, and initializes
> > > > + * both the quantized and floating-point members.
> > > > + */
> > > > +
> > > > +/**
> > > > + * \fn Quantized::Quantized(QuantizedType x)
> > > > + * \brief Construct a Quantized value from an existing quantized integer
> > > > + * \param[in] x The quantized integer value
> > > > + *
> > > > + * Converts the quantized integer \a x to its corresponding floating-point
> > > > + * value using the traits policy, and initializes both internal members.
> > > > + */
> > > > +
> > > > +/**
> > > > + * \fn Quantized::operator=(float x)
> > > > + * \brief Assign a floating-point value to the Quantized object
> > > > + * \param[in] x The floating-point value to assign
> > > > + * \return A reference to the updated Quantized object
> > > > + *
> > > > + * Converts the floating-point value \a x to its quantized integer
> > > > + * representation using the traits policy and updates both members.
> > > > + */
> > > > +
> > > > +/**
> > > > + * \fn Quantized::operator=(QuantizedType x)
> > > > + * \brief Assign a quantized integer value to the Quantized object
> > > > + * \param[in] x The quantized integer value to assign
> > > > + * \return A reference to the updated Quantized object
> > > > + *
> > > > + * Converts the quantized integer \a x to its corresponding floating-point
> > > > + * value using the traits policy and updates both members.
> > > > + */
> > > > +
> > > > +/**
> > > > + * \fn Quantized::value() const noexcept
> > > > + * \brief Retrieve the floating-point representation
> > > > + * \return The floating-point value corresponding to the quantized value
> > > > + */
> > > > +
> > > > +/**
> > > > + * \fn Quantized::quantized() const noexcept
> > > > + * \brief Retrieve the quantized integer representation
> > > > + * \return The quantized integer value
> > > > + */
> > > > +
> > > > +/**
> > > > + * \fn Quantized::operator==(const Quantized &other) const noexcept
> > > > + * \brief Compare two Quantized objects for equality
> > > > + * \param[in] other The other Quantized object to compare against
> > > > + * \return True if both objects have the same quantized integer value
> > > > + */
> > > > +
> > > > +/**
> > > > + * \fn Quantized::operator!=(const Quantized &other) const noexcept
> > > > + * \brief Compare two Quantized objects for inequality
> > > > + * \param[in] other The other Quantized object to compare against
> > > > + * \return True if the quantized integer values differ
> > > > + */
> > > > +
> > > > +/**
> > > > + * \fn std::ostream &Quantized::operator<<(std::ostream &out, const Quantized<Traits> &q)
> > > > + * \brief Insert a text representation of a Quantized into an output stream
> > > > + * \param[in] out The output stream
> > > > + * \param[in] q The Quantized
> > > > + * \return The output stream \a out
> > > > + */
> > > > +
> > > > +} /* namespace ipa */
> > > > +
> > > > +} /* namespace libcamera */
> > > > diff --git a/src/ipa/libipa/quantized.h b/src/ipa/libipa/quantized.h
> > > > new file mode 100644
> > > > index 000000000000..0b2f7148c821
> > > > --- /dev/null
> > > > +++ b/src/ipa/libipa/quantized.h
> > > > @@ -0,0 +1,75 @@
> > > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > > > +/*
> > > > + * Copyright (C) 2025, Ideas On Board Oy
> > > > + *
> > > > + * Helper class to manage conversions between floating point types and quantized
> > > > + * storage and representation of those values.
> > > > + */
> > > > +
> > > > +#pragma once
> > > > +
> > > > +#include <sstream>
> > > > +#include <type_traits>
> > > > +
> > > > +#include <libcamera/base/utils.h>
> > > > +
> > > > +namespace libcamera {
> > > > +
> > > > +namespace ipa {
> > > > +
> > > > +template<typename Traits>
> > > > +struct Quantized {
> > > 
> > > Any reason to use a struct and not a class ? We usually use struct only
> > > (or mostly) for plain old C structures with no member functions.
> > > 
> > > > +   using TraitsType = Traits;
> > > > +   using QuantizedType = typename Traits::QuantizedType;
> > > > +   static_assert(std::is_arithmetic_v<QuantizedType>,
> > > > +                 "Quantized: QuantizedType must be arithmetic");
> > > > +
> > > > +   Quantized()
> > > > +           : Quantized(0.0f) {}
> > > > +   Quantized(float x) { *this = x; }
> > > > +   Quantized(QuantizedType x) { *this = x; }
> > > 
> > > There's an open question in the v4 thread about whether or not we should
> > > make these constructors explicit. Let's continue the discussion there.
> 
> I remember not liking that .. but lets try to close this.
> 
> > > > +
> > > > +   Quantized &operator=(float x)
> > > > +   {
> > > > +           quantized_ = Traits::fromFloat(x);
> > > > +           value_ = Traits::toFloat(quantized_);
> > > > +           return *this;
> > > > +   }
> > > > +
> > > > +   Quantized &operator=(QuantizedType x)
> > > > +   {
> > > > +           value_ = Traits::toFloat(x);
> > > > +           quantized_ = x;
> > > > +           return *this;
> > > > +   }
> > > > +
> > > > +   float value() const noexcept { return value_; }
> > > 
> > > As we don't use exceptions, is noexcept useful ?
> 
> At this stage, this was from a long time ago I don't remember why
> noexcept is here. Do you want me to just remove it ?

I'd drop it yes. If we want noexcept annotations, they should be added
through the whole code base.

> > > Apart from those comments this looks good.
> > 
> > Ah I forgot to mention that the operator<<() should not be a friend
> > member function any more now that Barnabás has fixed the ADL issue.
> 
> Except that's not merged yet - I posted somewhere that a patch is
> already available on top, but this series works and passes CI on master,
> so it has to use the friend for the moment.

Sure. I think Barnabás' patch is ready to be merged, so this can be
solved quickly.

> > > > +   QuantizedType quantized() const noexcept { return quantized_; }
> > > > +
> > > > +   bool operator==(const Quantized &other) const noexcept
> > > > +   {
> > > > +           return quantized_ == other.quantized_;
> > > > +   }
> > > > +
> > > > +   bool operator!=(const Quantized &other) const noexcept
> > > > +   {
> > > > +           return !(*this == other);
> > > > +   }
> > > > +
> > > > +   friend std::ostream &operator<<(std::ostream &out,
> > > > +                                   const Quantized<Traits> &q)
> > > > +   {
> > > > +           out << "[" << utils::hex(q.quantized())
> > > > +               << ":" << q.value() << "]";
> > > > +
> > > > +           return out;
> > > > +   }
> > > > +
> > > > +private:
> > > > +   QuantizedType quantized_;
> > > > +   float value_;
> > > > +};
> > > > +
> > > > +} /* namespace ipa */
> > > > +
> > > > +} /* namespace libcamera */

Patch
diff mbox series

diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build
index 7202df869c2f..963c5ee73063 100644
--- a/src/ipa/libipa/meson.build
+++ b/src/ipa/libipa/meson.build
@@ -17,6 +17,7 @@  libipa_headers = files([
     'lux.h',
     'module.h',
     'pwl.h',
+    'quantized.h',
     'v4l2_params.h',
 ])
 
@@ -37,6 +38,7 @@  libipa_sources = files([
     'lux.cpp',
     'module.cpp',
     'pwl.cpp',
+    'quantized.cpp',
     'v4l2_params.cpp',
 ])
 
diff --git a/src/ipa/libipa/quantized.cpp b/src/ipa/libipa/quantized.cpp
new file mode 100644
index 000000000000..06143a97ab3e
--- /dev/null
+++ b/src/ipa/libipa/quantized.cpp
@@ -0,0 +1,135 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2025, Ideas On Board Oy
+ *
+ * Helper class to manage conversions between floating point types and quantized
+ * storage and representation of those values.
+ */
+
+#include "quantized.h"
+
+/**
+ * \file quantized.h
+ * \brief Quantized storage and Quantizer representations
+ */
+
+namespace libcamera {
+
+namespace ipa {
+
+/**
+ * \struct libcamera::ipa::Quantized
+ * \brief Wrapper that stores a value in both quantized and floating-point form
+ * \tparam Traits The traits class defining the quantization behaviour
+ *
+ * The Quantized struct template provides a thin wrapper around a quantized
+ * representation of a floating-point value. It uses a traits type \a Traits
+ * to define the conversion policy between the floating-point domain and the
+ * quantized integer domain.
+ *
+ * Each Quantized instance maintains two synchronized members:
+ *  - the quantized integer representation, and
+ *  - the corresponding floating-point value.
+ *
+ * The traits type defines:
+ *  - the integer storage type used for quantization,
+ *  - the static conversion functions \c fromFloat() and \c toFloat(), and
+ *  - optional metadata such as value ranges.
+ *
+ * Quantized provides convenient constructors and assignment operators from
+ * either representation, as well as comparison and string formatting utilities.
+ */
+
+/**
+ * \typedef Quantized::TraitsType
+ * \brief The traits policy type defining the quantization behaviour
+ *
+ * Exposes the associated traits type used by this Quantized instance.
+ * This allows external code to refer to constants or metadata defined in
+ * the traits, such as \c TraitsType::min or \c TraitsType::max.
+ */
+
+/**
+ * \typedef Quantized::QuantizedType
+ * \brief The integer type used for the quantized representation
+ *
+ * This alias corresponds to \c TraitsType::QuantizedType, as defined by
+ * the traits class.
+ */
+
+/**
+ * \fn Quantized::Quantized(float x)
+ * \brief Construct a Quantized value from a floating-point number
+ * \param[in] x The floating-point value to be quantized
+ *
+ * Converts the floating-point input \a x to its quantized integer
+ * representation using the associated traits policy, and initializes
+ * both the quantized and floating-point members.
+ */
+
+/**
+ * \fn Quantized::Quantized(QuantizedType x)
+ * \brief Construct a Quantized value from an existing quantized integer
+ * \param[in] x The quantized integer value
+ *
+ * Converts the quantized integer \a x to its corresponding floating-point
+ * value using the traits policy, and initializes both internal members.
+ */
+
+/**
+ * \fn Quantized::operator=(float x)
+ * \brief Assign a floating-point value to the Quantized object
+ * \param[in] x The floating-point value to assign
+ * \return A reference to the updated Quantized object
+ *
+ * Converts the floating-point value \a x to its quantized integer
+ * representation using the traits policy and updates both members.
+ */
+
+/**
+ * \fn Quantized::operator=(QuantizedType x)
+ * \brief Assign a quantized integer value to the Quantized object
+ * \param[in] x The quantized integer value to assign
+ * \return A reference to the updated Quantized object
+ *
+ * Converts the quantized integer \a x to its corresponding floating-point
+ * value using the traits policy and updates both members.
+ */
+
+/**
+ * \fn Quantized::value() const noexcept
+ * \brief Retrieve the floating-point representation
+ * \return The floating-point value corresponding to the quantized value
+ */
+
+/**
+ * \fn Quantized::quantized() const noexcept
+ * \brief Retrieve the quantized integer representation
+ * \return The quantized integer value
+ */
+
+/**
+ * \fn Quantized::operator==(const Quantized &other) const noexcept
+ * \brief Compare two Quantized objects for equality
+ * \param[in] other The other Quantized object to compare against
+ * \return True if both objects have the same quantized integer value
+ */
+
+/**
+ * \fn Quantized::operator!=(const Quantized &other) const noexcept
+ * \brief Compare two Quantized objects for inequality
+ * \param[in] other The other Quantized object to compare against
+ * \return True if the quantized integer values differ
+ */
+
+/**
+ * \fn std::ostream &Quantized::operator<<(std::ostream &out, const Quantized<Traits> &q)
+ * \brief Insert a text representation of a Quantized into an output stream
+ * \param[in] out The output stream
+ * \param[in] q The Quantized
+ * \return The output stream \a out
+ */
+
+} /* namespace ipa */
+
+} /* namespace libcamera */
diff --git a/src/ipa/libipa/quantized.h b/src/ipa/libipa/quantized.h
new file mode 100644
index 000000000000..0b2f7148c821
--- /dev/null
+++ b/src/ipa/libipa/quantized.h
@@ -0,0 +1,75 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2025, Ideas On Board Oy
+ *
+ * Helper class to manage conversions between floating point types and quantized
+ * storage and representation of those values.
+ */
+
+#pragma once
+
+#include <sstream>
+#include <type_traits>
+
+#include <libcamera/base/utils.h>
+
+namespace libcamera {
+
+namespace ipa {
+
+template<typename Traits>
+struct Quantized {
+	using TraitsType = Traits;
+	using QuantizedType = typename Traits::QuantizedType;
+	static_assert(std::is_arithmetic_v<QuantizedType>,
+		      "Quantized: QuantizedType must be arithmetic");
+
+	Quantized()
+		: Quantized(0.0f) {}
+	Quantized(float x) { *this = x; }
+	Quantized(QuantizedType x) { *this = x; }
+
+	Quantized &operator=(float x)
+	{
+		quantized_ = Traits::fromFloat(x);
+		value_ = Traits::toFloat(quantized_);
+		return *this;
+	}
+
+	Quantized &operator=(QuantizedType x)
+	{
+		value_ = Traits::toFloat(x);
+		quantized_ = x;
+		return *this;
+	}
+
+	float value() const noexcept { return value_; }
+	QuantizedType quantized() const noexcept { return quantized_; }
+
+	bool operator==(const Quantized &other) const noexcept
+	{
+		return quantized_ == other.quantized_;
+	}
+
+	bool operator!=(const Quantized &other) const noexcept
+	{
+		return !(*this == other);
+	}
+
+	friend std::ostream &operator<<(std::ostream &out,
+					const Quantized<Traits> &q)
+	{
+		out << "[" << utils::hex(q.quantized())
+		    << ":" << q.value() << "]";
+
+		return out;
+	}
+
+private:
+	QuantizedType quantized_;
+	float value_;
+};
+
+} /* namespace ipa */
+
+} /* namespace libcamera */