blob: f6d55547b7ac06aab9f702cc78e393a003e00c44 [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.
*/
#include "risp4ml/isp_stages/dg.h"
#include "pw_unit_test/framework.h"
#include "risp4ml/common/constants.h"
#include "risp4ml/common/test_utils.h"
#include "risp4ml/common/utils.h"
static constexpr uint16_t kDgFractional = kRawPipelineFraction;
static constexpr uint16_t kFrameSize = 16;
class DgTest : public ::testing::Test {
protected:
void SetUp() override {
in_ = image_new(1, kFrameSize, kFrameSize);
out_ = image_new(1, kFrameSize, kFrameSize);
num_bytes_ =
in_->num_channels * in_->height * in_->width * sizeof(pixel_type_t);
}
void TearDown() override {
image_delete(in_);
image_delete(out_);
}
Image* in_;
Image* out_;
uint32_t num_bytes_;
};
TEST_F(DgTest, Bypass) {
InitImage(in_, 1);
memcpy(out_->data, in_->data, num_bytes_);
// Set gain to 2x, gain is in 8.16 format.
uint16_t gain = 2 << kDgFractional;
DgParams params = {.enable = false, .gains = {gain, gain, gain, gain}};
set_dg_params(&params);
dg_process(out_);
// Expect no change.
for (uint16_t c = 0; c < in_->num_channels; ++c) {
for (uint16_t y = 0; y < in_->height; ++y) {
for (uint16_t x = 0; x < in_->width; ++x) {
ASSERT_EQ(image_pixel_val(in_, c, y, x),
image_pixel_val(out_, c, y, x));
}
}
}
}
TEST_F(DgTest, NoChangeRandomPixel) {
InitImageRandom(in_, kRawPipelineMinVal, kRawPipelineMaxVal);
// Force max/min values to be included.
*image_pixel(in_, 0, 0, 0) = 0;
*image_pixel(in_, 0, 0, 1) = kRawPipelineMaxVal;
memcpy(out_->data, in_->data, num_bytes_);
// Set gain to 1x, gain is in_ 8.8 format.
uint16_t gain = 1 << kDgFractional;
DgParams params = {.enable = true, .gains = {gain, gain, gain, gain}};
set_dg_params(&params);
dg_process(out_);
// Expect no change.
for (uint16_t c = 0; c < in_->num_channels; ++c) {
for (uint16_t y = 0; y < in_->height; ++y) {
for (uint16_t x = 0; x < in_->width; ++x) {
ASSERT_EQ(image_pixel_val(in_, c, y, x),
image_pixel_val(out_, c, y, x));
}
}
}
}
TEST_F(DgTest, TwoTimesGainRandomPixel) {
// Set pixel values inside 0.5x max range, to multiply by 2x without clipping.
InitImageRandom(in_, kRawPipelineMinVal / 2, kRawPipelineMaxVal / 2);
// Force max/min values to be included.
*image_pixel(in_, 0, 0, 0) = kRawPipelineMinVal / 2;
*image_pixel(in_, 0, 0, 1) = kRawPipelineMaxVal / 2;
memcpy(out_->data, in_->data, num_bytes_);
// Set gain to 2x, gain is in_ 8.16 format.
uint16_t gain = 2 << kDgFractional;
DgParams params = {.enable = true, .gains = {gain, gain, gain, gain}};
set_dg_params(&params);
dg_process(out_);
// Expect all pixel values to be doubled, as no clipping.
for (uint16_t c = 0; c < in_->num_channels; ++c) {
for (uint16_t y = 0; y < in_->height; ++y) {
for (uint16_t x = 0; x < in_->width; ++x) {
ASSERT_EQ(image_pixel_val(in_, c, y, x) * 2,
image_pixel_val(out_, c, y, x));
}
}
}
}
TEST_F(DgTest, ClampHighRandomPixel) {
// Init image with range of values that will clamp to max with 2x gain.
InitImageRandom(in_, kRawPipelineMaxVal / 2, kRawPipelineMaxVal);
memcpy(out_->data, in_->data, num_bytes_);
// Set gain to 2x, gain is in_ 8.16 format.
uint16_t gain = 2 << kDgFractional;
DgParams params = {.enable = true, .gains = {gain, gain, gain, gain}};
set_dg_params(&params);
dg_process(out_);
// Expect all pixel values to be clamped high.
for (uint16_t c = 0; c < in_->num_channels; ++c) {
for (uint16_t y = 0; y < in_->height; ++y) {
for (uint16_t x = 0; x < in_->width; ++x) {
ASSERT_EQ(image_pixel_val(out_, c, y, x), kRawPipelineMaxVal);
}
}
}
}
TEST_F(DgTest, ClampLowRandomPixel) {
// Init image with range of values that will clamp to min with 2x gain.
InitImageRandom(in_, kRawPipelineMinVal / 2, kRawPipelineMinVal);
memcpy(out_->data, in_->data, num_bytes_);
// Set gain to 2x, gain is in_ 8.8 format.
uint16_t gain = 2 << kDgFractional;
DgParams params = {.enable = true, .gains = {gain, gain, gain, gain}};
set_dg_params(&params);
dg_process(out_);
// Expect all pixel values to be clamped low.
for (uint16_t c = 0; c < in_->num_channels; ++c) {
for (uint16_t y = 0; y < in_->height; ++y) {
for (uint16_t x = 0; x < in_->width; ++x) {
ASSERT_EQ(image_pixel_val(out_, c, y, x), kRawPipelineMinVal);
}
}
}
}
TEST_F(DgTest, MaxGainLinearRampInput) {
// Set pixel values from left to right with progressively larger values.
// 1, 2, 4, ... 2^15
for (uint16_t c = 0; c < in_->num_channels; ++c) {
for (uint16_t y = 0; y < in_->height; ++y) {
for (uint16_t x = 0; x < in_->width; ++x) {
*image_pixel(in_, c, y, x) =
(pixel_type_t)Clamp(1 << y, 0, kRawPipelineMaxVal);
}
}
}
memcpy(out_->data, in_->data, num_bytes_);
// Set gain to max gain, 0xFFFF in_ 8.8 format, approximately 256.
constexpr uint32_t kMaxGain =
(0xFF << kDgFractional) + ((1 << kDgFractional) - 1);
DgParams params = {.enable = true,
.gains = {kMaxGain, kMaxGain, kMaxGain, kMaxGain}};
set_dg_params(&params);
dg_process(out_);
// Expect values 1, 2, ... 64 to be gained by 256, 128 and above to clip.
for (uint16_t c = 0; c < in_->num_channels; ++c) {
for (uint16_t y = 0; y < in_->height; ++y) {
for (uint16_t x = 0; x < in_->width; ++x) {
ASSERT_EQ((pixel_type_t)Clamp((1 << 8) << y, 0, kRawPipelineMaxVal),
image_pixel_val(out_, c, y, x));
}
}
}
}
TEST_F(DgTest, MinValidGainLinearRampInput) {
// Set pixel values from left to right with progressively larger values.
// 1, 2, 4, ... 2^15
for (uint16_t c = 0; c < in_->num_channels; ++c) {
for (uint16_t y = 0; y < in_->height; ++y) {
for (uint16_t x = 0; x < in_->width; ++x) {
*image_pixel(in_, c, y, x) =
(pixel_type_t)Clamp(1 << y, 0, kRawPipelineMaxVal);
}
}
}
memcpy(out_->data, in_->data, num_bytes_);
// Set gain to min non-zero gain, 0x000001 in_ 8.8 format, 1/256.
DgParams params = {.enable = true, .gains = {1, 1, 1, 1}};
set_dg_params(&params);
dg_process(out_);
constexpr uint32_t kExpectedOutput[] = {0, 0, 0, 0, 0, 0, 0, 1,
1, 2, 4, 8, 16, 32, 64, 128};
// Expect all output values to be zero, as 32767/65536=0.
for (uint16_t c = 0; c < in_->num_channels; ++c) {
for (uint16_t y = 0; y < in_->height; ++y) {
for (uint16_t x = 0; x < in_->width; ++x) {
ASSERT_EQ(kExpectedOutput[y], image_pixel_val(out_, c, y, x));
}
}
}
}
TEST_F(DgTest, MinUsefulGainLinearRampInput) {
// Set pixel values from left to right with progressively larger values.
// 1, 2, 4, ... kRawPipelineMaxVal
for (uint16_t c = 0; c < in_->num_channels; ++c) {
for (uint16_t y = 0; y < in_->height; ++y) {
for (uint16_t x = 0; x < in_->width; ++x) {
*image_pixel(in_, c, y, x) =
(pixel_type_t)Clamp(1 << y, 0, kRawPipelineMaxVal);
}
}
}
memcpy(out_->data, in_->data, num_bytes_);
// Set gain to min useful gain, 0x000002 in_ 8.8 format, 1/256.
DgParams params = {.enable = true, .gains = {2, 2, 2, 2}};
set_dg_params(&params);
dg_process(out_);
// Expect all output values < 128 to be zero, 128->1, 256->2.
constexpr uint32_t kExpectedOutput[] = {0, 0, 0, 0, 0, 0, 1, 1,
2, 4, 8, 16, 32, 64, 128, 256};
for (uint16_t c = 0; c < in_->num_channels; ++c) {
for (uint16_t y = 0; y < in_->height; ++y) {
for (uint16_t x = 0; x < in_->width; ++x) {
ASSERT_EQ(kExpectedOutput[y], image_pixel_val(out_, c, y, x));
}
}
}
}
TEST_F(DgTest, ZeroGainRandomInput) {
// Init image with range of values.
InitImageRandom(in_, kRawPipelineMinVal, kRawPipelineMinVal);
memcpy(out_->data, in_->data, num_bytes_);
// Set gain to 0x, gain is in_ 8.16 format.
DgParams params = {.enable = true, .gains = {0, 0, 0, 0}};
set_dg_params(&params);
dg_process(out_);
// Expect all output values to be zero.
for (uint16_t c = 0; c < in_->num_channels; ++c) {
for (uint16_t y = 0; y < in_->height; ++y) {
for (uint16_t x = 0; x < in_->width; ++x) {
ASSERT_EQ(0, image_pixel_val(out_, c, y, x));
}
}
}
}