blob: e371ff164062ef88917c13dad43f243b9ee642ed [file] [log] [blame]
/*
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Use separate test for RVV as algorithm is implemented differently
// Implementation based on:
// https://chao-ji.github.io/jekyll/update/2018/07/19/BilinearResize.html
#include <climits>
#include <cmath>
#include "pw_unit_test/framework.h"
#include "risp4ml/common/constants.h"
#include "risp4ml/common/test_utils.h"
#include "risp4ml/isp_stages/downscale.h"
static constexpr uint16_t kScalePrecision = 10;
static constexpr uint32_t kScaleFixedOne = (1 << kScalePrecision);
static constexpr float kOutBitsShift = 1 << (kRawPipelineBpp - kPipeOutputBpp);
class DownscaleRvvTest : public ::testing::Test {
protected:
void setup(uint16_t in_ch, uint16_t in_height, uint16_t in_width,
uint16_t out_ch, uint16_t out_height, uint16_t out_width) {
in_ = image_new(in_ch, in_height, in_width);
out_ = imageu8_new(out_ch, out_height, out_width);
params_.enable = true;
params_.scale_precision = kScalePrecision;
params_.scale_fixed_one = kScaleFixedOne;
}
void TearDown() override {
image_delete(in_);
imageu8_delete(out_);
}
ImageU8* imageu8_new(uint16_t num_channels, uint16_t height, uint16_t width);
void imageu8_delete(ImageU8* image) {
if (image) {
if (image->data) free(image->data);
free(image);
}
}
pixel_type_t imageu8_pixel_val(ImageU8* image, uint16_t c, uint16_t y,
uint16_t x) {
const uint32_t stride_c = 1;
const uint16_t stride_y = image->num_channels * image->width;
const uint16_t stride_x = image->num_channels;
return *(image->data + c * stride_c + y * stride_y + x * stride_x);
}
float ExpectedOut(uint16_t y, uint16_t x);
void ScaleRampImageTest(uint16_t output_width, uint16_t output_height,
uint16_t input_width = 640,
uint16_t input_height = 480);
struct BilinearScaleTestValues {
uint16_t output_width;
uint16_t output_height;
uint16_t input_width;
uint16_t input_height;
};
Image* in_;
ImageU8* out_;
DownscaleParams params_;
};
ImageU8* DownscaleRvvTest::imageu8_new(uint16_t num_channels, uint16_t height,
uint16_t width) {
ImageU8* image = reinterpret_cast<ImageU8*>(malloc(sizeof(ImageU8)));
if (image) {
image->num_channels = num_channels;
image->height = height;
image->width = width;
uint32_t num_pixels = width * height * num_channels;
image->data =
reinterpret_cast<uint8_t*>(malloc(num_pixels * sizeof(uint8_t)));
}
return image;
}
float DownscaleRvvTest::ExpectedOut(uint16_t y, uint16_t x) {
float x_ratio = (static_cast<float>(in_->width) - 1) / (out_->width - 1);
float y_ratio = (static_cast<float>(in_->height) - 1) / (out_->height - 1);
uint32_t x_l = (uint32_t)(x_ratio * x);
uint32_t x_h = (x_l == in_->width - 1) ? x_l : x_l + 1;
uint32_t y_l = y_ratio * y;
uint32_t y_h = y_l == in_->height - 1 ? y_l : y_l + 1;
float x_weight = (x_ratio * x) - x_l;
float y_weight = (y_ratio * y) - y_l;
pixel_type_t a = image_pixel_val(in_, 0, y_l, x_l);
pixel_type_t b = image_pixel_val(in_, 0, y_l, x_h);
pixel_type_t c = image_pixel_val(in_, 0, y_h, x_l);
pixel_type_t d = image_pixel_val(in_, 0, y_h, x_h);
float expected_out = a * (1 - x_weight) * (1 - y_weight) +
b * x_weight * (1 - y_weight) +
c * y_weight * (1 - x_weight) + d * x_weight * y_weight;
expected_out = floorf(expected_out / kOutBitsShift);
return expected_out;
}
// Helper function for 2D ramp tests. image is downscaled successfully.
void DownscaleRvvTest::ScaleRampImageTest(uint16_t output_width,
uint16_t output_height,
uint16_t input_width,
uint16_t input_height) {
constexpr int kTolerance = 1; // Tolerance for rounding error.
setup(1, input_height, input_width, 1, output_height, output_width);
// Fill in_ images as 2D ramp whose values are increased from the
// top-left corner to the bottom-right corner.
for (uint16_t y = 0; y < input_height; ++y) {
for (uint16_t x = 0; x < input_width; ++x) {
*image_pixel(in_, 0, y, x) = (y * input_width + x) % (1024);
}
}
set_downscale_param(&params_);
downscale_process(in_, out_);
for (uint16_t y = 0; y < output_height; ++y) {
for (uint16_t x = 0; x < output_width; ++x) {
float expected_out = ExpectedOut(y, x);
float diff = std::abs(
static_cast<float>(imageu8_pixel_val(out_, 0, y, x)) - expected_out);
ASSERT_LE(diff, kTolerance);
}
}
}
TEST_F(DownscaleRvvTest, NoScaleTest) {
constexpr uint16_t kOutputWidth = 128;
constexpr uint16_t kInputHeight = 96;
setup(1, kInputHeight, kOutputWidth, 1, kInputHeight, kOutputWidth);
// Generate random image.
InitImageRandom(in_, 0, USHRT_MAX);
set_downscale_param(&params_);
downscale_process(in_, out_);
// Verify the out_ image is identical to the in_ image.
for (uint16_t y = 0; y < kInputHeight; ++y) {
for (uint16_t x = 0; x < kOutputWidth; ++x) {
ASSERT_EQ(imageu8_pixel_val(out_, 0, y, x),
static_cast<pixel_type_t>(
floorf(image_pixel_val(in_, 0, y, x) >>
(kRawPipelineBpp - kPipeOutputBpp))));
}
}
}
TEST_F(DownscaleRvvTest, DownscaleRvvTest) {
std::vector<BilinearScaleTestValues> tests = {
{8, 12, 64, 64}, {320, 240, 640, 480}, {80, 60, 640, 480},
{220, 95, 640, 480}, {415, 125, 640, 480}, {122, 13, 200, 100}};
for (const auto& test : tests) {
ScaleRampImageTest(test.output_width, test.output_height, test.input_width,
test.input_height);
}
}
TEST_F(DownscaleRvvTest, Trivial3DTest) {
constexpr uint16_t kChannels = 3;
constexpr uint16_t kInputHeight = 5;
constexpr uint16_t kInputWidth = 5;
constexpr uint16_t kVerScale = 2;
constexpr uint16_t kHorScale = 2;
constexpr uint16_t kOutputHeight = (kInputHeight - 1) / kVerScale + 1;
constexpr uint16_t kOutputWidth = (kInputWidth - 1) / kHorScale + 1;
setup(kChannels, kInputHeight, kInputWidth, kChannels, kOutputHeight,
kOutputWidth);
for (uint16_t c = 0; c < kChannels; ++c) {
for (uint16_t y = 0; y < kInputHeight; ++y) {
for (uint16_t x = 0; x < kInputWidth; ++x) {
*image_pixel(in_, c, y, x) = ((y * kInputWidth + x) * 10 + c) << 8;
}
}
}
set_downscale_param(&params_);
downscale_process(in_, out_);
// for exact integer ratios out_ is just downsampled in_
for (uint16_t c = 0; c < kChannels; ++c) {
for (uint16_t y = 0; y < kOutputHeight; ++y) {
for (uint16_t x = 0; x < kOutputWidth; ++x) {
ASSERT_EQ(imageu8_pixel_val(out_, c, y, x),
image_pixel_val(in_, c, y * kVerScale, x * kHorScale) >> 8);
}
}
}
}