| /* |
| * 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(¶ms); |
| |
| 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(¶ms); |
| |
| 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(¶ms); |
| |
| 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(¶ms); |
| |
| 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(¶ms); |
| |
| 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(¶ms); |
| |
| 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(¶ms); |
| |
| 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(¶ms); |
| |
| 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(¶ms); |
| |
| 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)); |
| } |
| } |
| } |
| } |