Visual Servoing Platform  version 3.6.1 under development (2024-10-02)
testJsonArrayConversion.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 vpArray2D and children JSON parse / save.
32  */
33 
40 #include <visp3/core/vpConfig.h>
41 
42 #if defined(VISP_HAVE_NLOHMANN_JSON) && defined(VISP_HAVE_CATCH2)
43 
44 #include <random>
45 #include <visp3/core/vpArray2D.h>
46 #include <visp3/core/vpIoTools.h>
47 
48 #include <nlohmann/json.hpp>
49 using json = nlohmann::json;
50 
51 #define CATCH_CONFIG_RUNNER
52 #include "catch.hpp"
53 
54 #ifdef ENABLE_VISP_NAMESPACE
55 using namespace VISP_NAMESPACE_NAME;
56 #endif
57 
58 namespace
59 {
60 using StringMatcherBase = Catch::Matchers::StdString::StringMatcherBase;
61 class vpExceptionMatcher : public Catch::Matchers::Impl::MatcherBase<vpException>
62 {
64  const StringMatcherBase &m_messageMatcher;
65 
66 public:
67  vpExceptionMatcher(vpException::generalExceptionEnum type, const StringMatcherBase &messageMatcher)
68  : m_type(type), m_messageMatcher(messageMatcher)
69  { }
70 
71  bool match(vpException const &in) const VP_OVERRIDE
72  {
73  return m_type == in.getCode() && m_messageMatcher.match(in.getStringMessage());
74  }
75 
76  std::string describe() const VP_OVERRIDE
77  {
78  std::ostringstream ss;
79  ss << "vpException has type " << m_type << " and message " << m_messageMatcher.describe();
80  return ss.str();
81  }
82 };
83 
84 class vpRandomArray2DGenerator : public Catch::Generators::IGenerator<vpArray2D<double> >
85 {
86 private:
87  std::minstd_rand m_rand;
88  std::uniform_real_distribution<> m_val_dist;
89  std::uniform_int_distribution<> m_dim_dist;
90 
91  vpArray2D<double> current;
92 
93 public:
94  vpRandomArray2DGenerator(double valueRange, int minSize, int maxSize)
95  : m_rand(std::random_device {}()), m_val_dist(-valueRange, valueRange), m_dim_dist(minSize, maxSize)
96 
97  {
98  static_cast<void>(next());
99  }
100 
101  vpArray2D<double> const &get() const VP_OVERRIDE { return current; }
102  bool next() VP_OVERRIDE
103  {
104  const unsigned nCols = m_dim_dist(m_rand);
105  const unsigned nRows = m_dim_dist(m_rand);
106  current.resize(nRows, nCols);
107  for (unsigned i = 0; i < nRows; ++i) {
108  for (unsigned j = 0; j < nCols; ++j) {
109  current[i][j] = m_val_dist(m_rand);
110  }
111  }
112  return true;
113  }
114 };
115 class vpRandomColVectorGenerator : public Catch::Generators::IGenerator<vpColVector>
116 {
117 private:
118  std::minstd_rand m_rand;
119  std::uniform_real_distribution<> m_val_dist;
120  std::uniform_int_distribution<> m_dim_dist;
121 
122  vpColVector current;
123 
124 public:
125  vpRandomColVectorGenerator(double valueRange, int minSize, int maxSize)
126  : m_rand(std::random_device {}()), m_val_dist(-valueRange, valueRange), m_dim_dist(minSize, maxSize)
127 
128  {
129  static_cast<void>(next());
130  }
131 
132  const vpColVector &get() const VP_OVERRIDE { return current; }
133  bool next() VP_OVERRIDE
134  {
135  const unsigned nRows = m_dim_dist(m_rand);
136  current.resize(nRows);
137  for (unsigned i = 0; i < nRows; ++i) {
138  current[i] = m_val_dist(m_rand);
139  }
140  return true;
141  }
142 };
143 Catch::Generators::GeneratorWrapper<vpArray2D<double> > randomArray(double v, int minSize, int maxSize)
144 {
145  return Catch::Generators::GeneratorWrapper<vpArray2D<double> >(
146  std::unique_ptr<Catch::Generators::IGenerator<vpArray2D<double> > >(
147  new vpRandomArray2DGenerator(v, minSize, maxSize)));
148 }
149 Catch::Generators::GeneratorWrapper<vpColVector> randomColVector(double v, int minSize, int maxSize)
150 {
151  return Catch::Generators::GeneratorWrapper<vpColVector>(
152  std::unique_ptr<Catch::Generators::IGenerator<vpColVector> >(new vpRandomColVectorGenerator(v, minSize, maxSize)));
153 }
154 vpExceptionMatcher matchVpException(vpException::generalExceptionEnum type, const StringMatcherBase &matcher)
155 {
156  return vpExceptionMatcher(type, matcher);
157 }
158 } // namespace
159 SCENARIO("Serializing a vpArray2D", "[json]")
160 {
161  GIVEN("A random vpArray2D<double>")
162  {
163  const vpArray2D<double> array = GENERATE(take(10, randomArray(50.0, 1, 10)));
164  WHEN("Serializing to a JSON object")
165  {
166  const json j = array;
167  THEN("JSON object is a dictionary") { REQUIRE(j.is_object()); }
168  THEN("JSON object contains correct number of columns")
169  {
170  REQUIRE(j.at("cols").get<unsigned int>() == array.getCols());
171  }
172  THEN("JSON object contains correct number of rows")
173  {
174  REQUIRE(j.at("rows").get<unsigned int>() == array.getRows());
175  }
176  THEN("JSON object contains the array values")
177  {
178  const json jData = j.at("data");
179  THEN("The data field is an array") { REQUIRE(jData.is_array()); }
180  THEN("The data field contains the correct number of values") { REQUIRE(jData.size() == array.size()); }
181  THEN("The data field contains the correct number of values")
182  {
183  const double *const start = array[0];
184  const std::vector<double> vec(start, start + array.size());
185  REQUIRE(vec == jData.get<std::vector<double> >());
186  }
187  }
188  }
189  }
190 }
191 
192 SCENARIO("Trying to instantiate a vpArray with a wrong type of object", "[json]")
193 {
194  GIVEN("A random scalar converted to a JSON representation")
195  {
196  vpArray2D<double> array;
197  std::minstd_rand rand;
198  std::uniform_real_distribution<> dist;
199  const json j = GENERATE(take(50, random(-200.0, 200.0)));
200  THEN("An exception is thrown")
201  {
202  const auto matcher = Catch::Matchers::Contains("is not an array or object");
203  REQUIRE_THROWS_MATCHES(from_json(j, array), vpException, matchVpException(vpException::badValue, matcher));
204  }
205  }
206 }
207 
208 SCENARIO("Recovering a vpArray2D from a JSON array", "[json]")
209 {
210  GIVEN("An empty array")
211  {
212  const json j = json::array_t();
213  WHEN("Converting to a vpArray2D")
214  {
215  vpArray2D<double> array = j;
216  THEN("The resulting array is empty") { REQUIRE(array.size() == 0); }
217  }
218  }
219  GIVEN("A 1D array")
220  {
221  const json j = { 10.0, 20.0, 30.0 };
222  WHEN("Converting to a vpArray2D")
223  {
224  THEN("An exception is thrown, since this is an ambiguous array")
225  {
226  vpArray2D<double> array;
227  const auto matcher = Catch::Matchers::Contains("is not an array of array");
228  REQUIRE_THROWS_MATCHES(from_json(j, array), vpException, matchVpException(vpException::badValue, matcher));
229  }
230  }
231  }
232  GIVEN("A vpArray2D converted to a json 2D array")
233  {
234  vpArray2D<double> array = GENERATE(take(10, randomArray(50.0, 2, 10)));
235  json j;
236  for (unsigned i = 0; i < array.getRows(); ++i) {
237  json jRow;
238  for (unsigned j = 0; j < array.getCols(); ++j) {
239  jRow.push_back(array[i][j]);
240  }
241  j.push_back(jRow);
242  }
243  WHEN("Converting back to a vpArray2D")
244  {
245  vpArray2D<double> array2 = j;
246  THEN("The values are correct") { REQUIRE(array == array2); }
247  }
248  WHEN("Removing elements from rows so that they do not have the same size")
249  {
250  j[0].erase(0); // Generating at least a 2x2 array
251  THEN("An exception is thrown")
252  {
253  const auto matcher = Catch::Matchers::Contains("row arrays that are not of the same size");
254  REQUIRE_THROWS_MATCHES(from_json(j, array), vpException, matchVpException(vpException::badValue, matcher));
255  }
256  }
257  }
258 }
259 
260 SCENARIO("Recovering a vpArray2D from a JSON object as serialized by ViSP", "[json]")
261 {
262  GIVEN("A vpArray2D converted to JSON format")
263  {
264  const vpArray2D<double> array = GENERATE(take(10, randomArray(50.0, 1, 10)));
265  json j = array;
266  WHEN("Converting back to a vpArray2D")
267  {
268  const vpArray2D<double> array2 = j;
269  THEN("The 2 arrays are equal") { REQUIRE(array == array2); }
270  }
271  WHEN("Removing or adding some values from the data field so that its size does not match the expected array size")
272  {
273  j.at("data").erase(0);
274  THEN("An exception is thrown")
275  {
276  vpArray2D<double> array2;
277  const auto matcher = Catch::Matchers::Contains("must be an array of size");
278  REQUIRE_THROWS_MATCHES(from_json(j, array2), vpException, matchVpException(vpException::badValue, matcher));
279  }
280  }
281  }
282 }
283 
284 SCENARIO("Serializing and deserializing a vpColVector", "[json]")
285 {
286  GIVEN("A random vpColVector")
287  {
288  const vpColVector v = GENERATE(take(100, randomColVector(100.0, 1, 50)));
289  WHEN("Serializing to JSON")
290  {
291  const json j = v;
292  THEN("There is only one column") { REQUIRE(j.at("cols") == 1); }
293  THEN("The type is vpColVector") { REQUIRE(j.at("type") == "vpColVector"); }
294  WHEN("Deserializing back to a vpColVector")
295  {
296  const vpColVector v2 = j;
297  THEN("The 2 vectors are the same") { REQUIRE(v == v2); }
298  }
299  }
300  }
301  GIVEN("A random 2D array with number of cols > 1")
302  {
303  const vpArray2D<double> array = GENERATE(take(10, randomArray(50.0, 2, 5)));
304  WHEN("Serializing this array to JSON")
305  {
306  const json j = array;
307  THEN("Serializing back to a vpColVector throws an exception")
308  {
309  vpColVector v;
310  const auto matcher = Catch::Matchers::Contains("tried to read a 2D array into a vpColVector");
311  REQUIRE_THROWS_MATCHES(from_json(j, v), vpException, matchVpException(vpException::badValue, matcher));
312  }
313  }
314  }
315  GIVEN("A random 2D array with number of cols = 1")
316  {
317  const vpColVector v = GENERATE(take(10, randomColVector(100.0, 1, 50)));
318  const vpArray2D<double> array = (vpArray2D<double>)v;
319  WHEN("Serializing this array to JSON")
320  {
321  const json j = array;
322  THEN("Serializing back to a vpColVector is ok and gives the same vector")
323  {
324  const vpColVector v2 = j;
325  REQUIRE(v == v2);
326  }
327  }
328  }
329 }
330 
331 int main(int argc, char *argv[])
332 {
333  Catch::Session session; // There must be exactly one instance
334  session.applyCommandLine(argc, argv);
335 
336  int numFailed = session.run();
337  return numFailed;
338 }
339 
340 #else
341 
342 int main() { return EXIT_SUCCESS; }
343 
344 #endif
unsigned int getCols() const
Definition: vpArray2D.h:337
void resize(unsigned int nrows, unsigned int ncols, bool flagNullify=true, bool recopy_=true)
Definition: vpArray2D.h:362
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
void resize(unsigned int i, bool flagNullify=true)
Definition: vpColVector.h:1143
error that can be emitted by ViSP classes.
Definition: vpException.h:60
@ badValue
Used to indicate that a value is not in the allowed range.
Definition: vpException.h:73