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