EZC3D
README.md
1 # EZC3D
2 
3 <img src="logo/logo.svg" width="40%" height="40%">
4 
5 EZC3D is an easy to use reader, modifier and writer for C3D format files. It is written en C++ with proper binders for Python and MATLAB scripting langages.
6 
7 C3D (http://c3d.org) is a format specifically designed to store biomechanics data. Hence many biomechanics softwares can produce C3D files in order to share data. However, there is a lack in the biomechanics community of an easy to use, free and open source library to read, modify and write them as needed when it gets to the data analysis. There was at some point the BTK project (https://github.com/Biomechanical-ToolKit/BTKCore) that was targeting this goal, but the project is now obsolete.
8 
9 EZC3D addresses these issues. It offers a comprehensive and light API to read and write C3D files. The source code is written in C++ allowing to be compiled and used by higher level langages thanks to SWIG (http://www.swig.org/). Still, proper interface are written on top of the SWIG binder in order to facilitate the experience of the coders in their respective langages.
10 
11 So, without further ado, let's begin C3Ding!
12 
13 # How to install
14 There are two main ways to install EZC3D on your computer: installing the binaries from Anaconda (easiest) or compiling the source code yourself (more versatile and up to date).
15 
16 ## Anaconda (For Windows and Linux, Mac is coming)
17 The easiest way to install EZC3D is to download the binaries from anaconda (https://anaconda.org/) repositories. The project is host on the pyomeca channel (https://anaconda.org/pyomeca/ezc3d).
18 
19 After having install properly an anaconda client [my suggestion would be Miniconda (https://conda.io/miniconda.html)] and loaded the desired environment to install EZC3D in, just type the following command:
20 ```bash
21 conda install -c conda-forge ezc3d
22 ```
23 The binaries and includes of the core of EZC3D will be installed in `bin` and `include` folders of the environment respectively. Moreover, the Python3 binder will also be installed in the environment.
24 
25 ## Compiling (For Windows, Linux and Mac)
26 The main drawback with downloading the pre-compiled version from Anaconda is that this version may be out-of-date. Moreover, since it is already compiled, it doesn't allow you to modify EZC3D if you need it. Therefore, a more versatile way to enjoy EZC3D is to compile it by yourself.
27 
28 EZC3D comes with a CMake (https://cmake.org/) project. If you don't know how to use CMake, you will find many examples via Internet. The main variables to set are:
29 
30 > `CMAKE_INSTALL_PREFIX` Which is the `path/to/install` EZC3D in. If you compile the Python3 binder, a valid installation of Python with Numpy should be installed relatived to this path.
31 >
32 > `BUILD_SHARED_LIBS` If you wan to build ezc3d in a shared `TRUE` or static `FALSE` library manner. Default is `TRUE`.
33 >
34 > `CMAKE_BUILD_TYPE` Which type of build you want. Options are `Debug`, `RelWithDebInfo`, `MinSizeRel` or `Release`. This is relevant only for the build done using the `make` command. Please note that you may experience a slow EZC3D library if you compile it without any optimization (i.e. `Debug`) especially on Windows.
35 >
36 > `BUILD_EXAMPLE` If you want `TRUE` or not `FALSE` to build the C++ example. Default is `TRUE`.
37 >
38 > `BUILD_TESTS` If you want `ON` or not `OFF` to build the tests of the project. Please note that this will download gtest (https://github.com/google/googletest). Default is `OFF`.
39 >
40 > `BUILD_DOC` If you want `ON` or not `OFF` to build the documentation of the project. Default is `OFF`.
41 >
42 > `BINDER_PYTHON3` If you want `ON` or not `OFF` to build the Python binder. Default is `OFF`.
43 >
44 > `Python3_EXECUTABLE` If `BINDER_PYTHON3` is set to `ON` then this variable should point to the Python executable. This python should have swig and Numpy installed with it. This variable should be found automatically.
45 >
46 > `SWIG_EXECUTABLE` If `BINDER_PYTHON3` is set to `ON` then this variable should point to the SWIG executable. This variable should be found automatically.
47 >
48 > `BINDER_MATLAB` If you want `ON` or not `OFF` to build the MATLAB binder. Default is `OFF`.
49 >
50 > `MATLAB_ROOT_DIR` If `BINDER_MATLAB` is set to `ON` then this variable should point to the root path of MATLAB directory. Please note that the MATLAB binder is based on MATLAB R2018a API and won't compile on earlier versions. This variable should be found automatically.
51 >
52 > `MATLAB_ezc3d_INSTALL_DIR` If `BINDER_MATLAB` is set to `ON` then this variable should point to the path where you want to install ezc3d. Typically, this is {MY DOCUMENTS}/MATLAB. The default value is the toolbox folder of MATLAB. Please note that if you leave the default value, you will probably need to grant administrator rights to the installer.
53 
54 # How to use
55 The aim of EZC3D is to be, indeed, eazy to use. Still, it is a C++ library and therefore requires so time to adapt. This section aims to help you level up as fast as possible, in order to enjoy EZC3D as fast as possible.
56 
57 There is example codes for C++, Python3 and MATLAB in the folder `example` that can be used as template to perform all the day-to-day tasks. Moreover, the test files in the tests folder can also be very useful.
58 
59 ## The C++ API
60 The core code is written in C++, meaning you can fully create from scratch, read and write C3D from C++.
61 The informations that follows is a basic guide that should allow you to perform anything you want to do.
62 
63 ### Create an empty yet valid C3D structure
64 To create a new valid yet empty C3D, just call the `c3d` class without parameter.
65 ```C++
66 ezc3d::c3d c3d;
67 ```
68 
69 ### Read a C3D
70 To read a C3D file you simply have to call the `c3d` class with a path
71 ```C++
72 ezc3d::c3d c3d("path_to_c3d.c3d");
73 ```
74 Please note that on Windows, the path must be `/` or `\\` separated, for obvious reasons.
75 
76 ### Write a C3D
77 A `c3d` class is able to write itself to a file using the method `write`
78 ```C++
79 ezc3d::c3d c3d;
80 c3d.write("path_to_c3d.c3d")
81 ```
82 
83 ### Navigating through the C3D class
84 The C3D class mimics the C3D structures as defined by the standard, that is separated into a `header`, a `parameters` and a `data` class. You can get a const-reference to these classes by simply calling their names (see below for more specific examples)
85 
86 #### Get a value from the header
87 To retrieve some information from the header, just call the `header` class and then the specific information you are interested in. If for example, you want to get the frame rate of the cameras, you should do as follow:
88 ```C++
89 ezc3d::c3d c3d("path_to_c3d.c3d");
90 float pointRate(c3d.header().frameRate());
91 ```
92 Please note that the names mimics those used by the C3D format as described by the c3d.org documentation. For more information on what you can get from the header, please refer to the documentation on [header](https://pyomeca.github.io/Documentation/ezc3d/classezc3d_1_1Header.html).
93 
94 #### Set a value to the header
95 It is not possible from outside to add, remove or even modify the header directly. The reason for that is that the header has a very specific formatting to be compliant to the standard. Therefore, the header will update itself if needed when the parameters class is modify. If it doesn't this is a bug that should be reported.
96 
97 #### Get a parameter
98 Parameters in C3D are arranged in a GROUP:PAMETER manner and the classes in EZC3D mimic this arrangement. Therefore a particular parameter always stands inside of a group. For example, if you are interested in the labels of the points, you can navigate up to the POINT group and then to the LABELS parameter.
99 ```C++
100 ezc3d::c3d c3d;
101 std::vector<std::string> point_labels(c3d.parameters().group("POINT").parameter("LABELS").valuesAsString());
102 for (size_t m = 0; m < point_labels.size(); ++m){
103  std::cout << point_labels[m] << std::endl;
104 }
105 ```
106 For more information on what you can get from the parameters, please refer to the documentation on [parameters](https://pyomeca.github.io/Documentation/ezc3d/classezc3d_1_1ParametersNS_1_1Parameters.html).
107 
108 #### Set a parameter
109 To set a parameter into a group, you must call an accessor method provided into the `c3d` class. The first parameter of the function is the name of the group to set the new parameter in, and the second parameter of the function is the actual parameter to set.
110 ```C++
111 ezc3d::c3d c3d;
112 ezc3d::ParametersNS::GroupNS::Parameter param("name_of_my_new_parameter"); // Create a new parameter
113 param.set(2.0); // Give a value to the parameter
114 c3d.parameter("GroupName", param); // Add the parameter to the c3d structure
115 ```
116 Please note that if this parameter already exist in the group named "GroupName", then this parameter is replaced by the new one. Otherwise, if it doesn't exist or the group doesn't exist, then it is added to the group. For more information on how to set a new parameter from `c3d` accessors methods, please refer to the documentation on [c3d](https://pyomeca.github.io/Documentation/ezc3d/classezc3d_1_1c3d.html).
117 
118 #### Get data
119 Point and analogous data are the core of the C3D file. To understand the structure though it is essential to understand that everything is based on points. For example, the base frame rate the point frame rate, while the analogous data is based on the number of data per point frame. Therefor to get a particular point in time, you must get the data at a certain frame and specify which point you are interested in, while to get a particular analogous data you must also specify the subframe.
120 ```C++
121 ezc3d::c3d c3d("path_to_c3d.c3d");
122 ezc3d::DataNS::Points3dNS::Point pt(new_c3d.c3d.data().frame(f).points().point(0));
123 pt.print();
124 ezc3d::DataNS::AnalogsNS::Channel channel(new_c3d.c3d.data().frame(0).analogs().subframe(0).channel("channel1"));
125 channel.print();
126 ```
127 For more information on what you can get from the points, please refer to the documentation on [points](https://pyomeca.github.io/Documentation/ezc3d/classezc3d_1_1DataNS_1_1Points3dNS_1_1Points.html) or [analogs](https://pyomeca.github.io/Documentation/ezc3d/classezc3d_1_1DataNS_1_1AnalogsNS_1_1Analogs.html).
128 
129 #### Set data
130 There are two ways to add data to the data set.
131 
132 ##### Using the c3d accessor
133 The first and prefered way is to add a frame via the accessors method of the class `c3d`. The parameter to send is the filled frame to add/replace to the data structure.
134 Please note that the points and channel must have been declare to the parameters before adding them to the data set. This is so the whole c3d structure is properly harmonized.
135 Please also note, for the same reason, that POINT:RATE and ANALOG:RATE must have been declared before adding points and analogs.
136 Here is a full example that creates a new C3D, add points and analogs and print it to the console.
137 ```C++
138 // Create an empyt c3d
139 ezc3d::c3d c3d_empty;
140 
141 // Declare rates
142 ezc3d::ParametersNS::GroupNS::Parameter pointRate("RATE");
143 pointRate.set(std::vector<float>() = {100}, {1});
144 c3d_empty.parameter("POINT", pointRate);
145 
146 ezc3d::ParametersNS::GroupNS::Parameter analogRate("RATE");
147 analogRate.set(std::vector<float>() = {1000}, {1});
148 c3d_empty.parameter("ANALOG", analogRate);
149 
150 // Declare the points and channels to the c3d
151 c3d_empty.point("new_marker1"); // Add empty
152 c3d_empty.point("new_marker2"); // Add empty
153 c3d_empty.analog("new_analog1"); // add the empty
154 c3d_empty.analog("new_analog2"); // add the empty
155 
156 // Fill them with some random values
157 ezc3d::DataNS::Frame f;
158 std::vector<std::string>labels(c3d_empty.parameters().group("POINT").parameter("LABELS").valuesAsString());
159 int nPoints(c3d_empty.parameters().group("POINT").parameter("USED").valuesAsInt()[0]);
160 ezc3d::DataNS::Points3dNS::Points pts;
161 for (size_t i=0; i<static_cast<size_t>(nPoints); ++i){
162  ezc3d::DataNS::Points3dNS::Point pt;
163  pt.name(labels[i]);
164  pt.x(1.0);
165  pt.y(2.0);
166  pt.z(3.0);
167  pts.point(pt);
168 }
169 ezc3d::DataNS::AnalogsNS::Analogs analog;
170 ezc3d::DataNS::AnalogsNS::SubFrame subframe;
171 for (size_t i=0; i < c3d_empty.header().nbAnalogs(); ++i){
172  ezc3d::DataNS::AnalogsNS::Channel c;
173  c.data(i+1);
174  subframe.channel(c);
175 }
176 for (size_t i=0; i < c3d_empty.header().nbAnalogByFrame(); ++i)
177  analog.subframe(subframe);
178 
179 // add them to the data set
180 f.add(pts, analog);
181 c3d_empty.frame(f);
182 c3d_empty.frame(f); // Why not adding a second frame?
183 
184 // Print them to the console
185 c3d_empty.print();
186 ```
187 For more information on how to set data from `c3d` accessors methods, please refer to the documentation on [c3d](https://pyomeca.github.io/Documentation/ezc3d/classezc3d_1_1c3d.html).
188 
189 ##### Using the nonConst reference
190 The second method is more designed for internal purpose. However, you may find yourself in situation where the normal method is just to long or restrictive for what you want to do. Then you can access directly the data via a nonConst reference. For example, you can add channels that way:
191 ```C++
192 // Add a new analog to the c3d (one filled with zeros, the other one with data)
193 ezc3d::c3d c3d;
194 
195 // Add a analog rate
196 ezc3d::ParametersNS::GroupNS::Parameter analog_rate("RATE");
197 analog_rate.set(1000.0);
198 c3d.parameter("ANALOG", analog_rate);
199 
200 c3d.analog("new_analog1"); // Declare an empty channel (Note the name will be overriden)
201 std::vector<ezc3d::DataNS::Frame> frames_analog;
202 ezc3d::DataNS::Frame frame;
203 // Fill the frame
204 for (size_t sf = 0; sf < c3d.header().nbAnalogByFrame(); ++sf){
205  ezc3d::DataNS::AnalogsNS::Channel newChannel("new_analogs2");
206  newChannel.data(sf+1);
207  ezc3d::DataNS::AnalogsNS::SubFrame subframes_analog;
208  subframes_analog.channel(newChannel);
209  frame.analogs_nonConst().subframe(subframes_analog); // The non-const reference makes it easier to add the subframe
210 }
211 c3d.frame(frame);
212 
213 // Print it
214 c3d.print();
215 ```
216 Please note that this method by-passes some protection and may create invalid C3D if not used properly.
217 
218 ## MATLAB
219 MATLAB (https://www.mathworks.com/) is a prototyping langage largely used in industry and faily used by the biomecanical scientific community. Despite the growing popularity of Python as a free and open-source alternative or Octave as a very similar langage open-source, MATLAB remains an important player. Therefore EZC3D comes with a binder for MATLAB.
220 
221 MATLAB stands for Matrix laboratory. As the name suggest, it is mainly used to perform operation on matrix. With that in mind, the binder was written to organize the point so it is easy to perform matrix multiplication on them. Hence, EZC3D works on MATLAB structure that separate the `header`, the `parameter` and the `data`. Into the `header` structure, you will find information on the `points`, the `analogs` and the `events`. Into the `parameter`, you will find all the groups and parameters as they appear in the C3D file. Finally, in the `data`, there is the `points` values organized into a 3d hypermatrix (XYZ x N_POINTS x N_FRAMES) and the `analogs` values organized into a 2d matrix (N_FRAMES x N_CHANNELS).
222 
223 ### Create an empty yet valid C3D structure
224 To create a new valid yet empty C3D, just call the `ezc3dRead` without any argument.
225 ```MATLAB
226 c3d = ezc3dRead();
227 disp(c3d.parameter.POINT.USED); % Print the number of points used
228 ```
229 
230 ### Read a C3D
231 To read a C3D file you simply to call the `ezc3dRead` with the path to c3d as the first argument.
232 ```MATLAB
233 c3d = ezc3dRead('path_to_c3d.c3d');
234 disp(c3d.parameter.POINT.USED); % Print the number of points used
235 ```
236 
237 ### Write a C3D
238 To write a C3D to a file, you must call the `ezc3dWrite` function. This function waits for the path of the C3D to write and a valid structure. Please note that the header is actually ignore since it is fully constructed from required parameters. Hence, a valid structure may omit the header. Still, for simplicity, it is easier to send a structure created via the `ezc3dRead` function.
239 ```MATLAB
240 % Create a valid structure to work on
241 c3d = ezc3dRead();
242 
243 % Add a point to the structure.
244 c3d.parameter.POINT.RATE = 100;
245 c3d.parameter.POINT.USED = c3d.parameter.POINT.USED + 1;
246 c3d.parameter.POINT.LABELS = [c3d.parameter.POINT.LABELS, 'NewMarkerName'];
247 c3d.data.points = rand(3,1,100);
248 
249 % Write the C3D
250 ezc3dWrite('path_to_c3d.c3d', c3d);
251 ```
252 ## Python 3
253 Python (https://www.python.org/) is a scripting langage that has taken more and more importance over the past years. So much that now it is one of the prefered langage of the scientific community. It simplicity yet its large power perform a large variety of tasks makes it almost a certainty that its popularity won't decrease for the next years.
254 
255 To interface the C++ code with Python, SWIG is a great tool. It creates very efficiently an interface in the target langage with minimal code to write. However, the resulting code in the target langage is far from being easy to use. Actually, it gives a mixed-API not far from the original C++ langage. When this is useful to rapidly create the interface, it lacks of user-friendlyness. EZC3D interface the C++ code using SWIG, but add a more pythonic layer on top of it. This top layer is not mandatory for the user (it is possible to call directly the SWIG interface via `ezc3d.ezc3d` instead of `ezc3d.c3d`), but the time lost to organized the data into a dictionary is insignificant compared to the ease of use this interface provides. I therefore strongly suggest to used this python interface.
256 
257 Please note, to navigate the c3d struture provided by the interface, the easiest way is to use the `keys()` method since this is a dictionary.
258 
259 ### Create an empty yet valid C3D structure
260 To create a new valid yet empty C3D, just call the `ezc3d.c3d()` method without any argument.
261 ```python3
262 from ezc3d import c3d
263 c = c3d()
264 print(c['parameters']['POINT']['USED']['value'][0]); # Print the number of points used
265 ```
266 
267 ### Read a C3D
268 To read a C3D file you simply to call the `ezc3d.c3d()` with the path to c3d as the first argument.
269 ```python3
270 from ezc3d import c3d
271 c = c3d('path_to_c3d.c3d')
272 print(c['parameters']['POINT']['USED']['value'][0]); # Print the number of points used
273 point_data = c['data']['points']
274 analog_data = c['data']['analogs']
275 ```
276 > Please note that the shape of `point_data` is 4xNxT, where 4 represent the components XYZ1 (the extra 1 allows for rototranslation multiplications), N is the number of points and T is the number of frames.
277 > Similarly, and to be consistent with the point shape, the shape of `analog_data` are 1xNxT, where 1 is the value, N is the number of analogous data and T is the number of frames.
278 
279 ### Write a C3D
280 To write a C3D to a file, you must call the `write` method of a c3d dictionnary. This method waits for the path of the C3D to write. Please note that the header is actually ignore since it is fully constructed from required parameters.
281 
282 The example that follows contructs a new C3D from scratch, adding data and adding a custom parameter.
283 ```python3
284 import numpy as np
285 
286 import ezc3d
287 
288 # Load an empty c3d structure
289 c3d = ezc3d.c3d()
290 
291 # Fill it with random data
292 c3d['parameters']['POINT']['RATE']['value'] = [100]
293 c3d['parameters']['POINT']['LABELS']['value'] = ('point1', 'point2', 'point3', 'point4', 'point5')
294 c3d['data']['points'] = np.random.rand(4, 5, 100)
295 c3d['data']['points'][1, :, :] = 2
296 c3d['data']['points'][2, :, :] = 3
297 
298 c3d['parameters']['ANALOG']['RATE']['value'] = [1000]
299 c3d['parameters']['ANALOG']['LABELS']['value'] = ('analog1', 'analog2', 'analog3', 'analog4', 'analog5', 'analog6')
300 c3d['data']['analogs'] = np.random.rand(1, 6, 1000)
301 c3d['data']['analogs'][0, 0, :] = 4
302 c3d['data']['analogs'][0, 1, :] = 5
303 c3d['data']['analogs'][0, 2, :] = 6
304 c3d['data']['analogs'][0, 3, :] = 7
305 c3d['data']['analogs'][0, 4, :] = 8
306 c3d['data']['analogs'][0, 5, :] = 9
307 
308 # Add a custom parameter to the POINT group
309 c3d.add_parameter("POINT", "newParam", [1, 2, 3])
310 
311 # Add a custom parameter a new group
312 c3d.add_parameter("NewGroup", "newParam", ["MyParam1", "MyParam2"])
313 
314 # Write the data
315 c3d.write("path_to_c3d.c3d")
316 ```
317 
318 # How to contribute
319 You are very welcome to contribute to the project! There are to main ways to contribute.
320 
321 The first way is to actually code new features to EZC3D. The easiest way to do so is to fork the project make the modifications and then open a pull request to the main project. Don't forget to add your name to the contributor in the documentation of the page if you do so!
322 
323 The second way is to provide me with non-working C3D files (See the C3D Softwares section below for more details). There is another repository for test files in the pyomeca (https://github.com/pyomeca/ezc3d_c3dTestFiles). You can fork this project, add your C3D in according to the recommandations and pull request it. This will be greatly appreciated by me and the biomechanics community!
324 
325 # Supported generated C3D
326 The software companies have loosely implemented the C3D standard proposed by http://C3D.org. Hence, there are some workaround that must be incorporated to the code to be able to read the C3D created using third-party softwares. So far, C3D from three different companies were tested. Vicon (https://www.vicon.com/), Qualisys (https://www.qualisys.com/) and Optotrak (https://www.ndigital.com/msci/products/optotrak-certus/). But I am sure there is plenty of other obscure companies or simply cases that were not tested from these companies (simply because I don't have C3D to test). If you find yourself with a bug when trying to read a C3D that should work, please open an issue and provide me with the corresponding C3D (see How to contribute).
327 
328 # Documentation
329 ## C3D format
330 The C3D format is maintained by http://c3d.org. They provide recommandation on how to implement reader/writer for the format. There is a copy of the documentation PDF in the `doc` folder. You are also welcome to have a look at a newer version if they ever create an update.
331 
332 ## EZC3D
333 The documentation is automatically generated using Doxygen (http://www.doxygen.org/). You can compile it youself if you want (by setting `BUILD_DOC` to `ON`). Otherwise, you can access a copy of it that I try to keep up-to-date in the Documentation project of pyomeca (https://pyomeca.github.io/Documentation/) by selecting `ezc3d`.
334 
335 # Troubleshoots
336 Despite my efforts to make a bug-free library, EZC3D may fails sometimes. If it does, please refer to the section below to know what to do. I will fill this section with the issue over time.
337 
338 ## Slow C3D opening
339 If you experience a slow C3D opening (more than 10 seconds), even for a huge C3D file. You may be in one of two cases.
340 
341 First, mak sure you are using EZC3D compiled with optimizations (RelWithDebInfo or Release). Indeed, the way C3D files are formated implies back and fourth memory allocations between points and analogs. If the optimization are turned off, it may take a little while to perform.
342 
343 If you actually are using a released level of optimization, you may actually experience a bug. You are therefore welcomed to send me the long to open C3D file so I can optimize few things by myself. Everyone will benefit!
344 
345 ## Non-working C3D
346 The C3D format allows for some pretty old and probably useless stuff. For example, you are allowed to store the points in the form of integers instead of floating points that you would scale afterwards. Since it may have make sense many years ago, it is very unlikely anyone would need this nowadays. Hence, and because I did not have any examples of such C3D to test, I decided to ignore these features (you would know easily since the code raises a `not implemented exception`). However, at some point, for some reason, you may need these features. If so, you are welcomed to open an issue and to provide me with the non-working C3D. I will make my best to add the feature ASAP.
347 
348 Moreover, as stated before, some (all?) companies were pretty loose in their implementation of the C3D standard. Actually, the standard itself states how much you don't need to follow it, which it kind of strange, the least to say. Because of that, entire sections that are supposed to be mandatory may be missing, or checksum may have the wrong value (these are real omissions...), or anything which hasn't happened yet may occurs. There is no way for me, of course, to know that in advance, hence these exception are not implemented yet. If you encounter such files (the exception raised may be from any nature, but the most probable is segmentation fault), again do not hesitate to open an issue and to provide me with the non-working C3D.
349 
350 ## Cite
351 If you use ezc3d, we would be grateful if you could cite it as follows:
352 
353 ```
354 @misc{Michaud2018ezc3d,
355  author = {Michaud, Benjamin and Begon, Mickael},
356  title = {EZC3D: Easy to use C3D reader/writer in C++, Python and Matlab},
357  howpublished={Web page},
358  url = {https://github.com/pyomeca/ezc3d},
359  year = {2018}
360 }
361 ```
362 
363 # Changes log
364 ## Version 0.1.0
365 First working version of a C++ C3D reader.
366 
367 ## Version 0.2.0
368 Reader and writer in C++, Python interface with SWIG for the reader, MATLAB interface for the reader and writer
369 
370 ## Version 0.3.0
371 Pythonic interface for the python reader and started to interface the writer.
372 
373 ## Version 0.3.1
374 Documentation using Doxygen added for the C++ code, Major refactor of the code in order to harmonized it across the classes.
375 
376 ## Version 0.3.2
377 Added tests and example files for Python3 and MATLAB.