Visual Servoing Platform  version 3.6.1 under development (2024-07-27)
testRotation.cpp
1 /*
2  * ViSP, open source Visual Servoing Platform software.
3  * Copyright (C) 2005 - 2024 by Inria. All rights reserved.
4  *
5  * This software is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  * See the file LICENSE.txt at the root directory of this source
10  * distribution for additional information about the GNU GPL.
11  *
12  * For using ViSP with software that can not be combined with the GNU
13  * GPL, please contact Inria about acquiring a ViSP Professional
14  * Edition License.
15  *
16  * See https://visp.inria.fr for more information.
17  *
18  * This software was developed at:
19  * Inria Rennes - Bretagne Atlantique
20  * Campus Universitaire de Beaulieu
21  * 35042 Rennes Cedex
22  * France
23  *
24  * If you have questions regarding the use of this file, please contact
25  * Inria at visp@inria.fr
26  *
27  * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
28  * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
29  *
30  * Description:
31  * Test theta.u and quaternion multiplication.
32  */
33 
39 #include <visp3/core/vpConfig.h>
40 
41 #ifdef VISP_HAVE_CATCH2
42 
43 #include <visp3/core/vpThetaUVector.h>
44 #include <visp3/core/vpUniRand.h>
45 
46 #define CATCH_CONFIG_RUNNER
47 #include <catch.hpp>
48 
49 #ifdef ENABLE_VISP_NAMESPACE
50 using namespace VISP_NAMESPACE_NAME;
51 #endif
52 
53 namespace
54 {
55 vpThetaUVector generateThetaU(vpUniRand &rng)
56 {
57  return vpThetaUVector(
58  vpMath::rad(rng.uniform(-180.0, 180.0)) *
59  vpColVector({ rng.uniform(-1.0, 1.0), rng.uniform(-1.0, 1.0), rng.uniform(-1.0, 1.0) }).normalize());
60 }
61 
62 vpQuaternionVector generateQuat(vpUniRand &rng)
63 {
64  const double angle = vpMath::rad(rng.uniform(-180.0, 180.0));
65  const double ctheta = std::cos(angle);
66  const double stheta = std::sin(angle);
67  const double ax = rng.uniform(-1.0, 1.0);
68  const double ay = rng.uniform(-1.0, 1.0);
69  const double az = rng.uniform(-1.0, 1.0);
70  return vpQuaternionVector(stheta * ax, stheta * ay, stheta * az, ctheta);
71 }
72 } // namespace
73 
74 
75 bool test(const std::string &s, const vpArray2D<double> &v, const std::vector<double> &bench)
76 {
77  std::cout << s << "(" << v.getRows() << "," << v.getCols() << ") = [" << v << "]" << std::endl;
78  if (bench.size() != v.size()) {
79  std::cout << "Test fails: bad size wrt bench" << std::endl;
80  return false;
81  }
82  for (unsigned int i = 0; i < v.size(); i++) {
83  if (std::fabs(v.data[i] - bench[i]) > std::fabs(v.data[i]) * std::numeric_limits<double>::epsilon()) {
84  std::cout << "Test fails: bad content" << std::endl;
85  return false;
86  }
87  }
88 
89  return true;
90 }
91 
92 bool test(const std::string &s, const vpArray2D<double> &v, const vpColVector &bench)
93 {
94  std::cout << s << "(" << v.getRows() << "," << v.getCols() << ") = [" << v << "]" << std::endl;
95  if (bench.size() != v.size()) {
96  std::cout << "Test fails: bad size wrt bench" << std::endl;
97  return false;
98  }
99  for (unsigned int i = 0; i < v.size(); i++) {
100  if (std::fabs(v.data[i] - bench[i]) > std::fabs(v.data[i]) * std::numeric_limits<double>::epsilon()) {
101  std::cout << "Test fails: bad content" << std::endl;
102  return false;
103  }
104  }
105 
106  return true;
107 }
108 
109 bool test(const std::string &s, const vpRotationVector &v, const double &bench)
110 {
111  std::cout << s << "(" << v.getRows() << "," << v.getCols() << ") = [" << v << "]" << std::endl;
112  for (unsigned int i = 0; i < v.size(); i++) {
113  if (std::fabs(v[i] - bench) > std::fabs(v[i]) * std::numeric_limits<double>::epsilon()) {
114  std::cout << "Test fails: bad content" << std::endl;
115  return false;
116  }
117  }
118 
119  return true;
120 }
121 
122 bool test_matrix_equal(const vpHomogeneousMatrix &M1, const vpHomogeneousMatrix &M2, double epsilon = 1e-10)
123 {
124  for (unsigned int i = 0; i < 4; i++) {
125  for (unsigned int j = 0; j < 4; j++) {
126  if (!vpMath::equal(M1[i][j], M2[i][j], epsilon)) {
127  return false;
128  }
129  }
130  }
131  return true;
132 }
133 
134 TEST_CASE("Common rotation operations", "[rotation]")
135 {
136  SECTION("Theta u initialization")
137  {
139  std::vector<double> bench1(3, vpMath::rad(10));
140  vpColVector bench3(3, vpMath::rad(10));
141  CHECK(test("r1", r1, bench1));
142 
143  bench1.clear();
144  bench1 = r1.toStdVector();
145  CHECK(test("r1", r1, bench1));
146 
147  r1.build(bench3);
148  CHECK(test("r1", r1, bench3));
149 
150  vpThetaUVector r2 = r1;
151  CHECK(test("r2", r2, bench1));
152  CHECK(r2.data != r1.data);
153 
154  CHECK(test("r2", r2, vpMath::rad(10)));
155 
156  vpThetaUVector r3;
157  r3 = vpMath::rad(10);
158  CHECK(test("r3", r3, bench1));
159 
160  for (unsigned int i = 0; i < r3.size(); i++) {
161  CHECK(std::fabs(r3[i] - bench1[i]) < std::fabs(r3[i]) * std::numeric_limits<double>::epsilon());
162  }
163 
164  const vpColVector r4 = 0.5 * r1;
165  std::vector<double> bench2(3, vpMath::rad(5));
166  CHECK(test("r4", r4, bench2));
167 
168  const vpThetaUVector r5(r3);
169  CHECK(test("r5", r5, bench1));
170  }
171  SECTION("Rxyz initialization")
172  {
174  std::vector<double> bench1(3, vpMath::rad(10));
175  vpColVector bench3(3, vpMath::rad(10));
176  CHECK(test("r1", r1, bench1));
177 
178  bench1.clear();
179  bench1 = r1.toStdVector();
180  CHECK(test("r1", r1, bench1));
181 
182  r1.build(bench3);
183  CHECK(test("r1", r1, bench3));
184 
185  vpRxyzVector r2 = r1;
186  CHECK(test("r2", r2, bench1));
187 
188  CHECK(test("r2", r2, vpMath::rad(10)));
189 
190  vpRxyzVector r3;
191  r3 = vpMath::rad(10);
192  CHECK(test("r3", r3, bench1));
193 
194  for (unsigned int i = 0; i < r3.size(); i++) {
195  CHECK(std::fabs(r3[i] - bench1[i]) <= std::fabs(r3[i]) * std::numeric_limits<double>::epsilon());
196  }
197 
198  vpColVector r4 = 0.5 * r1;
199  std::vector<double> bench2(3, vpMath::rad(5));
200  CHECK(test("r4", r4, bench2));
201 
202  vpRxyzVector r5(r3);
203  CHECK(test("r5", r5, bench1));
204  }
205  SECTION("rzyx initialization")
206  {
208  std::vector<double> bench1(3, vpMath::rad(10));
209  vpColVector bench3(3, vpMath::rad(10));
210  CHECK(test("r1", r1, bench1));
211 
212  bench1.clear();
213  bench1 = r1.toStdVector();
214  CHECK(test("r1", r1, bench1));
215 
216  r1.build(bench3);
217  CHECK(test("r1", r1, bench3));
218 
219  vpRzyxVector r2 = r1;
220  CHECK(test("r2", r2, bench1));
221 
222  CHECK(test("r2", r2, vpMath::rad(10)));
223 
224  vpRzyxVector r3;
225  r3 = vpMath::rad(10);
226  CHECK(test("r3", r3, bench1));
227 
228  for (unsigned int i = 0; i < r3.size(); i++) {
229  CHECK(std::fabs(r3[i] - bench1[i]) <= std::fabs(r3[i]) * std::numeric_limits<double>::epsilon());
230  }
231 
232  vpColVector r4 = 0.5 * r1;
233  std::vector<double> bench2(3, vpMath::rad(5));
234  CHECK(test("r4", r4, bench2));
235 
236  vpRzyxVector r5(r3);
237  CHECK(test("r5", r5, bench1));
238  }
239  SECTION("rzyz initialiation")
240  {
242  std::vector<double> bench1(3, vpMath::rad(10));
243  vpColVector bench3(3, vpMath::rad(10));
244  CHECK(test("r1", r1, bench1));
245 
246  bench1.clear();
247  bench1 = r1.toStdVector();
248  CHECK(test("r1", r1, bench1));
249 
250  r1.build(bench3);
251  CHECK(test("r1", r1, bench3));
252 
253  vpRzyzVector r2 = r1;
254  CHECK(test("r2", r2, bench1));
255 
256  CHECK(test("r2", r2, vpMath::rad(10)));
257 
258  vpRzyzVector r3;
259  r3 = vpMath::rad(10);
260  CHECK(test("r3", r3, bench1));
261 
262  for (unsigned int i = 0; i < r3.size(); i++) {
263  CHECK(std::fabs(r3[i] - bench1[i]) <= std::fabs(r3[i]) * std::numeric_limits<double>::epsilon());
264  }
265 
266  vpColVector r4 = 0.5 * r1;
267  std::vector<double> bench2(3, vpMath::rad(5));
268  CHECK(test("r4", r4, bench2));
269 
270  vpRzyzVector r5(r3);
271  CHECK(test("r5", r5, bench1));
272  }
273  SECTION("Test quaternion initialization", "[quaternion]")
274  {
276  std::vector<double> bench1(4, vpMath::rad(10));
277  vpColVector bench3(4, vpMath::rad(10));
278  CHECK(test("r1", r1, bench1));
279 
280  bench1.clear();
281  bench1 = r1.toStdVector();
282  CHECK(test("r1", r1, bench1));
283 
284  r1.build(bench3);
285  CHECK(test("r1", r1, bench3));
286 
287  vpQuaternionVector r2 = r1;
288  CHECK(test("r2", r2, bench1));
289 
290  CHECK(test("r2", r2, vpMath::rad(10)));
291 
293  r3.set(vpMath::rad(10), vpMath::rad(10), vpMath::rad(10), vpMath::rad(10));
294  CHECK(test("r3", r3, bench1));
295 
296  for (unsigned int i = 0; i < r3.size(); i++) {
297  CHECK(std::fabs(r3[i] - bench1[i]) <= std::fabs(r3[i]) * std::numeric_limits<double>::epsilon());
298  }
299 
300  vpColVector r4 = 0.5 * r1;
301  std::vector<double> bench2(4, vpMath::rad(5));
302  CHECK(test("r4", r4, bench2));
303 
304  vpQuaternionVector r5(r3);
305  CHECK(test("r5", r5, bench1));
306  }
307  SECTION("Conversions")
308  {
310  for (int i = -10; i < 10; i++) {
311  for (int j = -10; j < 10; j++) {
312  vpThetaUVector tu(vpMath::rad(90 + i), vpMath::rad(170 + j), vpMath::rad(45));
313  tu.build(vpRotationMatrix(tu)); // put some coherence into rotation convention
314 
315  std::cout << "Initialization " << std::endl;
316 
317  double theta;
318  vpColVector u;
319  tu.extract(theta, u);
320 
321  std::cout << "theta=" << vpMath::deg(theta) << std::endl;
322  std::cout << "u=" << u << std::endl;
323 
324  std::cout << "From vpThetaUVector to vpRotationMatrix " << std::endl;
325  R.build(tu);
326 
327  std::cout << "Matrix R";
328  CHECK(R.isARotationMatrix());
329 
330  std::cout << R << std::endl;
331 
332  std::cout << "From vpRotationMatrix to vpQuaternionVector " << std::endl;
333  vpQuaternionVector q(R);
334  CHECK(q.magnitude() == Approx(1.0).margin(1e-4));
335  std::cout << q << std::endl;
336 
337  R.build(q);
338  CHECK(R.isARotationMatrix());
339  std::cout << "From vpQuaternionVector to vpRotationMatrix " << std::endl;
340 
341  std::cout << "From vpRotationMatrix to vpRxyzVector " << std::endl;
342  vpRxyzVector RxyzbuildR(R);
343  std::cout << RxyzbuildR << std::endl;
344 
345  std::cout << "From vpRxyzVector to vpThetaUVector " << std::endl;
346  std::cout << " use From vpRxyzVector to vpRotationMatrix " << std::endl;
347  std::cout << " use From vpRotationMatrix to vpThetaUVector " << std::endl;
348 
349  vpThetaUVector tubuildEu;
350  tubuildEu.build(R);
351 
352  std::cout << std::endl;
353  std::cout << "result : should equivalent to the first one " << std::endl;
354 
355  double theta2;
356  vpColVector u2;
357 
358  tubuildEu.extract(theta2, u2);
359  std::cout << "theta=" << vpMath::deg(theta2) << std::endl;
360  std::cout << "u=" << u2 << std::endl;
361 
362  CHECK(vpMath::abs(theta2 - theta) < std::numeric_limits<double>::epsilon() * 1e10);
363  CHECK(vpMath::abs(u[0] - u2[0]) < std::numeric_limits<double>::epsilon() * 1e10);
364  CHECK(vpMath::abs(u[1] - u2[1]) < std::numeric_limits<double>::epsilon() * 1e10);
365  CHECK(vpMath::abs(u[2] - u2[2]) < std::numeric_limits<double>::epsilon() * 1e10);
366  }
367  }
368  SECTION("Conversion from and to rzyz vector")
369  {
370  vpRzyzVector rzyz(vpMath::rad(180), vpMath::rad(120), vpMath::rad(45));
371  std::cout << "Initialization vpRzyzVector " << std::endl;
372  std::cout << rzyz << std::endl;
373  std::cout << "From vpRzyzVector to vpRotationMatrix " << std::endl;
374  R.build(rzyz);
375  CHECK(R.isARotationMatrix());
376  std::cout << "From vpRotationMatrix to vpRzyzVector " << std::endl;
377  vpRzyzVector rzyz_final;
378  rzyz_final.build(R);
379  CHECK(test("rzyz", rzyz_final, vpColVector(rzyz)));
380  std::cout << rzyz_final << std::endl;
381  }
382  SECTION("Conversion from and to rzyx vector")
383  {
384  vpRzyxVector rzyx(vpMath::rad(180), vpMath::rad(120), vpMath::rad(45));
385  std::cout << "Initialization vpRzyxVector " << std::endl;
386  std::cout << rzyx << std::endl;
387  std::cout << "From vpRzyxVector to vpRotationMatrix " << std::endl;
388  R.build(rzyx);
389  CHECK(R.isARotationMatrix());
390  std::cout << R << std::endl;
391  std::cout << "From vpRotationMatrix to vpRzyxVector " << std::endl;
392  vpRzyxVector rzyx_final;
393  rzyx_final.build(R);
394  bool ret = test("rzyx", rzyx_final, vpColVector(rzyx));
395  if (ret == false) {
396  // Euler angle representation is not unique
397  std::cout << "Rzyx vector differ. Test rotation matrix..." << std::endl;
398  vpRotationMatrix RR(rzyx_final);
399  if (R == RR) {
400  std::cout << "Rzyx vector differ but rotation matrix is valid" << std::endl;
401  ret = true;
402  }
403  }
404  CHECK(ret);
405  std::cout << rzyx_final << std::endl;
406  }
407  }
408  SECTION("Rotation matrix extraction from homogeneous matrix and multiplication")
409  {
410  // Test rotation_matrix * homogeneous_matrix
411  vpHomogeneousMatrix _1_M_2_truth;
412  _1_M_2_truth[0][0] = 0.9835;
413  _1_M_2_truth[0][1] = -0.0581;
414  _1_M_2_truth[0][2] = 0.1716;
415  _1_M_2_truth[0][3] = 0;
416  _1_M_2_truth[1][0] = -0.0489;
417  _1_M_2_truth[1][1] = -0.9972;
418  _1_M_2_truth[1][2] = -0.0571;
419  _1_M_2_truth[1][3] = 0;
420  _1_M_2_truth[2][0] = 0.1744;
421  _1_M_2_truth[2][1] = 0.0478;
422  _1_M_2_truth[2][2] = -0.9835;
423  _1_M_2_truth[2][3] = 0;
424  vpHomogeneousMatrix _2_M_3_;
425  _2_M_3_[0][0] = 0.9835;
426  _2_M_3_[0][1] = -0.0581;
427  _2_M_3_[0][2] = 0.1716;
428  _2_M_3_[0][3] = 0.0072;
429  _2_M_3_[1][0] = -0.0489;
430  _2_M_3_[1][1] = -0.9972;
431  _2_M_3_[1][2] = -0.0571;
432  _2_M_3_[1][3] = 0.0352;
433  _2_M_3_[2][0] = 0.1744;
434  _2_M_3_[2][1] = 0.0478;
435  _2_M_3_[2][2] = -0.9835;
436  _2_M_3_[2][3] = 0.9470;
437 
438  vpRotationMatrix _1_R_2_ = _1_M_2_truth.getRotationMatrix();
439  vpHomogeneousMatrix _1_M_3_(_1_R_2_* _2_M_3_);
440  vpHomogeneousMatrix _1_M_3_truth(_1_M_2_truth * _2_M_3_);
441  CHECK(test_matrix_equal(_1_M_3_, _1_M_3_truth));
442  }
443 }
444 
445 TEST_CASE("Theta u multiplication", "[theta.u]")
446 {
447  const int nTrials = 100;
448  const uint64_t seed = 0x123456789;
449  vpUniRand rng(seed);
450  for (int iter = 0; iter < nTrials; iter++) {
451  const vpThetaUVector tu0 = generateThetaU(rng);
452  const vpThetaUVector tu1 = generateThetaU(rng);
453 
454  const vpRotationMatrix c1Rc2(tu0);
455  const vpRotationMatrix c2Rc3(tu1);
456  const vpRotationMatrix c1Rc3_ref = c1Rc2 * c2Rc3;
457  const vpThetaUVector c1_tu_c3 = tu0 * tu1;
458  // two rotation vectors can represent the same rotation,
459  // that is why we compare the rotation matrices
460  const vpRotationMatrix c1Rc3(c1_tu_c3);
461 
462  const double tolerance = 1e-9;
463  for (unsigned int i = 0; i < 3; i++) {
464  for (unsigned int j = 0; j < 3; j++) {
465  CHECK(c1Rc3_ref[i][j] == Approx(c1Rc3[i][j]).epsilon(0).margin(tolerance));
466  }
467  }
468  }
469 }
470 
471 TEST_CASE("Quaternion multiplication", "[quaternion]")
472 {
473  const int nTrials = 100;
474  const uint64_t seed = 0x123456789;
475  vpUniRand rng(seed);
476  for (int iter = 0; iter < nTrials; iter++) {
477  const vpQuaternionVector q0 = generateQuat(rng);
478  const vpQuaternionVector q1 = generateQuat(rng);
479 
480  const vpRotationMatrix c1Rc2(q0);
481  const vpRotationMatrix c2Rc3(q1);
482  const vpRotationMatrix c1Rc3_ref = c1Rc2 * c2Rc3;
483 
484  const vpQuaternionVector c1_q_c3 = q0 * q1;
485  // two quaternions of opposite sign can represent the same rotation,
486  // that is why we compare the rotation matrices
487  const vpRotationMatrix c1Rc3(c1_q_c3);
488 
489  const double tolerance = 1e-9;
490  for (unsigned int i = 0; i < 3; i++) {
491  for (unsigned int j = 0; j < 3; j++) {
492  CHECK(c1Rc3_ref[i][j] == Approx(c1Rc3[i][j]).epsilon(0).margin(tolerance));
493  }
494  }
495  }
496 }
497 
498 int main(int argc, char *argv[])
499 {
500  Catch::Session session; // There must be exactly one instance
501 
502  // Let Catch (using Clara) parse the command line
503  session.applyCommandLine(argc, argv);
504 
505  int numFailed = session.run();
506 
507  // numFailed is clamped to 255 as some unices only use the lower 8 bits.
508  // This clamping has already been applied, so just return it here
509  // You can also do any post run clean-up here
510  return numFailed;
511 }
512 #else
513 #include <iostream>
514 
515 int main() { return EXIT_SUCCESS; }
516 #endif
unsigned int getCols() const
Definition: vpArray2D.h:337
Type * data
Address of the first element of the data array.
Definition: vpArray2D.h:148
unsigned int size() const
Return the number of elements of the 2D array.
Definition: vpArray2D.h:349
unsigned int getRows() const
Definition: vpArray2D.h:347
Implementation of column vector and the associated operations.
Definition: vpColVector.h:191
vpColVector extract(unsigned int r, unsigned int colsize) const
Definition: vpColVector.h:405
Implementation of an homogeneous matrix and operations on such kind of matrices.
vpRotationMatrix getRotationMatrix() const
static double rad(double deg)
Definition: vpMath.h:129
static Type abs(const Type &x)
Definition: vpMath.h:269
static bool equal(double x, double y, double threshold=0.001)
Definition: vpMath.h:458
static double deg(double rad)
Definition: vpMath.h:119
Implementation of a rotation vector as quaternion angle minimal representation.
void set(double x, double y, double z, double w)
Implementation of a rotation matrix and operations on such kind of matrices.
vpRotationMatrix & build(const vpHomogeneousMatrix &M)
bool isARotationMatrix(double threshold=1e-6) const
Implementation of a generic rotation vector.
Implementation of a rotation vector as Euler angle minimal representation.
Definition: vpRxyzVector.h:183
Implementation of a rotation vector as Euler angle minimal representation.
Definition: vpRzyxVector.h:184
vpRzyxVector & build(const vpRotationMatrix &R)
Implementation of a rotation vector as Euler angle minimal representation.
Definition: vpRzyzVector.h:182
vpRzyzVector & build(const vpRotationMatrix &R)
Implementation of a rotation vector as axis-angle minimal representation.
vpThetaUVector & build(const vpHomogeneousMatrix &M)
void extract(double &theta, vpColVector &u) const
Class for generating random numbers with uniform probability density.
Definition: vpUniRand.h:125
int uniform(int a, int b)
Definition: vpUniRand.cpp:159