Visual Servoing Platform  version 3.6.1 under development (2025-03-15)
catchNPZ.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 visp::cnpy::npz_load() / visp::cnpy::npy_save() functions.
32  */
33 
37 #include <iostream>
38 #include <visp3/core/vpConfig.h>
39 #include <visp3/core/vpEndian.h>
40 
41 #if defined(VISP_HAVE_CATCH2) && \
42  (defined(_WIN32) || (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)))) && \
43  defined(VISP_LITTLE_ENDIAN) && defined(VISP_HAVE_MINIZ) && defined(VISP_HAVE_WORKING_REGEX)
44 
45 #include <catch_amalgamated.hpp>
46 
47 #include <type_traits>
48 #include <complex>
49 #include <visp3/core/vpIoTools.h>
50 #include <visp3/core/vpImage.h>
51 
52 #ifdef ENABLE_VISP_NAMESPACE
53 using namespace VISP_NAMESPACE_NAME;
54 #endif
55 
56 namespace
57 {
58 std::string createTmpDir()
59 {
60  std::string directory_filename = vpIoTools::getTempPath() + "/testNPZ";
61 
62  vpIoTools::makeDirectory(directory_filename);
63  return directory_filename;
64 }
65 }
66 
67 TEST_CASE("Test visp::cnpy::npy_load/npz_save", "[visp::cnpy I/O]")
68 {
69  std::string directory_filename = createTmpDir();
70  REQUIRE(vpIoTools::checkDirectory(directory_filename));
71  std::string npz_filename = directory_filename + "/test_npz_read_write.npz";
72 
73  SECTION("Read/Save string data")
74  {
75  const std::string save_string = "Open Source Visual Servoing Platform";
76  std::vector<char> vec_save_string(save_string.begin(), save_string.end());
77  const std::string identifier = "String";
78  visp::cnpy::npz_save(npz_filename, identifier, &vec_save_string[0], { vec_save_string.size() }, "w");
79 
80  visp::cnpy::npz_t npz_data = visp::cnpy::npz_load(npz_filename);
81  REQUIRE(npz_data.find(identifier) != npz_data.end());
82 
83  visp::cnpy::NpyArray arr_string_data = npz_data[identifier];
84  std::vector<char> vec_arr_string_data = arr_string_data.as_vec<char>();
85  // For null-terminated character handling, see:
86  // https://stackoverflow.com/a/8247804
87  // https://stackoverflow.com/a/45491652
88  const std::string read_string = std::string(vec_arr_string_data.begin(), vec_arr_string_data.end());
89  CHECK(save_string == read_string);
90  }
91 
92  SECTION("Read/Save multi-dimensional array")
93  {
94  const std::string identifier = "Array";
95  size_t height = 5, width = 7, channels = 3;
96  std::vector<int> save_vec_copy;
97  {
98  std::vector<int> save_vec;
99  save_vec.reserve(height*width*channels);
100  for (int i = 0; i < static_cast<int>(height*width*channels); ++i) {
101  save_vec.push_back(i);
102  }
103 
104  visp::cnpy::npz_save(npz_filename, identifier, &save_vec[0], { height, width, channels }, "a"); // append
105  save_vec_copy = save_vec;
106  }
107 
108  {
109  visp::cnpy::npz_t npz_data = visp::cnpy::npz_load(npz_filename);
110  REQUIRE(npz_data.find(identifier) != npz_data.end());
111 
112  visp::cnpy::NpyArray arr_vec_data = npz_data[identifier];
113  std::vector<int> read_vec = arr_vec_data.as_vec<int>();
114 
115  REQUIRE(save_vec_copy.size() == read_vec.size());
116  for (size_t i = 0; i < read_vec.size(); ++i) {
117  CHECK(save_vec_copy[i] == read_vec[i]);
118  }
119  }
120  }
121 
122  SECTION("Read/Save vpImage<vpRGBa>")
123  {
124  // CHECK(std::is_trivially_copyable<vpRGBa>::value == true); // false
125  // CHECK(std::is_trivial<vpRGBa>::value == true); // false
126  CHECK(sizeof(vpRGBa) == (4 * sizeof(unsigned char)));
127 
128  const std::string identifier = "vpImage<vpRGBa>";
129  vpImage<vpRGBa> I_save_copy;
130  {
131  vpImage<vpRGBa> I_save(11, 17);
132  for (unsigned int i = 0; i < I_save.getRows(); i++) {
133  for (unsigned int j = 0; j < I_save.getCols(); j++) {
134  I_save[i][j].R = 4 * (i*I_save.getCols() + j) + 0;
135  I_save[i][j].G = 4 * (i*I_save.getCols() + j) + 1;
136  I_save[i][j].B = 4 * (i*I_save.getCols() + j) + 2;
137  I_save[i][j].A = 4 * (i*I_save.getCols() + j) + 3;
138  }
139  }
140 
141  visp::cnpy::npz_save(npz_filename, identifier, &I_save.bitmap[0], { I_save.getRows(), I_save.getCols() }, "a"); // append
142  I_save_copy = I_save;
143  }
144 
145  {
146  visp::cnpy::npz_t npz_data = visp::cnpy::npz_load(npz_filename);
147  REQUIRE(npz_data.find(identifier) != npz_data.end());
148 
149  visp::cnpy::NpyArray arr_vec_data = npz_data[identifier];
150  const bool copy_data = false;
151  vpImage<vpRGBa> I_read(arr_vec_data.data<vpRGBa>(), static_cast<unsigned int>(arr_vec_data.shape[0]),
152  static_cast<unsigned int>(arr_vec_data.shape[1]), copy_data);
153 
154  CHECK(I_save_copy.getSize() == I_read.getSize());
155  CHECK(I_save_copy == I_read);
156  }
157  }
158 
159  SECTION("Read/Save std::complex<double>")
160  {
161  // Handling of std::complex<>?
162  // - https://github.com/rogersce/cnpy/blob/4e8810b1a8637695171ed346ce68f6984e585ef4/cnpy.cpp#L40-L42
163  // - https://github.com/rogersce/cnpy/blob/4e8810b1a8637695171ed346ce68f6984e585ef4/cnpy.h#L129
164  // https://en.cppreference.com/w/cpp/named_req/TriviallyCopyable
165 
166  // Next CHECK() call may fail when g++ < 5 (case on centos-7-2 ci). That's why we ensure that c++ standard is > 11
167  // See https://stackoverflow.com/questions/25123458/is-trivially-copyable-is-not-a-member-of-std
168 #if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_11)
169  CHECK(std::is_trivially_copyable<std::complex<double>>::value == true);
170 #endif
171  // https://en.cppreference.com/w/cpp/types/is_trivial
172  // CHECK(std::is_trivial<std::complex<double>>::value == true); // false
173 
174  const std::string identifier = "std::complex<double>";
175  std::complex<double> complex_data_copy;
176  {
177  std::complex<double> complex_data(99, 3.14);
178  visp::cnpy::npz_save(npz_filename, identifier, &complex_data, { 1 }, "a"); // append
179  complex_data_copy = complex_data;
180  }
181 
182  {
183  visp::cnpy::npz_t npz_data = visp::cnpy::npz_load(npz_filename);
184  REQUIRE(npz_data.find(identifier) != npz_data.end());
185 
186  visp::cnpy::NpyArray arr_vec_data = npz_data[identifier];
187  std::complex<double> complex_data_read = *arr_vec_data.data<std::complex<double>>();
188 
189  CHECK(complex_data_copy.real() == complex_data_read.real());
190  CHECK(complex_data_copy.imag() == complex_data_read.imag());
191  }
192  }
193 
194  SECTION("Read/Save std::vector<std::complex<double>>")
195  {
196  const std::string identifier = "std::vector<std::complex<double>>";
197  std::vector<std::complex<double>> vec_complex_data_copy;
198  {
199  std::vector<std::complex<double>> vec_complex_data;
200  std::complex<double> complex_data(99, 3.14);
201  vec_complex_data.push_back(complex_data);
202 
203  complex_data.real(-77.12);
204  complex_data.imag(-100.95);
205  vec_complex_data.push_back(complex_data);
206 
207  visp::cnpy::npz_save(npz_filename, identifier, &vec_complex_data[0], { vec_complex_data.size() }, "a"); // append
208  vec_complex_data_copy = vec_complex_data;
209  }
210 
211  {
212  visp::cnpy::npz_t npz_data = visp::cnpy::npz_load(npz_filename);
213  REQUIRE(npz_data.find(identifier) != npz_data.end());
214 
215  visp::cnpy::NpyArray arr_vec_data = npz_data[identifier];
216  std::vector<std::complex<double>> vec_complex_data_read = arr_vec_data.as_vec<std::complex<double>>();
217 
218  REQUIRE(vec_complex_data_copy.size() == vec_complex_data_read.size());
219  for (size_t i = 0; i < vec_complex_data_copy.size(); i++) {
220  CHECK(vec_complex_data_copy[i].real() == vec_complex_data_read[i].real());
221  CHECK(vec_complex_data_copy[i].imag() == vec_complex_data_read[i].imag());
222  }
223  }
224  }
225 
226  SECTION("Read/Save vpHomogeneousMatrix")
227  {
228  const std::string identifier = "vpHomogeneousMatrix";
229  vpHomogeneousMatrix cMo_save_copy;
230  {
231  vpHomogeneousMatrix cMo_save(vpTranslationVector(10, 20, 30), vpThetaUVector(1, 2, 3));
232  // std::cout << "cMo_save:\n" << cMo_save << std::endl;
233 
234  visp::cnpy::npz_save(npz_filename, identifier, &cMo_save.data[0], { cMo_save.getRows(), cMo_save.getCols() }, "a"); // append
235  cMo_save_copy = cMo_save;
236  }
237 
238  {
239  visp::cnpy::npz_t npz_data = visp::cnpy::npz_load(npz_filename);
240  REQUIRE(npz_data.find(identifier) != npz_data.end());
241 
242  visp::cnpy::NpyArray arr_vec_data = npz_data[identifier];
243  vpHomogeneousMatrix cMo_read(arr_vec_data.as_vec<double>());
244  // std::cout << "cMo_read:\n" << cMo_read << std::endl;
245 
246  CHECK(cMo_save_copy == cMo_read);
247  }
248  }
249 
250  SECTION("Read/Save std::vector<vpHomogeneousMatrix>")
251  {
252  const std::string identifier = "std::vector<vpHomogeneousMatrix>";
253  std::vector<vpHomogeneousMatrix> vec_cMo_save_copy;
254  {
255  std::vector<double> vec_cMo_save;
256  for (size_t i = 0; i < 5; i++) {
257  vpHomogeneousMatrix cMo_save(vpTranslationVector(1. +10.*i, 2. +20.*i, 3. +30.*i), vpThetaUVector(0.1+i, 0.2+i, 0.3+i));
258  vec_cMo_save_copy.push_back(cMo_save);
259  vec_cMo_save.insert(vec_cMo_save.end(), cMo_save.data, cMo_save.data+cMo_save.size());
260  // std::cout << "cMo_save:\n" << cMo_save << std::endl;
261  }
262 
263  visp::cnpy::npz_save(npz_filename, identifier, &vec_cMo_save[0], { vec_cMo_save.size()/16, 16 }, "a"); // append
264  }
265 
266  {
267  visp::cnpy::npz_t npz_data = visp::cnpy::npz_load(npz_filename);
268  REQUIRE(npz_data.find(identifier) != npz_data.end());
269 
270  visp::cnpy::NpyArray arr_vec_data = npz_data[identifier];
271  std::vector<double> vec_cMo_read = arr_vec_data.as_vec<double>();
272  REQUIRE(vec_cMo_save_copy.size() == arr_vec_data.shape[0]);
273 
274  for (size_t i = 0; i < arr_vec_data.shape[0]; i++) {
275  std::vector<double>::const_iterator first = vec_cMo_read.begin() + i*arr_vec_data.shape[1];
276  std::vector<double>::const_iterator last = first + arr_vec_data.shape[1];
277  std::vector<double> subvec_cMo_read(first, last);
278 
279  vpHomogeneousMatrix cMo_read(subvec_cMo_read);
280  // std::cout << "cMo_read:\n" << cMo_read << std::endl;
281  CHECK(vec_cMo_save_copy[i] == cMo_read);
282  }
283  }
284  }
285  REQUIRE(vpIoTools::remove(directory_filename));
286  REQUIRE(!vpIoTools::checkDirectory(directory_filename));
287 }
288 
289 // https://en.cppreference.com/w/cpp/types/integer
290 // https://github.com/catchorg/Catch2/blob/devel/docs/test-cases-and-sections.md#type-parametrised-test-cases
291 using BasicTypes = std::tuple<uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t, uint64_t, int64_t, float, double>;
292 TEMPLATE_LIST_TEST_CASE("Test visp::cnpy::npy_load/npz_save", "[BasicTypes][list]", BasicTypes)
293 {
294  std::string directory_filename = createTmpDir();
295  REQUIRE(vpIoTools::checkDirectory(directory_filename));
296  std::string npz_filename = directory_filename + "/test_npz_read_write.npz";
297 
298  const std::string identifier = "data";
299  TestType save_data_copy;
300  {
301  TestType save_data = std::numeric_limits<TestType>::min();
302  visp::cnpy::npz_save(npz_filename, identifier, &save_data, { 1 }, "w");
303  save_data_copy = save_data;
304  }
305  {
306  visp::cnpy::npz_t npz_data = visp::cnpy::npz_load(npz_filename);
307  REQUIRE(npz_data.find(identifier) != npz_data.end());
308  visp::cnpy::NpyArray arr_data = npz_data[identifier];
309  TestType read_data = *arr_data.data<TestType>();
310  CHECK(save_data_copy == read_data);
311  }
312 
313  {
314  TestType save_data = std::numeric_limits<TestType>::max();
315  visp::cnpy::npz_save(npz_filename, identifier, &save_data, { 1 }, "a"); // append
316  save_data_copy = save_data;
317  }
318  {
319  visp::cnpy::npz_t npz_data = visp::cnpy::npz_load(npz_filename);
320  REQUIRE(npz_data.find(identifier) != npz_data.end());
321  visp::cnpy::NpyArray arr_data = npz_data[identifier];
322  TestType read_data = *arr_data.data<TestType>();
323  CHECK(save_data_copy == read_data);
324  }
325 
326  REQUIRE(vpIoTools::remove(directory_filename));
327  REQUIRE(!vpIoTools::checkDirectory(directory_filename));
328 }
329 
330 int main(int argc, char *argv[])
331 {
332  Catch::Session session;
333  session.applyCommandLine(argc, argv);
334  int numFailed = session.run();
335  return numFailed;
336 }
337 
338 #else
339 int main() { return EXIT_SUCCESS; }
340 #endif
Implementation of an homogeneous matrix and operations on such kind of matrices.
unsigned int getSize() const
Definition: vpImage.h:221
static std::string getTempPath()
Definition: vpIoTools.cpp:189
static bool checkDirectory(const std::string &dirname)
Definition: vpIoTools.cpp:396
static void makeDirectory(const std::string &dirname)
Definition: vpIoTools.cpp:550
static bool remove(const std::string &filename)
Definition: vpIoTools.cpp:921
Definition: vpRGBa.h:70
Implementation of a rotation vector as axis-angle minimal representation.
Class that consider the case of a translation vector.
void npz_save(std::string zipname, std::string fname, const T *data, const std::vector< size_t > &shape, std::string mode="w")
Definition: vpIoTools.h:235
VISP_EXPORT npz_t npz_load(std::string fname)
std::map< std::string, NpyArray > npz_t
Definition: vpIoTools.h:130
std::vector< size_t > shape
Definition: vpIoTools.h:124
std::vector< T > as_vec() const
Definition: vpIoTools.h:112