Skip to content

Commit f304a51

Browse files
committed
added support for 2D std-vector and 2D std-arrays; added imshow example; code refactoring
1 parent c42976b commit f304a51

File tree

5 files changed

+356
-172
lines changed

5 files changed

+356
-172
lines changed

README.md

Lines changed: 48 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,15 @@ The macro `_p` captures variable name and expands into ("variable_name", variabl
7878

7979
**Note**: Every container that is passed to python for plotting will be converted into an numpy array. This means fancy array slicing and array manipulations is possible. Just treat data as if it is originated in python with numpy.
8080

81+
Currently supports
82+
* 1D std vector
83+
* 1D std array
84+
* 2D std vector
85+
* 2D std array
86+
* Eigen containers
87+
88+
For information on how to include support for custom containers read section [custom container support](https://github.com/muralivnv/Cppyplot#custom-container-support).
89+
8190
## How-it-works
8291
Plot object `cppyplot` passes all the commands and containers to a python server (which is spawned automatically when the `cppyplot` object is created) using ZeroMQ. The spawned python server uses [asteval](https://anaconda.org/conda-forge/asteval) library to parse the passed commands. This means any command that can be used in python can be written on C++ side.
8392

@@ -96,63 +105,69 @@ As the library uses zmq, link the source file which uses cppyplot.hpp with the l
96105
## Custom Container Support
97106
By defining 3 helper functions, any c++ container can be adapted to pass onto python side.
98107
99-
#### Define `size_str`
100-
Define function to transform container size (total number of elems) into string object. See example down below where `size_str` function was defined for containers of type `std::array` and `std::vector`.
108+
Define the following functions at the end of the **cppyplot_container_support.h** located in the include folder.
109+
110+
#### Define `container_size`
111+
Define function to return container size (total number of elems). See example down below where `container_size` function was defined for containers of type `std::array` and `std::vector`.
101112
102113
```cpp
103114
// 1D-array
104-
template<typename T, std::size_t>
105-
std::string size_str(const std::array<T>& container)
106-
{
107-
return std::to_string(container.size());
108-
}
115+
template<typename T, std::size_t N>
116+
inline std::size_t container_size(const std::array<T, N>& data)
117+
{ (void)data; return N; }
109118
110119
// 2D-vector
111120
template<typename T>
112-
std::string size_str(const std::vector<std::vector<T>>& container)
113-
{
114-
return std::to_string(container.size()*container[0].size());
115-
}
121+
inline std::size_t container_size(const std::vector<std::vector<T>>& data)
122+
{ return data.size()*data[0].size(); }
116123
```
117124

118-
Follow the link, [Eigen_Support](https://github.com/muralivnv/Cppyplot/blob/master/include/cppyplot.hpp#L171) to look at Eigen container support.
119-
**Note**: This function need to be defined before the class defintion `cppyplot` in file `cppyplot.hpp`.
120-
121-
#### Define `shape_str`
122-
Define function to transform container shape (number of row, number of cols) into string object. See example down below where `shape_str` function was defined for containers of type `std::vector` and `std::vector<std::vector>`. This shape information will be used to reshape the array on python side using numpy.
125+
#### Define `container_shape`
126+
Define function to return container shape (number of row, number of cols). See example down below where `container_shape` function was defined for containers of type `std::vector` and `std::vector<std::vector>`. This shape information will be used to reshape the array on python side using numpy.
123127

124128
```cpp
125129
// 1D-vector
126130
template<typename T>
127-
std::string shape_str(const std::vector<T>& container)
128-
{
129-
return "(" + std::to_string(container.size()) + ",)";
130-
} // returns "(n_rows,)"
131+
inline std::array<std::size_t,1> container_shape(const std::vector<T>& data)
132+
{ return std::array<std::size_t, 1>{data.size()}; }
131133

132134
// 2D-vector
133135
template<typename T>
134-
std::string shape_str(const std::vector<std::vector<T>>& container)
135-
{
136-
return "(" + std::to_string(container.size()) + "," + container.front().size() + ")";
137-
} // returns "(n_rows, n_cols)"
136+
inline std::array<std::size_t, 2> container_shape(const std::vector<std::vector<T>>& data)
137+
{ return std::array<std::size_t, 2>{data.size(), data[0].size()}; }
138138
```
139139
140-
#### Define `void_ptr`
141-
The underlying zmq publisher requires pointer to the raw buffer inside the container to pass the data to python server. For that purpose define function `void_ptr` to extract data pointer and cast it into `(void*)`. See example down below which shows how to extract raw pointers for containers of type `std::vector` and `std::array`.
140+
#### Define `fill_zmq_buffer`
141+
The underlying zmq buffer requires either copying data to its buffer or pointing the zmq buffer to the container buffer.
142+
143+
Use the technique down below to point zmq buffer to the container buffer if the data inside the container is stored in one single continuous buffer. See example down below, where `fill_zmq_buffer` is defined for 1D-vector. As the internal vector buffer is stored in one continuous buffer, we can just point zmq buffer to vector buffer with custom dealloc function.
142144
143145
```cpp
144146
// 1D-vector
145147
template<typename T>
146-
void* void_ptr(const std::vector<T>& vec)
148+
inline void fill_zmq_buffer(const std::vector<T>& data, zmq::message_t& buffer)
147149
{
148-
return (void*)vec.data();
150+
buffer.rebuild((void*)data.data(), sizeof(T)*data.size(), custom_dealloc, nullptr);
149151
}
152+
```
150153

151-
// 1D-array
152-
template<typename T, std::size_t N>
153-
void* void_ptr(const std::array<T, N>& arr)
154+
If the buffer insider custom container is not stored in one single continuous buffer(for example `vector<vector<float>>`) then use mempcy to copy data.
155+
156+
```cpp
157+
// 2D vector
158+
// this uses mempcpy, there will be a runtime overhead
159+
template<typename T, std::size_t N, std::size_t M>
160+
inline void fill_zmq_buffer(const std::array<std::array<T, M>, N>& data, zmq::message_t& buffer)
154161
{
155-
return (void*)arr.data();
162+
buffer.rebuild(sizeof(T)*N*M);
163+
char * ptr = static_cast<char*>(buffer.data());
164+
165+
std::size_t offset = 0u;
166+
size_t n_bytes = sizeof(T)*M;
167+
for (std::size_t i = 0u; i < N; i++)
168+
{
169+
memcpy(ptr+offset, data[i].data(), n_bytes);
170+
offset += n_bytes;
171+
}
156172
}
157-
158173
```
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#include "../../include/cppyplot.hpp"
2+
3+
#include <cmath>
4+
#include <algorithm>
5+
#include <random>
6+
7+
int main()
8+
{
9+
std::random_device seed;
10+
std::mt19937 gen(seed());
11+
std::normal_distribution<float> norm(0.0F, 10.0F);
12+
13+
std::vector<std::vector<float>> image_vec(50, std::vector<float>(60));
14+
std::array<std::array<float, 100>, 100> image_arr;
15+
for (auto& row: image_vec)
16+
{
17+
std::iota(row.begin(), row.end(), 1.0F);
18+
}
19+
for (auto& row : image_arr)
20+
{
21+
for (auto& elem : row)
22+
{ elem = norm(gen); }
23+
}
24+
25+
Cppyplot::cppyplot pyp;
26+
27+
pyp.raw(R"pyp(
28+
plt.figure(figsize=(6,5))
29+
plt.imshow(image_vec)
30+
plt.title("2D-C++ std vector")
31+
plt.show(block=False)
32+
plt.figure(figsize=(6,5))
33+
plt.imshow(image_arr)
34+
plt.title("2D-C++ std array")
35+
plt.show(block=True)
36+
)pyp");
37+
pyp.data_args(_p(image_vec), _p(image_arr));
38+
39+
std::cout << "Good bye ...";
40+
41+
return EXIT_SUCCESS;
42+
}

include/cppyplot.hpp

Lines changed: 24 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -55,143 +55,15 @@ constexpr decltype(auto) compile_time_str(const char(&str)[N])
5555

5656
namespace Cppyplot
5757
{
58-
59-
/* ZMQ requires custom dealloc function for zero-copy */
60-
void custom_dealloc(void* data, void* hint)
61-
{
62-
(void)data;
63-
(void)hint;
64-
return;
65-
}
6658

6759
// utility function for raw string literal parsing
6860
std::string dedent_string(const std::string_view raw_str);
6961

7062
template<typename T>
71-
struct ValType{
72-
const static std::size_t elem_size = 0u;
73-
inline const static std::string typestr{"0"};
74-
};
75-
76-
template<>
77-
struct ValType<char>{
78-
const static std::size_t elem_size = sizeof(char);
79-
inline const static std::string typestr{"c"};
80-
};
81-
82-
template<>
83-
struct ValType<signed char>{
84-
const static std::size_t elem_size = sizeof(signed char);
85-
inline const static std::string typestr{"b"};
86-
};
87-
88-
template<>
89-
struct ValType<unsigned char>{
90-
const static std::size_t elem_size = sizeof(unsigned char);
91-
inline const static std::string typestr{"B"};
92-
};
93-
94-
template<>
95-
struct ValType<short>{
96-
const static std::size_t elem_size = sizeof(short);
97-
inline const static std::string typestr{"h"};
98-
};
99-
100-
template<>
101-
struct ValType<unsigned short>{
102-
const static std::size_t elem_size = sizeof(unsigned short);
103-
inline const static std::string typestr{"H"};
104-
};
105-
106-
template<>
107-
struct ValType<int>{
108-
const static std::size_t elem_size = sizeof(int);
109-
inline const static std::string typestr{"i"};
110-
};
63+
std::string shape_str(const T& shape);
11164

112-
template<>
113-
struct ValType<unsigned int>{
114-
const static std::size_t elem_size = sizeof(unsigned int);
115-
inline const static std::string typestr{"I"};
116-
};
117-
118-
template<>
119-
struct ValType<long>{
120-
const static std::size_t elem_size = sizeof(long);
121-
inline const static std::string typestr{"l"};
122-
};
123-
124-
template<>
125-
struct ValType<unsigned long>{
126-
const static std::size_t elem_size = sizeof(unsigned long);
127-
inline const static std::string typestr{"L"};
128-
};
129-
130-
template<>
131-
struct ValType<long long>{
132-
const static std::size_t elem_size = sizeof(long long);
133-
inline const static std::string typestr{"q"};
134-
};
135-
136-
template<>
137-
struct ValType<unsigned long long>{
138-
const static std::size_t elem_size = sizeof(unsigned long long);
139-
inline const static std::string typestr{"Q"};
140-
};
141-
142-
template<>
143-
struct ValType<float>{
144-
const static std::size_t elem_size = sizeof(float);
145-
inline const static std::string typestr{"f"};
146-
};
147-
148-
template<>
149-
struct ValType<double>{
150-
const static std::size_t elem_size = sizeof(double);
151-
inline const static std::string typestr{"d"};
152-
};
153-
154-
155-
template<typename T>
156-
inline std::string shape_str(const std::vector<T>& data)
157-
{
158-
return "(" + std::to_string(data.size()) +",)";
159-
}
160-
161-
template<typename T>
162-
inline std::string size_str(const std::vector<T>& data)
163-
{
164-
return std::to_string(data.size());
165-
}
166-
167-
template<typename T>
168-
inline void* void_ptr(const std::vector<T>& data) noexcept
169-
{
170-
return (void*)data.data();
171-
}
172-
173-
174-
#if defined (EIGEN_AVAILABLE)
175-
/*Reference: https://eigen.tuxfamily.org/dox/TopicFunctionTakingEigenTypes.html */
176-
template<typename Derived>
177-
inline std::string shape_str(const Eigen::EigenBase<Derived>& eigen_container)
178-
{
179-
return "(" + std::to_string(eigen_container.rows()) + "," + std::to_string(eigen_container.cols()) +")";
180-
}
181-
182-
template<typename Derived>
183-
inline std::string size_str(const Eigen::EigenBase<Derived>& eigen_container)
184-
{
185-
return std::to_string(eigen_container.size());
186-
}
187-
188-
template<typename Derived>
189-
inline void* void_ptr(const Eigen::EigenBase<Derived>& eigen_container)
190-
{
191-
return (void*)eigen_container.derived().data();
192-
}
193-
194-
#endif
65+
#include "cppyplot_types.h"
66+
#include "cppyplot_container_support.h"
19567

19668
class cppyplot{
19769
private:
@@ -240,38 +112,36 @@ class cppyplot{
240112
template<typename T>
241113
inline std::string create_header(const std::string& key, const T& cont) noexcept
242114
{
243-
using elem_type = typename T::value_type;
115+
auto elem_type = get_ValType(cont);
244116
std::string header{"data|"};
245117

246118
// variable name
247119
header += key;
248120
header.append("|");
249121

250122
// container element type (float or int or ...)
251-
header += ValType<elem_type>::typestr;
123+
header += std::string{decltype(elem_type)::typestr};
252124
header.append("|");
253125

254126
// total number of elements in the container
255-
header += size_str(cont);
127+
header += std::to_string(container_size(cont));
256128
header.append("|");
257129

258130
// shape of the container
259-
header += shape_str(cont);
131+
header += shape_str(container_shape(cont));
260132

261133
return header;
262134
}
263135

264136
template <typename T>
265137
void send_container(const std::string& key, const T& cont)
266138
{
267-
using elem_type = typename T::value_type;
268-
269139
std::string data_header{create_header(key, cont)};
270140
zmq::message_t msg(data_header.c_str(), data_header.length());
271141
socket_.send(msg, zmq::send_flags::none);
272142

273-
zmq::message_t payload(void_ptr(cont), ValType<elem_type>::elem_size*cont.size(),
274-
custom_dealloc, nullptr);
143+
zmq::message_t payload;
144+
fill_zmq_buffer(cont, payload);
275145
socket_.send(payload, zmq::send_flags::none);
276146
}
277147

@@ -352,6 +222,21 @@ std::string dedent_string(const std::string_view raw_str)
352222
}
353223
}
354224

225+
226+
template<typename T>
227+
std::string shape_str(const T& shape)
228+
{
229+
std::string out_str("(");
230+
for (auto size : shape)
231+
{
232+
out_str.append(std::to_string(size));
233+
out_str.append(",");
234+
}
235+
out_str.append(")");
236+
237+
return out_str;
238+
}
239+
355240
}
356241

357242
#endif

0 commit comments

Comments
 (0)