blob: f4e486b0f26fbd13b9294e57c460765b0c2279dd [file] [log] [blame]
// Copyright 2020 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.
// Test the directional filter.
#include <array>
#include <tuple>
#include "include/helpers.h"
#include "include/helpers_filter.h"
#include "src/dec/filters/restoration_filter.h"
#include "src/dsp/math.h"
#include "src/utils/utils.h"
#include "src/wp2/format_constants.h"
using testing::ElementsAre;
namespace WP2 {
namespace {
//------------------------------------------------------------------------------
YUVPlane CreateNoisyPixels(uint32_t width, uint32_t height,
BitDepth bit_depth) {
const int16_t min = bit_depth.min(), max = bit_depth.max();
YUVPlane pixels;
WP2_ASSERT_STATUS(pixels.Resize(width, height));
pixels.Fill(Ayuv38b{kAlphaMax, min, (int16_t)((min + max) / 2), max});
for (Channel channel : {kYChannel, kUChannel, kVChannel, kAChannel}) {
testutil::Noise(bit_depth.min(), bit_depth.max(), /*seed=*/channel, 5,
&pixels.GetChannel(channel));
}
return pixels;
}
// Applies the restoration filter on 'pixels' with 'filter_strength'.
void Filter(BitDepth bit_depth, const int8_t tap_weights[kWieFltTapDist],
float quality, YUVPlane* const pixels) {
const Vector<Segment> segments = testutil::CreateSegments(quality, bit_depth);
const double residual_density = 1. * quality / kMaxLossyQuality;
const double min_bpp = 4. * quality / kMaxLossyQuality;
FilterBlockMap blocks;
testutil::CreateBlocks(/*tile_x=*/0, /*tile_y=*/0, segments, residual_density,
min_bpp, bit_depth, pixels, &blocks);
RstrFltParams params(pixels->Y.w_, pixels->Y.h_);
params.SetAll(tap_weights);
DecoderConfig config = DecoderConfig::kDefault;
config.enable_restoration_filter = true;
RestorationFilter restoration_filter(config, blocks, params);
ASSERT_WP2_OK(restoration_filter.Allocate());
uint32_t num_deblocked_rows = 0;
for (uint32_t num_decoded_yuv_rows = 0; num_decoded_yuv_rows <= pixels->Y.h_;
++num_decoded_yuv_rows) {
const uint32_t n = restoration_filter.Enhances(num_decoded_yuv_rows);
ASSERT_GE(n, num_deblocked_rows);
num_deblocked_rows = n;
}
EXPECT_EQ(num_deblocked_rows, pixels->Y.h_);
}
//------------------------------------------------------------------------------
constexpr int8_t kIdentityTapWeights[kWieFltTapDist]{0, 0, 0};
constexpr int8_t kDefaultTapWeights[kWieFltTapDist]{3, -7, 15};
class RestorationFilterTest
: public testing::TestWithParam<std::tuple<BitDepth, uint32_t, uint32_t>> {
};
// Tests that no pixel is modified if each plane has one unique color.
TEST_P(RestorationFilterTest, FilteringPlain) {
const BitDepth bit_depth = std::get<0>(GetParam());
const uint32_t width = std::get<1>(GetParam());
const uint32_t height = std::get<2>(GetParam());
YUVPlane pixels = testutil::CreatePlainPixels(width, height, bit_depth);
YUVPlane reference;
ASSERT_WP2_OK(reference.Copy(pixels, /*resize_if_needed=*/true));
ASSERT_NO_FATAL_FAILURE(
Filter(bit_depth, kDefaultTapWeights, /*quality=*/0.f, &pixels));
EXPECT_TRUE(testutil::AreEqual(pixels.Y, reference.Y));
EXPECT_TRUE(testutil::AreEqual(pixels.U, reference.U));
EXPECT_TRUE(testutil::AreEqual(pixels.V, reference.V));
}
// Tests that pixels are filtered.
TEST_P(RestorationFilterTest, MaxFilteringBlocked) {
const BitDepth bit_depth = std::get<0>(GetParam());
const uint32_t width = std::get<1>(GetParam());
const uint32_t height = std::get<2>(GetParam());
YUVPlane pixels = CreateNoisyPixels(width, height, bit_depth);
YUVPlane reference;
ASSERT_WP2_OK(reference.Copy(pixels, /*resize_if_needed=*/true));
ASSERT_NO_FATAL_FAILURE(
Filter(bit_depth, kDefaultTapWeights, /*quality=*/0.f, &pixels));
EXPECT_FALSE(testutil::AreEqual(pixels.Y, reference.Y));
EXPECT_FALSE(testutil::AreEqual(pixels.U, reference.U));
EXPECT_FALSE(testutil::AreEqual(pixels.V, reference.V));
}
// Tests that no pixel is modified if it's the highest quality.
TEST_P(RestorationFilterTest, NoFiltering) {
const BitDepth bit_depth = std::get<0>(GetParam());
const uint32_t width = std::get<1>(GetParam());
const uint32_t height = std::get<2>(GetParam());
YUVPlane pixels = CreateNoisyPixels(width, height, bit_depth);
YUVPlane reference;
ASSERT_WP2_OK(reference.Copy(pixels, /*resize_if_needed=*/true));
ASSERT_NO_FATAL_FAILURE(Filter(bit_depth, kDefaultTapWeights,
/*quality=*/kMaxLossyQuality, &pixels));
EXPECT_TRUE(testutil::AreEqual(pixels.Y, reference.Y));
EXPECT_TRUE(testutil::AreEqual(pixels.U, reference.U));
EXPECT_TRUE(testutil::AreEqual(pixels.V, reference.V));
}
// Tests that no pixel is modified if the filter tap weights are the identity.
TEST_P(RestorationFilterTest, Identity) {
const BitDepth bit_depth = std::get<0>(GetParam());
const uint32_t width = std::get<1>(GetParam());
const uint32_t height = std::get<2>(GetParam());
YUVPlane pixels = CreateNoisyPixels(width, height, bit_depth);
YUVPlane reference;
ASSERT_WP2_OK(reference.Copy(pixels, /*resize_if_needed=*/true));
ASSERT_NO_FATAL_FAILURE(
Filter(bit_depth, kIdentityTapWeights, /*quality=*/0.f, &pixels));
EXPECT_TRUE(testutil::AreEqual(pixels.Y, reference.Y));
EXPECT_TRUE(testutil::AreEqual(pixels.U, reference.U));
EXPECT_TRUE(testutil::AreEqual(pixels.V, reference.V));
}
INSTANTIATE_TEST_SUITE_P(
RestorationFilterTestInstantiation, RestorationFilterTest,
testing::Combine(testing::Values(BitDepth{8, /*is_signed=*/true},
BitDepth{10, /*is_signed=*/true}),
testing::Values(kWieFltWidth / 3), // width
testing::Values(3 * kWieFltHeight - 1u) // height
));
// Disabled to reduce the number of test cases.
// Can still be run with flag --test_arg=--gunit_also_run_disabled_tests
INSTANTIATE_TEST_SUITE_P(
DISABLED_MoreRestorationFilterTestInstantiation, RestorationFilterTest,
testing::Combine(testing::Values(BitDepth{8, /*is_signed=*/true},
BitDepth{10, /*is_signed=*/true},
BitDepth{12, /*is_signed=*/true}),
testing::Values(kWieFltWidth / 2,
kWieFltWidth), // width
testing::Values(kWieFltHeight / 5, kWieFltHeight,
3 * kWieFltHeight + 7u) // height
));
//------------------------------------------------------------------------------
// Tests that PSNR is good with only restoration filtering.
TEST(RestorationFilterTest, Simple) {
EncoderConfig encoder_config = EncoderConfig::kDefault;
encoder_config.quality = 40; // Expect compression artifacts.
DecoderConfig decoder_config = DecoderConfig::kDefault;
decoder_config.enable_deblocking_filter = false;
decoder_config.enable_directional_filter = false;
decoder_config.enable_restoration_filter = true;
ASSERT_TRUE(testutil::EncodeDecodeCompare("source4.webp", encoder_config,
decoder_config));
}
// Tests that PSNR is better with the restoration filter than without.
TEST(RestorationFilterTest, BetterPSNR) {
EncoderConfig encoder_config = EncoderConfig::kDefault;
encoder_config.quality = 50; // Expect compression artifacts.
DecoderConfig decoder_config_no_filter = DecoderConfig::kDefault;
decoder_config_no_filter.enable_restoration_filter = false;
DecoderConfig decoder_config_filter = decoder_config_no_filter;
decoder_config_filter.enable_restoration_filter = true;
float disto_diff[5];
ASSERT_WP2_OK(testutil::GetDistortionDiff(
"source1.png", {1, 0, 300, 200}, encoder_config, decoder_config_no_filter,
decoder_config_filter, disto_diff));
EXPECT_GT(disto_diff[4], 0.f);
}
//------------------------------------------------------------------------------
TEST(WienerTest, WienerHalfToFullWgts) {
std::vector<int32_t> h{1, 2, 3, 0, 0, 0, 0};
WienerHalfToFullWgts(h.data(), h.data());
EXPECT_THAT(h, ElementsAre(h[0], h[1], h[2], 128 - 2 * (h[0] + h[1] + h[2]),
h[2], h[1], h[0]));
}
//------------------------------------------------------------------------------
// Test some 3x3 patterns, filtered by kernels of 3 horizontal and 3 vertical
// taps.
WP2Status FilterCanvas(const std::array<int32_t, 3>& half_tap_weights_h,
const std::array<int32_t, 3>& half_tap_weights_v,
uint32_t width, uint32_t height,
const std::vector<int16_t>& canvas,
std::vector<int16_t>* const filtered) {
WienerFilterInit();
int32_t tap_weights_h[kWieFltNumTaps];
int32_t tap_weights_v[kWieFltNumTaps];
WienerHalfToFullWgts(half_tap_weights_h.data(), tap_weights_h);
WienerHalfToFullWgts(half_tap_weights_v.data(), tap_weights_v);
std::vector<uint8_t> strength_map(canvas.size(), 63);
*filtered = canvas;
return WienerFilter(width, height, nullptr, 0, nullptr, 0, nullptr, 0,
nullptr, 0, 0, 0, 0, 0, tap_weights_h, tap_weights_v,
strength_map.data(), width, /*num_precision_bits=*/8,
width, filtered->data());
}
// clang-format off
TEST(WienerTest, ExpectOnes_3x3) {
std::vector<int16_t> filtered;
ASSERT_WP2_OK(
FilterCanvas(/*half_tap_weights_h=*/{0, 0, 0}, // = 0.0, 1.0, 0.0
/*half_tap_weights_v=*/{ 0,
0,
0},
3, 3, /*canvas=*/{1, 2, 3,
4, 5, 6,
6, 6, 6}, &filtered));
EXPECT_THAT(filtered, ElementsAre(1, 2, 3,
4, 5, 6,
6, 6, 6)); // Expect no change.
}
TEST(WienerTest, ExpectZeros_3x3) {
const int32_t h = 1 << (kWieFltNumBitsTapWgts - 2);
std::vector<int16_t> filtered;
ASSERT_WP2_OK(
FilterCanvas(/*half_tap_weights_h=*/{0, 0, h}, // = 0.5, 0.0, 0.5
/*half_tap_weights_v=*/{ 0,
0,
h},
3, 3, /*canvas=*/{0, 1, 0,
1, 1, 1,
0, 1, 0}, &filtered));
EXPECT_THAT(filtered, ElementsAre(0, 0, 0,
0, 0, 0,
0, 0, 0)); // Expect all values erased.
}
TEST(WienerTest, ExpectBlurH_3x3) {
const int32_t p = 1 << (kWieFltNumBitsTapWgts - 3);
std::vector<int16_t> filtered;
ASSERT_WP2_OK(
FilterCanvas(/*half_tap_weights_h=*/{0, 0, p}, // = 0.25, 0.5, 0.25
/*half_tap_weights_v=*/{ 0,
0,
0},
3, 3, /*canvas=*/{9, 0, 9,
0, 9, 0,
9, 9, 9}, &filtered));
EXPECT_THAT(filtered, ElementsAre(6, 4, 6,
2, 4, 2,
9, 9, 9)); // Horizontal blur.
}
TEST(WienerTest, ExpectBlurV_3x3) {
const int32_t p = 1 << (kWieFltNumBitsTapWgts - 3);
std::vector<int16_t> filtered;
ASSERT_WP2_OK(
FilterCanvas(/*half_tap_weights_h=*/{0, 0, 0},
/*half_tap_weights_v=*/{ 0,
0,
p},
3, 3, /*canvas=*/{9, 0, 9,
0, 9, 0,
9, 9, 9}, &filtered));
EXPECT_THAT(filtered, ElementsAre(6, 2, 6,
4, 6, 4,
6, 9, 6)); // Vertical blur.
}
TEST(WienerTest, ExpectBlur_2x2) {
const int32_t p = 1 << (kWieFltNumBitsTapWgts - 3);
std::vector<int16_t> filtered;
ASSERT_WP2_OK(
FilterCanvas(/*half_tap_weights_h=*/{p / 8, p / 4, p / 2},
/*half_tap_weights_v=*/{ p / 8,
p / 4,
p / 2},
2, 2, /*canvas=*/{9, 0,
0, 9}, &filtered));
EXPECT_THAT(filtered, ElementsAre(5, 3,
3, 5)); // Blur.
}
TEST(WienerTest, ExpectBlur_8x1) {
const int32_t p = 1 << (kWieFltNumBitsTapWgts - 3);
std::vector<int16_t> filtered;
ASSERT_WP2_OK(
FilterCanvas(/*half_tap_weights_h=*/{p / 8, p / 4, p / 2},
/*half_tap_weights_v=*/{ p / 8,
p / 4,
p / 2},
8, 1, /*canvas=*/{9, 0, 9, 0, 9, 0, 9, 0}, &filtered));
EXPECT_THAT(filtered, ElementsAre(7, 3, 6, 2, 6, 2, 5, 1)); // Blur.
}
TEST(WienerTest, ExpectSharpenV_3x5) {
const int32_t p = 1 << (kWieFltNumBitsTapWgts - 3);
std::vector<int16_t> filtered;
ASSERT_WP2_OK(
FilterCanvas(/*half_tap_weights_h=*/{0, 0, 0},
/*half_tap_weights_v=*/{ -p / 8,
-p / 4,
-p / 2},
3, 5, /*canvas=*/{3, 3, 3,
0, 3, 3,
3, 3, 3,
3, 3, 10,
3, 3, 3}, &filtered));
EXPECT_THAT(filtered, ElementsAre( 3, 3, 2,
-2, 3, 2,
3, 3, 2,
3, 3, 13,
3, 3, 2)); // Vertical sharpening.
}
// clang-format on
//------------------------------------------------------------------------------
} // namespace
} // namespace WP2