Visual Servoing Platform  version 3.6.1 under development (2024-11-15)
vpPclViewer.cpp
1 /****************************************************************************
2  *
3  * ViSP, open source Visual Servoing Platform software.
4  * Copyright (C) 2005 - 2023 by Inria. All rights reserved.
5  *
6  * This software is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  * See the file LICENSE.txt at the root directory of this source
11  * distribution for additional information about the GNU GPL.
12  *
13  * For using ViSP with software that can not be combined with the GNU
14  * GPL, please contact Inria about acquiring a ViSP Professional
15  * Edition License.
16  *
17  * See https://visp.inria.fr for more information.
18  *
19  * This software was developed at:
20  * Inria Rennes - Bretagne Atlantique
21  * Campus Universitaire de Beaulieu
22  * 35042 Rennes Cedex
23  * France
24  *
25  * If you have questions regarding the use of this file, please contact
26  * Inria at visp@inria.fr
27  *
28  * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
29  * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
30  * Description:
31  * Real-time 3D point clouds plotter based on the PCL library.
32  *
33 *****************************************************************************/
34 
35 #ifndef DOXYGEN_SHOULD_SKIP_THIS
36 
37 #include <visp3/core/vpConfig.h>
38 #if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_IO) && defined(VISP_HAVE_THREADS)
39 // ViSP
40 #include <visp3/gui/vpPclViewer.h>
41 #include <visp3/gui/vpColorBlindFriendlyPalette.h>
42 #include <visp3/core/vpIoTools.h>
43 
44 // PCL
45 #include <pcl/io/pcd_io.h>
46 
47 BEGIN_VISP_NAMESPACE
51 
52 const unsigned int gc_nbColorMax = 7;
53 
54 pcl::visualization::PCLVisualizer::Ptr vpPclViewer::sp_viewer(nullptr);
55 
56 std::vector<std::vector<double>> vpPclViewer::s_vhandler;
57 
58 int vpPclViewer::s_width = 640;
59 int vpPclViewer::s_height = 480;
60 int vpPclViewer::s_posU = 40;
61 int vpPclViewer::s_posV = 40;
62 double vpPclViewer::s_ignoreThresh = 0.95;
63 
64 vpPclViewer::vpPclViewer(const std::string &title, const int &width, const int &height
65  , const int &posU, const int &posV
66  , const std::string &outFolder, const double &ignoreThreshold)
67  : m_hasToRun(false)
68  , m_title(title)
69  , m_hasToSavePCDs(false)
70  , m_outFolder(outFolder)
71 {
72  setOutFolder(outFolder);
73  setIgnoreThreshold(ignoreThreshold);
74  s_width = width;
75  s_height = height;
76  s_posU = posU;
77  s_posV = posV;
78 }
79 
81 {
82  // Asking to stop thread
83  stopThread();
84 
85  // Deleting point clouds
86  for (unsigned int i = 0; i < m_vPointClouds.size(); i++) {
87  m_vPointClouds[i].reset();
88  }
89  m_vPointClouds.clear();
90 
91  // Deleting mutexes
92  for (unsigned int id = 0; id < m_vpmutex.size(); id++) {
93  delete m_vpmutex[id];
94  }
95  m_vpmutex.clear();
96 
97  // Deleting the viewer
98  if (sp_viewer) {
99  sp_viewer.reset();
100  }
101 }
102 
103 void vpPclViewer::setNameWindow(const std::string &nameWindow)
104 {
105  sp_viewer->setWindowName(nameWindow);
106 }
107 
108 void vpPclViewer::setOutFolder(const std::string &outFolder)
109 {
110  m_outFolder = outFolder;
111  if (!m_outFolder.empty()) {
112  // An output folder has been set by the user
113  m_hasToSavePCDs = true;
115  // The output folder does not exist => creating it
117  }
118  }
119  else {
120  // No output folder was assigned to the visualizer
121  m_hasToSavePCDs = false;
122  }
123 }
124 
125 void vpPclViewer::setIgnoreThreshold(const double &ignoreThreshold)
126 {
127  if (ignoreThreshold < 0. || ignoreThreshold > 1.) {
128  throw(vpException(vpException::badValue, "[vpPclViewer::setIgnoreThreshold] Fatal error: threshold must be in range [0. ; 1.]"));
129  }
130  s_ignoreThresh = ignoreThreshold;
131 }
132 
133 void vpPclViewer::updateSurface(const pclPointCloudPointXYZRGB::Ptr &surface, const unsigned int &id, const bool &hasToKeepColor)
134 {
135  if (m_hasToRun) {
136  // Threaded mode
137  if (hasToKeepColor) {
138  // The point cloud will be displayed with its original colors
140  }
141  else {
142  // The point cloud will be displayed with its default colors
143  threadUpdateSurface(surface, id);
144  }
145  }
146  else {
147  // Blocking mode
148  vpColVector fakeWeights; // Fake weights that are all equal to 1, to keep all the points
149  updateSurface(surface, id, fakeWeights, hasToKeepColor);
150  }
151 }
152 
153 void vpPclViewer::updateSurface(const pclPointCloudPointXYZRGB::Ptr &surface, const unsigned int &id,
154  const vpColVector &weights, const bool &hasToKeepColor)
155 {
156  if (m_hasToRun) {
157  // Threaded mode
158  if (hasToKeepColor) {
159  // The point cloud will be displayed with its original colors
160  threadUpdateSurfaceOriginalColor(surface, id, weights);
161  }
162  else {
163  // The point cloud will be displayed with its default colors
164  threadUpdateSurface(surface, id, weights);
165  }
166  }
167  else {
168  // Blocking mode
169  // If the saved pcl corresponding to \b id was not initialized, initialize it
170  if (!m_vPointClouds[id]) {
171  m_vPointClouds[id].reset(new pclPointCloudPointXYZRGB());
172  }
173 
174  // Resize if needed the saved pcl corresponding to \b id
175  unsigned int nbPoints = surface->size();
176  m_vPointClouds[id]->resize(nbPoints);
177 
178  // Save the new weights and check if they must be used
179  m_vweights[id] = weights;
180  bool use_weights = (weights.size() > 0);
181 
182  // Keep only the points that are above \b s_ignoreThresh
183  for (unsigned int index = 0; index < nbPoints; index++) {
184  bool addPoint = false;
185  if (use_weights) {
186  addPoint = (weights[index] > s_ignoreThresh);
187  }
188  else {
189  addPoint = true;
190  }
191 
192  pclPointXYZRGB pt = surface->at(index);
193  if (addPoint) {
194  m_vPointClouds[id]->at(index).x = pt.x;
195  m_vPointClouds[id]->at(index).y = pt.y;
196  m_vPointClouds[id]->at(index).z = pt.z;
197 
198  m_vPointClouds[id]->at(index).r = s_vhandler[id][0];
199  m_vPointClouds[id]->at(index).g = s_vhandler[id][1];
200  m_vPointClouds[id]->at(index).b = s_vhandler[id][2];
201  }
202  }
203 
204  // Try to update the pcl corresponding to the ID
205  // Throw a \b vpException::notInitialized exception if the pcl was not added to the list of known pcl first
206  bool status = sp_viewer->updatePointCloud(m_vPointClouds[id], m_vmeshid[id]);
207  if (!status) {
208  std::stringstream err_msg;
209  err_msg << "[vpPclViewer ::updateSurface] ID " << m_vmeshid[id] << " not found !" << std::endl;
210  throw(vpException(vpException::notInitialized, err_msg.str()));
211  }
212  }
213 }
214 
215 unsigned int vpPclViewer::addSurface(const pclPointCloudPointXYZRGB::Ptr &surface, const std::string &name, const std::vector<unsigned char> &v_color)
216 {
217  vpColVector emptyWeights; // Fake weights that are all equal to 1, to keep all the points
218  return addSurface(surface, emptyWeights, name, v_color);
219 }
220 
221 unsigned int vpPclViewer::addSurface(const pclPointCloudPointXYZRGB::Ptr &surface, const vpColVector &weights, const std::string &name, const std::vector<unsigned char> &v_color)
222 {
223  static unsigned int nbSurfaces = 0;
224  unsigned int id = m_vPointClouds.size();
225 
226  // Creating a new pcl and saving it in the container
227  pclPointCloudPointXYZRGB::Ptr p_pointCloud(new pclPointCloudPointXYZRGB());
228  m_vPointClouds.push_back(p_pointCloud);
229 
230  // Sizing it accordingly to the input pcl
231  unsigned int nbPoints = surface->size();
232  m_vPointClouds[id]->resize(nbPoints);
233 
234  // Affecting a color to the point cloud and its legend
235  std::vector<unsigned char> v_RGB;
236  if (v_color.size() < 3) {
237  // Affecting a default color to the pcl and its legend, if the user does not want to use its original color
238  // when displaying it
239  vpColorBlindFriendlyPalette color(gcolor[nbSurfaces]);
240  v_RGB = color.to_RGB();
241  }
242  else {
243  // Keeping the colors decided by the user
244  v_RGB = v_color;
245  }
246 
247  std::vector<double> v_RGBdouble = {
248  (double)v_RGB[0],
249  (double)v_RGB[1],
250  (double)v_RGB[2]
251  };
252  s_vhandler.push_back(v_RGBdouble);
253 
254  // Storing the weights attached to the surface
255  m_vweights.push_back(weights);
256  bool use_weigths = weights.size() > 0;
257 
258  // Copying the coordinates of each point of the original pcl,
259  // while affecting them the default color.
260  // Points that have a weight below \b s_ignoreThresh are ignored
261  for (unsigned int index = 0; index < nbPoints; index++) {
262  bool shouldPointBeVisible = false;
263  if (use_weigths) {
264  // If weights are defined, the point should be visible only
265  // if the weight is greater than the ignore threshold.
266  shouldPointBeVisible = weights[index] > s_ignoreThresh;
267  }
268  else {
269  // No weights are used => every points must be considered
270  shouldPointBeVisible = true;
271  }
272 
273  pclPointXYZRGB pt = surface->at(index);
274  m_vPointClouds[id]->at(index).x = pt.x;
275  m_vPointClouds[id]->at(index).y = pt.y;
276  m_vPointClouds[id]->at(index).z = pt.z;
277 
278  if (shouldPointBeVisible) {
279  m_vPointClouds[id]->at(index).r = s_vhandler[id][0];
280  m_vPointClouds[id]->at(index).g = s_vhandler[id][1];
281  m_vPointClouds[id]->at(index).b = s_vhandler[id][2];
282  }
283  else {
284  m_vPointClouds[id]->at(index).r = 0.;
285  m_vPointClouds[id]->at(index).g = 0.;
286  m_vPointClouds[id]->at(index).b = 0.;
287  }
288 
289  }
290 
291  // std::cout << "Point cloud: width=" << m_vPointClouds[id]->width << ", height= " << m_vPointClouds[id]->height << std::endl;
292 
293  // Checking if the user specified a name for the pcl
294  if (!name.empty()) {
295  // Yes => we save it (will be used for the legend, and for
296  // the viewer to know which pcl we are changing when updating pcls)
297  m_vmeshid.push_back(name);
298  }
299  else {
300  // No => we create one, for the very same reasons
301  m_vmeshid.push_back("point_cloud" + std::to_string(id));
302  }
303  // std::cout << "[vpPclViewer ::addSurface] Added ID " << m_vmeshid[id] << " to the list of known point clouds" << std::endl;
304  if (sp_viewer) {
305  // The viewer is already on, we can add the pcl to its known list
306  sp_viewer->addPointCloud(m_vPointClouds[id], m_vmeshid[id]);
307  }
308 
309  // Updating the number of known pcls
310  nbSurfaces = (nbSurfaces + 1) % gc_nbColorMax;
311 
312  // Adding a mutex to protect the new pcl when using \b threadUpdateSurface
313  m_vpmutex.push_back(new std::mutex());
314 
315  // Adding a legend for this pcl
316  legendParams legend;
317  m_vlegends.push_back(legend);
318  legendParams &newLegend = m_vlegends[id];
319  newLegend.m_text = m_vmeshid[id];
320  newLegend.m_posU = 10;
321  newLegend.m_posV = 10;
322  newLegend.m_size = 16;
323  if (id > 0) {
324  newLegend.m_posV = m_vlegends[id - 1].m_posV + newLegend.m_size;
325  }
326  newLegend.m_rRatio = s_vhandler[id][0] / 255.0;
327  newLegend.m_gRatio = s_vhandler[id][1] / 255.0;
328  newLegend.m_bRatio = s_vhandler[id][2] / 255.0;
329 
330  if (sp_viewer) {
331  // The viewer is on => we add the legend on the screen
332  sp_viewer->addText(newLegend.m_text, newLegend.m_posU, newLegend.m_posV, newLegend.m_rRatio, newLegend.m_gRatio, newLegend.m_bRatio);
333  }
334 
335  return id;
336 }
337 
338 
339 
341 {
342  stopThread(); // We have to stop the thread to manipulate the viewer with a blocking waiting
343  if (!sp_viewer) {
344  // The viewer was not created => creating a new one
345  sp_viewer.reset(new pcl::visualization::PCLVisualizer(m_title));
346  sp_viewer->addCoordinateSystem(0.5); // Display a coordinate system whose axis are 0.5m long
347  sp_viewer->initCameraParameters(); // Initialize the viewer with default camera settings
348  sp_viewer->setSize(s_width, s_height); // Setting the size of the viewer window
349  sp_viewer->setPosition(s_posU, s_posV); // Setting the position of the viewer window on the screen
350  sp_viewer->resetCamera();
351 
352  for (unsigned int id = 0; id < m_vPointClouds.size(); id++) {
353  sp_viewer->addPointCloud(m_vPointClouds[id], m_vmeshid[id]);
354  sp_viewer->addText(m_vlegends[id].m_text, m_vlegends[id].m_posU, m_vlegends[id].m_posV, m_vlegends[id].m_rRatio, m_vlegends[id].m_gRatio, m_vlegends[id].m_bRatio);
355  }
356  }
357  sp_viewer->spin();
358 }
359 
360 void vpPclViewer::refresh(const int &timeout, const bool &waitForDrawing)
361 {
362  sp_viewer->spinOnce(timeout, waitForDrawing);
363 }
364 
366 {
367  // Check if the visualization thread is already started
368  if (!m_hasToRun) {
369  // Thread not started => starting it now
370  m_hasToRun = true;
371  m_threadDisplay = std::thread(vpPclViewer::runThread, this);
372  }
373 }
374 
376 {
377  // Check if the visualization thread is running
378  if (m_hasToRun) {
379  // Thread is running, asking it to stop
380  m_hasToRun = false;
381  m_threadDisplay.join();
382  }
383 }
384 
385 void vpPclViewer::runThread(vpPclViewer *p_visualizer)
386 {
387  p_visualizer->loopThread();
388 }
389 
391 {
392  bool useWeights;
393  sp_viewer.reset(new pcl::visualization::PCLVisualizer(m_title)); // Allocating a new viewer or resetting the old one.
394  sp_viewer->addCoordinateSystem(0.5); // Display a coordinate system whose axis are 0.5m long
395  sp_viewer->initCameraParameters(); // Initialize the viewer with default camera settings
396  sp_viewer->setSize(s_width, s_height); // Setting the size of the viewer window
397  sp_viewer->setPosition(s_posU, s_posV); // Setting the position of the viewer window on the screen
398  sp_viewer->resetCamera();
399  unsigned int iter = 0;
400 
401  // Running the main loop of the thread
402  while (m_hasToRun) {
403  for (unsigned int id = 0; id < m_vmeshid.size(); id++) {
404  m_vpmutex[id]->lock();
405  unsigned int nbPoints = m_vPointClouds[id]->size();
406  // Checking if the pcl[id] has weights attached to its points
407  unsigned int nbWeights = m_vweights[id].size();
408  useWeights = (nbWeights > 0);
409  if (useWeights) {
410  // Setting points for which the weights are lower than \b s_ignoreThresh to be of color \b s_colorRejectedPoints
411  for (unsigned int index = 0; index < nbPoints; index++) {
412  if (m_vweights[id][index] < s_ignoreThresh) {
413  m_vPointClouds[id]->at(index).r = 0.0;
414  m_vPointClouds[id]->at(index).g = 0.0;
415  m_vPointClouds[id]->at(index).b = 0.0;
416  }
417 
418  }
419  }
420 
421  // If updatePointCloud fails, it means that the pcl was not previously known by the viewer
422  if (!sp_viewer->updatePointCloud(m_vPointClouds[id], m_vmeshid[id])) {
423  // Add the pcl to the list of pcl known by the viewer + the according legend
424  sp_viewer->addPointCloud(m_vPointClouds[id], m_vmeshid[id]);
425  sp_viewer->addText(m_vlegends[id].m_text, m_vlegends[id].m_posU, m_vlegends[id].m_posV, m_vlegends[id].m_rRatio, m_vlegends[id].m_gRatio, m_vlegends[id].m_bRatio);
426  }
427 
428  // If the pcl is not empty and the \b vpPclViewer is asked to save the pcls,
429  // create a new file name and save the pcl
430  if (m_vPointClouds[id]->size() > 0 && m_hasToSavePCDs) {
431  std::string filename = vpIoTools::createFilePath(m_outFolder, m_vmeshid[id] + std::to_string(iter) + ".pcd");
432  pcl::io::savePCDFile(filename, *m_vPointClouds[id]);
433  }
434 
435  m_vpmutex[id]->unlock();
436  }
437  sp_viewer->spinOnce(100, true);
438 
439  iter++;
440  }
441  // When leaving the thread, resetting the viewer
442  sp_viewer.reset();
443 }
444 
445 void vpPclViewer::threadUpdateSurface(const pclPointCloudPointXYZRGB::Ptr &surface, const unsigned int &id)
446 {
447  threadUpdateSurface(surface, id, vpColVector());
448 }
449 
450 void vpPclViewer::threadUpdateSurfaceOriginalColor(const pclPointCloudPointXYZRGB::Ptr &surface, const unsigned int &id)
451 {
453 }
454 
455 void vpPclViewer::threadUpdateSurface(const pclPointCloudPointXYZRGB::Ptr &surface, const unsigned int &id, const vpColVector &weights)
456 {
457  m_vpmutex[id]->lock();
458  m_vweights[id] = weights; // Saving the weights affected to each point of the pcl
459  unsigned int nbPoints = surface->size();
460  m_vPointClouds[id]->resize(nbPoints); // Resizing internal point cloud to the size of the input surface
461 
462  // Iterating on each point of the pcl to change the color of the points
463  // for the default value affected to this pcl
464  for (unsigned int index = 0; index < nbPoints; index++) {
465  pclPointXYZRGB pt = surface->at(index);
466  m_vPointClouds[id]->at(index).x = pt.x;
467  m_vPointClouds[id]->at(index).y = pt.y;
468  m_vPointClouds[id]->at(index).z = pt.z;
469 
470  m_vPointClouds[id]->at(index).r = s_vhandler[id][0];
471  m_vPointClouds[id]->at(index).g = s_vhandler[id][1];
472  m_vPointClouds[id]->at(index).b = s_vhandler[id][2];
473  }
474  m_vpmutex[id]->unlock();
475 }
476 
477 void vpPclViewer::threadUpdateSurfaceOriginalColor(const pclPointCloudPointXYZRGB::Ptr &surface, const unsigned int &id, const vpColVector &weights)
478 {
479  m_vpmutex[id]->lock();
480  m_vweights[id] = weights; // Saving the weights affected to each point of the pcl
481  unsigned int nbPoints = surface->size();
482  m_vPointClouds[id]->resize(nbPoints); // Resizing internal point cloud to the size of the input surface
483 
484  // As we keep the color of the original pcl, a plain copy will do the job
485  pcl::copyPointCloud(*surface, *m_vPointClouds[id]);
486 
487  m_vpmutex[id]->unlock();
488 }
489 END_VISP_NAMESPACE
490 #elif !defined(VISP_BUILD_SHARED_LIBS)
491 // Work around to avoid warning: libvisp_core.a(vpD3DRenderer.cpp.o) has no symbols
492 void dummy_vpPCLPointCLoudVisualization() { };
493 #endif
494 #endif
unsigned int size() const
Return the number of elements of the 2D array.
Definition: vpArray2D.h:349
Implementation of column vector and the associated operations.
Definition: vpColVector.h:191
Class that furnishes a set of colors that color blind people should be able to distinguish one from a...
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
@ notInitialized
Used to indicate that a parameter is not initialized.
Definition: vpException.h:74
static bool checkDirectory(const std::string &dirname)
Definition: vpIoTools.cpp:396
static std::string createFilePath(const std::string &parent, const std::string &child)
Definition: vpIoTools.cpp:1427
static void makeDirectory(const std::string &dirname)
Definition: vpIoTools.cpp:550
static void runThread(vpPclViewer *p_viewer)
Internal method that is called by vpPclViewer::launchThread to launch the drawing thread.
std::string m_title
Definition: vpPclViewer.h:256
void display()
Blocking-mode display of the viewer.
static int s_posV
Definition: vpPclViewer.h:248
pcl::PointCloud< pclPointXYZRGB > pclPointCloudPointXYZRGB
Definition: vpPclViewer.h:72
static pcl::visualization::PCLVisualizer::Ptr sp_viewer
Definition: vpPclViewer.h:243
void loopThread()
The internal loop of the non-blocking drawing thread.
vpPclViewer(const std::string &title, const int &width=640, const int &height=480, const int &posU=720, const int &posV=560, const std::string &outFolder=std::string(), const double &ignoreThreshold=0.95)
Construct a new vpPclViewer object.
static double s_ignoreThresh
Definition: vpPclViewer.h:249
std::thread m_threadDisplay
Definition: vpPclViewer.h:254
void setOutFolder(const std::string &outputFolder)
Set the path to the output folder. If different from the empty string, the point clouds will be saved...
std::vector< std::mutex * > m_vpmutex
Definition: vpPclViewer.h:252
std::vector< std::string > m_vmeshid
Definition: vpPclViewer.h:250
std::vector< vpColVector > m_vweights
Definition: vpPclViewer.h:253
pcl::PointXYZRGB pclPointXYZRGB
Definition: vpPclViewer.h:71
void refresh(const int &timeout=100, const bool &waitForDrawing=true)
Refresh the display.
void launchThread()
Start the drawing thread that permits to have a non-blocking display.
void setNameWindow(const std::string &nameWindow)
Set the name of the PCL viewer window.
static std::vector< std::vector< double > > s_vhandler
Definition: vpPclViewer.h:244
bool m_hasToSavePCDs
Definition: vpPclViewer.h:257
static int s_width
Definition: vpPclViewer.h:245
std::string m_outFolder
Definition: vpPclViewer.h:258
bool m_hasToRun
Definition: vpPclViewer.h:255
static int s_posU
Definition: vpPclViewer.h:247
struct vpPclViewer::legendParams legendParams
Structure that contains all the required parameters to display a legend on the viewer.
void updateSurface(const pclPointCloudPointXYZRGB::Ptr &surface, const unsigned int &id, const bool &hasToKeepColor=false)
Update the surface known by id by the viewer.
unsigned int addSurface(const pclPointCloudPointXYZRGB::Ptr &surface, const std::string &name="", const std::vector< unsigned char > &v_color=std::vector< unsigned char >())
Add a surface to the list of point clouds known by the viewer.
std::vector< legendParams > m_vlegends
Definition: vpPclViewer.h:251
void threadUpdateSurfaceOriginalColor(const pclPointCloudPointXYZRGB::Ptr &surface, const unsigned int &id)
Method to update a point cloud known by the viewer when the drawing thread is running....
static int s_height
Definition: vpPclViewer.h:246
void stopThread()
Stop the drawing thread that permits to have a non-blocking display.
void threadUpdateSurface(const pclPointCloudPointXYZRGB::Ptr &surface, const unsigned int &id)
Method to update a point cloud known by the viewer when the drawing thread is running....
std::vector< pclPointCloudPointXYZRGB::Ptr > m_vPointClouds
Definition: vpPclViewer.h:242
void setIgnoreThreshold(const double &thresh)
Set the threshold below which a point must be displayed in black.