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