image_test_utils.h (8600B)
1 // Copyright (c) the JPEG XL Project Authors. All rights reserved. 2 // 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file. 5 6 #ifndef LIB_JXL_IMAGE_TEST_UTILS_H_ 7 #define LIB_JXL_IMAGE_TEST_UTILS_H_ 8 9 #include <inttypes.h> 10 #include <stddef.h> 11 #include <stdint.h> 12 13 #include <cmath> 14 #include <limits> 15 #include <sstream> 16 17 #include "lib/jxl/base/compiler_specific.h" 18 #include "lib/jxl/base/printf_macros.h" 19 #include "lib/jxl/base/random.h" 20 #include "lib/jxl/image.h" 21 22 namespace jxl { 23 24 template <typename T> 25 bool SamePixels(const Plane<T>& image1, const Plane<T>& image2, 26 std::stringstream& failures) { 27 const Rect rect(image1); 28 JXL_CHECK(SameSize(image1, image2)); 29 size_t mismatches = 0; 30 for (size_t y = rect.y0(); y < rect.ysize(); ++y) { 31 const T* const JXL_RESTRICT row1 = image1.Row(y); 32 const T* const JXL_RESTRICT row2 = image2.Row(y); 33 for (size_t x = rect.x0(); x < rect.xsize(); ++x) { 34 if (row1[x] != row2[x]) { 35 failures << "pixel mismatch" << x << ", " << y << ": " 36 << static_cast<double>(row1[x]) 37 << " != " << static_cast<double>(row2[x]) << "\n"; 38 if (++mismatches > 4) { 39 return false; 40 } 41 } 42 } 43 } 44 return mismatches == 0; 45 } 46 47 template <typename T> 48 bool SamePixels(const Image3<T>& image1, const Image3<T>& image2, 49 std::stringstream& failures) { 50 JXL_CHECK(SameSize(image1, image2)); 51 for (size_t c = 0; c < 3; ++c) { 52 if (!SamePixels(image1.Plane(c), image2.Plane(c), failures)) { 53 return false; 54 } 55 } 56 return true; 57 } 58 59 // Use for floating-point images with fairly large numbers; tolerates small 60 // absolute errors and/or small relative errors. 61 template <typename T> 62 bool VerifyRelativeError(const Plane<T>& expected, const Plane<T>& actual, 63 const double threshold_l1, 64 const double threshold_relative, 65 std::stringstream& failures, const intptr_t border = 0, 66 const int c = 0) { 67 JXL_CHECK(SameSize(expected, actual)); 68 const intptr_t xsize = expected.xsize(); 69 const intptr_t ysize = expected.ysize(); 70 71 // Max over current scanline to give a better idea whether there are 72 // systematic errors or just one outlier. Invalid if negative. 73 double max_l1 = -1; 74 double max_relative = -1; 75 bool any_bad = false; 76 for (intptr_t y = border; y < ysize - border; ++y) { 77 const T* const JXL_RESTRICT row_expected = expected.Row(y); 78 const T* const JXL_RESTRICT row_actual = actual.Row(y); 79 for (intptr_t x = border; x < xsize - border; ++x) { 80 const double l1 = std::abs(row_expected[x] - row_actual[x]); 81 82 // Cannot compute relative, only check/update L1. 83 if (std::abs(row_expected[x]) < 1E-10) { 84 if (l1 > threshold_l1) { 85 any_bad = true; 86 max_l1 = std::max(max_l1, l1); 87 } 88 } else { 89 const double relative = 90 l1 / std::abs(static_cast<double>(row_expected[x])); 91 if (l1 > threshold_l1 && relative > threshold_relative) { 92 // Fails both tolerances => will exit below, update max_*. 93 any_bad = true; 94 max_l1 = std::max(max_l1, l1); 95 max_relative = std::max(max_relative, relative); 96 } 97 } 98 } 99 } 100 if (!any_bad) { 101 return true; 102 } 103 // Never had a valid relative value, don't print it. 104 if (max_relative < 0) { 105 fprintf(stderr, "c=%d: max +/- %E exceeds +/- %.2E\n", c, max_l1, 106 threshold_l1); 107 } else { 108 fprintf(stderr, "c=%d: max +/- %E, x %E exceeds +/- %.2E, x %.2E\n", c, 109 max_l1, max_relative, threshold_l1, threshold_relative); 110 } 111 // Dump the expected image and actual image if the region is small enough. 112 const intptr_t kMaxTestDumpSize = 16; 113 if (xsize <= kMaxTestDumpSize + 2 * border && 114 ysize <= kMaxTestDumpSize + 2 * border) { 115 fprintf(stderr, "Expected image:\n"); 116 for (intptr_t y = border; y < ysize - border; ++y) { 117 const T* const JXL_RESTRICT row_expected = expected.Row(y); 118 for (intptr_t x = border; x < xsize - border; ++x) { 119 fprintf(stderr, "%10lf ", static_cast<double>(row_expected[x])); 120 } 121 fprintf(stderr, "\n"); 122 } 123 124 fprintf(stderr, "Actual image:\n"); 125 for (intptr_t y = border; y < ysize - border; ++y) { 126 const T* const JXL_RESTRICT row_expected = expected.Row(y); 127 const T* const JXL_RESTRICT row_actual = actual.Row(y); 128 for (intptr_t x = border; x < xsize - border; ++x) { 129 const double l1 = std::abs(row_expected[x] - row_actual[x]); 130 131 bool bad = l1 > threshold_l1; 132 if (row_expected[x] > 1E-10) { 133 const double relative = 134 l1 / std::abs(static_cast<double>(row_expected[x])); 135 bad &= relative > threshold_relative; 136 } 137 if (bad) { 138 fprintf(stderr, "%10lf ", static_cast<double>(row_actual[x])); 139 } else { 140 fprintf(stderr, "%10s ", "=="); 141 } 142 } 143 fprintf(stderr, "\n"); 144 } 145 } 146 147 // Find first failing x for further debugging. 148 for (intptr_t y = border; y < ysize - border; ++y) { 149 const T* const JXL_RESTRICT row_expected = expected.Row(y); 150 const T* const JXL_RESTRICT row_actual = actual.Row(y); 151 152 for (intptr_t x = border; x < xsize - border; ++x) { 153 const double l1 = std::abs(row_expected[x] - row_actual[x]); 154 155 bool bad = l1 > threshold_l1; 156 if (row_expected[x] > 1E-10) { 157 const double relative = 158 l1 / std::abs(static_cast<double>(row_expected[x])); 159 bad &= relative > threshold_relative; 160 } 161 if (bad) { 162 failures << x << ", " << y << " (" << expected.xsize() << " x " 163 << expected.ysize() << ") expected " 164 << static_cast<double>(row_expected[x]) << " actual " 165 << static_cast<double>(row_actual[x]); 166 return false; 167 } 168 } 169 } 170 return false; 171 } 172 173 template <typename T> 174 bool VerifyRelativeError(const Image3<T>& expected, const Image3<T>& actual, 175 const float threshold_l1, 176 const float threshold_relative, 177 std::stringstream& failures, 178 const intptr_t border = 0) { 179 for (size_t c = 0; c < 3; ++c) { 180 bool ok = VerifyRelativeError(expected.Plane(c), actual.Plane(c), 181 threshold_l1, threshold_relative, failures, 182 border, static_cast<int>(c)); 183 if (!ok) { 184 return false; 185 } 186 } 187 return true; 188 } 189 190 template <typename T, typename U = T> 191 void GenerateImage(Rng& rng, Plane<T>* image, U begin, U end) { 192 for (size_t y = 0; y < image->ysize(); ++y) { 193 T* const JXL_RESTRICT row = image->Row(y); 194 for (size_t x = 0; x < image->xsize(); ++x) { 195 if (std::is_same<T, float>::value || std::is_same<T, double>::value) { 196 row[x] = rng.UniformF(begin, end); 197 } else if (std::is_signed<T>::value) { 198 row[x] = rng.UniformI(begin, end); 199 } else { 200 row[x] = rng.UniformU(begin, end); 201 } 202 } 203 } 204 } 205 206 template <typename T> 207 void RandomFillImage(Plane<T>* image, const T begin, const T end, 208 const int seed = 129) { 209 Rng rng(seed); 210 GenerateImage(rng, image, begin, end); 211 } 212 213 template <typename T> 214 typename std::enable_if<std::is_integral<T>::value>::type RandomFillImage( 215 Plane<T>* image) { 216 Rng rng(129); 217 GenerateImage(rng, image, static_cast<int64_t>(0), 218 static_cast<int64_t>(std::numeric_limits<T>::max()) + 1); 219 } 220 221 JXL_INLINE void RandomFillImage(Plane<float>* image) { 222 Rng rng(129); 223 GenerateImage(rng, image, 0.0f, std::numeric_limits<float>::max()); 224 } 225 226 template <typename T, typename U> 227 void GenerateImage(Rng& rng, Image3<T>* image, U begin, U end) { 228 for (size_t c = 0; c < 3; ++c) { 229 GenerateImage(rng, &image->Plane(c), begin, end); 230 } 231 } 232 233 template <typename T> 234 typename std::enable_if<std::is_integral<T>::value>::type RandomFillImage( 235 Image3<T>* image) { 236 Rng rng(129); 237 GenerateImage(rng, image, static_cast<int64_t>(0), 238 static_cast<int64_t>(std::numeric_limits<T>::max()) + 1); 239 } 240 241 JXL_INLINE void RandomFillImage(Image3F* image) { 242 Rng rng(129); 243 GenerateImage(rng, image, 0.0f, std::numeric_limits<float>::max()); 244 } 245 246 template <typename T, typename U> 247 void RandomFillImage(Image3<T>* image, const U begin, const U end, 248 const int seed = 129) { 249 Rng rng(seed); 250 GenerateImage(rng, image, begin, end); 251 } 252 253 } // namespace jxl 254 255 #endif // LIB_JXL_IMAGE_TEST_UTILS_H_