NerianVisionPointCloudProvider.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 VisionX::ArmarXObjects::NerianVisionPointCloudProvider
17 * @author Julian Tusch <julian.tusch@student.kit.edu>
18 * @date 2025
19 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
20 * GNU General Public License
21 */
22
24
25#include <algorithm>
26#include <cstdint>
27#include <cstring>
28#include <exception>
29#include <limits>
30#include <memory>
31#include <string>
32#include <vector>
33
34#include <pcl/common/impl/io.hpp>
35#include <pcl/point_cloud.h>
36
37#include <opencv2/opencv.hpp> // IWYU pragma: keep
38
39#include <Ice/Config.h>
40#include <Ice/Current.h>
41#include <IceUtil/Time.h>
42
43
44// Nerian Vision SDK
45#include <visiontransfer/deviceenumeration.h>
46#include <visiontransfer/deviceinfo.h>
47#include <visiontransfer/deviceparameters.h>
48#include <visiontransfer/imageset.h>
49#include <visiontransfer/imagetransfer.h>
50#include <visiontransfer/reconstruct3d.h>
51
52// IVT
53#include <Image/ByteImage.h>
54#include <Image/ImageProcessor.h>
55
56// ArmarX
64
65// VisionX
68#include <VisionX/interface/components/Calibration.h>
69#include <VisionX/interface/core/DataTypes.h>
73
74namespace visionx
75{
76 void
82
83 void
85 {
86 using visiontransfer::DeviceEnumeration;
87 using visiontransfer::DeviceInfo;
88 using visiontransfer::DeviceParameters;
89
90 DeviceEnumeration deviceEnum;
91 DeviceEnumeration::DeviceList devices = deviceEnum.discoverDevices();
92 if (devices.size() == 0)
93 {
94 ARMARX_INFO << "No Nerian Vision devices detected! Shutting down...";
95 getArmarXManager()->asyncShutdown();
96 throw armarx::LocalException("No Nerian Vision devices detected!");
97 }
98
99 device = devices[0];
100 auto deviceModel = device.getModel();
101 setMetaInfo("model", new armarx::Variant(getNameForDeviceModel(deviceModel)));
102 setMetaInfo("serialNumber", new armarx::Variant(device.getSerialNumber()));
103 setMetaInfo("firmwareVersion", new armarx::Variant(device.getFirmwareVersion()));
104
105 ARMARX_INFO << "Using Nerian Vision device: " << device.toString();
106
107 if (deviceModel != DeviceInfo::DeviceModel::RUBY)
108 {
109 ARMARX_INFO << "Device model " << getNameForDeviceModel(deviceModel)
110 << " is currently not supported for this component! Shutting down...";
111 getArmarXManager()->asyncShutdown();
112 throw armarx::LocalException("Unsupported device model!");
113 }
114
116
117 std::unique_ptr<DeviceParameters> parameterPtr = nullptr;
118 try
119 {
120 parameterPtr = std::make_unique<DeviceParameters>(device);
121 }
122 catch (const std::exception& e)
123 {
124 ARMARX_INFO << "Failed to open device parameters."
125 << " (" << e.what() << ")";
126 return;
127 }
128
129 try
130 {
131 auto dim = parameterPtr->getParameter("RT_output_image_size").getTensorData();
132 dimensions.width = static_cast<int>(dim[0]);
133 dimensions.height = static_cast<int>(dim[1]);
134 }
135 catch (const std::exception& e)
136 {
137 ARMARX_INFO << "Failed to get dimension from device parameters. Using app property "
138 "`dimensions` as fallback."
139 << " (" << e.what() << ")";
140 dimensions = getProperty<visionx::ImageDimension>("dimensions").getValue();
141 }
142
143 ARMARX_DEBUG << "Using image dimensions: " << dimensions.width << "x" << dimensions.height;
144 setImageFormat(dimensions, visionx::eRgb, visionx::eBayerPatternRg);
145
146 ARMARX_DEBUG << "Configuring device.";
147
148 try
149 {
150 parameterPtr->setParameter("output_channel_left_enabled", false);
151 parameterPtr->setParameter("output_channel_right_enabled", false);
152 parameterPtr->setParameter("output_channel_color_enabled", true);
153 parameterPtr->setParameter("output_channel_color_mode", 0);
154 parameterPtr->setParameter("output_channel_disparity_enabled", true);
155 parameterPtr->setOperationMode(DeviceParameters::OperationMode::STEREO_MATCHING);
156 }
157 catch (const std::exception& e)
158 {
159 ARMARX_INFO << "Failed to configure device." << " (" << e.what() << ")";
161 << "Manual configuration of the device may be necessary. To do this, open the web "
162 "interface of the device at "
163 << device.getIpAddress() << " and verify/set the following settings: "
164 << "Output channels [color, disparity] are enabled and the channels [left, right] "
165 "are disabled. The Operation mode should be set to STEREO_MATCHING.";
166 }
167 }
168
169 void
171 {
172 pointcloud = std::make_unique<pcl::PointCloud<CloudPointType>>();
173 }
174
175 void
181
182 void
192
193 void
198
199 void
201 {
202 using visiontransfer::DeviceParameters;
203 using visiontransfer::ImageSet;
204 using visiontransfer::ImageTransfer;
205
206 visiontransfer::Reconstruct3D reconstructor;
207
208 ARMARX_DEBUG << "Started processing task.";
209
210 std::unique_ptr<ImageTransfer> imageTransferPtr = nullptr;
211 try
212 {
213 imageTransferPtr = std::make_unique<ImageTransfer>(device);
214 }
215 catch (const std::exception& e)
216 {
217 ARMARX_INFO << "Failed to open device."
218 << " (" << e.what() << ")";
219 return;
220 }
221
222 if (imageTransferPtr == nullptr)
223 {
224 ARMARX_INFO << "Failed to connnect to device!";
225 return;
226 }
227
228 while (not processingTask->isStopped())
229 {
230 ImageSet imageSet;
231 bool status = false;
232 try
233 {
234 // receiveImageSet() has an internal timout of 100ms
235 status = imageTransferPtr->receiveImageSet(imageSet);
236 }
237 catch (const std::exception& e)
238 {
239 ARMARX_INFO << deactivateSpam(30) << "Error receiving images from device."
240 << " (" << e.what() << ")";
241 continue;
242 }
243
244 if (!status)
245 {
246 ARMARX_DEBUG << deactivateSpam(30) << "Did not get frame until timeout of 100ms.";
247 continue;
248 }
249
251
252 int numImages = getNumberImages();
253 if (numImages != imageSet.getNumberOfImages())
254 {
255 ARMARX_INFO << deactivateSpam(30) << "Expected " << numImages << " images, but got "
256 << imageSet.getNumberOfImages()
257 << " images. Please verify the device and component configurations.";
258 continue;
259 }
260
261 if (!imageSet.hasImageType(ImageSet::ImageType::IMAGE_COLOR) ||
262 !imageSet.hasImageType(ImageSet::ImageType::IMAGE_DISPARITY))
263 {
265 << "Received image set does not contain the expected image types "
266 "(color, disparity). Please verify the device and component "
267 "configurations, then restart this component.";
268 continue;
269 }
270
272
273 int index_color = imageSet.getIndexOf(ImageSet::ImageType::IMAGE_COLOR);
274 if (imageSet.getPixelFormat(index_color) != ImageSet::ImageFormat::FORMAT_8_BIT_RGB)
275 {
277 << deactivateSpam(30)
278 << "Received unexpected image format for color image. Expected 8-bit RGB.";
279 continue;
280 }
281
282 int index_disparity = imageSet.getIndexOf(ImageSet::ImageType::IMAGE_DISPARITY);
283 if (imageSet.getPixelFormat(index_disparity) !=
284 ImageSet::ImageFormat::FORMAT_12_BIT_MONO)
285 {
287 << "Received unexpected image format for disparity image. Expected "
288 "12-bit Mono.";
289 continue;
290 }
291
292 std::vector<CByteImage*> imageBuffers(2);
293
294 // Convert color image
295 {
296 cv::Mat bgr_image;
297 imageSet.toOpenCVImage(index_color, bgr_image, true);
298 imageBuffers[0] = new CByteImage();
300 }
301
303
304 // Convert disparity image
305 {
306 const int dw = imageSet.getWidth();
307 const int dh = imageSet.getHeight();
308
309 imageBuffers[1] = new CByteImage(dw, dh, CByteImage::ImageType::eRGB24);
310 CByteImage* depth_image = imageBuffers[1];
311
312 auto* result_depth_buffer = depth_image->pixels;
313 const auto* pixelData = imageSet.getPixelData(index_disparity);
314 const auto* depth_buffer = reinterpret_cast<const uint16_t*>(pixelData);
315
316 int index = 0;
317 int index_2 = 0;
318 for (int y = 0; y < dh; ++y)
319 {
320 for (int x = 0; x < dw; ++x)
321 {
322 uint16_t depth_value = depth_buffer[index_2];
324 result_depth_buffer[index],
325 result_depth_buffer[index + 1],
326 result_depth_buffer[index + 2]);
327 index += 3;
328 index_2 += 1;
329 }
330 }
331 }
332
334
335 // Generate Pointcloud
336 {
337 ARMARX_CHECK_EXPRESSION(pointcloud);
338
339 // the coordinates are in meters (!)
340 auto temp = reconstructor.createXYZRGBCloud(imageSet, "");
341
342 pointcloud->header.stamp = temp->header.stamp;
343 pointcloud->is_dense = temp->is_dense;
344 pointcloud->width = temp->width;
345 pointcloud->height = temp->height;
346 pointcloud->points.resize(pointcloud->width * pointcloud->height);
347 pcl::detail::copyPointCloudMemcpy(*temp, *pointcloud);
348
349 float max_depth = getProperty<float>("MaxDepth").getValue(); // [mm]
350 for (auto& p : pointcloud->points)
351 {
352 // for some reason, red and blue channels are swapped here
353 auto red = p.r;
354 p.r = p.b;
355 p.b = red;
356
357 // convert from [m] to [mm]
358 p.x *= 1000.0F;
359 p.y *= 1000.0F;
360 p.z *= 1000.0F;
361
362 // apply max depth filter
363 float z = p.z;
364 if (z <= max_depth and z != 0)
365 {
366 p.z = z;
367 }
368 else
369 {
370 p.z = std::numeric_limits<float>::quiet_NaN();
371 }
372 }
373 }
374
376
377 // provide results
378 {
379 int t_s = 0;
380 int t_us = 0;
381 imageSet.getTimestamp(t_s, t_us);
382 const IceUtil::Time timestamp =
383 IceUtil::Time::seconds(t_s) + IceUtil::Time::microSeconds(t_us);
384
386
388
389 // FIXME:
390 // the check with virtualRobotReaderPlugin->get().synchronizeRobot(robot, timestamp)
391 // in PointCloudToArviz fails, and I suspect the timestamp might be the issue
392 providePointCloud(pointcloud);
393 }
394 }
395
396 ARMARX_DEBUG << "Stopped processing task.";
397 }
398
399 void
404
405 void
407 {
408 ARMARX_DEBUG << "Disconnecting " << getName();
409
410 // Stop task
411 {
412 ARMARX_DEBUG << "Stopping processing thread...";
413 processingTask->stop(false);
414 ARMARX_DEBUG << "Waiting for processing thread to stop...";
415 processingTask->waitForStop();
416 ARMARX_DEBUG << "Processing thread stopped";
417 }
418 }
419
420 void
426
427 void
431
432 void
436
437 visionx::StereoCalibration
439 {
440 StereoCalibration stereo_calibration;
441
442 // TODO: find out how to retrieve the calibration data from nerian vision devices
443 // stereo_calibration.calibrationLeft = stereo_calibration.calibrationRight = ...
444
445 // the images are rectified, so the distortion params are 0
446 auto& colorDistortionParams = stereo_calibration.calibrationLeft.cameraParam.distortion;
447 std::fill(colorDistortionParams.begin(), colorDistortionParams.end(), 0);
448 auto& depthDistortionParams = stereo_calibration.calibrationRight.cameraParam.distortion;
449 std::fill(depthDistortionParams.begin(), depthDistortionParams.end(), 0);
450
451 stereo_calibration.rectificationHomographyLeft =
452 stereo_calibration.rectificationHomographyRight =
453 visionx::tools::convertEigenMatToVisionX(Eigen::Matrix3f::Identity());
454
455 return stereo_calibration;
456 }
457
458 std::string
460 {
461 return {};
462 }
463
464 bool
466 {
467 // Ruby User Manual, Chapter 9.6 - Output Channels
468 // "If the operation mode is set to stereo matching [...],
469 // then the image data of all output channels are rectified."
470 return true;
471 }
472
473 void
475 {
476 }
477
478 void
480 {
481 }
482
483 void
485 const Ice::Current& /*c*/)
486 {
487 }
488
489 void
491 const Ice::Current& /*c*/)
492 {
493 }
494
495 bool
497 {
498 return true;
499 }
500
506} // namespace visionx
std::string timestamp()
uint8_t index
SpamFilterDataPtr deactivateSpam(SpamFilterDataPtr const &spamFilter, float deactivationDurationSec, const std::string &identifier, bool deactivate)
Definition Logging.cpp:75
std::string getConfigIdentifier()
Retrieve config identifier for this component as set in constructor.
Definition Component.cpp:90
Property< PropertyType > getProperty(const std::string &name)
std::string getName() const
Retrieve name of object.
void setMetaInfo(const std::string &id, const VariantBasePtr &value)
Allows to set meta information that can be queried live via Ice interface on the ArmarXManager.
ArmarXManagerPtr getArmarXManager() const
Returns the ArmarX manager used to add and remove components.
IceUtil::Handle< RunningTask< T > > pointer_type
Shared pointer type for convenience.
The Variant class is described here: Variants.
Definition Variant.h:224
void onInitComponent() override
void onDisconnectComponent() override
Hook for subclass.
void setImageFormat(ImageDimension imageDimension, ImageType imageType, BayerPatternType bayerPatternType=visionx::eBayerPatternRg)
Sets the image basic format data.
int getNumberImages(const Ice::Current &c=Ice::emptyCurrent) override
Retrieve number of images handled by this provider.
void provideImages(void **inputBuffers, const IceUtil::Time &imageTimestamp=IceUtil::Time())
send images raw.
void onConnectComponent() override
void setNumberImages(int numberImages)
Sets the number of images on each capture.
void ** imageBuffers
Image buffer memory.
void onExitComponent() override
void onInitComponent() override
Pure virtual hook for the subclass.
bool isCaptureEnabled(const Ice::Current &c) override
void changeFrameRate(Ice::Float framesPerSecond, const Ice::Current &c) override
void onConnectPointCloudProvider() override
This is called when the Component::onConnectComponent() setup is called.
armarx::PropertyDefinitionsPtr createPropertyDefinitions() override
void onConnectImageProvider() override
This is called when the Component::onConnectComponent() setup is called.
void onInitPointCloudProvider() override
This is called when the Component::onInitComponent() is called.
std::string getReferenceFrame(const Ice::Current &c) override
void startCaptureForNumFrames(int numFrames, const Ice::Current &c) override
void onConnectComponent() override
Pure virtual hook for the subclass.
static std::string getNameForDeviceModel(const visiontransfer::DeviceInfo::DeviceModel &model)
visionx::StereoCalibration getStereoCalibration(const Ice::Current &c) override
bool getImagesAreUndistorted(const ::Ice::Current &c) override
void onInitImageProvider() override
This is called when the Component::onInitComponent() is called.
void onExitImageProvider() override
This is called when the Component::onExitComponent() setup is called.
void onExitPointCloudProvider() override
This is called when the Component::onExitComponent() setup is called.
MetaPointCloudFormatPtr getPointCloudFormat(const Ice::Current &c=Ice::emptyCurrent) override
Returns the point cloud format info struct via Ice.
void providePointCloud(PointCloudPtrT pointCloudPtr)
offer the new point cloud.
#define ARMARX_CHECK_EXPRESSION(expression)
This macro evaluates the expression and if it turns out to be false it will throw an ExpressionExcept...
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:184
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:193
This file offers overloads of toIce() and fromIce() functions for STL container types.
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
void convert(const CByteImage &in, cv::Mat &out)
Converts an IVT CByteImage to OpenCV's BGR Mat.
Definition helper.cpp:40
void depthValueToRGB(unsigned int depthInMM, unsigned char &r, unsigned char &g, unsigned char &b, bool noiseResistant=false)
Definition ImageUtil.h:183
visionx::types::Mat convertEigenMatToVisionX(Eigen::MatrixXf m)
ArmarX headers.
#define ARMARX_TRACE
Definition trace.h:77