UserAssistedSegmenterGuiWidgetController.cpp
Go to the documentation of this file.
1/*
2 * This file is part of ArmarX.
3 *
4 * ArmarX is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * ArmarX is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * \package SceneUnderstanding::gui-plugins::UserAssistedSegmenterGuiWidgetController
17 * \author Rainer Kartmann ( rainer dot kartmann at student dot kit dot edu )
18 * \date 2018
19 * \copyright http://www.gnu.org/licenses/gpl-2.0.txt
20 * GNU General Public License
21 */
22
24
25#include <boost/lexical_cast.hpp>
26
27#include <pcl/common/colors.h>
28
30
33
36
37
38using namespace armarx;
39using namespace Eigen;
40
41template <class T>
42std::string
43str(const T& t)
44{
45 return boost::lexical_cast<std::string>(t);
46}
47
48template <class T>
49QString
50qstr(const T& t)
51{
52 return QString::fromStdString(boost::lexical_cast<std::string>(t));
53}
54
55namespace visionx
56{
57
58 QString
60 {
61 return "VisionX.UserAssistedSegmenterGui";
62 }
63
65 {
66 widget.setupUi(getWidget());
67
68
69 this->segmentsOverviewTable = new PointCloudSegmentsTable();
70 widget.tableSegmentsOverviewLayout->addWidget(segmentsOverviewTable);
71 widget.tableSegmentsOverviewPlaceholder->deleteLater();
72
73
74 // Setup tableUserSegmentation.
75 widget.tableUserGrouping->setColumnCount(3);
76 widget.tableUserGrouping->setRowCount(0);
77
78 QStringList header;
79 header << "C"
80 << "ID"
81 << "Space seperated segment IDs";
82 widget.tableUserGrouping->setHorizontalHeaderLabels(header);
83 widget.tableUserGrouping->setColumnWidth(0, 30);
84 widget.tableUserGrouping->setColumnWidth(1, 40);
85 // last column is stretched
86 }
87
91
92 void
94 {
95 (void)settings; // unused
96 }
97
98 void
100 {
101 (void)settings; // unused
102 }
103
104 QPointer<QDialog>
106 {
107 if (!configDialog)
108 {
109 configDialog = new UserAssistedSegmenterConfigDialog(parent);
110 }
111 return qobject_cast<UserAssistedSegmenterConfigDialog*>(configDialog);
112 }
113
114 void
116 {
117 userAssistedSegmenterProxyName = configDialog->getUserAssistedSegmenterProxyName();
118 userAssistedSegmenterTopicName = configDialog->getUserAssistedSegmenterTopicName();
119 }
120
121 void
123 {
124 usingTopic(userAssistedSegmenterTopicName);
125 drawer.offeringTopic(*this);
126
127 usingProxy(userAssistedSegmenterProxyName);
128 }
129
130 void
132 {
133 drawer.getTopic(*this);
134
135 segmenterProxy =
136 getProxy<visionx::UserAssistedSegmenterInterfacePrx>(userAssistedSegmenterProxyName);
137 if (!segmenterProxy)
138 {
139 ARMARX_ERROR << "Could not get proxy '" << userAssistedSegmenterProxyName << "'.";
140 }
141
142 connect(this, SIGNAL(newSourcePointCloud()), this, SLOT(onNewSourcePointCloud()));
143
144 connect(widget.spinBoxFilterSmallSegmentsNum,
145 SIGNAL(editingFinished()),
146 this,
148
149 connect(widget.checkBoxFilterSegment0, SIGNAL(toggled(bool)), this, SLOT(updateFilters()));
150 connect(
151 widget.checkBoxFilterSmallSegments, SIGNAL(toggled(bool)), this, SLOT(updateFilters()));
152
153 connect(widget.checkBoxShowIDs, SIGNAL(toggled(bool)), this, SLOT(onShowIDsToggled(bool)));
154 connect(widget.radioButtonShowInput,
155 SIGNAL(toggled(bool)),
156 this,
158 connect(widget.radioButtonShowResult,
159 SIGNAL(toggled(bool)),
160 this,
162
163 connect(widget.spinBoxNumObjects,
164 SIGNAL(valueChanged(int)),
165 this,
166 SLOT(updateUserGroupingRows(int)));
167 connect(widget.tableUserGrouping,
168 SIGNAL(cellChanged(int, int)),
169 this,
170 SLOT(onUserGroupingChanged(int, int)));
171
172 connect(widget.buttonClearVisu, SIGNAL(released()), this, SLOT(clearVisualization()));
173 connect(widget.buttonPublish, SIGNAL(released()), this, SLOT(publishSegmentation()));
174
175 connect(widget.tableUserGrouping,
176 SIGNAL(cellChanged(int, int)),
177 this,
178 SLOT(onTableGroupingCellChanged(int, int)));
179
180
181 for (auto& spinBox : {widget.spinTextSize, widget.spinSphereSize})
182 {
183 connect(spinBox, SIGNAL(valueChanged(int)), this, SLOT(updateSegmentIDs()));
184 }
185 }
186
187 void
189 const visionx::ColoredLabeledPointCloud& receivedPointCloud,
190 const Ice::Current&)
191 {
192 if (!widget.checkBoxListening->isChecked())
193 {
194 return;
195 }
196
197 ARMARX_IMPORTANT << "Received new point cloud segmentation";
198
199 ARMARX_INFO << "Setting source point cloud";
200 visionx::tools::convertToPCL(receivedPointCloud, sourcePointCloud);
201
202 ARMARX_INFO << "Emittinging signal receivedSegmentation().";
203 emit newSourcePointCloud();
204 }
205
206 void
208 {
209 ARMARX_INFO << "onNewSourcePointCloud()";
210
211 widget.labelPcStatus->setText(QString("Processing..."));
212
213 this->pointCloud = sourcePointCloud;
214
215 computeSegmentIndex(pointCloud);
216
217 applyFilters(pointCloud, segmentIndex);
218
219 updatePointCloud(pointCloud, segmentIndex);
220
221 // updateUserGroupingRows();
222
223 widget.labelPcStatus->setText(QString("Current point cloud:"));
224 }
225
226 void
228 {
229 widget.checkBoxFilterSmallSegments->setChecked(
230 widget.spinBoxFilterSmallSegmentsNum->value() > 0);
231 }
232
233 void
235 {
236 applyFilters(pointCloud, segmentIndex);
237 updatePointCloud(pointCloud, segmentIndex);
238 }
239
240 void
242 {
243 if (checked)
244 {
245 drawInputPointCloud(false);
246 }
247 }
248
249 void
251 {
252 if (checked)
253 {
254 drawResultPointCloud(false);
255 }
256 }
257
258 void
260 {
261 if (checked)
262 {
263 drawSegmentIDs(false);
264 }
265 else
266 {
267 drawer.clearLayer(layerSegmentIDs);
268 }
269 }
270
271 void
276
277 void
278 UserAssistedSegmenterGuiWidgetController::applyFilters(
279 PointCloudT& pointCloud,
280 std::map<uint32_t, pcl::PointIndices>& segmentIndex)
281 {
282 ARMARX_INFO << "Applying filters";
283
284 if (widget.checkBoxFilterSegment0)
285 {
286 auto ifLabelZero = [](const PointT& p) { return p.label == 0; };
287 pointCloud.erase(std::remove_if(pointCloud.begin(), pointCloud.end(), ifLabelZero),
288 pointCloud.end());
289 // Recompute index.
290 computeSegmentIndex(pointCloud);
291 }
292
293 if (widget.checkBoxFilterSmallSegments->isChecked())
294 {
295 const int minPoints = std::max(0, widget.spinBoxFilterSmallSegmentsNum->value());
296 ARMARX_CHECK_NONNEGATIVE(minPoints);
297
298 if (minPoints > 1)
299 {
300 auto ifLessPoints = [&segmentIndex, minPoints](const PointT& p) {
301 return segmentIndex.at(p.label).indices.size() <
302 static_cast<std::size_t>(minPoints);
303 };
304 pointCloud.erase(std::remove_if(pointCloud.begin(), pointCloud.end(), ifLessPoints),
305 pointCloud.end());
306 // Recompute index.
307 computeSegmentIndex(pointCloud);
308 }
309 }
310 }
311
312 void
313 UserAssistedSegmenterGuiWidgetController::updatePointCloud(
314 const PointCloudT& pointCloud,
315 const std::map<uint32_t, pcl::PointIndices>& segmentIndex)
316 {
317 ARMARX_INFO << "Updating point cloud.";
318 computeCenters(pointCloud, segmentIndex);
319
320 widget.labelPcPointsNum->setText(qstr(pointCloud.size()));
321 widget.labelPcSegmentsNum->setText(qstr(segmentIndex.size()));
322
323 updateTableOverview();
324 drawInputPointCloud();
325 drawSegmentIDs(true);
326 }
327
328 void
329 UserAssistedSegmenterGuiWidgetController::computeSegmentIndex(const PointCloudT& pointCloud)
330 {
331 segmentIndex.clear();
332 const bool excludeZero = false;
333 visionx::tools::fillLabelMap(pointCloud, segmentIndex, excludeZero);
334 }
335
336 void
337 UserAssistedSegmenterGuiWidgetController::computeCenters(
338 const PointCloudT& pointCloud,
339 const std::map<uint32_t, pcl::PointIndices>& segmentIndex)
340 {
341 ARMARX_INFO << "Computing centers";
342 centers.clear();
343 for (const auto& [segmentID, indices] : segmentIndex)
344 {
345 Vector3f center(0, 0, 0);
346 for (int i : indices.indices)
347 {
348 const PointT& p = pointCloud[static_cast<std::size_t>(i)];
349 center += Vector3f(p.x, p.y, p.z);
350 }
351 center /= segmentIndex.at(segmentID).indices.size();
352 centers[segmentID] = center;
353 }
354 }
355
356 void
357 UserAssistedSegmenterGuiWidgetController::updateTableOverview()
358 {
359 segmentsOverviewTable->setData(segmentIndex);
360 }
361
362 void
363 UserAssistedSegmenterGuiWidgetController::drawSegmentIDs(bool onlyIfChecked)
364 {
365 if (onlyIfChecked && !widget.checkBoxShowIDs->isChecked())
366 {
367 return;
368 }
369
370 drawer.clearLayer(layerSegmentIDs, true);
371
372 for (const auto& [segmentID, _] : segmentIndex)
373 {
374 const int textSize = widget.spinTextSize->value();
375 const int sphereSize = widget.spinSphereSize->value();
376
377 const DrawColor segColor = dcolor(segmentID);
378 const DrawColor textColor = (segColor.r + segColor.g + segColor.b) > 1.f
379 ? DrawColor{0, 0, 0, 1}
380 : DrawColor{1, 1, 1, 1};
381
382 drawer.drawText({layerSegmentIDs, "id" + str(segmentID)},
383 centers[segmentID],
384 str(segmentID),
385 textSize,
386 textColor);
387 drawer.drawSphere({layerSegmentIDs, "sph" + str(segmentID)},
388 centers[segmentID],
389 sphereSize,
390 segColor);
391 }
392 }
393
394 void
395 UserAssistedSegmenterGuiWidgetController::drawInputPointCloud(bool onlyIfChecked)
396 {
397 if (onlyIfChecked && !widget.radioButtonShowInput->isChecked())
398 {
399 return;
400 }
401
402 ARMARX_INFO << "Drawing input point cloud";
403
404 // Draw point cloud.
405 drawer.drawPointCloud({layerPointCloud, "PointCloud"},
406 pointCloud,
407 [this](const PointT& p) { return dcolor(p.label); });
408
409 // drawSegmentIDs(true);
410 }
411
412 void
413 UserAssistedSegmenterGuiWidgetController::drawResultPointCloud(bool onlyIfChecked)
414 {
415 if (onlyIfChecked && !widget.radioButtonShowResult->isChecked())
416 {
417 return;
418 }
419
420 ARMARX_INFO << "Drawing result point cloud";
421
422 DebugDrawerColoredPointCloud debugPointCloud;
423
424 for (std::size_t group = 0; group < userGrouping.size(); ++group)
425 {
426 for (uint32_t segment : userGrouping[group])
427 {
428 for (int i : segmentIndex.at(segment).indices)
429 {
430 PointT& p = pointCloud[static_cast<std::size_t>(i)];
431 debugPointCloud.points.push_back(
432 DebugDrawerColoredPointCloudElement{p.x, p.y, p.z, dcolor(group)});
433 }
434 }
435 }
436
437 drawer.drawPointCloud({layerPointCloud, "PointCloud"}, debugPointCloud);
438
439 // drawSegmentIDs(true);
440 }
441
442 DrawColor
443 UserAssistedSegmenterGuiWidgetController::dcolor(std::size_t id) const
444 {
445 return armarx::GlasbeyLUT::at(id);
446 }
447
448 int
449 toByte(float f)
450 {
451 return static_cast<int>(f * 255);
452 }
453
454 QColor
455 UserAssistedSegmenterGuiWidgetController::qcolor(std::size_t id) const
456 {
457 const DrawColor c = dcolor(id);
458 return QColor(toByte(c.r), toByte(c.g), toByte(c.b), toByte(c.a));
459 }
460
461 void
463 {
464 int previousNumRows = widget.tableUserGrouping->rowCount();
465 widget.tableUserGrouping->setRowCount(numRows);
466
467 std::set<uint32_t> segmentIDs;
468 for (const auto& [segmentID, _] : segmentIndex)
469 {
470 segmentIDs.insert(segmentID);
471 }
472
473 // Initialize new rows (does nothing if previousNumRows >= numRows).
474 for (int id = previousNumRows; id < numRows; ++id)
475 {
476 QTableWidgetItem* itemColor = new QTableWidgetItem(QString());
477 itemColor->setBackgroundColor(qcolor(static_cast<std::size_t>(id)));
478 widget.tableUserGrouping->setItem(id, 0, itemColor);
479
480 QTableWidgetItem* itemID = new QTableWidgetItem(qstr(id));
481 itemID->setTextAlignment(Qt::AlignCenter);
482 widget.tableUserGrouping->setItem(id, 1, itemID);
483
484 UserGroupingLineEdit* itemList =
485 new UserGroupingLineEdit({segmentIDs}, static_cast<uint32_t>(id));
486 connect(itemList,
487 SIGNAL(groupingChanged(uint32_t, std::vector<uint32_t>)),
488 this,
489 SLOT(onUserGroupingChanged(uint32_t, std::vector<uint32_t>)));
490 widget.tableUserGrouping->setCellWidget(id, 2, itemList);
491 }
492
493 userGrouping.resize(static_cast<std::size_t>(numRows));
494 }
495
496 void
498 uint32_t groupID,
499 std::vector<uint32_t> segmentIDs)
500 {
501 if (groupID < userGrouping.size())
502 {
503 userGrouping[groupID] = segmentIDs;
504
505 ARMARX_INFO << "Updated user grouping " << groupID << ": \n" << userGrouping[groupID];
506 drawResultPointCloud(true);
507 }
508 }
509
510 void
512 {
513 if (col == 0)
514 {
515 widget.tableUserGrouping->item(row, col)->setText(QString());
516 }
517 else if (col == 1)
518 {
519 widget.tableUserGrouping->item(row, col)->setText(qstr(row));
520 }
521 }
522
523 void
525 {
526 if (!segmenterProxy)
527 {
528 ARMARX_ERROR << "Segmenter proxy not set.";
529 return;
530 }
531
532 visionx::ColoredLabeledPointCloud resultCloud;
533
534 for (std::size_t groupID = 0; groupID < userGrouping.size(); ++groupID)
535 {
536 for (uint32_t segment : userGrouping[groupID])
537 {
538 for (int i : segmentIndex.at(segment).indices)
539 {
540 PointT& p = pointCloud.at(static_cast<std::size_t>(i));
541 resultCloud.push_back(
542 visionx::ColoredLabeledPoint3D{visionx::Point3D{p.x, p.y, p.z},
543 visionx::RGBA{p.r, p.g, p.b, p.a},
544 static_cast<int>(groupID)});
545 }
546 }
547 }
548 segmenterProxy->publishSegmentation(resultCloud);
549 }
550
551 void
553 {
554 drawer.clearLayer(layerSegmentIDs);
555 drawer.clearLayer(layerPointCloud);
556 drawer.clearColoredPointCloud({layerPointCloud, "PointCloud"});
557 }
558
559} // namespace visionx
int toByte(float f)
constexpr T c
QString qstr(const T &t)
std::string str(const T &t)
virtual QPointer< QWidget > getWidget()
getWidget returns a pointer to the a widget of this controller.
static DrawColor at(std::size_t id, float alpha=1.f)
bool usingProxy(const std::string &name, const std::string &endpoints="")
Registers a proxy for retrieval after initialization and adds it to the dependency list.
void usingTopic(const std::string &name, bool orderedPublishing=false)
Registers a proxy for subscription after initialization.
Ice::ObjectPrx getProxy(long timeoutMs=0, bool waitForScheduler=true) const
Returns the proxy of this object (optionally it waits for the proxy)
void onNewSourcePointCloud()
Handles an incoming segmented point cloud by reportReceivedSegmentation().
static QString GetWidgetName()
Returns the Widget name displayed in the ArmarXGui to create an instance of this class.
void onUserGroupingChanged(uint32_t groupID, std::vector< uint32_t > segmentIDs)
virtual void configured() override
This function must be implemented by the user, if he supplies a config dialog.
virtual void reportSegmentation(const visionx::ColoredLabeledPointCloud &pointCloud, const Ice::Current &) override
virtual ~UserAssistedSegmenterGuiWidgetController() override
Controller destructor.
virtual QPointer< QDialog > getConfigDialog(QWidget *parent) override
getConfigDialog returns a pointer to the a configuration widget of this controller.
#define ARMARX_CHECK_NONNEGATIVE(number)
Check whether number is nonnegative (>= 0).
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181
#define ARMARX_IMPORTANT
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
Definition Logging.h:190
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:196
This file offers overloads of toIce() and fromIce() functions for STL container types.
pcl::PointCloud< PointT > PointCloudT
Definition Common.h:32
pcl::PointIndices::Ptr indices(const PCG &g)
Retrieve the indices of the points of the point cloud stored in a point cloud graph that actually bel...
void convertToPCL(void **source, visionx::MetaPointCloudFormatPtr pointCloudFormat, pcl::PointCloud< pcl::PointXYZRGBA >::Ptr targetPtr)
std::enable_if< is_shared_ptr< LabeledPointCloudPtrT >::value, void >::type fillLabelMap(LabeledPointCloudPtrT labeledCloudPtr, std::map< uint32_t, pcl::PointIndices > &labelIndicesMap, bool excludeZero=true)
ArmarX headers.