Visual Servoing Platform  version 3.6.1 under development (2025-03-13)
render-based-tutorial-utils.h
1 #ifndef VP_RB_TRACKER_TUTORIAL_HELPER_H
2 #define VP_RB_TRACKER_TUTORIAL_HELPER_H
3 
4 #include <cstdlib>
5 #include <iostream>
6 #include <sstream>
7 #include <sys/stat.h>
8 
9 #include <fstream>
10 #include <math.h>
11 #include <string.h>
12 
13 #include <visp3/core/vpImage.h>
14 #include <visp3/core/vpImageFilter.h>
15 #include <visp3/core/vpImageConvert.h>
16 #include <visp3/core/vpImageTools.h>
17 #include <visp3/core/vpIoTools.h>
18 #include <visp3/core/vpDisplay.h>
19 #include <visp3/core/vpTime.h>
20 #include <visp3/gui/vpDisplayFactory.h>
21 
22 #include <visp3/io/vpImageIo.h>
23 #include <visp3/io/vpJsonArgumentParser.h>
24 #include <visp3/io/vpVideoWriter.h>
25 
26 #include <visp3/gui/vpPlot.h>
27 
28 #include <visp3/rbt/vpRBTracker.h>
29 #include <visp3/rbt/vpObjectMask.h>
30 #include <visp3/rbt/vpRBDriftDetector.h>
31 
32 #include "pStatClient.h"
33 
34 #ifndef DOXYGEN_SHOULD_SKIP_THIS
35 namespace vpRBTrackerTutorial
36 {
37 
38 #ifdef ENABLE_VISP_NAMESPACE
39 using namespace VISP_NAMESPACE_NAME;
40 #endif
41 
42 struct BaseArguments
43 {
44  BaseArguments() : trackerConfiguration(""), maxDepthDisplay(1.f), display(true), debugDisplay(false), enableRenderProfiling(false) { }
45 
46  void registerArguments(vpJsonArgumentParser &parser)
47  {
48  parser
49  .addArgument("--tracker", trackerConfiguration, false, "Path to the JSON file containing the tracker")
50  .addArgument("--object", object, false, "Name of the object to track. Used to potentially fetch the init file")
51  .addArgument("--init-file", initFile, false, "Path to the JSON file containing the 2D/3D correspondences for initialization by click")
52  .addArgument("--pose", inlineInit, false, "Initial pose of the object in the camera frame.")
53  .addArgument("--max-depth-display", maxDepthDisplay, false, "Maximum depth value, used to scale the depth display")
54  .addFlag("--no-display", display, "Disable display windows")
55  .addFlag("--debug-display", debugDisplay, "Enable additional displays from the renderer")
56  .addFlag("--profile", enableRenderProfiling, "Enable the use of Pstats to profile rendering times");
57  }
58 
59  void postProcessArguments()
60  {
61  if (trackerConfiguration.empty()) {
62  throw vpException(vpException::badValue, "No tracker configuration was specified");
63  }
64  if (object.empty()) {
65  object = vpIoTools::getName(trackerConfiguration);
66  object.erase(object.end() - 5, object.end());
67  }
68  if (initFile.empty()) {
69  initFile = vpIoTools::getParent(trackerConfiguration) + vpIoTools::separator + object + ".init";
70  }
71 
72  if (!display && inlineInit.empty()) {
73  throw vpException(vpException::badValue, "Cannot disable displays without specifying the initial pose");
74  }
75  if (inlineInit.size() > 0) {
76  if (inlineInit.size() != 6) {
77  throw vpException(vpException::dimensionError, "Inline pose initialization expected to have 6 values (tx, ty, tz, tux, tuy, tuz)");
78  }
79  for (unsigned int i = 0; i < 6; ++i) {
80  std::cout << "inline i = " << inlineInit[i] << std::endl;
81  }
82  cMoInit = vpHomogeneousMatrix(inlineInit[0], inlineInit[1], inlineInit[2], inlineInit[3], inlineInit[4], inlineInit[5]);
83  }
84  }
85 
86  bool hasInlineInit()
87  {
88  return !inlineInit.empty();
89  }
90 
91  std::string trackerConfiguration;
92  std::string object;
93  std::string initFile;
94  std::vector<double> inlineInit;
95  float maxDepthDisplay;
96  vpHomogeneousMatrix cMoInit;
97  bool display;
98  bool debugDisplay;
99  bool enableRenderProfiling;
100 };
101 
102 class vpRBExperimentLogger
103 {
104 public:
105  vpRBExperimentLogger() : enabled(false), videoEnabled(false), framerate(30)
106  { }
107 
108  void registerArguments(vpJsonArgumentParser &parser)
109  {
110  parser
111  .addFlag("--save", enabled, "Whether to save experiment data")
112  .addArgument("--save-path", folder, false, "Where to save the experiment log. The folder should not exist.")
113  .addFlag("--save-video", videoEnabled, "Whether to save the video")
114  .addArgument("--video-framerate", framerate, false, "Output video framerate");
115 
116  }
117 
118  void startLog()
119  {
120  if (enabled) {
121  if (folder.empty()) {
122  throw vpException(vpException::badValue, "Experiment logging enabled but folder not specified");
123  }
124  vpIoTools::makeDirectory(folder);
125  if (videoEnabled) {
126  videoWriter.setFramerate(framerate);
127  videoWriter.setCodec(cv::VideoWriter::fourcc('P', 'I', 'M', '1'));
128  videoWriter.setFileName(folder + vpIoTools::separator + "video.mp4");
129  }
130  }
131  }
132 
133  void logFrame(const vpRBTracker &tracker, unsigned int iter, const vpImage<unsigned char> &I, const vpImage<vpRGBa> &IRGB, const vpImage<unsigned char> &Idepth, const vpImage<unsigned char> &Imask)
134  {
135  if (videoEnabled) {
136  Iout.resize(IRGB.getHeight() * 2, IRGB.getWidth() * 2);
137 
138  vpDisplay::getImage(I, IgrayOverlay);
139  vpDisplay::getImage(IRGB, IColOverlay);
140  vpDisplay::getImage(Idepth, IdepthOverlay);
141  vpDisplay::getImage(Imask, ImaskOverlay);
142 #ifdef VISP_HAVE_OPENMP
143 #pragma omp parallel for
144 #endif
145  for (unsigned int i = 0; i < IRGB.getHeight(); ++i) {
146  memcpy(Iout[i], IgrayOverlay[i], IRGB.getWidth() * sizeof(vpRGBa));
147  memcpy(Iout[i] + IRGB.getWidth(), IColOverlay[i], IRGB.getWidth() * sizeof(vpRGBa));
148  memcpy(Iout[i + IRGB.getHeight()], IdepthOverlay[i], IRGB.getWidth() * sizeof(vpRGBa));
149  memcpy(Iout[i + IRGB.getHeight()] + IRGB.getWidth(), ImaskOverlay[i], IRGB.getWidth() * sizeof(vpRGBa));
150  }
151 
152  if (iter == 1) {
153  videoWriter.open(Iout);
154  }
155  else {
156  videoWriter.saveFrame(Iout);
157  }
158  }
159 
160  nlohmann::json iterLog;
162  tracker.getPose(cMo);
163  iterLog["cMo"] = cMo;
164 
165  log.push_back(iterLog);
166  }
167 
168  void close()
169  {
170  if (videoEnabled) {
171  videoWriter.close();
172  }
173  std::ofstream f(folder + vpIoTools::separator + "log.json");
174  f << log.dump(2) << std::endl;
175  f.close();
176  }
177 
178 private:
179  bool enabled;
180  std::string folder;
181 
182  vpImage<vpRGBa> IColOverlay;
183  vpImage<vpRGBa> IgrayOverlay;
184  vpImage<vpRGBa> IdepthOverlay;
185  vpImage<vpRGBa> ImaskOverlay;
186  vpImage<vpRGBa> Iout;
187 
188  bool videoEnabled;
189  unsigned int framerate;
190  vpVideoWriter videoWriter;
191 
192  nlohmann::json log;
193 };
194 
195 class vpRBExperimentPlotter
196 {
197 public:
198 
199  vpRBExperimentPlotter() : enabled(false), plotPose(false), plotPose3d(false), plotDivergenceMetrics(false), plotCovariance(false) { }
200 
201  void registerArguments(vpJsonArgumentParser &parser)
202  {
203  parser
204  .addFlag("--plot-pose", plotPose, "Plot the pose of the object in the camera frame")
205  .addFlag("--plot-position", plotPose3d, "Plot the position of the object in a 3d figure")
206  .addFlag("--plot-divergence", plotDivergenceMetrics, "Plot the metrics associated to the divergence threshold computation")
207  .addFlag("--plot-cov", plotCovariance, "Plot the pose covariance trace for each feature");
208 
209  }
210 
211  void postProcessArguments(bool displayEnabled)
212  {
213  enabled = plotPose || plotDivergenceMetrics || plotPose3d || plotCovariance;
214  if (enabled && !displayEnabled) {
215  throw vpException(vpException::badValue, "Tried to plot data, but display is disabled");
216  }
217  }
218 
219  void init(std::vector<std::shared_ptr<vpDisplay>> &displays)
220  {
221  if (!enabled) {
222  return;
223  }
224  int ypos = 0, xpos = 0;
225  for (std::shared_ptr<vpDisplay> &display : displays) {
226  ypos = std::min(ypos, display->getWindowYPosition());
227  xpos = std::max(xpos, display->getWindowXPosition() + static_cast<int>(display->getWidth()));
228  }
229 
230  numPlots = static_cast<int>(plotPose) + static_cast<int>(plotDivergenceMetrics) + static_cast<int>(plotPose3d) + static_cast<int>(plotCovariance);
231  plotter.init(numPlots, 600, 800, xpos, ypos, "Plot");
232  unsigned int plotIndex = 0;
233  if (plotPose) {
234  plotter.initGraph(plotIndex, 6);
235  plotter.setTitle(plotIndex, "cMo");
236  std::vector<std::string> legends = {
237  "tx", "ty", "tz", "tux", "tuy", "tuz"
238  };
239  for (unsigned int i = 0; i < 6; ++i) {
240  plotter.setLegend(plotIndex, i, legends[i]);
241  }
242  plotter.setGraphThickness(plotIndex, 2);
243  ++plotIndex;
244  }
245  if (plotPose3d) {
246  plotter.initGraph(plotIndex, 1);
247  plotter.setTitle(plotIndex, "3D object position");
248  plotter.setGraphThickness(plotIndex, 2);
249  ++plotIndex;
250  }
251 
252  if (plotDivergenceMetrics) {
253  plotter.initGraph(plotIndex, 1);
254  plotter.initRange(plotIndex, 0.0, 1.0, 0.0, 1.0);
255  plotter.setTitle(plotIndex, "Divergence");
256  ++plotIndex;
257  }
258  if (plotCovariance) {
259  plotter.initGraph(plotIndex, 2);
260  plotter.setLegend(plotIndex, 0, "Translation trace standard deviation (cm)");
261  plotter.setLegend(plotIndex, 1, "Rotation trace standard deviation (deg)");
262 
263  plotter.setTitle(plotIndex, "Covariance");
264  ++plotIndex;
265  }
266  }
267 
268  void plot(const vpRBTracker &tracker, double time)
269  {
270  if (!enabled) {
271  return;
272  }
273  unsigned int plotIndex = 0;
274  if (plotPose) {
276  tracker.getPose(cMo);
277  plotter.plot(plotIndex, time, vpPoseVector(cMo));
278  ++plotIndex;
279  }
280  if (plotPose3d) {
282  tracker.getPose(cMo);
284  plotter.plot(plotIndex, 0, t[0], t[1], t[2]);
285  ++plotIndex;
286  }
287  if (plotDivergenceMetrics) {
288  const std::shared_ptr<const vpRBDriftDetector> driftDetector = tracker.getDriftDetector();
289  double metric = driftDetector ? driftDetector->getScore() : 0.0;
290  plotter.plot(plotIndex, 0, time, metric);
291  ++plotIndex;
292  }
293  if (plotCovariance) {
294  vpMatrix cov = tracker.getCovariance();
295  double traceTranslation = 0.0, traceRotation = 0.0;
296  for (unsigned int i = 0; i < 3; ++i) {
297  traceTranslation += cov[i][i];
298  traceRotation += cov[i + 3][i + 3];
299  }
300  traceTranslation = sqrt(traceTranslation) * 100;
301  traceRotation = vpMath::deg(sqrt(traceRotation));
302 
303  plotter.plot(plotIndex, 0, time, traceTranslation);
304  plotter.plot(plotIndex, 1, time, traceRotation);
305 
306  ++plotIndex;
307  }
308  }
309 private:
310  bool enabled;
311  bool plotPose;
312  bool plotPose3d;
313  bool plotDivergenceMetrics;
314  bool plotCovariance;
315  int numPlots;
316  vpPlot plotter;
317 };
318 
319 std::vector<std::shared_ptr<vpDisplay>> createDisplays(
321  vpImage<unsigned char> &depthDisplay, vpImage<unsigned char> &probaDisplay)
322 {
324  2, 2,
325  0, 0,
326  80, 80,
327  "Grayscale", Id,
328  "Color", Icol,
329  "Depth", depthDisplay,
330  "Proba mask", probaDisplay
331  );
332 }
333 
334 std::vector<std::shared_ptr<vpDisplay>> createDisplays(
336 {
338  1, 3,
339  0, 0,
340  80, 80,
341  "Grayscale", Id,
342  "Color", Icol,
343  "Proba mask", probaDisplay
344  );
345 }
346 
347 void enableRendererProfiling()
348 {
349  if (PStatClient::is_connected()) {
350  PStatClient::disconnect();
351  }
352 
353  std::string host = ""; // Empty = default config var value
354  int port = -1; // -1 = default config var value
355  if (!PStatClient::connect(host, port)) {
356  std::cout << "Could not connect to PStat server." << std::endl;
357  }
358 }
359 
360 void displayNormals(const vpImage<vpRGBf> &normalsImage, vpImage<vpRGBa> &normalDisplayImage)
361 {
362 #ifdef VISP_HAVE_OPENMP
363 #pragma omp parallel for
364 #endif
365  for (unsigned int i = 0; i < normalsImage.getSize(); ++i) {
366  normalDisplayImage.bitmap[i].R = static_cast<unsigned char>((normalsImage.bitmap[i].R + 1.0) * 127.5f);
367  normalDisplayImage.bitmap[i].G = static_cast<unsigned char>((normalsImage.bitmap[i].G + 1.0) * 127.5f);
368  normalDisplayImage.bitmap[i].B = static_cast<unsigned char>((normalsImage.bitmap[i].B + 1.0) * 127.5f);
369  }
370 
371  vpDisplay::display(normalDisplayImage);
372  vpDisplay::flush(normalDisplayImage);
373 }
374 
375 void displayCanny(const vpImage<vpRGBf> &cannyRawData,
376  vpImage<unsigned char> &canny, const vpImage<unsigned char> &valid)
377 {
378 #ifdef VISP_HAVE_OPENMP
379 #pragma omp parallel for
380 #endif
381  for (unsigned int i = 0; i < cannyRawData.getSize(); ++i) {
382  //vpRGBf &px = cannyRawData.bitmap[i];
383  canny.bitmap[i] = valid.bitmap[i] * 255;
384  //canny.bitmap[i] = static_cast<unsigned char>(127.5f + 127.5f * atan(px.B));
385  }
386 
387  vpDisplay::display(canny);
388  for (unsigned int i = 0; i < canny.getHeight(); i += 4) {
389  for (unsigned int j = 0; j < canny.getWidth(); j += 4) {
390  if (!valid[i][j]) continue;
391  float angle = cannyRawData[i][j].B;
392  unsigned x = j + 10 * cos(angle);
393  unsigned y = i + 10 * sin(angle);
394  vpDisplay::displayArrow(canny, i, j, y, x, vpColor::green);
395  }
396  }
397  vpDisplay::flush(canny);
398 }
399 }
400 #endif
401 #endif
static const vpColor green
Definition: vpColor.h:201
static void display(const vpImage< unsigned char > &I)
static void getImage(const vpImage< unsigned char > &Is, vpImage< vpRGBa > &Id)
Definition: vpDisplay.cpp:140
static void flush(const vpImage< unsigned char > &I)
static void displayArrow(const vpImage< unsigned char > &I, const vpImagePoint &ip1, const vpImagePoint &ip2, const vpColor &color=vpColor::white, unsigned int w=4, unsigned int h=2, unsigned int thickness=1)
error that can be emitted by ViSP classes.
Definition: vpException.h:60
@ badValue
Used to indicate that a value is not in the allowed range.
Definition: vpException.h:73
@ dimensionError
Bad dimension.
Definition: vpException.h:71
Implementation of an homogeneous matrix and operations on such kind of matrices.
vpTranslationVector getTranslationVector() const
unsigned int getWidth() const
Definition: vpImage.h:242
unsigned int getSize() const
Definition: vpImage.h:221
Type * bitmap
points toward the bitmap
Definition: vpImage.h:135
unsigned int getHeight() const
Definition: vpImage.h:181
static void makeDirectory(const std::string &dirname)
Definition: vpIoTools.cpp:550
static std::string getParent(const std::string &pathname)
Definition: vpIoTools.cpp:1326
static std::string getName(const std::string &pathname)
Definition: vpIoTools.cpp:1217
static const char separator
Definition: vpIoTools.h:532
Command line argument parsing with support for JSON files. If a JSON file is supplied,...
vpJsonArgumentParser & addArgument(const std::string &name, T &parameter, const bool required=true, const std::string &help="No description")
Add an argument that can be provided by the user, either via command line or through the json file.
vpJsonArgumentParser & addFlag(const std::string &name, bool &parameter, const std::string &help="No description")
Add an argument that acts as a flag when specified on the command line. When this flag is specified,...
static double deg(double rad)
Definition: vpMath.h:119
Implementation of a matrix and operations on matrices.
Definition: vpMatrix.h:169
This class enables real time drawing of 2D or 3D graphics. An instance of the class open a window whi...
Definition: vpPlot.h:112
Implementation of a pose vector and operations on poses.
Definition: vpPoseVector.h:203
void getPose(vpHomogeneousMatrix &cMo) const
Definition: vpRBTracker.cpp:64
vpMatrix getCovariance() const
Definition: vpRBTracker.cpp:76
std::shared_ptr< vpRBDriftDetector > getDriftDetector() const
Definition: vpRBTracker.h:150
Definition: vpRGBa.h:70
unsigned char B
Blue component.
Definition: vpRGBa.h:187
unsigned char R
Red component.
Definition: vpRGBa.h:185
unsigned char G
Green component.
Definition: vpRGBa.h:186
float B
Blue component.
Definition: vpRGBf.h:157
float G
Green component.
Definition: vpRGBf.h:156
float R
Red component.
Definition: vpRGBf.h:155
Class that consider the case of a translation vector.
Class that enables to write easily a video file or a sequence of images.
std::vector< std::shared_ptr< vpDisplay > > makeDisplayGrid(unsigned int rows, unsigned int cols, unsigned int startX, unsigned int startY, unsigned int paddingX, unsigned int paddingY, Args &... args)
Create a grid of displays, given a set of images. All the displays will be initialized in the correct...