ImageProvider.cpp
Go to the documentation of this file.
1/*
2 * This file is part of ArmarX.
3 *
4 * Copyright (C) 2011-2016, High Performance Humanoid Technologies (H2T), Karlsruhe Institute of Technology (KIT), all rights reserved.
5 *
6 * ArmarX is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
9 *
10 * ArmarX is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * @package VisionX::Core
19 * @author Kai Welke (kai dot welke at kit dot edu)
20 * @date 2011
21 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
22 * GNU General Public License
23 */
24
25
26#include "ImageProvider.h"
27
28
29// STD/STL
30#include <iostream>
31#include <string>
32
33// OpenCV
34#include <opencv2/opencv.hpp>
35
36// Simox
37#include <SimoxUtility/algorithm.h>
38
39// ArmarX
43
47
48namespace visionx
49{
50
51 // ================================================================== //
52 // == ImageProvider ice interface =================================== //
53 // ================================================================== //
54 armarx::Blob
55 ImageProvider::getImages(const Ice::Current&)
56 {
57 if (numberImages == 0)
58 {
59 return armarx::Blob();
60 }
61
62 return sharedMemoryProvider->getData();
63 }
64
65 armarx::Blob
66 ImageProvider::getImagesAndMetaInfo(armarx::MetaInfoSizeBasePtr& info, const Ice::Current&)
67 {
68 if (numberImages == 0)
69 {
70 return armarx::Blob();
71 }
72 return sharedMemoryProvider->getData(info);
73 }
74
75 ImageFormatInfo
76 ImageProvider::getImageFormat(const Ice::Current&)
77 {
78 return imageFormat;
79 }
80
81 int
82 ImageProvider::getNumberImages(const Ice::Current&)
83 {
84 return numberImages;
85 }
86
87 // ================================================================== //
88 // == Component implementation =============================== //
89 // ================================================================== //
90 void
92 {
94 // init members
95 exiting = false;
96
97 // default image format (640x480, bayerpattern).
98 // call from within init to change!
99 setImageFormat(ImageDimension(640, 480), eBayerPattern, eBayerPatternRg);
101
102 // call setup of image provider implementation to setup image size
104 }
105
106 void
108 {
109 ARMARX_INFO << "onConnectComponent " << getName();
111 // init shared memory
112 int imageSize = getImageFormat().dimension.width * getImageFormat().dimension.height *
113 getImageFormat().bytesPerPixel;
114
115 if (numberImages != 0)
116 {
117 armarx::MetaInfoSizeBasePtr info(new armarx::MetaInfoSizeBase(
118 getNumberImages() * imageSize, getNumberImages() * imageSize, 0));
120 {
121 sharedMemoryProvider->stop();
122 }
124 new armarx::IceSharedMemoryProvider<unsigned char>(this, info, "ImageProvider");
125
126 // reference to shared memory
127 imageBuffers = (void**)new unsigned char*[getNumberImages()];
128
129 for (int i = 0; i < getNumberImages(); i++)
130 {
131 imageBuffers[i] = sharedMemoryProvider->getBuffer() + i * imageSize;
132 }
133
134 // offer topic for image events
135 offeringTopic(getName() + ".ImageListener");
136
137 // retrieve storm topic proxy
139 getTopic<ImageProcessorInterfacePrx>(getName() + ".ImageListener");
140
141 // start icesharedmemory provider
142 sharedMemoryProvider->start();
143 }
144
146 }
147
148 void
159
160 void
162 {
164 exiting = true;
165
167
168 if (numberImages != 0)
169 {
170 delete[] imageBuffers;
171 }
172 }
173
174 void
175 ImageProvider::updateTimestamp(Ice::Long timestamp, bool threadSafe)
176 {
178 auto sharedMemoryProvider = this->sharedMemoryProvider; // preserve from deleting
180 {
181 ARMARX_INFO << "Shared memory provider is null!"
182 << " Did you forget to set call setNumberImages and setImageFormat in "
183 "onInitImageProvider?";
184 return;
185 }
186 armarx::MetaInfoSizeBasePtr info = this->sharedMemoryProvider->getMetaInfo(threadSafe);
187 if (info)
188 {
189 info->timeProvided = timestamp;
190 }
191 else
192 {
193 info = new armarx::MetaInfoSizeBase(
194 imageFormat.dimension.width * imageFormat.dimension.height *
195 imageFormat.bytesPerPixel,
196 imageFormat.dimension.width * imageFormat.dimension.height *
197 imageFormat.bytesPerPixel,
198 timestamp);
199 }
200
201 this->sharedMemoryProvider->setMetaInfo(info, threadSafe);
202 }
203
204 void
205 ImageProvider::updateTimestamp(IceUtil::Time timestamp, bool threadSafe)
206 {
208 updateTimestamp(timestamp.toMicroSeconds(), threadSafe);
209 }
210
211 void
213 {
215 updateTimestamp(timestamp.toMicroSecondsSinceEpoch(), threadSafe);
216 }
217
218 armarx::Blob
219 ImageProvider::getCompressedImagesAndMetaInfo(CompressionType compressionType,
220 Ice::Int compressionQuality,
221 armarx::MetaInfoSizeBasePtr& info,
222 const Ice::Current&)
223 {
224 int type;
225 switch (imageFormat.type)
226 {
227 case eRgb:
228 type = CV_8UC3;
229 break;
230 case eGrayScale:
231 type = CV_8UC1;
232 break;
233 default:
234 throw armarx::LocalException()
235 << "unsupported image type " << (int)(imageFormat.type);
236 }
237
239 return {};
240 auto lock = sharedMemoryProvider->getScopedReadLock();
241 if (!lock) // already shutdown
242 {
243 return {};
244 }
245 if (numberImages == 0)
246 {
247 return armarx::Blob();
248 }
249 info = new armarx::MetaInfoSizeBase(*sharedMemoryProvider->getMetaInfo());
250
251 auto imageTimestamp = IceUtil::Time::microSeconds(info->timeProvided);
252 auto key = std::make_pair(compressionType, compressionQuality);
253
254 std::unique_lock lock2(compressionDataMapMutex);
255 if (compressionDataMap.count(key) &&
256 compressionDataMap.at(key).imageTimestamp == imageTimestamp)
257 {
258 ARMARX_VERBOSE << deactivateSpam(1) << "using already compressed image";
259 return compressionDataMap.at(key).compressedImage;
260 }
261 cv::Mat mat(imageFormat.dimension.height * numberImages,
262 imageFormat.dimension.width,
263 type,
264 imageBuffers[0]);
265
266
267 armarx::Blob encodedImg;
268
269 std::vector<int> compression_params;
270 std::string extension;
271 switch (compressionType)
272 {
273 case ePNG:
274 extension = ".png";
275 compression_params = {cv::IMWRITE_PNG_COMPRESSION,
276 compressionQuality,
277 cv::IMWRITE_PNG_STRATEGY,
278 cv::IMWRITE_PNG_STRATEGY_RLE};
279 break;
280 case eJPEG:
281 extension = ".jpg";
282 compression_params.push_back(cv::IMWRITE_JPEG_QUALITY);
283 compression_params.push_back(compressionQuality);
284 break;
285 default:
286 throw armarx::LocalException()
287 << "unsupported image type " << (int)(imageFormat.type);
288 }
289 cv::imencode("*" + extension, mat, encodedImg, compression_params);
290
291 compressionDataMap[key] = {encodedImg, imageTimestamp};
292
293 // cv::imwrite("/tmp/compressed" + extension, mat, compression_params);
294 ARMARX_DEBUG << deactivateSpam(1) << "size before compression: "
295 << imageFormat.dimension.height * numberImages * imageFormat.dimension.width *
296 imageFormat.bytesPerPixel
297 << " size after: " << encodedImg.size();
298 return encodedImg;
299 }
300
301 // ================================================================== //
302 // == Utility methods for ImageProviders ============================ //
303 // ================================================================== //
304 void
305 ImageProvider::setImageFormat(ImageDimension imageDimension,
306 ImageType imageType,
307 BayerPatternType bayerPatternType)
308 {
310 imageFormat.dimension = imageDimension;
311 imageFormat.type = imageType;
312 imageFormat.bpType = bayerPatternType;
313
314 switch (imageType)
315 {
316 case eGrayScale:
317 case eBayerPattern:
318 imageFormat.bytesPerPixel = 1;
319 break;
320
321 case eRgb:
322 imageFormat.bytesPerPixel = 3;
323 break;
324
325 case eFloat1Channel:
326 imageFormat.bytesPerPixel = 4;
327 break;
328
329 case eFloat3Channels:
330 imageFormat.bytesPerPixel = 12;
331 break;
332
333 case ePointsScan:
334 imageFormat.bytesPerPixel = 12;
335 break;
336
337 case eColoredPointsScan:
338 imageFormat.bytesPerPixel = 16;
339 break;
340 }
341 }
342
343 void
345 {
347 this->numberImages = numberImages;
348 }
349
350 void
351 ImageProvider::provideImages(void** inputBuffers, const IceUtil::Time& imageTimestamp)
352 {
354 if (numberImages == 0)
355 {
356 ARMARX_INFO << "Number of images is 0 - thus none can be provided";
357 return;
358 }
359
360 int imageSize =
361 imageFormat.dimension.width * imageFormat.dimension.height * imageFormat.bytesPerPixel;
362
363 // copy
364 {
365 // lock memory access
367 if (!lock) // already shutdown
368 {
369 return;
370 }
371 if (imageTimestamp > IceUtil::Time())
372 {
373 updateTimestamp(imageTimestamp, false);
374 }
375 for (int i = 0; i < numberImages; i++)
376 {
377 memcpy(imageBuffers[i], inputBuffers[i], imageSize);
378 }
379 }
380
381 recordImages(imageTimestamp);
382
383 // notify processors
385 {
386 ARMARX_DEBUG << "Notifying ImageProcessorProxy";
387 imageProcessorProxy->reportImageAvailable(getName());
388 }
389 else
390 {
392 << "imageProcessorProxy is NULL - could not report Image available";
393 }
394 }
395
396 void
397 ImageProvider::provideImages(CByteImage** images, const IceUtil::Time& imageTimestamp)
398 {
400 if (numberImages == 0)
401 {
402 ARMARX_INFO << "Number of images is 0 - thus none can be provided";
403 return;
404 }
405
406 //ISO C++ forbids variable length array [-Werror=vla] => use a vector (the array will be on the heap anyways)
407 std::vector<void*> imageBuffers(numberImages);
408
409 for (int i = 0; i < numberImages; i++)
410 {
411 //ARMARX_VERBOSE << i;
412 imageBuffers[i] = images[i]->pixels;
413 }
414
415 provideImages(imageBuffers.data(), imageTimestamp);
416 }
417
418 void
419 ImageProvider::provideImages(const std::vector<CByteImageUPtr>& images,
420 const IceUtil::Time& imageTimestamp)
421 {
423 if (numberImages == 0)
424 {
425 ARMARX_INFO << "Number of images is 0 - thus none can be provided";
426 return;
427 }
428
429 //ISO C++ forbids variable length array [-Werror=vla] => use a vector (the array will be on the heap anyways)
430 std::vector<void*> imageBuffers(numberImages);
431
432 for (int i = 0; i < numberImages; i++)
433 {
434 //ARMARX_VERBOSE << i;
435 imageBuffers[i] = images[i]->pixels;
436 }
437
438 provideImages(imageBuffers.data(), imageTimestamp);
439 }
440
441 void
442 ImageProvider::provideImages(CFloatImage** images, const IceUtil::Time& imageTimestamp)
443 {
445 if (numberImages == 0)
446 {
447 ARMARX_INFO << "Number of images is 0 - thus none can be provided";
448 return;
449 }
450
451 //ISO C++ forbids variable length array [-Werror=vla] => use a vector (the array will be on the heap anyways)
452 std::vector<void*> imageBuffers(numberImages);
453
454 for (int i = 0; i < numberImages; i++)
455 {
456 imageBuffers[i] = images[i]->pixels;
457 }
458
459 provideImages(imageBuffers.data(), imageTimestamp);
460 }
461
462 void
463 ImageProvider::recordImages(const IceUtil::Time& image_timestamp)
464 {
466
467 int imageSize =
468 imageFormat.dimension.width * imageFormat.dimension.height * imageFormat.bytesPerPixel;
469
470 const bool is_recording = [&]
471 {
473
474 std::scoped_lock l{rec.statusMutex};
475
476 if (rec.status.type == imrec::State::stopping)
477 {
478 rec.status.type = imrec::State::writing;
479 // Still record current frames to notify potentially waiting recording task.
480 return true;
481 }
482
483 return rec.status.type == imrec::State::running;
484 }();
485
486 if (is_recording)
487 {
489
490 std::chrono::microseconds timestamp{image_timestamp.toMicroSeconds()};
491 std::unordered_map<int, CByteImage*> current_frames;
492 for (int i = 0; i < numberImages; ++i)
493 {
494 if (not rec.config.channelConfigs[i].disabled)
495 {
496 current_frames[i] = tools::createByteImage(imageFormat, imageFormat.type);
497 std::memcpy(current_frames[i]->pixels, imageBuffers[i], imageSize);
498 }
499 }
500
501 ARMARX_DEBUG << "Pushing data to buffer.";
502 {
503 std::scoped_lock l{rec.bufferMutex};
504 rec.buffer.push_back({timestamp, current_frames});
505 }
506 ARMARX_DEBUG << "Signalling that buffer was filled...";
507 rec.cv.notify_all();
508 }
509 }
510
511 bool
512 ImageProvider::startImageRecording(const imrec::Config& cfg, const Ice::Current&)
513 {
515
516 ARMARX_DEBUG << "Starting recording...";
517
518 ARMARX_CHECK_EQUAL(int(cfg.channelConfigs.size()), numberImages)
519 << "Must supply same number of channel configs as there are channels";
520
521 std::scoped_lock l{rec.callMutex};
522
523 {
524 std::scoped_lock l{rec.statusMutex};
525
526 auto are_disabled = [](const auto& c) { return c.disabled; };
527
528 // A recording can only be started if the component is ready and if at least one channel
529 // is not disabled.
530 if (rec.status.type != imrec::State::ready or
531 std::all_of(cfg.channelConfigs.begin(), cfg.channelConfigs.end(), are_disabled))
532 {
533 return false;
534 }
535
536 rec.status.framesWritten = 0;
537 rec.status.framesBuffered = 0;
538 rec.status.type = imrec::State::scheduled;
539 }
540
541 rec.config = cfg;
542 rec.config.name = simox::alg::replace_all(
543 rec.config.name,
544 "%TIMESTAMP%",
545 imrec::datetime_to_string(std::chrono::microseconds{rec.config.startTimestamp}));
546
547 rec.runningTask =
549 rec.runningTask->start();
550
551 ARMARX_DEBUG << "Scheduled recordings...";
552
553 return true;
554 }
555
556 imrec::Status
558 {
560
561 std::scoped_lock l{rec.statusMutex};
562 return rec.status;
563 }
564
565 bool
567 {
569
570 std::scoped_lock l{rec.callMutex};
571
572 {
573 std::scoped_lock l{rec.statusMutex};
574
575 // A recording can only be stopped if one is scheduled or one is running. Otherwise
576 // there is no recording which could be stopped, or a recording is still being written.
577 if (rec.status.type != imrec::State::scheduled and
578 rec.status.type != imrec::State::running)
579 {
580 return false;
581 }
582
583 rec.status.type = imrec::State::stopping;
584 }
585
586 ARMARX_CHECK(rec.runningTask);
587
588 const bool join = true;
589 rec.runningTask->stop(join);
590
591 return true;
592 }
593
594 void
596 {
598
599 ARMARX_DEBUG << "Started recording task.";
600
601 // Loop.
602 while (true)
603 {
605
606 const imrec::State state = [&]
607 {
608 std::scoped_lock l{rec.statusMutex};
609 return rec.status.type;
610 }();
611
612 // Component finished a recording and is ready again.
613 if (state == imrec::State::ready)
614 {
616
617 ARMARX_DEBUG << "Recording image provider ready.";
618
619 break; // Done. Exit loop, stop recordings now and terminate thread.
620 }
621 // Component is scheduled to start a recording.
622 else if (state == imrec::State::scheduled)
623 {
625
626 const IceUtil::Time start_at =
627 IceUtil::Time::microSeconds(rec.config.startTimestamp);
628 const IceUtil::Time now = IceUtil::Time::now();
629 if (start_at < now)
630 {
631 ARMARX_DEBUG << "Starting recordings...";
632
633 const std::filesystem::path path =
634 armarx::PackagePath::toSystemPath(rec.config.location);
635 rec.channelRecordings.clear();
636 for (int i = 0; i < numberImages; ++i)
637 {
638 const imrec::ChannelConfig& channel_cfg = rec.config.channelConfigs[i];
639 if (not channel_cfg.disabled)
640 {
642 const std::string name = getName() + "_" + channel_cfg.name;
643 const imrec::Format format = imrec::str2format(channel_cfg.format);
645 path / rec.config.name, name, format, channel_cfg.fps);
646 r->startRecording();
647 r->writeMetadataDatetime(
648 "recording_manager_time",
649 std::chrono::microseconds{rec.config.startTimestamp});
650 r->writeMetadataLine("image_provider_name", "string", getName());
651 r->writeMetadataLine("channel_name", "string", channel_cfg.name);
652 rec.channelRecordings[i] = r;
653 }
654 }
655
656 ARMARX_DEBUG << "Started recordings.";
657
658 std::scoped_lock l{rec.statusMutex};
659 rec.status.type = imrec::State::running;
660 }
661
663 }
664 // Component is running, stopping, or writing. Either way, try writing frames.
665 else
666 {
668
669 bool write_frames = false;
670 std::chrono::microseconds timestamp;
671 std::unordered_map<int, CByteImage*> frames;
672
673 {
674 ARMARX_DEBUG << "Fetching frames from buffer...";
675
676 std::scoped_lock l{rec.bufferMutex, rec.statusMutex};
677 if (not rec.buffer.empty())
678 {
679 std::tie(timestamp, frames) = rec.buffer.front();
680 rec.buffer.pop_front();
681 write_frames = true;
682
683 rec.status.framesBuffered = static_cast<long>(rec.buffer.size());
684 ++rec.status.framesWritten;
685
686 ARMARX_DEBUG << "Fetched frames from buffer.";
687 }
688 else
689 {
690 ARMARX_DEBUG << "Buffer empty.";
691 if (state == imrec::State::writing)
692 {
693 ARMARX_DEBUG << "Buffer fully written to disk, exiting.";
694 rec.status.type = imrec::State::ready;
695 }
696 }
697 }
698
699 if (write_frames)
700 {
701 ARMARX_DEBUG << "Writing frames...";
702
703 for (auto& [i, frame] : frames)
704 {
705 imrec::Recording r = rec.channelRecordings.at(i);
706 r->recordFrame(*frame, timestamp);
707 delete frame;
708 }
709 frames.clear();
710
711 ARMARX_DEBUG << "Wrote frames.";
712 }
713 else if (state == imrec::State::running)
714 {
715 std::unique_lock l{rec.bufferMutex};
716 ARMARX_DEBUG << "Waiting for new frames...";
717 rec.cv.wait(l, [&] { return not rec.buffer.empty(); });
718 ARMARX_DEBUG << "Received new frames, continuing.";
719 }
720
722 }
723 }
724
726
727 ARMARX_DEBUG << "Stopping recordings...";
728
729 // Stop.
730 for (auto& [i, r] : rec.channelRecordings)
731 {
732 r->stopRecording();
733 }
734 rec.channelRecordings.clear();
735
736 ARMARX_DEBUG << "Done stopping recordings.";
737 ARMARX_DEBUG << "Stopping recording task...";
738
740 }
741
742 std::vector<imrec::ChannelPreferences>
744 {
746
747 std::vector<imrec::ChannelPreferences> default_names;
748 imrec::ChannelPreferences cp;
749 cp.requiresLossless = false;
750 for (int i = 0; i < numberImages; ++i)
751 {
752 cp.name = "c" + std::to_string(i);
753 default_names.push_back(cp);
754 }
755 return default_names;
756 }
757} // namespace visionx
std::string timestamp()
constexpr T c
The IceSharedMemoryProvider provides data via Ice or shared memory.
SpamFilterDataPtr deactivateSpam(float deactivationDurationSec=10.0f, const std::string &identifier="", bool deactivate=true) const
disables the logging for the current line for the given amount of seconds.
Definition Logging.cpp:99
void offeringTopic(const std::string &name)
Registers a topic for retrival after initialization.
TopicProxyType getTopic(const std::string &name)
Returns a proxy of the specified topic.
std::string getName() const
Retrieve name of object.
std::filesystem::path toSystemPath() const
Represents a point in time.
Definition DateTime.h:25
virtual void onExitImageProvider()=0
This is called when the Component::onExitComponent() setup is called.
void onInitComponent() override
armarx::Blob getImagesAndMetaInfo(armarx::MetaInfoSizeBasePtr &, const Ice::Current &) override
virtual void onDisconnectImageProvider()
void onDisconnectComponent() override
Hook for subclass.
void recordImages(const IceUtil::Time &image_timestamp)
armarx::IceSharedMemoryProvider< unsignedchar >::pointer_type sharedMemoryProvider
shared memory provider
virtual void onInitImageProvider()=0
This is called when the Component::onInitComponent() is called.
void updateTimestamp(Ice::Long timestamp, bool threadSafe=true)
Updates the timestamp of the currently captured image.
ImageFormatInfo getImageFormat(const Ice::Current &c=Ice::emptyCurrent) override
Returns the entire image format info struct via Ice.
void setImageFormat(ImageDimension imageDimension, ImageType imageType, BayerPatternType bayerPatternType=visionx::eBayerPatternRg)
Sets the image basic format data.
bool startImageRecording(const imrec::Config &cfg, const Ice::Current &) override
bool stopImageRecording(const Ice::Current &) override
int getNumberImages(const Ice::Current &c=Ice::emptyCurrent) override
Retrieve number of images handled by this provider.
virtual void onConnectImageProvider()
This is called when the Component::onConnectComponent() setup is called.
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.
armarx::SharedMemoryScopedWriteLockPtr getScopedWriteLock()
Retrieve scoped lock for writing to the memory.
std::vector< imrec::ChannelPreferences > getImageRecordingChannelPreferences(const Ice::Current &) override
ImageProcessorInterfacePrx imageProcessorProxy
Ice proxy of the image processor interface.
armarx::Blob getImages(const Ice::Current &c=Ice::emptyCurrent) override
Retrieve images via Ice.
void onExitComponent() override
imrec::Status getImageRecordingStatus(const Ice::Current &) override
armarx::Blob getCompressedImagesAndMetaInfo(CompressionType, Ice::Int compressionQuality, armarx::MetaInfoSizeBasePtr &info, const Ice::Current &) override
#define ARMARX_CHECK(expression)
Shortcut for ARMARX_CHECK_EXPRESSION.
#define ARMARX_CHECK_EQUAL(lhs, rhs)
This macro evaluates whether lhs is equal (==) rhs and if it turns out to be false it will throw an E...
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:196
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:184
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:187
std::shared_ptr< SharedMemoryScopedWriteLock > SharedMemoryScopedWriteLockPtr
std::string datetime_to_string(std::chrono::microseconds ts)
Definition helper.cpp:77
visionx::imrec::Recording newRecording(const std::filesystem::path &path, const std::string &name, const Format format, double fps)
Format str2format(const std::string &format_str)
Format
Supported recording Formats.
Definition public_api.h:54
std::shared_ptr< AbstractRecordingStrategy > Recording
Convenience alias for any recording strategy.
CByteImage * createByteImage(const ImageFormatInfo &imageFormat, const ImageType imageType)
Creates a ByteImage for the destination type specified in the given imageProviderInfo.
ArmarX headers.
#define ARMARX_TRACE
Definition trace.h:77