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