1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
|
/**
\page tutorial-json Tutorial: Using JSON serialization to save your data and read program arguments
\tableofcontents
\section tuto-json-intro Introduction
Since ViSP 3.6.0, a 3rd party library was introduced to allow the seamless use of JSON (JavaScript Object Notation) in ViSP.
The library that is used is <a target="_blank" href="https://github.com/nlohmann/json">JSON for modern C++</a>.
To install it on your system, look at \ref soft_tool_json installation instructions for your system.
The main use case for using JSON in your programs is to save your experimental results. The main advantage of using JSON is
that it is widely supported by default in other languages.
Thanks to this 3rd party, JSON serialization was introduced for vpCameraParameters, vpArray2D, vpColVector, vpPoseVector,
vpHomogeneousMatrix, vpMe and vpMbGenericTracker.
In this tutorial, we will use JSON to:
- Read program arguments from a configuration file. This makes it easier to run your program many times and makes storing
different configurations easier
- Save the data generated by running a visual servoing loop
- Generate plots in Python
\subsection tuto-json-overview JSON overview
First of all, it is necessary to understand the structure of JSON documents to better use them.
For a detailed description, you may be interested in <a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/JSON">the Mozilla Developer Network documentation</a>.
Much of this section is a repeat of the library's documentation, available <a target="_blank" href="https://json.nlohmann.me/">here</a>.
To use JSON in your code you should first include the relevant header:
\code{.cpp}
#include <nlohmann/json.hpp>
using json = nlohmann::json; // For convenience
\endcode
This introduces a new type named `json` which supports many types out of the box:
\code{.cpp}
json j = {
{"bool", false},
{"double", 3.14156},
{"string", "Hello World!"},
{"aVector", {1.0, 2.0, 3.0}}
};
std::cout << j.dump(4) << std::endl;
\endcode
This snippet will print
\verbatim
{
"aVector": [1.0, 2.0, 3.0],
"bool": false,
"double": 3.14156,
"string": "Hello World!"
}
\endverbatim
\subsection tuto-json-custom-conversion Defining a JSON conversion for your types
The library also allows to define explicit conversion between a custom type and JSON. This can be achieved by defining the two following functions:
\code
void from_json(const json& j, YourType& t);
void to_json(json& j, const YourType& t);
\endcode
These functions must be defined in the same scope namespace as YourType and must be accessible everywhere YourType is used. It is thus common to define it in the header where YourType is defined.
\code
#include <nlohmann/json.hpp>
class YourType {
public:
YourType(double x): x(x) {}
private:
double x;
friend from_json(const json& j, YourType& t); // Declare friends if you wish to access private members
friend void to_json(json& j, const YourType& t);
};
inline void from_json(const json& j, YourType& t)
{
t.x = j.at("x");
}
inline void to_json(json& j, const YourType& t)
{
j["x"] = t.x;
}
\endcode
Once this conversion is defined you can convert to and from json with:
\code{.cpp}
YourType t(5.0);
json j = t; // j == {"x": 5.0}, calls to_json
YourType t2 = j; // t2.x == 5.0, calls from_json
j = {YourType(1.0), YourType(2.0)}; // j == [{"x": 1.0}, {"x": 2.0}]
// JSON conversion for std vector is already known and call
// to_json for each of the elements of the vector
\endcode
To better understand the requirements and caveats when writing custom conversions, we encourage
you to read <a href="https://json.nlohmann.me/features/arbitrary_types/#basic-usage" target="_blank">the documentation on arbitrary type conversion</a>.
\section tuto-json-ibvs Example: Using JSON for a visual servoing experiment
This example will demonstrate some basic usage of JSON to save the data generated by running an IBVS and exporting it to python to plot some figures.
This example is a modification tutorial-ibvs-4pts.cpp and the full code is available at tutorial-ibvs-4pts-json.cpp.
This program can be run with:
\verbatim
$ cd $VISP_WS/visp-build/tutorial/visual-servo/ibvs
$ ./tutorial-ibvs-4pts-json --settings ibvs_settings.json --output results.json
\endverbatim
\subsection tuto-json-ibvs-arguments Using JSON for program arguments
We first start by defining the program arguments:
\snippet tutorial-ibvs-4pts-json.cpp Arguments
This struct contains the control law gain, the desired and starting pose, as well as the visual error threshold and robot sampling time.
Finally it contains a value of type `vpInteractionMatrixTypeSubset` which is defined as
\snippet tutorial-ibvs-4pts-json.cpp Enum
This value will be converted to a vpServo::vpServoIteractionMatrixType when defining the control law,
by calling `Arguments::getInteractionMatrixType()`.
Now that our arguments are defined we must now know to parse JSON to get them. We start by defining how to
serialize our enumeration `vpInteractionMatrixTypeSubset`:
\snippet tutorial-ibvs-4pts-json.cpp Enum conversion
Note that if the value is not in `CURRENT`, `DESIRED` or `MEAN`, then the parsing result will default to the value `vpInteractionMatrixTypeSubset::UNKNOWN`.
Next we define how to parse the full list of arguments:
\snippet tutorial-ibvs-4pts-json.cpp Arguments conversion
Where the method `from_json()` is used to parse JSON and, inversely, `to_json()` is used to convert the arguments to JSON, which we will use afterwards.
The syntax
\code
x = j.value("key", x);
\endcode
is used to fill in optional values from the JSON document. If the key is not found then `x` will be left unchanged
This syntax can also be used to set a default value, e.g.:
\code
a.lambda = j.value("lambda", 0.5);
\endcode
In the `from_json()` method, we ensure that all the arguments are correctly set:
- Lambda should be > 0
- Sampling time should be > 0
- Error threshold should be > 0
- The type of interaction matrix should be correct
With this conversion defined, we can read the program settings from a file (`ibvs_settings.json`), containing
\include ibvs_settings.json
Note that while the `Arguments` class represents the desired and starting poses as vpHomogeneousMatrix elements, they are defined in the JSON as vpPoseVector representations. ViSP will automatically convert one representation to the other when parsing JSON.
To read from the `ibvs_settings.json` file, we will define the following method:
\snippet tutorial-ibvs-4pts-json.cpp JSON input conversion
Which we will call in the `main()`, to finally obtain our program arguments:
\snippet tutorial-ibvs-4pts-json.cpp Main parsing
We can now start servoing and acquiring data, that will be saved to another JSON file.
\subsection tuto-json-ibvs-saving Collecting and saving visual servoing data
To save the data to a JSON file, we first define what we'll save
\snippet tutorial-ibvs-4pts-json.cpp Result structure
This includes:
- The arguments used to launch the program
- The desired visual features
- For each iteration
- The visual features
- The camera pose
- The squared norm of the visual error
- The velocity
- The interaction matrix
Before starting the servoing loop we create a `results` object:
\snippet tutorial-ibvs-4pts-json.cpp Results creation
We then start the visual servoing loop:
\snippet tutorial-ibvs-4pts-json.cpp VS loop
where at each iteration, we update our `results` object with
\snippet tutorial-ibvs-4pts-json.cpp Results update
Finally, to save the results to a file, we define a function to write to a file
\snippet tutorial-ibvs-4pts-json.cpp write json to file
and call it at the end of the program
\snippet tutorial-ibvs-4pts-json.cpp Save call
Our data is now saved in a JSON file, and we can reuse it in other programs, as we demonstrate further below.
\subsection tuto-json-ibvs-python Reusing the data in Python to generate plots
We will now reparse the JSON file saved in \ref tuto-json-ibvs-saving section to generate high quality plots
in Python with Matplotlib. The script, `plot-ibvs-control-law.py` can be found in the script folder of the ViSP
git repository. It has been tested with Python 3.7.9, matplotlib 3.3.2 and numpy 1.17.3.
If not already done, to use the script install the following:
- Install the latest [Python3](https://www.python.org/downloads/) release
- Check if `pip3` and `python3` are correctly installed
\verbatim
$ python3 --version
$ pip3 --version
\endverbatim
- Upgrade your pip to avoid errors during installation.
\verbatim
$ pip3 install --upgrade pip
\endverbatim
- Enter the following command to install Numpy using `pip3`
\verbatim
$ pip3 install numpy
\endverbatim
- Enter the following command to install Matplotlib using `pip3`
\verbatim
$ pip3 install matplotlib
\endverbatim
The script gives `plot-ibvs-control-law.py` a reference on how to plot with JSON data coming from ViSP. You may wish to modify
and extend it to fit your use case or control law.
To run the script, replace the json path that contains the data to plot and where you wish to save the
plots in the following command:
\verbatim
$ cd VISP_WS/visp/script
$ python3 plot-ibvs-control-law.py --data $VISP_WS/visp-build/tutorial/visual-servo/ibvs/results.json \
--plot_folder ./plots
\endverbatim
The script should produce similar output:
\verbatim
Generating error norm plot...
Generating velocity plot...
Generating 2D and 3D trajectory plots...
Generating features plot...
Plots were saved to $VISP_WS/visp/script/plots
\endverbatim
You can then view the generated plots in the folder where you saved them (in our case in `./plots/`
corresponding to `$VISP_WS/visp/script/plots` folder).
\verbatim
$ ls $VISP_WS/visp/script/plots
cMo.pdf error.pdf velocity.pdf
cMo_3d.pdf trajectories_2d.pdf
\endverbatim
You should obtain plots similar to those below, but with a higher quality (generated in pdf):
\image html image/tutorial/visual-servo/ibvs-python-plots/cMo.jpg
\image html image/tutorial/visual-servo/ibvs-python-plots/cMo_3d.jpg
\image html image/tutorial/visual-servo/ibvs-python-plots/velocity.jpg
\image html image/tutorial/visual-servo/ibvs-python-plots/error.jpg
\image html image/tutorial/visual-servo/ibvs-python-plots/trajectories_2d.jpg
\section tuto-json-next Next tutorial
You are now ready to see how to continue with \ref tutorial-mb-generic-json.
*/
|