[v4,02/21] test: libipa: Add tests for Quantized types
diff mbox series

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

Commit Message

Kieran Bingham Nov. 14, 2025, 12:54 a.m. UTC
Provide use case tests for the Quantized types to ensure construction
and usages are consistent and work as expected.

Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>

---
v3:
- Rename quantized_type to QuantizedType

 test/ipa/libipa/meson.build   |   1 +
 test/ipa/libipa/quantized.cpp | 130 ++++++++++++++++++++++++++++++++++
 2 files changed, 131 insertions(+)
 create mode 100644 test/ipa/libipa/quantized.cpp

Comments

Barnabás Pőcze Nov. 14, 2025, 6:08 p.m. UTC | #1
Hi

2025. 11. 14. 1:54 keltezéssel, Kieran Bingham írta:
> Provide use case tests for the Quantized types to ensure construction
> and usages are consistent and work as expected.
> 
> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> 
> ---
> v3:
> - Rename quantized_type to QuantizedType
> 
>   test/ipa/libipa/meson.build   |   1 +
>   test/ipa/libipa/quantized.cpp | 130 ++++++++++++++++++++++++++++++++++
>   2 files changed, 131 insertions(+)
>   create mode 100644 test/ipa/libipa/quantized.cpp
> 
> diff --git a/test/ipa/libipa/meson.build b/test/ipa/libipa/meson.build
> index 2070bed70222..c3e255871f4f 100644
> --- a/test/ipa/libipa/meson.build
> +++ b/test/ipa/libipa/meson.build
> @@ -5,6 +5,7 @@ libipa_test = [
>       {'name': 'histogram', 'sources': ['histogram.cpp']},
>       {'name': 'interpolator', 'sources': ['interpolator.cpp']},
>       {'name': 'pwl', 'sources': ['pwl.cpp'] },
> +    {'name': 'quantized', 'sources': ['quantized.cpp']},
>   ]
>   
>   foreach test : libipa_test
> diff --git a/test/ipa/libipa/quantized.cpp b/test/ipa/libipa/quantized.cpp
> new file mode 100644
> index 000000000000..3bbd6dd48c14
> --- /dev/null
> +++ b/test/ipa/libipa/quantized.cpp
> @@ -0,0 +1,130 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (C) 2025, Ideas on Board
> + *
> + * Dual Type and Quantizer tests
> + */
> +
> +#include "../src/ipa/libipa/quantized.h"
> +
> +#include <algorithm>
> +#include <cmath>
> +#include <iostream>
> +#include <map>
> +#include <stdint.h>
> +
> +#include "test.h"
> +
> +using namespace std;
> +using namespace libcamera;
> +using namespace ipa;
> +
> +struct BrightnessHueTraits {
> +	using QuantizedType = int8_t;
> +	static constexpr QuantizedType fromFloat(float v)

std::lround() is only constexpr since C++23; and `Quantized<>` itself does not
call the conversion functions in constexpr contexts, so I would drop it.


> +	{
> +		int quantized = std::lround(v * 128.0f);
> +		return static_cast<int8_t>(std::clamp<int>(quantized, -128, 127));
> +	}
> +	static float toFloat(QuantizedType v)
> +	{
> +		return static_cast<float>(v) / 128.0f;
> +	}
> +};
> +
> +using BrightnessHueQuantizer = Quantized<BrightnessHueTraits>;
> +
> +struct ContrastSaturationTraits {
> +	using QuantizedType = uint8_t;
> +	static constexpr QuantizedType fromFloat(float v)
> +	{
> +		int quantized = std::lround(v * 128.0f);
> +		return static_cast<uint8_t>(std::clamp<int>(quantized, 0, 255));
> +	}
> +	static float toFloat(QuantizedType v)
> +	{
> +		return static_cast<float>(v) / 128.0f;
> +	}
> +};
> +
> +using ContrastSaturationQuantizer = Quantized<ContrastSaturationTraits>;
> +
> +using BrightnessQ = BrightnessHueQuantizer;
> +using HueQ = BrightnessHueQuantizer;
> +using ContrastQ = ContrastSaturationQuantizer;
> +using SaturationQ = ContrastSaturationQuantizer;
> +
> +class QuantizedTest : public Test
> +{
> +protected:
> +	int run()
> +	{
> +		/* Test construction from float */
> +		{
> +			BrightnessQ b(0.5f);
> +			if (b.quantized() != 64 || std::abs(b.value() - 0.5f) > 0.01f)
> +				return TestFail;
> +		}
> +
> +		/* Test construction from T */
> +		{
> +			ContrastQ c(uint8_t(128));
> +			if (c.quantized() != 128 || std::abs(c.value() - 1.0f) > 0.01f)
> +				return TestFail;
> +		}
> +
> +		/*
> +		 * Test construction from non-float/non-T type (Expected Fail)
> +		 * These should be a compile-time error if uncommented:
> +		 */
> +		// BrightnessQ bad1(15); // overloaded ‘Quantized(int)’ is ambiguous
> +		// BrightnessQ bad2(0xff); // overloaded ‘Quantized(int)’ is ambiguous
> +		// ContrastQ bad3(0x33); // overloaded ‘Quantized(int)’ is ambiguous
> +		// ContrastQ bad4(55U); // overloaded ‘Quantized(unsigned int)’ is ambiguous

I think you should be able to do something e.g.

   static_assert(!std::is_constructible_v<BrightnessQ, unsigned int>);
   static_assert(!std::is_constructible_v<BrightnessQ, double>);
   // etc.

if you want to test this.


> +
> +		/* Test equality */
> +		{
> +			BrightnessQ b1(0.5f), b2((int8_t)64);
> +			if (!(b1 == b2))
> +				return TestFail;
> +		}
> +
> +		/* Test inequality */
> +		{
> +			BrightnessQ b1(0.5f), b2(-0.5f);
> +			if (b1 == b2)

I would do

   if (!(b1 != b2))


> +				return TestFail;
> +		}
> +
> +		/* Test copying */
> +		{
> +			BrightnessQ b1(0.25f);
> +			BrightnessQ b2 = b1;
> +			if (!(b1 == b2))
> +				return TestFail;
> +		}
> +
> +		/* Test moving */
> +		{
> +			BrightnessQ b1(0.25f);
> +			BrightnessQ b2 = std::move(b1); // Allow move semantics
> +			if (b2.value() != 0.25f)
> +				return TestFail;
> +		}
> +
> +		/* Test assignment */
> +		{
> +			ContrastQ c1(1.5f);
> +			ContrastQ c2(0.0f);
> +			c2 = c1;
> +			if (!(c1 == c2))
> +				return TestFail;
> +		}
> +
> +		std::cout << "Quantised tests passed successfully." << std::endl;
> +
> +		return TestPass;
> +	}
> +};
> +
> +TEST_REGISTER(QuantizedTest)
Kieran Bingham Nov. 14, 2025, 6:56 p.m. UTC | #2
Quoting Barnabás Pőcze (2025-11-14 18:08:06)
> Hi
> 
> 2025. 11. 14. 1:54 keltezéssel, Kieran Bingham írta:
> > Provide use case tests for the Quantized types to ensure construction
> > and usages are consistent and work as expected.
> > 
> > Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > 
> > ---
> > v3:
> > - Rename quantized_type to QuantizedType
> > 
> >   test/ipa/libipa/meson.build   |   1 +
> >   test/ipa/libipa/quantized.cpp | 130 ++++++++++++++++++++++++++++++++++
> >   2 files changed, 131 insertions(+)
> >   create mode 100644 test/ipa/libipa/quantized.cpp
> > 
> > diff --git a/test/ipa/libipa/meson.build b/test/ipa/libipa/meson.build
> > index 2070bed70222..c3e255871f4f 100644
> > --- a/test/ipa/libipa/meson.build
> > +++ b/test/ipa/libipa/meson.build
> > @@ -5,6 +5,7 @@ libipa_test = [
> >       {'name': 'histogram', 'sources': ['histogram.cpp']},
> >       {'name': 'interpolator', 'sources': ['interpolator.cpp']},
> >       {'name': 'pwl', 'sources': ['pwl.cpp'] },
> > +    {'name': 'quantized', 'sources': ['quantized.cpp']},
> >   ]
> >   
> >   foreach test : libipa_test
> > diff --git a/test/ipa/libipa/quantized.cpp b/test/ipa/libipa/quantized.cpp
> > new file mode 100644
> > index 000000000000..3bbd6dd48c14
> > --- /dev/null
> > +++ b/test/ipa/libipa/quantized.cpp
> > @@ -0,0 +1,130 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > +/*
> > + * Copyright (C) 2025, Ideas on Board
> > + *
> > + * Dual Type and Quantizer tests
> > + */
> > +
> > +#include "../src/ipa/libipa/quantized.h"
> > +
> > +#include <algorithm>
> > +#include <cmath>
> > +#include <iostream>
> > +#include <map>
> > +#include <stdint.h>
> > +
> > +#include "test.h"
> > +
> > +using namespace std;
> > +using namespace libcamera;
> > +using namespace ipa;
> > +
> > +struct BrightnessHueTraits {
> > +     using QuantizedType = int8_t;
> > +     static constexpr QuantizedType fromFloat(float v)
> 
> std::lround() is only constexpr since C++23; and `Quantized<>` itself does not
> call the conversion functions in constexpr contexts, so I would drop it.
> 

Ack.


> 
> > +     {
> > +             int quantized = std::lround(v * 128.0f);
> > +             return static_cast<int8_t>(std::clamp<int>(quantized, -128, 127));
> > +     }
> > +     static float toFloat(QuantizedType v)
> > +     {
> > +             return static_cast<float>(v) / 128.0f;
> > +     }
> > +};
> > +
> > +using BrightnessHueQuantizer = Quantized<BrightnessHueTraits>;
> > +
> > +struct ContrastSaturationTraits {
> > +     using QuantizedType = uint8_t;
> > +     static constexpr QuantizedType fromFloat(float v)
> > +     {
> > +             int quantized = std::lround(v * 128.0f);
> > +             return static_cast<uint8_t>(std::clamp<int>(quantized, 0, 255));
> > +     }
> > +     static float toFloat(QuantizedType v)
> > +     {
> > +             return static_cast<float>(v) / 128.0f;
> > +     }
> > +};
> > +
> > +using ContrastSaturationQuantizer = Quantized<ContrastSaturationTraits>;
> > +
> > +using BrightnessQ = BrightnessHueQuantizer;
> > +using HueQ = BrightnessHueQuantizer;
> > +using ContrastQ = ContrastSaturationQuantizer;
> > +using SaturationQ = ContrastSaturationQuantizer;
> > +
> > +class QuantizedTest : public Test
> > +{
> > +protected:
> > +     int run()
> > +     {
> > +             /* Test construction from float */
> > +             {
> > +                     BrightnessQ b(0.5f);
> > +                     if (b.quantized() != 64 || std::abs(b.value() - 0.5f) > 0.01f)
> > +                             return TestFail;
> > +             }
> > +
> > +             /* Test construction from T */
> > +             {
> > +                     ContrastQ c(uint8_t(128));
> > +                     if (c.quantized() != 128 || std::abs(c.value() - 1.0f) > 0.01f)
> > +                             return TestFail;
> > +             }
> > +
> > +             /*
> > +              * Test construction from non-float/non-T type (Expected Fail)
> > +              * These should be a compile-time error if uncommented:
> > +              */
> > +             // BrightnessQ bad1(15); // overloaded ‘Quantized(int)’ is ambiguous
> > +             // BrightnessQ bad2(0xff); // overloaded ‘Quantized(int)’ is ambiguous
> > +             // ContrastQ bad3(0x33); // overloaded ‘Quantized(int)’ is ambiguous
> > +             // ContrastQ bad4(55U); // overloaded ‘Quantized(unsigned int)’ is ambiguous
> 
> I think you should be able to do something e.g.
> 
>    static_assert(!std::is_constructible_v<BrightnessQ, unsigned int>);
>    static_assert(!std::is_constructible_v<BrightnessQ, double>);
>    // etc.
> 
> if you want to test this.

Interesting, I didn't know I could do something like that - I'll give it
a go.



> 
> 
> > +
> > +             /* Test equality */
> > +             {
> > +                     BrightnessQ b1(0.5f), b2((int8_t)64);
> > +                     if (!(b1 == b2))
> > +                             return TestFail;
> > +             }
> > +
> > +             /* Test inequality */
> > +             {
> > +                     BrightnessQ b1(0.5f), b2(-0.5f);
> > +                     if (b1 == b2)
> 
> I would do
> 
>    if (!(b1 != b2))
> 

ack

> 
> > +                             return TestFail;
> > +             }
> > +
> > +             /* Test copying */
> > +             {
> > +                     BrightnessQ b1(0.25f);
> > +                     BrightnessQ b2 = b1;
> > +                     if (!(b1 == b2))
> > +                             return TestFail;
> > +             }
> > +
> > +             /* Test moving */
> > +             {
> > +                     BrightnessQ b1(0.25f);
> > +                     BrightnessQ b2 = std::move(b1); // Allow move semantics
> > +                     if (b2.value() != 0.25f)
> > +                             return TestFail;
> > +             }
> > +
> > +             /* Test assignment */
> > +             {
> > +                     ContrastQ c1(1.5f);
> > +                     ContrastQ c2(0.0f);
> > +                     c2 = c1;
> > +                     if (!(c1 == c2))
> > +                             return TestFail;
> > +             }
> > +
> > +             std::cout << "Quantised tests passed successfully." << std::endl;
> > +
> > +             return TestPass;
> > +     }
> > +};
> > +
> > +TEST_REGISTER(QuantizedTest)
>
Isaac Scott Nov. 18, 2025, 12:37 p.m. UTC | #3
Hi Kieran,

Thank you for the patch!

Reviewed-by: Isaac Scott <isaac.scott@ideasonboard.com>

Quoting Kieran Bingham (2025-11-14 00:54:06)
> Provide use case tests for the Quantized types to ensure construction
> and usages are consistent and work as expected.
> 
> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> 
> ---
> v3:
> - Rename quantized_type to QuantizedType
> 
>  test/ipa/libipa/meson.build   |   1 +
>  test/ipa/libipa/quantized.cpp | 130 ++++++++++++++++++++++++++++++++++
>  2 files changed, 131 insertions(+)
>  create mode 100644 test/ipa/libipa/quantized.cpp
> 
> diff --git a/test/ipa/libipa/meson.build b/test/ipa/libipa/meson.build
> index 2070bed70222..c3e255871f4f 100644
> --- a/test/ipa/libipa/meson.build
> +++ b/test/ipa/libipa/meson.build
> @@ -5,6 +5,7 @@ libipa_test = [
>      {'name': 'histogram', 'sources': ['histogram.cpp']},
>      {'name': 'interpolator', 'sources': ['interpolator.cpp']},
>      {'name': 'pwl', 'sources': ['pwl.cpp'] },
> +    {'name': 'quantized', 'sources': ['quantized.cpp']},
>  ]
>  
>  foreach test : libipa_test
> diff --git a/test/ipa/libipa/quantized.cpp b/test/ipa/libipa/quantized.cpp
> new file mode 100644
> index 000000000000..3bbd6dd48c14
> --- /dev/null
> +++ b/test/ipa/libipa/quantized.cpp
> @@ -0,0 +1,130 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (C) 2025, Ideas on Board
> + *
> + * Dual Type and Quantizer tests
> + */
> +
> +#include "../src/ipa/libipa/quantized.h"
> +
> +#include <algorithm>
> +#include <cmath>
> +#include <iostream>
> +#include <map>
> +#include <stdint.h>
> +
> +#include "test.h"
> +
> +using namespace std;
> +using namespace libcamera;
> +using namespace ipa;
> +
> +struct BrightnessHueTraits {
> +       using QuantizedType = int8_t;
> +       static constexpr QuantizedType fromFloat(float v)
> +       {
> +               int quantized = std::lround(v * 128.0f);
> +               return static_cast<int8_t>(std::clamp<int>(quantized, -128, 127));
> +       }
> +       static float toFloat(QuantizedType v)
> +       {
> +               return static_cast<float>(v) / 128.0f;
> +       }
> +};
> +
> +using BrightnessHueQuantizer = Quantized<BrightnessHueTraits>;
> +
> +struct ContrastSaturationTraits {
> +       using QuantizedType = uint8_t;
> +       static constexpr QuantizedType fromFloat(float v)
> +       {
> +               int quantized = std::lround(v * 128.0f);
> +               return static_cast<uint8_t>(std::clamp<int>(quantized, 0, 255));
> +       }
> +       static float toFloat(QuantizedType v)
> +       {
> +               return static_cast<float>(v) / 128.0f;
> +       }
> +};
> +
> +using ContrastSaturationQuantizer = Quantized<ContrastSaturationTraits>;
> +
> +using BrightnessQ = BrightnessHueQuantizer;
> +using HueQ = BrightnessHueQuantizer;
> +using ContrastQ = ContrastSaturationQuantizer;
> +using SaturationQ = ContrastSaturationQuantizer;
> +
> +class QuantizedTest : public Test
> +{
> +protected:
> +       int run()
> +       {
> +               /* Test construction from float */
> +               {
> +                       BrightnessQ b(0.5f);
> +                       if (b.quantized() != 64 || std::abs(b.value() - 0.5f) > 0.01f)
> +                               return TestFail;
> +               }
> +
> +               /* Test construction from T */
> +               {
> +                       ContrastQ c(uint8_t(128));
> +                       if (c.quantized() != 128 || std::abs(c.value() - 1.0f) > 0.01f)
> +                               return TestFail;
> +               }
> +
> +               /*
> +                * Test construction from non-float/non-T type (Expected Fail)
> +                * These should be a compile-time error if uncommented:
> +                */
> +               // BrightnessQ bad1(15); // overloaded ‘Quantized(int)’ is ambiguous
> +               // BrightnessQ bad2(0xff); // overloaded ‘Quantized(int)’ is ambiguous
> +               // ContrastQ bad3(0x33); // overloaded ‘Quantized(int)’ is ambiguous
> +               // ContrastQ bad4(55U); // overloaded ‘Quantized(unsigned int)’ is ambiguous
> +
> +               /* Test equality */
> +               {
> +                       BrightnessQ b1(0.5f), b2((int8_t)64);
> +                       if (!(b1 == b2))
> +                               return TestFail;
> +               }
> +
> +               /* Test inequality */
> +               {
> +                       BrightnessQ b1(0.5f), b2(-0.5f);
> +                       if (b1 == b2)
> +                               return TestFail;
> +               }
> +
> +               /* Test copying */
> +               {
> +                       BrightnessQ b1(0.25f);
> +                       BrightnessQ b2 = b1;
> +                       if (!(b1 == b2))
> +                               return TestFail;
> +               }
> +
> +               /* Test moving */
> +               {
> +                       BrightnessQ b1(0.25f);
> +                       BrightnessQ b2 = std::move(b1); // Allow move semantics
> +                       if (b2.value() != 0.25f)
> +                               return TestFail;
> +               }
> +
> +               /* Test assignment */
> +               {
> +                       ContrastQ c1(1.5f);
> +                       ContrastQ c2(0.0f);
> +                       c2 = c1;
> +                       if (!(c1 == c2))
> +                               return TestFail;
> +               }
> +
> +               std::cout << "Quantised tests passed successfully." << std::endl;
> +
> +               return TestPass;
> +       }
> +};
> +
> +TEST_REGISTER(QuantizedTest)
> -- 
> 2.51.1
>
Laurent Pinchart Nov. 19, 2025, 4:23 a.m. UTC | #4
On Fri, Nov 14, 2025 at 12:54:06AM +0000, Kieran Bingham wrote:
> Provide use case tests for the Quantized types to ensure construction
> and usages are consistent and work as expected.
> 
> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> 
> ---
> v3:
> - Rename quantized_type to QuantizedType
> 
>  test/ipa/libipa/meson.build   |   1 +
>  test/ipa/libipa/quantized.cpp | 130 ++++++++++++++++++++++++++++++++++
>  2 files changed, 131 insertions(+)
>  create mode 100644 test/ipa/libipa/quantized.cpp
> 
> diff --git a/test/ipa/libipa/meson.build b/test/ipa/libipa/meson.build
> index 2070bed70222..c3e255871f4f 100644
> --- a/test/ipa/libipa/meson.build
> +++ b/test/ipa/libipa/meson.build
> @@ -5,6 +5,7 @@ libipa_test = [
>      {'name': 'histogram', 'sources': ['histogram.cpp']},
>      {'name': 'interpolator', 'sources': ['interpolator.cpp']},
>      {'name': 'pwl', 'sources': ['pwl.cpp'] },
> +    {'name': 'quantized', 'sources': ['quantized.cpp']},
>  ]
>  
>  foreach test : libipa_test
> diff --git a/test/ipa/libipa/quantized.cpp b/test/ipa/libipa/quantized.cpp
> new file mode 100644
> index 000000000000..3bbd6dd48c14
> --- /dev/null
> +++ b/test/ipa/libipa/quantized.cpp
> @@ -0,0 +1,130 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (C) 2025, Ideas on Board
> + *
> + * Dual Type and Quantizer tests
> + */
> +
> +#include "../src/ipa/libipa/quantized.h"
> +
> +#include <algorithm>
> +#include <cmath>
> +#include <iostream>
> +#include <map>
> +#include <stdint.h>
> +
> +#include "test.h"
> +
> +using namespace std;
> +using namespace libcamera;
> +using namespace ipa;
> +
> +struct BrightnessHueTraits {
> +	using QuantizedType = int8_t;
> +	static constexpr QuantizedType fromFloat(float v)
> +	{
> +		int quantized = std::lround(v * 128.0f);
> +		return static_cast<int8_t>(std::clamp<int>(quantized, -128, 127));
> +	}
> +	static float toFloat(QuantizedType v)
> +	{
> +		return static_cast<float>(v) / 128.0f;
> +	}
> +};
> +
> +using BrightnessHueQuantizer = Quantized<BrightnessHueTraits>;
> +
> +struct ContrastSaturationTraits {
> +	using QuantizedType = uint8_t;
> +	static constexpr QuantizedType fromFloat(float v)
> +	{
> +		int quantized = std::lround(v * 128.0f);
> +		return static_cast<uint8_t>(std::clamp<int>(quantized, 0, 255));
> +	}
> +	static float toFloat(QuantizedType v)
> +	{
> +		return static_cast<float>(v) / 128.0f;
> +	}
> +};
> +
> +using ContrastSaturationQuantizer = Quantized<ContrastSaturationTraits>;
> +
> +using BrightnessQ = BrightnessHueQuantizer;
> +using HueQ = BrightnessHueQuantizer;
> +using ContrastQ = ContrastSaturationQuantizer;
> +using SaturationQ = ContrastSaturationQuantizer;
> +
> +class QuantizedTest : public Test
> +{
> +protected:
> +	int run()
> +	{
> +		/* Test construction from float */
> +		{
> +			BrightnessQ b(0.5f);
> +			if (b.quantized() != 64 || std::abs(b.value() - 0.5f) > 0.01f)
> +				return TestFail;
> +		}
> +
> +		/* Test construction from T */
> +		{
> +			ContrastQ c(uint8_t(128));
> +			if (c.quantized() != 128 || std::abs(c.value() - 1.0f) > 0.01f)
> +				return TestFail;
> +		}
> +
> +		/*
> +		 * Test construction from non-float/non-T type (Expected Fail)
> +		 * These should be a compile-time error if uncommented:
> +		 */
> +		// BrightnessQ bad1(15); // overloaded ‘Quantized(int)’ is ambiguous
> +		// BrightnessQ bad2(0xff); // overloaded ‘Quantized(int)’ is ambiguous
> +		// ContrastQ bad3(0x33); // overloaded ‘Quantized(int)’ is ambiguous
> +		// ContrastQ bad4(55U); // overloaded ‘Quantized(unsigned int)’ is ambiguous

C-style comments.

> +
> +		/* Test equality */
> +		{
> +			BrightnessQ b1(0.5f), b2((int8_t)64);
> +			if (!(b1 == b2))
> +				return TestFail;
> +		}
> +
> +		/* Test inequality */
> +		{
> +			BrightnessQ b1(0.5f), b2(-0.5f);
> +			if (b1 == b2)
> +				return TestFail;
> +		}
> +
> +		/* Test copying */
> +		{
> +			BrightnessQ b1(0.25f);
> +			BrightnessQ b2 = b1;
> +			if (!(b1 == b2))
> +				return TestFail;
> +		}
> +
> +		/* Test moving */
> +		{
> +			BrightnessQ b1(0.25f);
> +			BrightnessQ b2 = std::move(b1); // Allow move semantics

C-style comments.

> +			if (b2.value() != 0.25f)
> +				return TestFail;
> +		}

There are no move constructors or assignment operators in the class, I'd
drop this.

> +
> +		/* Test assignment */
> +		{
> +			ContrastQ c1(1.5f);
> +			ContrastQ c2(0.0f);
> +			c2 = c1;
> +			if (!(c1 == c2))
> +				return TestFail;
> +		}
> +
> +		std::cout << "Quantised tests passed successfully." << std::endl;
> +
> +		return TestPass;
> +	}
> +};
> +
> +TEST_REGISTER(QuantizedTest)
Kieran Bingham Nov. 19, 2025, 9:39 a.m. UTC | #5
Quoting Laurent Pinchart (2025-11-19 04:23:35)
> On Fri, Nov 14, 2025 at 12:54:06AM +0000, Kieran Bingham wrote:
> > Provide use case tests for the Quantized types to ensure construction
> > and usages are consistent and work as expected.
> > 
> > Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > 
> > ---
> > v3:
> > - Rename quantized_type to QuantizedType
> > 
> >  test/ipa/libipa/meson.build   |   1 +
> >  test/ipa/libipa/quantized.cpp | 130 ++++++++++++++++++++++++++++++++++
> >  2 files changed, 131 insertions(+)
> >  create mode 100644 test/ipa/libipa/quantized.cpp
> > 
> > diff --git a/test/ipa/libipa/meson.build b/test/ipa/libipa/meson.build
> > index 2070bed70222..c3e255871f4f 100644
> > --- a/test/ipa/libipa/meson.build
> > +++ b/test/ipa/libipa/meson.build
> > @@ -5,6 +5,7 @@ libipa_test = [
> >      {'name': 'histogram', 'sources': ['histogram.cpp']},
> >      {'name': 'interpolator', 'sources': ['interpolator.cpp']},
> >      {'name': 'pwl', 'sources': ['pwl.cpp'] },
> > +    {'name': 'quantized', 'sources': ['quantized.cpp']},
> >  ]
> >  
> >  foreach test : libipa_test
> > diff --git a/test/ipa/libipa/quantized.cpp b/test/ipa/libipa/quantized.cpp
> > new file mode 100644
> > index 000000000000..3bbd6dd48c14
> > --- /dev/null
> > +++ b/test/ipa/libipa/quantized.cpp
> > @@ -0,0 +1,130 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > +/*
> > + * Copyright (C) 2025, Ideas on Board
> > + *
> > + * Dual Type and Quantizer tests
> > + */
> > +
> > +#include "../src/ipa/libipa/quantized.h"
> > +
> > +#include <algorithm>
> > +#include <cmath>
> > +#include <iostream>
> > +#include <map>
> > +#include <stdint.h>
> > +
> > +#include "test.h"
> > +
> > +using namespace std;
> > +using namespace libcamera;
> > +using namespace ipa;
> > +
> > +struct BrightnessHueTraits {
> > +     using QuantizedType = int8_t;
> > +     static constexpr QuantizedType fromFloat(float v)
> > +     {
> > +             int quantized = std::lround(v * 128.0f);
> > +             return static_cast<int8_t>(std::clamp<int>(quantized, -128, 127));
> > +     }
> > +     static float toFloat(QuantizedType v)
> > +     {
> > +             return static_cast<float>(v) / 128.0f;
> > +     }
> > +};
> > +
> > +using BrightnessHueQuantizer = Quantized<BrightnessHueTraits>;
> > +
> > +struct ContrastSaturationTraits {
> > +     using QuantizedType = uint8_t;
> > +     static constexpr QuantizedType fromFloat(float v)
> > +     {
> > +             int quantized = std::lround(v * 128.0f);
> > +             return static_cast<uint8_t>(std::clamp<int>(quantized, 0, 255));
> > +     }
> > +     static float toFloat(QuantizedType v)
> > +     {
> > +             return static_cast<float>(v) / 128.0f;
> > +     }
> > +};
> > +
> > +using ContrastSaturationQuantizer = Quantized<ContrastSaturationTraits>;
> > +
> > +using BrightnessQ = BrightnessHueQuantizer;
> > +using HueQ = BrightnessHueQuantizer;
> > +using ContrastQ = ContrastSaturationQuantizer;
> > +using SaturationQ = ContrastSaturationQuantizer;
> > +
> > +class QuantizedTest : public Test
> > +{
> > +protected:
> > +     int run()
> > +     {
> > +             /* Test construction from float */
> > +             {
> > +                     BrightnessQ b(0.5f);
> > +                     if (b.quantized() != 64 || std::abs(b.value() - 0.5f) > 0.01f)
> > +                             return TestFail;
> > +             }
> > +
> > +             /* Test construction from T */
> > +             {
> > +                     ContrastQ c(uint8_t(128));
> > +                     if (c.quantized() != 128 || std::abs(c.value() - 1.0f) > 0.01f)
> > +                             return TestFail;
> > +             }
> > +
> > +             /*
> > +              * Test construction from non-float/non-T type (Expected Fail)
> > +              * These should be a compile-time error if uncommented:
> > +              */
> > +             // BrightnessQ bad1(15); // overloaded ‘Quantized(int)’ is ambiguous
> > +             // BrightnessQ bad2(0xff); // overloaded ‘Quantized(int)’ is ambiguous
> > +             // ContrastQ bad3(0x33); // overloaded ‘Quantized(int)’ is ambiguous
> > +             // ContrastQ bad4(55U); // overloaded ‘Quantized(unsigned int)’ is ambiguous
> 
> C-style comments.

This was intentional to allow easy 'uncommenting for test'.
Of course that doesn't apply to the text after which is the actual
comment.

But anyway, thanks to Barnabas' proposal these are removed so there's no
issue here anymore.


> 
> > +
> > +             /* Test equality */
> > +             {
> > +                     BrightnessQ b1(0.5f), b2((int8_t)64);
> > +                     if (!(b1 == b2))
> > +                             return TestFail;
> > +             }
> > +
> > +             /* Test inequality */
> > +             {
> > +                     BrightnessQ b1(0.5f), b2(-0.5f);
> > +                     if (b1 == b2)
> > +                             return TestFail;
> > +             }
> > +
> > +             /* Test copying */
> > +             {
> > +                     BrightnessQ b1(0.25f);
> > +                     BrightnessQ b2 = b1;
> > +                     if (!(b1 == b2))
> > +                             return TestFail;
> > +             }
> > +
> > +             /* Test moving */
> > +             {
> > +                     BrightnessQ b1(0.25f);
> > +                     BrightnessQ b2 = std::move(b1); // Allow move semantics
> 
> C-style comments.
> 
> > +                     if (b2.value() != 0.25f)
> > +                             return TestFail;
> > +             }
> 
> There are no move constructors or assignment operators in the class, I'd
> drop this.

Ok.

> > +
> > +             /* Test assignment */
> > +             {
> > +                     ContrastQ c1(1.5f);
> > +                     ContrastQ c2(0.0f);
> > +                     c2 = c1;
> > +                     if (!(c1 == c2))
> > +                             return TestFail;
> > +             }
> > +
> > +             std::cout << "Quantised tests passed successfully." << std::endl;
> > +
> > +             return TestPass;
> > +     }
> > +};
> > +
> > +TEST_REGISTER(QuantizedTest)
> 
> -- 
> Regards,
> 
> Laurent Pinchart

Patch
diff mbox series

diff --git a/test/ipa/libipa/meson.build b/test/ipa/libipa/meson.build
index 2070bed70222..c3e255871f4f 100644
--- a/test/ipa/libipa/meson.build
+++ b/test/ipa/libipa/meson.build
@@ -5,6 +5,7 @@  libipa_test = [
     {'name': 'histogram', 'sources': ['histogram.cpp']},
     {'name': 'interpolator', 'sources': ['interpolator.cpp']},
     {'name': 'pwl', 'sources': ['pwl.cpp'] },
+    {'name': 'quantized', 'sources': ['quantized.cpp']},
 ]
 
 foreach test : libipa_test
diff --git a/test/ipa/libipa/quantized.cpp b/test/ipa/libipa/quantized.cpp
new file mode 100644
index 000000000000..3bbd6dd48c14
--- /dev/null
+++ b/test/ipa/libipa/quantized.cpp
@@ -0,0 +1,130 @@ 
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2025, Ideas on Board
+ *
+ * Dual Type and Quantizer tests
+ */
+
+#include "../src/ipa/libipa/quantized.h"
+
+#include <algorithm>
+#include <cmath>
+#include <iostream>
+#include <map>
+#include <stdint.h>
+
+#include "test.h"
+
+using namespace std;
+using namespace libcamera;
+using namespace ipa;
+
+struct BrightnessHueTraits {
+	using QuantizedType = int8_t;
+	static constexpr QuantizedType fromFloat(float v)
+	{
+		int quantized = std::lround(v * 128.0f);
+		return static_cast<int8_t>(std::clamp<int>(quantized, -128, 127));
+	}
+	static float toFloat(QuantizedType v)
+	{
+		return static_cast<float>(v) / 128.0f;
+	}
+};
+
+using BrightnessHueQuantizer = Quantized<BrightnessHueTraits>;
+
+struct ContrastSaturationTraits {
+	using QuantizedType = uint8_t;
+	static constexpr QuantizedType fromFloat(float v)
+	{
+		int quantized = std::lround(v * 128.0f);
+		return static_cast<uint8_t>(std::clamp<int>(quantized, 0, 255));
+	}
+	static float toFloat(QuantizedType v)
+	{
+		return static_cast<float>(v) / 128.0f;
+	}
+};
+
+using ContrastSaturationQuantizer = Quantized<ContrastSaturationTraits>;
+
+using BrightnessQ = BrightnessHueQuantizer;
+using HueQ = BrightnessHueQuantizer;
+using ContrastQ = ContrastSaturationQuantizer;
+using SaturationQ = ContrastSaturationQuantizer;
+
+class QuantizedTest : public Test
+{
+protected:
+	int run()
+	{
+		/* Test construction from float */
+		{
+			BrightnessQ b(0.5f);
+			if (b.quantized() != 64 || std::abs(b.value() - 0.5f) > 0.01f)
+				return TestFail;
+		}
+
+		/* Test construction from T */
+		{
+			ContrastQ c(uint8_t(128));
+			if (c.quantized() != 128 || std::abs(c.value() - 1.0f) > 0.01f)
+				return TestFail;
+		}
+
+		/*
+		 * Test construction from non-float/non-T type (Expected Fail)
+		 * These should be a compile-time error if uncommented:
+		 */
+		// BrightnessQ bad1(15); // overloaded ‘Quantized(int)’ is ambiguous
+		// BrightnessQ bad2(0xff); // overloaded ‘Quantized(int)’ is ambiguous
+		// ContrastQ bad3(0x33); // overloaded ‘Quantized(int)’ is ambiguous
+		// ContrastQ bad4(55U); // overloaded ‘Quantized(unsigned int)’ is ambiguous
+
+		/* Test equality */
+		{
+			BrightnessQ b1(0.5f), b2((int8_t)64);
+			if (!(b1 == b2))
+				return TestFail;
+		}
+
+		/* Test inequality */
+		{
+			BrightnessQ b1(0.5f), b2(-0.5f);
+			if (b1 == b2)
+				return TestFail;
+		}
+
+		/* Test copying */
+		{
+			BrightnessQ b1(0.25f);
+			BrightnessQ b2 = b1;
+			if (!(b1 == b2))
+				return TestFail;
+		}
+
+		/* Test moving */
+		{
+			BrightnessQ b1(0.25f);
+			BrightnessQ b2 = std::move(b1); // Allow move semantics
+			if (b2.value() != 0.25f)
+				return TestFail;
+		}
+
+		/* Test assignment */
+		{
+			ContrastQ c1(1.5f);
+			ContrastQ c2(0.0f);
+			c2 = c1;
+			if (!(c1 == c2))
+				return TestFail;
+		}
+
+		std::cout << "Quantised tests passed successfully." << std::endl;
+
+		return TestPass;
+	}
+};
+
+TEST_REGISTER(QuantizedTest)