From 2ea4d1793153446f8257fe2255d8354c3f1b4be5 Mon Sep 17 00:00:00 2001 From: Ioana Ghiban Date: Tue, 13 Feb 2024 07:50:15 +0100 Subject: [PATCH] [test] Add tests for scale --- intrinsiccv/include/intrinsiccv/intrinsiccv.h | 21 ++ test/api/test_scale.cpp | 352 +++++++++++++++++- test/framework/operation.h | 2 +- 3 files changed, 372 insertions(+), 3 deletions(-) diff --git a/intrinsiccv/include/intrinsiccv/intrinsiccv.h b/intrinsiccv/include/intrinsiccv/intrinsiccv.h index 7ebc3589f..bea02524a 100644 --- a/intrinsiccv/include/intrinsiccv/intrinsiccv.h +++ b/intrinsiccv/include/intrinsiccv/intrinsiccv.h @@ -1089,6 +1089,27 @@ intrinsiccv_error_t intrinsiccv_min_max_loc_u8(const uint8_t *src, size_t *min_offset, size_t *max_offset); +/// Multiplies the elements in `src` by `scale`, then adds `shift` to the +/// result and stores it in `dst`. +/// +/// The result is saturated, i.e. it is the smallest/largest number of the +/// type of the element if the result would underflow/overflow. Source data +/// length (in bytes) is `stride` * `height`. Width and height are the same +/// for the source and destination. +/// +/// @param src Pointer to the source data. Must be non-null. +/// @param src_stride Distance in bytes from the start of one row to the +/// start of the next row for the source data. +/// Must not be less than width * sizeof(type). +/// @param dst Pointer to the destination data. Must be non-null. +/// @param dst_stride Distance in bytes from the start of one row to the +/// start of the next row for the destination data. +/// Must not be less than width * sizeof(type). +/// @param width Number of elements in a row. +/// @param height Number of rows in the data. +/// @param scale Value to multiply the input by. +/// @param shift Value to add to the result. +/// intrinsiccv_error_t intrinsiccv_scale_u8(const uint8_t *src, size_t src_stride, uint8_t *dst, size_t dst_stride, size_t width, size_t height, diff --git a/test/api/test_scale.cpp b/test/api/test_scale.cpp index 989fbd942..46ed272a1 100644 --- a/test/api/test_scale.cpp +++ b/test/api/test_scale.cpp @@ -4,8 +4,8 @@ #include -#include - +#include "framework/array.h" +#include "framework/generator.h" #include "framework/operation.h" #include "intrinsiccv/intrinsiccv.h" @@ -14,12 +14,360 @@ INTRINSICCV_SCALE(uint8_t, u8); +template +class ScaleTestBase : public UnaryOperationTest { + using UnaryOperationTest::min; + using UnaryOperationTest::max; + + // Calls the API-under-test in the appropriate way. + intrinsiccv_error_t call_api() override { + return intrinsiccv_scale_u8( + this->inputs_[0].data(), this->inputs_[0].stride(), + this->actual_[0].data(), this->actual_[0].stride(), this->width(), + this->height(), this->scale(), this->shift()); + } + virtual float scale() = 0; + virtual float shift() = 0; + + // Prepares expected outputs for the operation. + void setup() override { + ElementType expected = 0; + if (shift() < min()) { + expected = min(); + } else if (shift() > max()) { + expected = max(); + } else { + expected = lrintf(shift()); + } + this->expected_[0].fill(expected); + UnaryOperationTest::setup(); + } +}; // end of class ScaleTestBase + +template +class ScaleTestLinearBase { + static constexpr ElementType min() { + return std::numeric_limits::min(); + } + static constexpr ElementType max() { + return std::numeric_limits::max(); + } + virtual float scale() = 0; + virtual float shift() = 0; + + public: + void test_scalar() { + size_t width = test::Options::vector_length() - 1; + test_linear(width); + } + void test_vector() { + size_t width = test::Options::vector_length() * 2; + test_linear(width); + } + + private: + class GenerateLinearSeries : public test::Generator { + public: + explicit GenerateLinearSeries(ElementType start_from) + : counter_{start_from} {} + + std::optional next() override { return counter_++; } + + private: + ElementType counter_; + }; // end of class GenerateLinearSeries + + void test_linear(size_t width) { + size_t height = ((std::numeric_limits::max() - + std::numeric_limits::min()) / + width) + + 1; + test::Array2D source(width, height, 1, 1); + test::Array2D expected(width, height, 1, 1); + test::Array2D actual = + test::Array2D(width, height, 1, 1); + + GenerateLinearSeries generator(std::numeric_limits::min()); + + source.fill(&generator); + + calculate_expected(source, expected); + + ASSERT_EQ( + INTRINSICCV_OK, + intrinsiccv_scale_u8(source.data(), source.stride(), actual.data(), + actual.stride(), width, height, scale(), shift())); + + EXPECT_EQ_ARRAY2D(expected, actual); + } + + protected: + void calculate_expected(const test::Array2D& source, + test::Array2D& expected) { + for (size_t hindex = 0; hindex < source.height(); ++hindex) { + for (size_t vindex = 0; vindex < source.width(); ++vindex) { + ElementType calculated = 0; + // NOLINTBEGIN(clang-analyzer-core.UndefinedBinaryOperatorResult) + float result = *source.at(hindex, vindex) * scale() + shift(); + // NOLINTEND(clang-analyzer-core.UndefinedBinaryOperatorResult) + // NOLINTBEGIN(clang-analyzer-core.uninitialized.Assign) + if (result > max()) { + calculated = max(); + } else if (result < min()) { + calculated = min(); + } else { + calculated = lrintf(result); + } + *expected.at(hindex, vindex) = calculated; + // NOLINTEND(clang-analyzer-core.uninitialized.Assign) + } + } + } +}; // end of class ScaleTestLinearBase + +template +class ScaleTestLinear1 final : public ScaleTestLinearBase { + float scale() override { return 10; }; + float shift() override { return 13; }; +}; + +template +class ScaleTestLinear2 final : public ScaleTestLinearBase { + float scale() override { return 14.69; }; + float shift() override { return 10.13; }; +}; + +template +class ScaleTestLinear3 final : public ScaleTestLinearBase { + float scale() override { return 0.18; }; + float shift() override { return 1.41; }; +}; + +template +class ScaleTestAdd final : public ScaleTestBase { + using ArrayType = test::Array2D; + using Elements = typename UnaryOperationTest::Elements; + + float scale() override { return 6; } + float shift() override { return 2; } + + const std::vector& test_elements() override { + static const std::vector kTestElements = { + // clang-format off + { 8, 50}, + {12, 74}, + // clang-format on + }; + return kTestElements; + } +}; + +template +class ScaleTestSubtract final : public ScaleTestBase { + using Elements = typename UnaryOperationTest::Elements; + + float scale() override { return 8; } + float shift() override { return -3; } + + const std::vector& test_elements() override { + static const std::vector kTestElements = { + // clang-format off + { 6, 45}, + { 20, 157}, + // clang-format on + }; + return kTestElements; + } +}; + +template +class ScaleTestDivide final : public ScaleTestBase { + using Elements = typename UnaryOperationTest::Elements; + + float scale() override { return 0.25; } + float shift() override { return 3; } + + const std::vector& test_elements() override { + static const std::vector kTestElements = { + // clang-format off + { 252, 66}, + { 255, 67}, + // clang-format on + }; + return kTestElements; + } +}; + +template +class ScaleTestMultiply final : public ScaleTestBase { + using Elements = typename UnaryOperationTest::Elements; + + float scale() override { return 3.14; } + float shift() override { return 2.72; } + + const std::vector& test_elements() override { + static const std::vector kTestElements = { + // clang-format off + { 60, 191}, + { 75, 238}, + // clang-format on + }; + return kTestElements; + } +}; + +template +class ScaleTestZero final : public ScaleTestBase { + using Elements = typename UnaryOperationTest::Elements; + using UnaryOperationTest::min; + using UnaryOperationTest::max; + + float scale() override { return 0; } + float shift() override { return 0; } + + const std::vector& test_elements() override { + static const std::vector kTestElements = { + // clang-format off + { min(), 0}, + { 0, 0}, + { max(), 0}, + // clang-format on + }; + return kTestElements; + } +}; + +template +class ScaleTestUnderflowByShift final : public ScaleTestBase { + using Elements = typename UnaryOperationTest::Elements; + using UnaryOperationTest::min; + using UnaryOperationTest::max; + + float scale() override { return 1; } + float shift() override { return -1; } + + const std::vector& test_elements() override { + static const std::vector kTestElements = { + // clang-format off + {min() + 1, min()}, + { min(), min()}, + // clang-format on + }; + return kTestElements; + } +}; + +template +class ScaleTestOverflowByShift final : public ScaleTestBase { + using Elements = typename UnaryOperationTest::Elements; + using UnaryOperationTest::max; + + float scale() override { return 1; } + float shift() override { return 1; } + + const std::vector& test_elements() override { + static const std::vector kTestElements = { + // clang-format off + {max() - 1, max()}, + { max(), max()}, + // clang-format on + }; + return kTestElements; + } +}; + +template +class ScaleTestUnderflowByScale final : public ScaleTestBase { + using Elements = typename UnaryOperationTest::Elements; + using UnaryOperationTest::max; + using UnaryOperationTest::min; + + float scale() override { return -2; } + float shift() override { return 0; } + + const std::vector& test_elements() override { + static const std::vector kTestElements = { + // clang-format off + { max(), min()} + // clang-format on + }; + return kTestElements; + } +}; + +template +class ScaleTestOverflowByScale final : public ScaleTestBase { + using Elements = typename UnaryOperationTest::Elements; + using UnaryOperationTest::max; + + float scale() override { return 2; } + float shift() override { return 0; } + + const std::vector& test_elements() override { + static const std::vector kTestElements = { + // clang-format off + { max(), max()}, + // clang-format on + }; + return kTestElements; + } +}; + template class ScaleTest : public testing::Test {}; using ElementTypes = ::testing::Types; + +// Tests \ref intrinsiccv_scale_u8 API. TYPED_TEST_SUITE(ScaleTest, ElementTypes); +TYPED_TEST(ScaleTest, TestScalar1) { + ScaleTestLinear1{}.test_scalar(); +} +TYPED_TEST(ScaleTest, TestVector1) { + ScaleTestLinear1{}.test_vector(); +} + +TYPED_TEST(ScaleTest, TestScalar2) { + ScaleTestLinear2{}.test_scalar(); +} +TYPED_TEST(ScaleTest, TestVector2) { + ScaleTestLinear2{}.test_vector(); +} + +TYPED_TEST(ScaleTest, TestScalar3) { + ScaleTestLinear3{}.test_scalar(); +} +TYPED_TEST(ScaleTest, TestVector3) { + ScaleTestLinear3{}.test_vector(); +} + +TYPED_TEST(ScaleTest, TestAdd) { ScaleTestAdd{}.test(); } + +TYPED_TEST(ScaleTest, TestSubtract) { ScaleTestSubtract{}.test(); } + +TYPED_TEST(ScaleTest, TestDivide) { ScaleTestDivide{}.test(); } + +TYPED_TEST(ScaleTest, TestMultiply) { ScaleTestMultiply{}.test(); } + +TYPED_TEST(ScaleTest, TestZero) { ScaleTestZero{}.test(); } + +TYPED_TEST(ScaleTest, TestUnderflowByShift) { + ScaleTestUnderflowByShift{}.test(); +} + +TYPED_TEST(ScaleTest, TestOverflowByShift) { + ScaleTestOverflowByShift{}.test(); +} + +TYPED_TEST(ScaleTest, TestUnderflowByScale) { + ScaleTestUnderflowByScale{}.test(); +} + +TYPED_TEST(ScaleTest, TestOverflowByScale) { + ScaleTestOverflowByScale{}.test(); +} + TYPED_TEST(ScaleTest, NullPointer) { TypeParam src[1] = {}, dst[1]; test::test_null_args(scale(), src, sizeof(TypeParam), dst, diff --git a/test/framework/operation.h b/test/framework/operation.h index d03735983..08d3ec05b 100644 --- a/test/framework/operation.h +++ b/test/framework/operation.h @@ -58,7 +58,7 @@ class OperationTest { virtual const std::vector& test_elements() = 0; // Prepares inputs and expected outputs for the operation. - void setup() { + virtual void setup() { auto elements_list = test_elements(); // Check that the number of elements fit into the buffers. ASSERT_LE(elements_list.size(), height()); -- GitLab