0.18: Camera calibration and LaserScan Info refactoring (#324)

* Saving full camera calibration in database, added angle min/max/inc to LaserScan.

* Updated laserscan info save/load in db

* Database: added Tag table, added env_sensors field to Node

* fixed serialization/deserialization of stereo camera model

* fixed multi-calibration db saving

* fixed rebase errors

* Tango: Added saving environmental sensors option

* Memory: Save env sensors

* Tango: fixed env sensor ids

* DBViewer: show env sensors values

* DBViewer: added calibration details on tooltip

* increased package version to 0.18.0

* Fixed LaserScan copies when angleIncrement is valid

* fixed build error without OctoMap dependency
This commit is contained in:
matlabbe
2018-10-23 14:35:14 -04:00
committed by GitHub
parent 8701ae6de0
commit 8e99291e13
51 changed files with 2063 additions and 491 deletions

View File

@@ -157,8 +157,10 @@ private:
QLabel * labelMapId,
QLabel * labelPose,
QLabel * labelVelocity,
QLabel * labeCalib,
QLabel * labelCalib,
QLabel * labelScan,
QLabel * labelGps,
QLabel * labelSensors,
bool updateConstraintView);
void updateStereo(const SensorData * data);
void updateWordsMatching();

View File

@@ -34,7 +34,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <QtCore/QMap>
#include <QtCore/QSettings>
#include <rtabmap/core/Link.h>
#include <rtabmap/core/GeodeticCoords.h>
#include <rtabmap/core/GPS.h>
#include <opencv2/opencv.hpp>
#include <map>
#include <vector>

View File

@@ -1178,6 +1178,7 @@ void DatabaseViewer::exportDatabase()
std::map<int, double> stamps;
std::map<int, Transform> groundTruths;
std::map<int, GPS> gpsValues;
std::map<int, EnvSensors> sensorsValues;
for(int i=0; i<ids_.size(); i+=1+framesIgnored)
{
Transform odomPose, groundTruth;
@@ -1187,7 +1188,8 @@ void DatabaseViewer::exportDatabase()
double stamp = 0;
std::vector<float> velocity;
GPS gps;
if(dbDriver_->getNodeInfo(ids_[i], odomPose, mapId, weight, label, stamp, groundTruth, velocity, gps))
EnvSensors sensors;
if(dbDriver_->getNodeInfo(ids_[i], odomPose, mapId, weight, label, stamp, groundTruth, velocity, gps, sensors))
{
if(frameRate == 0 ||
previousStamp == 0 ||
@@ -1211,6 +1213,10 @@ void DatabaseViewer::exportDatabase()
{
gpsValues.insert(std::make_pair(ids_[i], gps));
}
if(sensors.size())
{
sensorsValues.insert(std::make_pair(ids_[i], sensors));
}
}
}
if(sessionExported >= 0 && mapId > sessionExported)
@@ -1289,6 +1295,10 @@ void DatabaseViewer::exportDatabase()
{
sensorData.setGPS(gpsValues.at(id));
}
if(sensorsValues.find(id)!=sensorsValues.end())
{
sensorData.setEnvSensors(sensorsValues.at(id));
}
recorder.addData(sensorData, dialog.isOdomExported()?poses.at(id):Transform(), covariance);
@@ -1527,7 +1537,8 @@ void DatabaseViewer::updateIds()
int mapId;
std::vector<float> v;
GPS gps;
dbDriver_->getNodeInfo(ids_[i], p, mapId, w, l, s, g, v, gps);
EnvSensors sensors;
dbDriver_->getNodeInfo(ids_[i], p, mapId, w, l, s, g, v, gps, sensors);
mapIds_.insert(std::make_pair(ids_[i], mapId));
weights_.insert(std::make_pair(ids_[i], w));
if(wmStates.find(ids_[i]) != wmStates.end())
@@ -2059,7 +2070,8 @@ void DatabaseViewer::exportPoses(int format)
int mapId;
std::vector<float> v;
GPS gps;
dbDriver_->getNodeInfo(iter->first, p, mapId, w, l, stamp, g, v, gps);
EnvSensors sensors;
dbDriver_->getNodeInfo(iter->first, p, mapId, w, l, stamp, g, v, gps, sensors);
values.insert(std::make_pair(iter->first, GPS(stamp, coord.longitude(), coord.latitude(), coord.altitude(), 0, 0)));
}
@@ -2265,7 +2277,8 @@ void DatabaseViewer::exportPoses(int format)
int mapId;
std::vector<float> v;
GPS gps;
if(dbDriver_->getNodeInfo(iter->first, p, mapId, w, l, stamp, g, v, gps))
EnvSensors sensors;
if(dbDriver_->getNodeInfo(iter->first, p, mapId, w, l, stamp, g, v, gps, sensors))
{
stamps.insert(std::make_pair(iter->first, stamp));
}
@@ -2886,7 +2899,8 @@ void DatabaseViewer::regenerateLocalMaps()
QString msg;
std::vector<float> velocity;
GPS gps;
if(dbDriver_->getNodeInfo(data.id(), odomPose, mapId, weight, label, stamp, groundTruth, velocity, gps))
EnvSensors sensors;
if(dbDriver_->getNodeInfo(data.id(), odomPose, mapId, weight, label, stamp, groundTruth, velocity, gps, sensors))
{
Signature s = data;
s.setPose(odomPose);
@@ -3009,7 +3023,8 @@ void DatabaseViewer::regenerateCurrentLocalMaps()
QString msg;
std::vector<float> velocity;
GPS gps;
if(dbDriver_->getNodeInfo(data.id(), odomPose, mapId, weight, label, stamp, groundTruth, velocity, gps))
EnvSensors sensors;
if(dbDriver_->getNodeInfo(data.id(), odomPose, mapId, weight, label, stamp, groundTruth, velocity, gps, sensors))
{
Signature s = data;
s.setPose(odomPose);
@@ -3444,7 +3459,9 @@ void DatabaseViewer::sliderAValueChanged(int value)
ui_->label_poseA,
ui_->label_velA,
ui_->label_calibA,
ui_->label_scanA,
ui_->label_gpsA,
ui_->label_sensorsA,
true);
}
@@ -3463,7 +3480,9 @@ void DatabaseViewer::sliderBValueChanged(int value)
ui_->label_poseB,
ui_->label_velB,
ui_->label_calibB,
ui_->label_scanB,
ui_->label_gpsB,
ui_->label_sensorsB,
true);
}
@@ -3480,7 +3499,9 @@ void DatabaseViewer::update(int value,
QLabel * labelPose,
QLabel * labelVelocity,
QLabel * labelCalib,
QLabel * labelScan,
QLabel * labelGps,
QLabel * labelSensors,
bool updateConstraintView)
{
UTimer timer;
@@ -3494,7 +3515,9 @@ void DatabaseViewer::update(int value,
labelVelocity->clear();
stamp->clear();
labelCalib->clear();
labelScan ->clear();
labelGps->clear();
labelSensors->clear();
QRectF rect;
if(value >= 0 && value < ids_.size())
{
@@ -3545,7 +3568,8 @@ void DatabaseViewer::update(int value,
double s;
std::vector<float> v;
GPS gps;
dbDriver_->getNodeInfo(id, odomPose, mapId, w, l, s, g, v, gps);
EnvSensors sensors;
dbDriver_->getNodeInfo(id, odomPose, mapId, w, l, s, g, v, gps, sensors);
weight->setNum(w);
label->setText(l.c_str());
@@ -3566,8 +3590,56 @@ void DatabaseViewer::update(int value,
labelGps->setText(QString("stamp=%1 longitude=%2 latitude=%3 altitude=%4m error=%5m bearing=%6deg").arg(QString::number(gps.stamp(), 'f')).arg(gps.longitude()).arg(gps.latitude()).arg(gps.altitude()).arg(gps.error()).arg(gps.bearing()));
labelGps->setToolTip(QDateTime::fromMSecsSinceEpoch(gps.stamp()*1000.0).toString("dd.MM.yyyy hh:mm:ss.zzz"));
}
if(sensors.size())
{
QString sensorsStr;
QString tooltipStr;
for(EnvSensors::iterator iter=sensors.begin(); iter!=sensors.end(); ++iter)
{
if(iter != sensors.begin())
{
sensorsStr += " | ";
tooltipStr += " | ";
}
if(iter->first == EnvSensor::kWifiSignalStrength)
{
sensorsStr += uFormat("%.1f dbm", iter->second.value()).c_str();
tooltipStr += "Wifi signal strength";
}
else if(iter->first == EnvSensor::kAmbientTemperature)
{
sensorsStr += uFormat("%.1f \u00B0C", iter->second.value()).c_str();
tooltipStr += "Ambient Temperature";
}
else if(iter->first == EnvSensor::kAmbientAirPressure)
{
sensorsStr += uFormat("%.1f hPa", iter->second.value()).c_str();
tooltipStr += "Ambient Air Pressure";
}
else if(iter->first == EnvSensor::kAmbientLight)
{
sensorsStr += uFormat("%.0f lx", iter->second.value()).c_str();
tooltipStr += "Ambient Light";
}
else if(iter->first == EnvSensor::kAmbientRelativeHumidity)
{
sensorsStr += uFormat("%.0f %%", iter->second.value()).c_str();
tooltipStr += "Ambient Relative Humidity";
}
else
{
sensorsStr += uFormat("%.2f", iter->second.value()).c_str();
tooltipStr += QString("Type %1").arg((int)iter->first);
}
}
labelSensors->setText(sensorsStr);
labelSensors->setToolTip(tooltipStr);
}
if(data.cameraModels().size() || data.stereoCameraModel().isValidForProjection())
{
std::stringstream calibrationDetails;
if(data.cameraModels().size())
{
if(!data.depthRaw().empty() && data.depthRaw().cols!=data.imageRaw().cols && data.imageRaw().cols)
@@ -3596,6 +3668,17 @@ void DatabaseViewer::update(int value,
.arg(data.cameraModels()[0].cy())
.arg(data.cameraModels()[0].localTransform().prettyPrint().c_str()));
}
for(unsigned int i=0; i<data.cameraModels().size();++i)
{
if(i!=0) calibrationDetails << std::endl;
calibrationDetails << "Id: " << i << " Size=" << data.cameraModels()[i].imageWidth() << "x" << data.cameraModels()[i].imageWidth() << std::endl;
if( data.cameraModels()[i].K_raw().total()) calibrationDetails << "K=" << data.cameraModels()[i].K_raw() << std::endl;
if( data.cameraModels()[i].D_raw().total()) calibrationDetails << "D=" << data.cameraModels()[i].D_raw() << std::endl;
if( data.cameraModels()[i].R().total()) calibrationDetails << "R=" << data.cameraModels()[i].R() << std::endl;
if( data.cameraModels()[i].P().total()) calibrationDetails << "P=" << data.cameraModels()[i].P() << std::endl;
}
}
else
{
@@ -3609,7 +3692,25 @@ void DatabaseViewer::update(int value,
.arg(data.stereoCameraModel().left().cy())
.arg(data.stereoCameraModel().baseline())
.arg(data.stereoCameraModel().localTransform().prettyPrint().c_str()));
calibrationDetails << "Left:" << " Size=" << data.stereoCameraModel().left().imageWidth() << "x" << data.stereoCameraModel().left().imageWidth() << std::endl;
if( data.stereoCameraModel().left().K_raw().total()) calibrationDetails << "K=" << data.stereoCameraModel().left().K_raw() << std::endl;
if( data.stereoCameraModel().left().D_raw().total()) calibrationDetails << "D=" << data.stereoCameraModel().left().D_raw() << std::endl;
if( data.stereoCameraModel().left().R().total()) calibrationDetails << "R=" << data.stereoCameraModel().left().R() << std::endl;
if( data.stereoCameraModel().left().P().total()) calibrationDetails << "P=" << data.stereoCameraModel().left().P() << std::endl;
calibrationDetails << std::endl;
calibrationDetails << "Right:" << " Size=" << data.stereoCameraModel().right().imageWidth() << "x" << data.stereoCameraModel().right().imageWidth() << std::endl;
if( data.stereoCameraModel().right().K_raw().total()) calibrationDetails << "K=" << data.stereoCameraModel().right().K_raw() << std::endl;
if( data.stereoCameraModel().right().D_raw().total()) calibrationDetails << "D=" << data.stereoCameraModel().right().D_raw() << std::endl;
if( data.stereoCameraModel().right().R().total()) calibrationDetails << "R=" << data.stereoCameraModel().right().R() << std::endl;
if( data.stereoCameraModel().right().P().total()) calibrationDetails << "P=" << data.stereoCameraModel().right().P() << std::endl;
calibrationDetails << std::endl;
if( data.stereoCameraModel().R().total()) calibrationDetails << "R=" << data.stereoCameraModel().R() << std::endl;
if( data.stereoCameraModel().T().total()) calibrationDetails << "T=" << data.stereoCameraModel().T() << std::endl;
if( data.stereoCameraModel().F().total()) calibrationDetails << "F=" << data.stereoCameraModel().F() << std::endl;
if( data.stereoCameraModel().E().total()) calibrationDetails << "E=" << data.stereoCameraModel().E() << std::endl;
}
labelCalib->setToolTip(calibrationDetails.str().c_str());
}
else
@@ -3617,6 +3718,23 @@ void DatabaseViewer::update(int value,
labelCalib->setText("NA");
}
if(data.laserScanRaw().size())
{
labelScan->setText(tr("Format=%1 Points=%2 [max=%3] Range=[%4->%5 m] Angle=[%6->%7 rad inc=%8] Has [Color=%9 2D=%10 Normals=%11 Intensity=%12]")
.arg(data.laserScanRaw().format())
.arg(data.laserScanRaw().size())
.arg(data.laserScanRaw().maxPoints())
.arg(data.laserScanRaw().rangeMin())
.arg(data.laserScanRaw().rangeMax())
.arg(data.laserScanRaw().angleMin())
.arg(data.laserScanRaw().angleMax())
.arg(data.laserScanRaw().angleIncrement())
.arg(data.laserScanRaw().hasRGB()?1:0)
.arg(data.laserScanRaw().is2d()?1:0)
.arg(data.laserScanRaw().hasNormals()?1:0)
.arg(data.laserScanRaw().hasIntensity()?1:0));
}
//stereo
if(!data.depthOrRightRaw().empty() && data.depthOrRightRaw().type() == CV_8UC1)
{
@@ -4576,7 +4694,9 @@ void DatabaseViewer::updateConstraintView(
ui_->label_poseA,
ui_->label_velA,
ui_->label_calibA,
ui_->label_scanA,
ui_->label_gpsA,
ui_->label_sensorsA,
false); // don't update constraints view!
this->update(idToIndex_.value(link.to()),
ui_->label_indexB,
@@ -4591,7 +4711,9 @@ void DatabaseViewer::updateConstraintView(
ui_->label_poseB,
ui_->label_velB,
ui_->label_calibB,
ui_->label_scanB,
ui_->label_gpsB,
ui_->label_sensorsB,
false); // don't update constraints view!
}
@@ -4633,7 +4755,8 @@ void DatabaseViewer::updateConstraintView(
Transform p,g;
std::vector<float> v;
GPS gps;
dbDriver_->getNodeInfo(link.from(), p, m, w, l, s, g, v, gps);
EnvSensors sensors;
dbDriver_->getNodeInfo(link.from(), p, m, w, l, s, g, v, gps, sensors);
if(!p.isNull())
{
// keep just the z and roll/pitch rotation
@@ -6054,7 +6177,7 @@ void DatabaseViewer::refineConstraint(int from, int to, bool silent)
assembledData.setLaserScanRaw(LaserScan(
assembledScan,
fromScan.maxPoints()?fromScan.maxPoints():maxPoints,
fromScan.maxRange(),
fromScan.rangeMax(),
fromScan.format(),
fromScan.is2d()?Transform(0,0,fromScan.localTransform().z(),0,0,0):Transform::getIdentity()));

View File

@@ -2575,7 +2575,8 @@ bool ExportCloudsDialog::getExportedClouds(
std::string l;
double s;
GPS gps;
_dbDriver->getNodeInfo(jter->first, p, m, w, l, s, gt, velocity, gps);
EnvSensors sensors;
_dbDriver->getNodeInfo(jter->first, p, m, w, l, s, gt, velocity, gps, sensors);
}
}
cv::Size imageSize = img.size();
@@ -2608,7 +2609,8 @@ bool ExportCloudsDialog::getExportedClouds(
std::string l;
double s;
GPS gps;
_dbDriver->getNodeInfo(jter->first, p, m, w, l, s, gt, velocity, gps);
EnvSensors sensors;
_dbDriver->getNodeInfo(jter->first, p, m, w, l, s, gt, velocity, gps, sensors);
}
}
}

View File

@@ -2004,7 +2004,7 @@ void MainWindow::processStats(const rtabmap::Statistics & stat)
LaserScan(
cv::Mat(),
signature.sensorData().laserScanRaw().maxPoints(),
signature.sensorData().laserScanRaw().maxRange(),
signature.sensorData().laserScanRaw().rangeMax(),
signature.sensorData().laserScanRaw().format(),
signature.sensorData().laserScanRaw().localTransform()));
s.sensorData().clearOccupancyGridRaw();
@@ -3359,7 +3359,7 @@ void MainWindow::createAndAddScanToMap(int nodeId, const Transform & pose, int m
added = _cloudViewer->addCloud(scanName, cloudRGBWithNormals, pose, color);
if(added && nodeId > 0)
{
scan = LaserScan(util3d::laserScanFromPointCloud(*cloudRGBWithNormals, scan.localTransform().inverse()), scan.maxPoints(), scan.maxRange(), LaserScan::kXYZRGBNormal, scan.localTransform());
scan = LaserScan(util3d::laserScanFromPointCloud(*cloudRGBWithNormals, scan.localTransform().inverse()), scan.maxPoints(), scan.rangeMax(), LaserScan::kXYZRGBNormal, scan.localTransform());
}
}
else if(cloudIWithNormals.get())
@@ -3369,11 +3369,11 @@ void MainWindow::createAndAddScanToMap(int nodeId, const Transform & pose, int m
{
if(scan.is2d())
{
scan = LaserScan(util3d::laserScan2dFromPointCloud(*cloudIWithNormals, scan.localTransform().inverse()), scan.maxPoints(), scan.maxRange(), LaserScan::kXYINormal, scan.localTransform());
scan = LaserScan(util3d::laserScan2dFromPointCloud(*cloudIWithNormals, scan.localTransform().inverse()), scan.maxPoints(), scan.rangeMax(), LaserScan::kXYINormal, scan.localTransform());
}
else
{
scan = LaserScan(util3d::laserScanFromPointCloud(*cloudIWithNormals, scan.localTransform().inverse()), scan.maxPoints(), scan.maxRange(), LaserScan::kXYZINormal, scan.localTransform());
scan = LaserScan(util3d::laserScanFromPointCloud(*cloudIWithNormals, scan.localTransform().inverse()), scan.maxPoints(), scan.rangeMax(), LaserScan::kXYZINormal, scan.localTransform());
}
}
}
@@ -3384,11 +3384,11 @@ void MainWindow::createAndAddScanToMap(int nodeId, const Transform & pose, int m
{
if(scan.is2d())
{
scan = LaserScan(util3d::laserScan2dFromPointCloud(*cloudWithNormals, scan.localTransform().inverse()), scan.maxPoints(), scan.maxRange(), LaserScan::kXYNormal, scan.localTransform());
scan = LaserScan(util3d::laserScan2dFromPointCloud(*cloudWithNormals, scan.localTransform().inverse()), scan.maxPoints(), scan.rangeMax(), LaserScan::kXYNormal, scan.localTransform());
}
else
{
scan = LaserScan(util3d::laserScanFromPointCloud(*cloudWithNormals, scan.localTransform().inverse()), scan.maxPoints(), scan.maxRange(), LaserScan::kXYZNormal, scan.localTransform());
scan = LaserScan(util3d::laserScanFromPointCloud(*cloudWithNormals, scan.localTransform().inverse()), scan.maxPoints(), scan.rangeMax(), LaserScan::kXYZNormal, scan.localTransform());
}
}
}
@@ -3397,7 +3397,7 @@ void MainWindow::createAndAddScanToMap(int nodeId, const Transform & pose, int m
added = _cloudViewer->addCloud(scanName, cloudRGB, pose, color);
if(added && nodeId > 0)
{
scan = LaserScan(util3d::laserScanFromPointCloud(*cloudRGB, scan.localTransform().inverse()), scan.maxPoints(), scan.maxRange(), LaserScan::kXYZRGB, scan.localTransform());
scan = LaserScan(util3d::laserScanFromPointCloud(*cloudRGB, scan.localTransform().inverse()), scan.maxPoints(), scan.rangeMax(), LaserScan::kXYZRGB, scan.localTransform());
}
}
else if(cloudI.get())
@@ -3407,11 +3407,11 @@ void MainWindow::createAndAddScanToMap(int nodeId, const Transform & pose, int m
{
if(scan.is2d())
{
scan = LaserScan(util3d::laserScan2dFromPointCloud(*cloudI, scan.localTransform().inverse()), scan.maxPoints(), scan.maxRange(), LaserScan::kXYI, scan.localTransform());
scan = LaserScan(util3d::laserScan2dFromPointCloud(*cloudI, scan.localTransform().inverse()), scan.maxPoints(), scan.rangeMax(), LaserScan::kXYI, scan.localTransform());
}
else
{
scan = LaserScan(util3d::laserScanFromPointCloud(*cloudI, scan.localTransform().inverse()), scan.maxPoints(), scan.maxRange(), LaserScan::kXYZI, scan.localTransform());
scan = LaserScan(util3d::laserScanFromPointCloud(*cloudI, scan.localTransform().inverse()), scan.maxPoints(), scan.rangeMax(), LaserScan::kXYZI, scan.localTransform());
}
}
}
@@ -3423,11 +3423,11 @@ void MainWindow::createAndAddScanToMap(int nodeId, const Transform & pose, int m
{
if(scan.is2d())
{
scan = LaserScan(util3d::laserScan2dFromPointCloud(*cloud, scan.localTransform().inverse()), scan.maxPoints(), scan.maxRange(), LaserScan::kXY, scan.localTransform());
scan = LaserScan(util3d::laserScan2dFromPointCloud(*cloud, scan.localTransform().inverse()), scan.maxPoints(), scan.rangeMax(), LaserScan::kXY, scan.localTransform());
}
else
{
scan = LaserScan(util3d::laserScanFromPointCloud(*cloud, scan.localTransform().inverse()), scan.maxPoints(), scan.maxRange(), LaserScan::kXYZ, scan.localTransform());
scan = LaserScan(util3d::laserScanFromPointCloud(*cloud, scan.localTransform().inverse()), scan.maxPoints(), scan.rangeMax(), LaserScan::kXYZ, scan.localTransform());
}
}
}

View File

@@ -61,8 +61,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>301</width>
<height>242</height>
<width>287</width>
<height>288</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout" columnstretch="0,1">
@@ -202,14 +202,14 @@
</property>
</widget>
</item>
<item row="9" column="0">
<item row="10" column="0">
<widget class="QLabel" name="label_childrenA_16">
<property name="text">
<string>GPS</string>
</property>
</widget>
</item>
<item row="9" column="1">
<item row="10" column="1">
<widget class="QLabel" name="label_gpsA">
<property name="text">
<string/>
@@ -236,6 +236,40 @@
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QLabel" name="label_childrenA_20">
<property name="text">
<string>Sensors</string>
</property>
</widget>
</item>
<item row="11" column="1">
<widget class="QLabel" name="label_sensorsA">
<property name="text">
<string/>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_childrenA_22">
<property name="text">
<string>Scan</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QLabel" name="label_scanA">
<property name="text">
<string/>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
@@ -253,8 +287,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>301</width>
<height>242</height>
<width>287</width>
<height>288</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_2" columnstretch="0,1">
@@ -302,14 +336,14 @@
</property>
</widget>
</item>
<item row="9" column="0">
<item row="10" column="0">
<widget class="QLabel" name="label_childrenA_17">
<property name="text">
<string>GPS</string>
</property>
</widget>
</item>
<item row="9" column="1">
<item row="10" column="1">
<widget class="QLabel" name="label_gpsB">
<property name="text">
<string/>
@@ -428,6 +462,40 @@
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QLabel" name="label_childrenA_21">
<property name="text">
<string>Sensors</string>
</property>
</widget>
</item>
<item row="11" column="1">
<widget class="QLabel" name="label_sensorsB">
<property name="text">
<string/>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_childrenA_23">
<property name="text">
<string>Scan</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QLabel" name="label_scanB">
<property name="text">
<string/>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
</widget>