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

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

Commit Message

Kieran Bingham Jan. 14, 2026, 5:39 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: 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 | 142 +++++++++++++++++++++++++++++++++++
 src/ipa/libipa/quantized.h   |  83 ++++++++++++++++++++
 3 files changed, 227 insertions(+)
 create mode 100644 src/ipa/libipa/quantized.cpp
 create mode 100644 src/ipa/libipa/quantized.h

Comments

Barnabás Pőcze Jan. 15, 2026, 4:29 p.m. UTC | #1
2026. 01. 14. 18:39 keltezéssel, Kieran Bingham írta:
> 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: 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 | 142 +++++++++++++++++++++++++++++++++++
>   src/ipa/libipa/quantized.h   |  83 ++++++++++++++++++++
>   3 files changed, 227 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..115739e71654
> --- /dev/null
> +++ b/src/ipa/libipa/quantized.cpp
> @@ -0,0 +1,142 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2025, Ideas On Board Oy

2026 ?


> + *
> + * 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.

I think maybe it's worth highlighting with `\note` that both members are
updated, and that likely `value() != x` after the assignment.


> + */
> +
> +/**
> + * \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::toString() const
> + * \brief Format the quantized and floating-point values as a string
> + * \return A string containing the hexadecimal quantized value and its
> + *         floating-point equivalent.
> + */
> +
> +/**
> + * \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..045ccb755a85
> --- /dev/null
> +++ b/src/ipa/libipa/quantized.h
> @@ -0,0 +1,83 @@
> +/* 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_; }
> +
> +	std::string toString() const
> +	{
> +		std::stringstream ss;
> +		ss << *this;
> +
> +		return ss.str();
> +	}

This doesn't seem to be used (outside of the tests), so I think I'd probably
remove it so that people won't use it with `<<` accidentally.


> +
> +	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;
> +	}

I have fixed the reason why this couldn't be declared outside the type:
https://patchwork.libcamera.org/project/libcamera/list/?series=5709

But I think it's fine to leave it in the class as well.

Reviewed-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>


> +
> +private:
> +	QuantizedType quantized_;
> +	float value_;
> +};
> +
> +} /* namespace ipa */
> +
> +} /* namespace libcamera */
Kieran Bingham Jan. 16, 2026, 11:57 a.m. UTC | #2
Quoting Barnabás Pőcze (2026-01-15 16:29:02)
> 2026. 01. 14. 18:39 keltezéssel, Kieran Bingham írta:
> > 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: 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 | 142 +++++++++++++++++++++++++++++++++++
> >   src/ipa/libipa/quantized.h   |  83 ++++++++++++++++++++
> >   3 files changed, 227 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..115739e71654
> > --- /dev/null
> > +++ b/src/ipa/libipa/quantized.cpp
> > @@ -0,0 +1,142 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2025, Ideas On Board Oy
> 
> 2026 ?

I think copyright normally starts when it's authored/first published,
which was 2025 so I won't bother updating this one in particular.

> > + *
> > + * 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.
> 
> I think maybe it's worth highlighting with `\note` that both members are
> updated, and that likely `value() != x` after the assignment.

Oh yes - that's a good one. I'll add that.

> 
> 
> > + */
> > +
> > +/**
> > + * \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::toString() const
> > + * \brief Format the quantized and floating-point values as a string
> > + * \return A string containing the hexadecimal quantized value and its
> > + *         floating-point equivalent.
> > + */
> > +
> > +/**
> > + * \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..045ccb755a85
> > --- /dev/null
> > +++ b/src/ipa/libipa/quantized.h
> > @@ -0,0 +1,83 @@
> > +/* 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_; }
> > +
> > +     std::string toString() const
> > +     {
> > +             std::stringstream ss;
> > +             ss << *this;
> > +
> > +             return ss.str();
> > +     }
> 
> This doesn't seem to be used (outside of the tests), so I think I'd probably
> remove it so that people won't use it with `<<` accidentally.

Ack! I like less code.

> > +     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;
> > +     }
> 
> I have fixed the reason why this couldn't be declared outside the type:
> https://patchwork.libcamera.org/project/libcamera/list/?series=5709

That is awesome. I spent hours on that last year and just got myself in
a confused mess! I couldn't see the wood for the trees for sure!

Thank you!


> But I think it's fine to leave it in the class as well.

I've made this work now - so I almost want to leave it - but also - I
went through so much pain trying to get it like all the other
implementations ... I also sort of want to see that through - so I might
try on top of your patches as we're already spinning a v6 here.

If it's 'easy' now - I'll convert over. If it's hard - I'll keep what
works ;-)

> Reviewed-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
> 

Thanks again.

Kieran

> 
> > +
> > +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..115739e71654
--- /dev/null
+++ b/src/ipa/libipa/quantized.cpp
@@ -0,0 +1,142 @@ 
+/* 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::toString() const
+ * \brief Format the quantized and floating-point values as a string
+ * \return A string containing the hexadecimal quantized value and its
+ *         floating-point equivalent.
+ */
+
+/**
+ * \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..045ccb755a85
--- /dev/null
+++ b/src/ipa/libipa/quantized.h
@@ -0,0 +1,83 @@ 
+/* 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_; }
+
+	std::string toString() const
+	{
+		std::stringstream ss;
+		ss << *this;
+
+		return ss.str();
+	}
+
+	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 */