RawSpeed
fast raw decoding library
Loading...
Searching...
No Matches
SplineTest.cpp
Go to the documentation of this file.
1/*
2 RawSpeed - RAW file decoder.
3
4 Copyright (C) 2018 Roman Lebedev
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2 of the License, or (at your option) any later version.
10
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; withexpected even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public
17 License along with this library; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19*/
20
21#include "common/Spline.h"
22#include "adt/Casts.h"
23#include "adt/Point.h"
24#include <algorithm>
25#include <array>
26#include <cassert>
27#include <cmath>
28#include <cstdint>
29#include <cstdlib>
30#include <iterator>
31#include <limits>
32#include <ostream>
33#include <string>
34#include <tuple>
35#include <type_traits>
36#include <vector>
37#include <gtest/gtest.h>
38
40using std::make_tuple;
41
42namespace rawspeed {
43
44static ::std::ostream& operator<<(::std::ostream& os, const iPoint2D p) {
45 return os << "(" << p.x << ", " << p.y << ")";
46}
47
48} // namespace rawspeed
49
50namespace rawspeed_test {
51
52namespace {
53
54TEST(SplineStaticTest, DefaultIsUshort16) {
55 static_assert(std::is_same<Spline<>::value_type, uint16_t>::value,
56 "wrong default type");
57}
58
59#ifndef NDEBUG
60TEST(SplineDeathTest, AtLeastTwoPoints) {
61 ASSERT_DEATH(
62 {
63 Spline<> s({});
64 (void)s.calculateCurve();
65 },
66 "at least two points");
67 ASSERT_DEATH(
68 {
69 Spline<> s({{0, 0}});
70 (void)s.calculateCurve();
71 },
72 "at least two points");
73 ASSERT_EXIT(
74 {
75 Spline<> s({{0, {}}, {65535, {}}});
76 (void)s.calculateCurve();
77 exit(0);
78 },
79 ::testing::ExitedWithCode(0), "");
80}
81
82TEST(SplineDeathTest, XIsFullRange) {
83 ASSERT_DEATH(
84 {
85 Spline<> s({{1, {}}, {65535, {}}});
86 (void)s.calculateCurve();
87 },
88 "front.*0");
89 ASSERT_DEATH(
90 {
91 Spline<> s({{0, {}}, {65534, {}}});
92 (void)s.calculateCurve();
93 },
94 "back.*65535");
95}
96
97TEST(SplineDeathTest, YIsLimited) {
98 ASSERT_DEATH(
99 {
100 Spline<> s({{0, {}}, {32767, -1}, {65535, {}}});
101 (void)s.calculateCurve();
102 },
103 "y >= .*min");
104 ASSERT_DEATH(
105 {
106 Spline<> s({{0, {}}, {32767, 65536}, {65535, {}}});
107 (void)s.calculateCurve();
108 },
109 "y <= .*max");
110}
111
112TEST(SplineDeathTest, XIsStrictlyIncreasing) {
113 ASSERT_DEATH(
114 {
115 Spline<> s({{0, {}}, {0, {}}, {65535, {}}});
116 (void)s.calculateCurve();
117 },
118 "strictly increasing");
119 ASSERT_DEATH(
120 {
121 Spline<> s({{0, {}}, {32767, {}}, {32767, {}}, {65535, {}}});
122 (void)s.calculateCurve();
123 },
124 "strictly increasing");
125 ASSERT_DEATH(
126 {
127 Spline<> s({{0, {}}, {65535, {}}, {65535, {}}});
128 (void)s.calculateCurve();
129 },
130 "strictly increasing");
131 ASSERT_DEATH(
132 {
133 Spline<> s({{0, {}}, {32767, {}}, {32766, {}}, {65535, {}}});
134 (void)s.calculateCurve();
135 },
136 "strictly increasing");
137}
138#endif
139
140TEST(SplineDeathTest, ClampUshort16Min) {
141 ASSERT_EXIT(
142 {
143 Spline<> s({{0, 0},
144 {2, 0},
145 {54, 0},
146 {128, 0},
147 {256, 0},
148 {21504, 0},
149 {32768, 0},
150 {57088, 0},
151 {65535, 65535}});
152 (void)s.calculateCurve();
153 // for x = 484, this used to compute -1.0046832758220527,
154 // which is unrepresentable in uint16_t.
155 exit(0);
156 },
157 ::testing::ExitedWithCode(0), "");
158}
159TEST(SplineDeathTest, ClampUshort16Max) {
160 ASSERT_EXIT(
161 {
162 Spline<> s({{0, 0},
163 {2, 0},
164 {56, 0},
165 {128, 0},
166 {256, 0},
167 {21504, 0},
168 {32768, 0},
169 {57088, 0},
170 {65535, 65535}});
171 (void)s.calculateCurve();
172 // for x = 65535, this used to compute 65535.000000000007,
173 // which is unrepresentable in uint16_t.
174 exit(0);
175 },
176 ::testing::ExitedWithCode(0), "");
177}
178
179using identityType = std::tuple<std::array<rawspeed::iPoint2D, 2>,
180 std::vector<std::array<double, 4>>>;
181template <typename T>
182class IdentityTest : public ::testing::TestWithParam<identityType> {
183protected:
184 IdentityTest() = default;
185
186 void CheckSegments() const {
187 ASSERT_EQ(gotSegments.size(), expSegments.size());
188 for (auto i = 0U; i < gotSegments.size(); i++) {
189 const auto s0 = gotSegments[i];
190 const auto s1 = expSegments[i];
191 ASSERT_EQ(s0.a, s1[0]);
192 ASSERT_EQ(s0.b, s1[1]);
193 ASSERT_EQ(s0.c, s1[2]);
194 ASSERT_EQ(s0.d, s1[3]);
195 }
196 }
197
198 virtual void SetUp() {
199 const auto p = GetParam();
200 edges = std::get<0>(p);
201 expSegments = std::get<1>(p);
202
203 Spline<T> s({std::begin(edges), std::end(edges)});
204 gotSegments = s.getSegments();
205 interpolated = s.calculateCurve();
206
207 ASSERT_FALSE(interpolated.empty());
208 ASSERT_EQ(interpolated.size(), 65536);
209 }
210
211 std::array<rawspeed::iPoint2D, 2> edges;
212 std::vector<std::array<double, 4>> expSegments;
213 std::vector<typename Spline<T>::Segment> gotSegments;
214 std::vector<T> interpolated;
215};
217 make_tuple(std::array<rawspeed::iPoint2D, 2>{{{0, 0}, {65535, 65535}}},
218 std::vector<std::array<double, 4>>{{{0.0, 1.0, 0.0, 0.0}}}),
219 make_tuple(
220 std::array<rawspeed::iPoint2D, 2>{{{0, 65535}, {65535, 0}}},
221 std::vector<std::array<double, 4>>{{{65535.0, -1.0, 0.0, 0.0}}})};
222
225 ::testing::ValuesIn(identityValues));
226TEST_P(IntegerIdentityTest, ValuesAreLinearlyInterpolated) {
227 for (auto x = edges.front().y; x < edges.back().y; ++x)
228 ASSERT_EQ(interpolated[x], x);
229}
230TEST_P(IntegerIdentityTest, SegmentCoeffients) { CheckSegments(); }
231
234 ::testing::ValuesIn(identityValues));
235TEST_P(DoubleIdentityTest, ValuesAreLinearlyInterpolated) {
236 for (auto x = edges.front().y; x < edges.back().y; ++x) {
237 ASSERT_DOUBLE_EQ(interpolated[x], x);
238 ASSERT_EQ(interpolated[x], x);
239 }
240}
241TEST_P(DoubleIdentityTest, SegmentCoeffients) { CheckSegments(); }
242
243template <typename T> T lerp(T v0, T v1, T t) {
244 return ((1.0 - t) * v0) + (t * v1);
245}
246
247template <typename T = int>
248 requires std::is_arithmetic_v<T>
249std::vector<T> calculateSteps(int numCp) {
250 std::vector<T> steps;
251
252 const auto ptsTotal = 2U + numCp;
253 steps.reserve(ptsTotal);
254
255 std::generate_n(std::back_inserter(steps), ptsTotal,
256 [ptsTotal, &steps]() -> T {
257 const double t = double(steps.size()) / (ptsTotal - 1);
258 const double x = lerp(0.0, 65535.0, t);
259 if constexpr (std::is_floating_point<T>::value)
260 return x;
261 else
262 return rawspeed::implicit_cast<T>(std::lround(x));
263 });
264
265 assert(ptsTotal == steps.size());
266 return steps;
267}
269 const int steps = 65534;
270 const auto pts = calculateSteps(steps);
271 for (auto x = 0U; x < pts.size(); x++)
272 ASSERT_EQ(pts[x], x);
273}
274
275class CalculateStepsEdgesTest : public ::testing::TestWithParam<int> {
276protected:
278 virtual void SetUp() {
279 extraSteps = GetParam();
281 }
282
284 std::vector<int> got;
285};
287 ::testing::Range(0, 254));
289 ASSERT_EQ(got.size(), 2 + extraSteps);
290}
292 ASSERT_EQ(got.front(), 0);
293 ASSERT_EQ(got.back(), 65535);
294}
295
296using calculateStepsType = std::tuple<int, std::vector<int>>;
297
298template <typename T>
299class CalculateStepsTest : public ::testing::TestWithParam<calculateStepsType> {
300protected:
302 virtual void SetUp() {
303 const auto p = GetParam();
304 extraSteps = std::get<0>(p);
305 expected = std::get<1>(p);
306
308 }
309
311 std::vector<int> expected;
312 std::vector<T> got;
313};
315 // clang-format off
316 make_tuple(0, std::vector<int>{0, 65535}),
317 make_tuple(1, std::vector<int>{0, 32768, 65535}),
318 make_tuple(2, std::vector<int>{0, 21845, 43690, 65535}),
319 make_tuple(3, std::vector<int>{0, 16384, 32768, 49151, 65535}),
320 make_tuple(4, std::vector<int>{0, 13107, 26214, 39321, 52428, 65535}),
321 make_tuple(5, std::vector<int>{0, 10923, 21845, 32768, 43690, 54613, 65535}),
322 make_tuple(6, std::vector<int>{0, 9362, 18724, 28086, 37449, 46811, 56173, 65535}),
323 make_tuple(7, std::vector<int>{0, 8192, 16384, 24576, 32768, 40959, 49151, 57343, 65535}),
324 make_tuple(8, std::vector<int>{0, 7282, 14563, 21845, 29127, 36408, 43690, 50972, 58253, 65535}),
325 // clang-format on
326};
327
330 ::testing::ValuesIn(calculateStepsValues));
332 ASSERT_EQ(expected.size(), got.size());
333 ASSERT_EQ(got.size(), 2 + extraSteps);
334}
335TEST_P(DoubleCalculateStepsTest, GotExpectedOutput) {
336 for (auto i = 0U; i < got.size(); i++) {
337 ASSERT_NEAR(got[i], expected[i], 0.5);
338
339 // Check that rounding halfway cases away from zero will work
340 ASSERT_GE(got[i], expected[i] - 0.5);
341 ASSERT_LT(got[i], expected[i] + 0.5);
342 }
343}
344
347 ::testing::ValuesIn(calculateStepsValues));
349 ASSERT_EQ(expected.size(), got.size());
350 ASSERT_EQ(got.size(), 2 + extraSteps);
351}
353 ASSERT_EQ(got, expected);
354}
355
356using constantType = std::tuple<int, int>;
357template <typename T>
358class ConstantTest : public ::testing::TestWithParam<constantType> {
359protected:
360 ConstantTest() = default;
361
363 const auto steps = calculateSteps(numCp);
364 edges.reserve(steps.size());
365
366 for (const int step : steps)
367 edges.emplace_back(step, constant);
368
369 assert(steps.size() == edges.size());
370 }
371
372 void CheckSegments() const {
373 for (auto i = 0U; i < gotSegments.size(); i++) {
374 const auto s0 = gotSegments[i];
375 ASSERT_EQ(s0.a, constant);
376 ASSERT_EQ(s0.b, 0);
377 ASSERT_EQ(s0.c, 0);
378 ASSERT_EQ(s0.d, 0);
379 }
380 }
381
382 virtual void SetUp() {
383 auto p = GetParam();
384
385 constant = std::get<0>(p);
386 numCp = std::get<1>(p);
387
389
390 // EXPECT_TRUE(false) << ::testing::PrintToString((edges));
391
392 Spline<T> s({std::begin(edges), std::end(edges)});
393 gotSegments = s.getSegments();
394 interpolated = s.calculateCurve();
395
396 ASSERT_FALSE(interpolated.empty());
397 ASSERT_EQ(interpolated.size(), 65536);
398 }
399
401 int numCp;
402
403 std::vector<rawspeed::iPoint2D> edges;
404 std::vector<typename Spline<T>::Segment> gotSegments;
405 std::vector<T> interpolated;
406};
407
408constexpr auto NumExtraSteps = 3;
409const auto constantValues =
410 ::testing::Combine(::testing::ValuesIn(calculateSteps(NumExtraSteps)),
411 ::testing::Range(0, 1 + NumExtraSteps));
412
416TEST_P(IntegerConstantTest, AllValuesAreEqual) {
417 for (const auto value : interpolated)
418 ASSERT_EQ(value, constant);
419}
420TEST_P(IntegerConstantTest, SegmentCoeffients) { CheckSegments(); }
421
425TEST_P(DoubleConstantTest, AllValuesAreEqual) {
426 for (const auto value : interpolated) {
427 ASSERT_DOUBLE_EQ(value, constant);
428 ASSERT_EQ(value, constant);
429 }
430}
431TEST_P(DoubleConstantTest, SegmentCoeffients) { CheckSegments(); }
432
434public:
435 using T = long double;
436
437 const T xMax = 65535;
438 const T yMax = std::numeric_limits<rawspeed::iPoint2D::value_type>::max();
439
440 virtual T calculateRefVal(int x) const = 0;
441
442 virtual ~AbstractReferenceTest() = default;
443};
444
445template <int mul, int div>
447public:
448 T calculateRefVal(int x) const final {
449 const T pi = std::acos(T(-1));
450 const T x2arg = T(mul) * pi / (div * xMax);
451
452 return yMax * std::sin(x2arg * T(x));
453 }
454
455 virtual ~SinReferenceTest() override = default;
456};
457
458using referenceType = std::tuple<int, long double>;
459
460template <typename Tb>
461class ReferenceTest : public Tb,
462 public ::testing::TestWithParam<referenceType> {
463protected:
465
466 ReferenceTest() = default;
467
469 const auto xPoints = calculateSteps(numPts);
470
471 reference.reserve(xPoints.size());
472 for (const auto xPoint : xPoints)
473 reference.emplace_back(xPoint, this->calculateRefVal(xPoint));
474
475 assert(reference.size() == xPoints.size());
476 }
477
478 virtual void SetUp() {
479 const auto p = GetParam();
480
481 numPts = std::get<0>(p);
482 absError = std::get<1>(p);
483
485
487 interpolated = s.calculateCurve();
488 ASSERT_FALSE(interpolated.empty());
489 ASSERT_EQ(interpolated.size(), AbstractReferenceTest::xMax + 1);
490 }
491
492 void check() const {
493 for (auto x = reference.front().x; x < reference.back().x; ++x) {
494 const T referen = this->calculateRefVal(x) / this->yMax;
495 const T interpo = T(interpolated[x]) / this->yMax;
496 ASSERT_NEAR(interpo, referen, absError);
497 }
498 }
499
501 long double absError;
502
503 std::vector<rawspeed::iPoint2D> reference;
504 std::vector<T> interpolated;
505};
506
509 // clang-format off
510 make_tuple(0, 1.0E-00),
511 make_tuple(1, 1.0E+01), // FIXME: should be 1.0E-00
512 make_tuple(2, 1.0E-00),
513 make_tuple(3, 1.0E-01),
514 make_tuple(4, 1.0E-02),
515 make_tuple(5, 1.0E-02),
516 make_tuple(6, 1.0E-02),
517 make_tuple(7, 1.0E-02),
518 make_tuple(8, 1.0E-03),
519 make_tuple(9, 1.0E-03),
520 make_tuple(10, 1.0E-03),
521 make_tuple(11, 1.0E-03),
522 make_tuple(12, 1.0E-03),
523 make_tuple(13, 1.0E-03),
524 make_tuple(14, 1.0E-04),
525 // clang-format on
526};
528 ::testing::ValuesIn(sin2PiRefValues));
529TEST_P(Sin2PiRefTest, NearlyMatchesReference) { check(); }
530
533 // clang-format off
534 make_tuple(0, 1.0E-00),
535 make_tuple(1, 1.0E-01),
536 make_tuple(2, 1.0E-02),
537 make_tuple(3, 1.0E-02),
538 make_tuple(4, 1.0E-03),
539 make_tuple(5, 1.0E-03),
540 make_tuple(6, 1.0E-03),
541 make_tuple(7, 1.0E-04),
542 make_tuple(8, 1.0E-04),
543 make_tuple(9, 1.0E-04),
544 make_tuple(10, 1.0E-04),
545 make_tuple(11, 1.0E-04),
546 make_tuple(12, 1.0E-05),
547 // clang-format on
548};
550 ::testing::ValuesIn(sinPiRefValues));
551TEST_P(SinPiRefTest, NearlyMatchesReference) { check(); }
552
553} // namespace
554
555} // namespace rawspeed_test
#define s
INSTANTIATE_TEST_SUITE_P(MD5Test, MD5Test, ::testing::ValuesIn(testCases))
TEST_P(MD5Test, CheckTestCaseSet)
Definition MD5Test.cpp:388
assert(dim.area() >=area)
dim x
Definition Common.cpp:50
std::vector< typename Spline< uint16_t >::Segment > gotSegments
std::vector< typename Spline< uint16_t >::Segment > gotSegments
std::tuple< std::array< rawspeed::iPoint2D, 2 >, std::vector< std::array< double, 4 > > > identityType
std::tuple< int, std::vector< int > > calculateStepsType
ReferenceTest< SinReferenceTest< 2, 1 > > Sin2PiRefTest
TEST(SplineStaticTest, DefaultIsUshort16)
CalculateStepsTest< double > DoubleCalculateStepsTest
ReferenceTest< SinReferenceTest< 1, 1 > > SinPiRefTest
constexpr RAWSPEED_READNONE Ttgt implicit_cast(Tsrc value)
Definition Casts.h:32
static inline ::std::ostream & operator<<(::std::ostream &os, const T &b)