Node highlighting addition to previous pull request (#1348)

* Clicking Nodes\Links brings them up in viewer

* Removed duplicated feature, kept spin box and single click node event. Removed unrelated border option crashing.

* added code to highlight selected nodes

* Refactored: merged A-B functions into single one, fixed highlighted node color not reset after selecting another node, highlighting also when changing constraint sliders, change node color instead of border, added menu options to change color (added to settings).

* cleanup

* Fixed duplicates in neighbor and loop closure sliders, fixed WM checkbox not visible.

* Fixed forward neighbors slider scrolling showing only one color.

---------

Co-authored-by: Chuck Ellison <charles.c.ellison@erdc.dren.mil>
Co-authored-by: matlabbe <matlabbe@gmail.com>
Co-authored-by: Alex Perez <alex.s.perez@erdc.dren.mil>
This commit is contained in:
Charles Ellison
2024-09-28 20:40:46 -05:00
committed by GitHub
parent 8c0e57bc52
commit 538bc681d4
4 changed files with 219 additions and 64 deletions

View File

@@ -587,6 +587,7 @@ void DatabaseViewer::readSettings()
// GraphViewer settings
ui_->graphViewer->loadSettings(settings, "GraphView");
ui_->graphViewer->setReferentialVisible(false);
settings.beginGroup("optimization");
ui_->doubleSpinBox_gainCompensationRadius->setValue(settings.value("gainCompensationRadius", ui_->doubleSpinBox_gainCompensationRadius->value()).toDouble());
@@ -1873,7 +1874,6 @@ void DatabaseViewer::updateIds()
if(wmStates.find(ids_[i]) != wmStates.end())
{
wmStates_.insert(std::make_pair(ids_[i], wmStates.at(ids_[i])));
ui_->checkBox_wmState->setVisible(true);
}
if(w < 0)
{
@@ -1952,6 +1952,8 @@ void DatabaseViewer::updateIds()
}
}
ui_->checkBox_wmState->setVisible(!wmStates_.empty() || !lastWmIds_.empty());
progressDialog->appendText("Loading info for all nodes... done!");
progressDialog->incrementStep();
progressDialog->appendText("Loading optimized poses and maps...");
@@ -2106,27 +2108,6 @@ void DatabaseViewer::updateIds()
ui_->menuExport_poses->setEnabled(!odomPoses_.empty());
graphes_.clear();
graphLinks_.clear();
neighborLinks_.clear();
loopLinks_.clear();
for(std::multimap<int, rtabmap::Link>::iterator iter = links_.begin(); iter!=links_.end(); ++iter)
{
if(!iter->second.transform().isNull())
{
if(iter->second.type() == rtabmap::Link::kNeighbor ||
iter->second.type() == rtabmap::Link::kNeighborMerged)
{
neighborLinks_.append(iter->second);
}
else if(iter->second.from()!=iter->second.to())
{
loopLinks_.append(iter->second);
}
}
else
{
UERROR("Transform null for link from %d to %d", iter->first, iter->second.to());
}
}
if(ids_.size())
{
@@ -2160,20 +2141,9 @@ void DatabaseViewer::updateIds()
ui_->label_idB->setText("NaN");
}
if(neighborLinks_.size())
{
ui_->horizontalSlider_neighbors->setMinimum(0);
ui_->horizontalSlider_neighbors->setMaximum(neighborLinks_.size()-1);
ui_->horizontalSlider_neighbors->setEnabled(true);
ui_->horizontalSlider_neighbors->setSliderPosition(0);
}
else
{
ui_->horizontalSlider_neighbors->setEnabled(false);
}
if(ids_.size())
{
updateNeighborsSlider();
updateLoopClosuresSlider();
if(ui_->graphViewer->isVisible() || ui_->dockWidget_occupancyGridView->isVisible())
{
@@ -4347,28 +4317,43 @@ void DatabaseViewer::detectMoreLoopClosures()
void DatabaseViewer::updateAllNeighborCovariances()
{
updateCovariances(neighborLinks_);
std::multimap<int, Link> allLinks = updateLinksWithModifications(links_);
QList<rtabmap::Link> links;
for(std::multimap<int, Link>::iterator iter=allLinks.begin(); iter!=allLinks.end(); ++iter)
{
if(iter->second.type() == Link::kNeighbor ||
iter->second.type() == Link::kNeighborMerged)
{
links.push_back(iter->second);
}
}
updateCovariances(links);
}
void DatabaseViewer::updateAllLoopClosureCovariances()
{
std::multimap<int, Link> allLinks = updateLinksWithModifications(links_);
QList<rtabmap::Link> links;
for(int i=0; i<loopLinks_.size(); ++i)
for(std::multimap<int, Link>::iterator iter=allLinks.begin(); iter!=allLinks.end(); ++iter)
{
if(loopLinks_.at(i).type() != Link::kLandmark)
if(iter->second.type() != Link::kNeighbor &&
iter->second.type() != Link::kNeighborMerged &&
iter->second.type() != Link::kLandmark &&
iter->second.from() != iter->second.to())
{
links.push_back(loopLinks_.at(i));
links.push_back(iter->second);
}
}
updateCovariances(links);
}
void DatabaseViewer::updateAllLandmarkCovariances()
{
std::multimap<int, Link> allLinks = updateLinksWithModifications(links_);
QList<rtabmap::Link> links;
for(int i=0; i<loopLinks_.size(); ++i)
for(std::multimap<int, Link>::iterator iter=allLinks.begin(); iter!=allLinks.end(); ++iter)
{
if(loopLinks_.at(i).type() == Link::kLandmark)
if(iter->second.type() == Link::kLandmark)
{
links.push_back(loopLinks_.at(i));
links.push_back(iter->second);
}
}
updateCovariances(links);
@@ -4378,13 +4363,7 @@ void DatabaseViewer::updateCovariances(const QList<Link> & links)
{
if(links.size())
{
cv::Mat infMatrix = links.first().infMatrix();
std::multimap<int, Link>::iterator findIter = rtabmap::graph::findLink(linksRefined_, links.first().from() ,links.first().to(), false, links.first().type());
if(findIter != linksRefined_.end())
{
infMatrix = findIter->second.infMatrix();
}
cv::Mat infMatrix = links.first().infMatrix();
EditConstraintDialog dialog(Transform::getIdentity(), infMatrix.inv());
dialog.setPoseGroupVisible(false);
if(dialog.exec() != QDialog::Accepted)
@@ -4575,6 +4554,7 @@ void DatabaseViewer::resetAllChanges()
linksRemoved_.clear();
generatedLocalMaps_.clear();
modifiedLaserScans_.clear();
updateNeighborsSlider();
updateLoopClosuresSlider();
this->updateGraphView();
}
@@ -4711,6 +4691,10 @@ void DatabaseViewer::update(int value,
labelId->setText(QString::number(id));
if(id>0)
{
if(ui_->dockWidget_graphView->isVisible()) {
ui_->graphViewer->highlightNode(id, spinBoxIndex==ui_->spinBox_indexB?1:0);
}
//image
QImage img;
cv::Mat imgDepth;
@@ -7075,7 +7059,9 @@ void DatabaseViewer::sliderIterationsValueChanged(int value)
ui_->graphViewer->updateGTGraph(groundTruthPoses_);
ui_->graphViewer->updateGPSGraph(gpsPoses_, gpsValues_);
ui_->graphViewer->updateGraph(graph, graphLinks_, mapIds_, weights_);
if(!ui_->checkBox_wmState->isChecked())
if(ui_->checkBox_wmState->isEnabled() &&
ui_->checkBox_wmState->isChecked() &&
!lastWmIds_.empty())
{
bool allNodesAreInWM = true;
std::map<int, float> colors;
@@ -7380,7 +7366,9 @@ void DatabaseViewer::updateGraphView()
graphLinks_.clear();
std::map<int, rtabmap::Transform> poses = odomPoses_;
if(ui_->checkBox_wmState->isChecked() && uContains(wmStates_, fromId))
if(ui_->checkBox_wmState->isEnabled() &&
ui_->checkBox_wmState->isChecked() &&
uContains(wmStates_, fromId))
{
std::map<int, rtabmap::Transform> wmPoses;
std::vector<int> & wmState = wmStates_.at(fromId);
@@ -7421,7 +7409,6 @@ void DatabaseViewer::updateGraphView()
ui_->menuExport_poses->setEnabled(true);
std::multimap<int, rtabmap::Link> links = links_;
loopLinks_.clear();
// filter current map if not spanning to all maps
if(!ui_->checkBox_spanAllMaps->isChecked() && uContains(mapIds_, fromId) && mapIds_.at(fromId) >= 0)
@@ -7544,7 +7531,6 @@ void DatabaseViewer::updateGraphView()
links.erase(iter++);
continue;
}
loopLinks_.push_back(iter->second);
if(isUnique)
++totalGlobal;
}
@@ -7555,7 +7541,6 @@ void DatabaseViewer::updateGraphView()
links.erase(iter++);
continue;
}
loopLinks_.push_back(iter->second);
if(isUnique)
++totalLocalSpace;
}
@@ -7566,7 +7551,6 @@ void DatabaseViewer::updateGraphView()
links.erase(iter++);
continue;
}
loopLinks_.push_back(iter->second);
if(isUnique)
++totalLocalTime;
}
@@ -7577,7 +7561,6 @@ void DatabaseViewer::updateGraphView()
links.erase(iter++);
continue;
}
loopLinks_.push_back(iter->second);
if(isUnique)
++totalUser;
}
@@ -7593,7 +7576,6 @@ void DatabaseViewer::updateGraphView()
{
poses.insert(std::make_pair(iter->second.to(), poses.at(iter->second.from())*iter->second.transform()));
}
loopLinks_.push_back(iter->second);
if(isUnique)
++totalLandmarks;
@@ -7621,10 +7603,6 @@ void DatabaseViewer::updateGraphView()
if(isUnique)
++totalGravity;
}
else
{
loopLinks_.push_back(iter->second);
}
++iter;
}
updateLoopClosuresSlider();
@@ -9113,7 +9091,6 @@ void DatabaseViewer::rejectConstraint()
if(priorId==0)
{
this->updateGraphView();
updateLoopClosuresSlider();
}
else
{
@@ -9159,7 +9136,16 @@ std::multimap<int, rtabmap::Link> DatabaseViewer::updateLinksWithModifications(
findIter = rtabmap::graph::findLink(linksRefined_, iter->second.from(), iter->second.to());
if(findIter!=linksRefined_.end())
{
links.insert(*findIter); // add the refined link
// add the refined link
if(iter->second.from() == findIter->second.to() &&
iter->second.from() != iter->second.to())
{
links.insert(std::make_pair(iter->second.from(), findIter->second.inverse()));
}
else
{
links.insert(*findIter);
}
UDEBUG("Updated link (%d->%d, %d)", iter->second.from(), iter->second.to(), iter->second.type());
continue;
}
@@ -9176,17 +9162,83 @@ std::multimap<int, rtabmap::Link> DatabaseViewer::updateLinksWithModifications(
if(findIter!=linksRefined_.end())
{
links.insert(*findIter); // add the refined link
links.insert(std::make_pair(findIter->second.to(), findIter->second.inverse())); // return both ways
UDEBUG("Added refined link (%d->%d, %d)", findIter->second.from(), findIter->second.to(), findIter->second.type());
continue;
}
UDEBUG("Added link (%d->%d, %d)", iter->second.from(), iter->second.to(), iter->second.type());
links.insert(*iter);
links.insert(std::make_pair(iter->second.to(), iter->second.inverse())); // return both ways
}
return links;
}
void DatabaseViewer::updateNeighborsSlider(int from, int to)
{
UDEBUG("%d %d", from, to);
neighborLinks_.clear();
std::multimap<int, Link> links = updateLinksWithModifications(links_);
int position = ui_->horizontalSlider_neighbors->value();
std::multimap<int, Link> linksSortedByChildren;
for(std::multimap<int, rtabmap::Link>::iterator iter = links.begin(); iter!=links.end(); ++iter)
{
if(iter->second.from() < iter->second.to())
{
linksSortedByChildren.insert(*iter);
}
}
for(std::multimap<int, rtabmap::Link>::iterator iter = linksSortedByChildren.begin(); iter!=linksSortedByChildren.end(); ++iter)
{
if(!iter->second.transform().isNull())
{
if(iter->second.type() == rtabmap::Link::kNeighbor ||
iter->second.type() == rtabmap::Link::kNeighborMerged)
{
if((iter->second.from() == from && iter->second.to() == to) ||
(iter->second.to() == from && iter->second.from() == to))
{
position = neighborLinks_.size();
}
neighborLinks_.append(iter->second);
}
}
else
{
UERROR("Transform null for link from %d to %d", iter->first, iter->second.to());
}
}
if(neighborLinks_.size())
{
if(neighborLinks_.size() == 1)
{
// just to be able to move the cursor of the neighbor slider
neighborLinks_.push_back(neighborLinks_.front());
}
ui_->horizontalSlider_neighbors->setMinimum(0);
ui_->horizontalSlider_neighbors->setMaximum(neighborLinks_.size()-1);
ui_->horizontalSlider_neighbors->setEnabled(true);
if(position != ui_->horizontalSlider_neighbors->value())
{
ui_->horizontalSlider_neighbors->setValue(position);
}
else
{
this->updateConstraintView(neighborLinks_.at(position));
}
}
else
{
ui_->horizontalSlider_neighbors->setEnabled(false);
constraintsViewer_->removeAllClouds();
constraintsViewer_->refreshView();
updateConstraintButtons();
}
}
void DatabaseViewer::updateLoopClosuresSlider(int from, int to)
{
UDEBUG("%d %d", from, to);
@@ -9196,11 +9248,11 @@ void DatabaseViewer::updateLoopClosuresSlider(int from, int to)
std::multimap<int, Link> linksSortedByParents;
for(std::multimap<int, rtabmap::Link>::iterator iter = links.begin(); iter!=links.end(); ++iter)
{
if(iter->second.to() > iter->second.from())
if(iter->second.to() > iter->second.from() && iter->second.from() < 0) // landmark
{
linksSortedByParents.insert(std::make_pair(iter->second.to(), iter->second.inverse()));
}
else if(iter->second.to() != iter->second.from())
else if(iter->second.to() < iter->second.from())
{
linksSortedByParents.insert(*iter);
}

View File

@@ -463,6 +463,9 @@ GraphViewer::GraphViewer(QWidget * parent) :
_odomCacheOverlay->setBrush(QBrush(QColor(255, 255, 255, 150)));
_odomCacheOverlay->setPen(QPen(Qt::NoPen));
_highlightedNodes.push_back(QPair<QColor, NodeItem*>(Qt::magenta, nullptr));
_highlightedNodes.push_back(QPair<QColor, NodeItem*>(Qt::yellow, nullptr));
this->restoreDefaults();
this->fitInView(this->sceneRect(), Qt::KeepAspectRatio);
@@ -702,6 +705,14 @@ void GraphViewer::updateGraph(const std::map<int, Transform> & poses,
{
if(!iter.value()->isVisible())
{
for(int i=0; i<_highlightedNodes.size(); ++i)
{
if(_highlightedNodes[i].second && _highlightedNodes[i].second == iter.value())
{
_highlightedNodes[i].second = nullptr;
}
}
delete iter.value();
iter = _nodeItems.erase(iter);
}
@@ -1208,6 +1219,33 @@ void GraphViewer::updateLocalPath(const std::vector<int> & localPath)
_localPathRoot->setVisible(wasVisible);
}
void GraphViewer::highlightNode(int nodeId, int highlightIndex)
{
if(highlightIndex<0 || highlightIndex>_highlightedNodes.size())
{
UERROR("Unsupported highlight color index %d", highlightIndex);
return;
}
for(int i=0; i<_highlightedNodes.size(); ++i)
{
if(_highlightedNodes[i].second &&
(_highlightedNodes[i].second->id() == nodeId || i == highlightIndex))
{
// reset to normal color
_highlightedNodes[i].second->setColor(_nodeColor);
_highlightedNodes[i].second = nullptr;
}
}
QMap<int, NodeItem*>::iterator iter = _nodeItems.find(nodeId);
if(iter != _nodeItems.end())
{
iter.value()->setColor(_highlightedNodes[highlightIndex].first);
_highlightedNodes[highlightIndex].second = iter.value();
}
}
void GraphViewer::clearGraph()
{
qDeleteAll(_nodeItems);
@@ -1227,6 +1265,11 @@ void GraphViewer::clearGraph()
qDeleteAll(_gpsLinkItems);
_gpsLinkItems.clear();
for(int i=0; i<_highlightedNodes.size(); ++i)
{
_highlightedNodes[i].second=nullptr;
}
_root->resetTransform();
_worldMapRotation = 0.0f;
_referential->resetTransform();
@@ -1266,6 +1309,10 @@ void GraphViewer::saveSettings(QSettings & settings, const QString & group) cons
settings.setValue("node_color", this->getNodeColor());
settings.setValue("node_odom_cache_color", this->getNodeOdomCacheColor());
settings.setValue("current_goal_color", this->getCurrentGoalColor());
for(int i=0; i<_highlightedNodes.size(); ++i)
{
settings.setValue(QString("highlighting_color_%1").arg(i), _highlightedNodes[i].first);
}
settings.setValue("neighbor_color", this->getNeighborColor());
settings.setValue("global_color", this->getGlobalLoopClosureColor());
settings.setValue("local_color", this->getLocalLoopClosureColor());
@@ -1312,6 +1359,10 @@ void GraphViewer::loadSettings(QSettings & settings, const QString & group)
this->setNodeColor(settings.value("node_color", this->getNodeColor()).value<QColor>());
this->setNodeOdomCacheColor(settings.value("node_odom_cache_color", this->getNodeOdomCacheColor()).value<QColor>());
this->setCurrentGoalColor(settings.value("current_goal_color", this->getCurrentGoalColor()).value<QColor>());
for(int i=0; i<_highlightedNodes.size(); ++i)
{
this->setHighlightColor(settings.value(QString("highlighting_color_%1").arg(i), _highlightedNodes[i].first).value<QColor>(), i);
}
this->setNeighborColor(settings.value("neighbor_color", this->getNeighborColor()).value<QColor>());
this->setGlobalLoopClosureColor(settings.value("global_color", this->getGlobalLoopClosureColor()).value<QColor>());
this->setLocalLoopClosureColor(settings.value("local_color", this->getLocalLoopClosureColor()).value<QColor>());
@@ -1596,6 +1647,20 @@ void GraphViewer::setGPSColor(const QColor & color)
iter.value()->setColor(_gpsPathColor);
}
}
void GraphViewer::setHighlightColor(const QColor & color, int index)
{
if(index<0 || index > _highlightedNodes.size())
{
UERROR("Unsupported highlight color index %d", index);
return;
}
_highlightedNodes[index].first = color;
NodeItem * node = _highlightedNodes[index].second;
if(node != nullptr)
{
node->setColor(color);
}
}
void GraphViewer::setIntraSessionLoopColor(const QColor & color)
{
_loopIntraSessionColor = color;
@@ -1765,13 +1830,25 @@ void GraphViewer::restoreDefaults()
setNodeRadius(0.01f);
setLinkWidth(0.0f);
setNodeColor(Qt::blue);
setNodeOdomCacheColor(Qt::darkGreen);
setCurrentGoalColor(Qt::darkMagenta);
setHighlightColor(Qt::magenta, 0);
setHighlightColor(Qt::yellow, 1);
setNeighborColor(Qt::blue);
setGlobalLoopClosureColor(Qt::red);
setLocalLoopClosureColor(Qt::yellow);
setUserLoopClosureColor(Qt::red);
setVirtualLoopClosureColor(Qt::magenta);
setNeighborMergedColor(QColor(255,170,0));
setRejectedLoopClosureColor(Qt::black);
setLandmarkColor(Qt::darkGreen);
setLocalPathColor(Qt::cyan);
setGlobalPathColor(Qt::darkMagenta);
setGTColor(Qt::gray);
setGPSColor(Qt::darkCyan);
setIntraSessionLoopColor(Qt::red);
setInterSessionLoopColor(Qt::green);
setIntraInterSessionColorsEnabled(false);
setGridMapVisible(true);
setGraphVisible(true);
setGlobalPathVisible(true);
@@ -1853,6 +1930,12 @@ void GraphViewer::contextMenuEvent(QContextMenuEvent * event)
QAction * aChangeNodeColor = menu.addAction(createIcon(_nodeColor), tr("Set node color..."));
QAction * aChangeNodeOdomCacheColor = menu.addAction(createIcon(_nodeOdomCacheColor), tr("Set node odom cache color..."));
QAction * aChangeCurrentGoalColor = menu.addAction(createIcon(_currentGoalColor), tr("Set current goal color..."));
QMenu * menuChangeHighlightingColors = menu.addMenu(tr("Set node highlighting colors..."));
QVector<QAction*> aChangeHighlightingColors(_highlightedNodes.size());
for(int i=0; i<_highlightedNodes.size(); ++i) {
aChangeHighlightingColors[i] = menuChangeHighlightingColors->addAction(createIcon(_highlightedNodes[i].first), tr("Color %1...").arg(i+1));
aChangeHighlightingColors[i]->setIconVisibleInMenu(true);
}
aChangeNodeColor->setIconVisibleInMenu(true);
aChangeNodeOdomCacheColor->setIconVisibleInMenu(true);
aChangeCurrentGoalColor->setIconVisibleInMenu(true);
@@ -2050,6 +2133,13 @@ void GraphViewer::contextMenuEvent(QContextMenuEvent * event)
QAction * aRestoreDefaults = menu.addAction(tr("Restore defaults"));
QAction * r = menu.exec(event->globalPos());
int aChangeHighlightingColorIndex = 0;
for(int i=1; i<aChangeHighlightingColors.size(); ++i)
{
if(r == aChangeHighlightingColors[i]) {
aChangeHighlightingColorIndex = i;
}
}
if(r == aScreenShot)
{
if(_root)
@@ -2217,6 +2307,7 @@ void GraphViewer::contextMenuEvent(QContextMenuEvent * event)
else if(r == aChangeNodeColor ||
r == aChangeNodeOdomCacheColor ||
r == aChangeCurrentGoalColor ||
r == aChangeHighlightingColors[aChangeHighlightingColorIndex] ||
r == aChangeNeighborColor ||
r == aChangeGlobalLoopColor ||
r == aChangeLocalLoopColor ||
@@ -2244,6 +2335,10 @@ void GraphViewer::contextMenuEvent(QContextMenuEvent * event)
{
color = _currentGoalColor;
}
else if(r == aChangeHighlightingColors[aChangeHighlightingColorIndex])
{
color = _highlightedNodes[aChangeHighlightingColorIndex].first;
}
else if(r == aChangeGlobalLoopColor)
{
color = _loopClosureColor;
@@ -2316,6 +2411,10 @@ void GraphViewer::contextMenuEvent(QContextMenuEvent * event)
{
this->setCurrentGoalColor(color);
}
else if(r == aChangeHighlightingColors[aChangeHighlightingColorIndex])
{
this->setHighlightColor(color, aChangeHighlightingColorIndex);
}
else if(r == aChangeGlobalLoopColor)
{
this->setGlobalLoopClosureColor(color);