16#include <SimoxUtility/color/cmaps.h>
17#include <SimoxUtility/math/SoftMinMax.h>
20#include <RobotAPI/libraries/armem/aron/MemoryID.aron.generated.h>
38 splitter(new QSplitter(Qt::Orientation::Vertical)),
tree(new QTreeWidget(this))
42 QLayout* layout =
new QVBoxLayout();
43 this->setLayout(layout);
45 layout->setContentsMargins(margin, margin, margin, margin);
55 tree->setColumnCount(columns.size());
56 tree->setHeaderLabels(columns);
64 tree->setContextMenuPolicy(Qt::CustomContextMenu);
66 &QTreeWidget::customContextMenuRequested,
98 std::optional<MemoryID>
113 child->addWidget(dataView);
135 QTreeWidgetItem* item =
new QTreeWidgetItem({
"(No data.)"});
166 statusLabel->setText(QString::fromStdString(message));
170 std::optional<aron::Path>
173 QStringList qpath = item->data(
int(
Columns::KEY), Qt::UserRole).toStringList();
185 std::optional<MemoryID>
198 element =
data->navigateAbsolute(elementPath);
206 catch (
const armarx::LocalException& e)
212 std::stringstream couldNotParseMsg;
213 couldNotParseMsg <<
"Element " << elementPath.
toString()
214 <<
" could not be parsed as MemoryID.";
216 auto dictElement = std::dynamic_pointer_cast<aron::data::Dict>(element);
219 showErrorMessage(couldNotParseMsg.str() +
" (Failed to cast to DictNavigator.)");
225 arondto::MemoryID
dto;
226 dto.fromAron(dictElement);
240 DataView::makeActionResolveMemoryID(
const MemoryID&
id)
242 auto* action =
new QAction(
"Resolve memory ID");
244 if (not(
id.hasEntityName() and
id.isWellDefined()))
246 action->setDisabled(
true);
247 action->setText(action->text() +
" (incomplete Memory ID)");
263 QAction* action =
new QAction(
"Copy memory ID to clipboard");
269 const QString idStr = QString::fromStdString(
id.
str());
272 QClipboard* clipboard = QApplication::clipboard();
273 clipboard->setText(idStr);
274 QApplication::processEvents();
280 std::vector<QAction*>
281 DataView::makeActionsCopyDataToClipboard()
291 std::vector<QAction*>
292 DataView::makeActionsCopyDataToClipboard(
const aron::Path& path)
311 catch (
const aron::error::AronException& e)
316 return makeCopyActions(element, elementType);
318 catch (
const aron::error::AronException& e)
320 ARMARX_WARNING <<
"Could not convert Aron data to JSON: " << e.getReason();
325 std::vector<QAction*>
329 auto* easyJsonAction =
new QAction(
"Copy data to clipboard as easy JSON");
330 connect(easyJsonAction,
332 [
this, element, elementType]()
336 TreeTypedJSONConverter conv;
338 QClipboard* clipboard = QApplication::clipboard();
339 clipboard->setText(QString::fromStdString(conv.getJSON().dump(2)));
340 QApplication::processEvents();
342 catch (
const aron::error::AronException& e)
344 ARMARX_WARNING <<
"Could not convert Aron data to JSON: " << e.getReason();
348 auto* aronJsonAction =
new QAction(
"Copy data to clipboard as aron JSON");
349 connect(aronJsonAction,
355 nlohmann::json json =
358 QClipboard* clipboard = QApplication::clipboard();
359 clipboard->setText(QString::fromStdString(json.dump(2)));
360 QApplication::processEvents();
362 catch (
const aron::error::AronException& e)
364 ARMARX_WARNING <<
"Could not convert Aron data to JSON: " << e.getReason();
368 return {easyJsonAction, aronJsonAction};
374 QMenu* menu =
new QMenu(
this);
376 const QTreeWidgetItem* item =
tree->itemAt(pos);
384 auto actions = makeActionsCopyDataToClipboard();
385 for (
const auto& action :
actions)
389 menu->addAction(action);
402 QAction* viewAction =
new QAction(
"Show image");
403 menu->addAction(viewAction);
415 const std::vector<int> shape = imageData->getShape();
416 if (std::find(shape.begin(), shape.end(), 0) != shape.end())
418 viewAction->setText(viewAction->text() +
" (image is empty)");
419 viewAction->setEnabled(
false);
426 catch (
const armarx::LocalException&)
437 const std::string typeName = item->text(
int(
Columns::TYPE)).toStdString();
446 menu->addAction(action);
448 if (QAction* action = makeActionResolveMemoryID(
id.value()))
450 menu->addAction(action);
456 const std::optional<aron::Path> elementPath =
getElementPath(item);
459 auto actions = makeActionsCopyDataToClipboard(elementPath.value());
460 for (
const auto& action :
actions)
464 menu->addAction(action);
472 std::vector<aron::Path> leafPaths;
477 leafPaths = collectPlottableLeafPaths(
478 data->navigateAbsolute(elementPath.value()));
484 catch (
const armarx::LocalException&)
488 if (not leafPaths.empty())
490 menu->addAction(makeActionToggleLivePlot(entityID.value(), leafPaths));
498 std::vector<aron::Path>
501 std::vector<aron::Path> result;
507 switch (element->getDescriptor())
515 result.push_back(element->getPath());
522 result.push_back(element->getPath());
528 const std::vector<aron::Path> childLeaves = collectPlottableLeafPaths(child);
529 result.insert(result.end(), childLeaves.begin(), childLeaves.end());
539 DataView::makeActionToggleLivePlot(
const MemoryID& entityID,
540 const std::vector<aron::Path>& leafPaths)
542 std::vector<armem::client::util::MemoryValueID> ids;
543 ids.reserve(leafPaths.size());
544 for (
const aron::Path& path : leafPaths)
546 ids.push_back({entityID, path});
549 QAction* action =
new QAction(leafPaths.size() > 1
550 ?
"Live plot values to Debug Observer"
551 :
"Live plot to Debug Observer");
552 action->setCheckable(
true);
557 action->setText(action->text() +
" (no Debug Observer connected)");
558 action->setEnabled(
false);
578 static const QColor highlightColor(180, 230, 180);
580 std::function<void(QTreeWidgetItem*)> walk = [&](QTreeWidgetItem* item)
582 bool highlight =
false;
591 const QBrush brush = highlight ? QBrush(highlightColor) : QBrush();
592 for (
int col = 0; col <
tree->columnCount(); ++col)
594 item->setBackground(col, brush);
597 for (
int i = 0; i < item->childCount(); ++i)
599 walk(item->child(i));
610 if (menu->actions().isEmpty())
654 const std::vector<int> shape =
aron.getShape();
656 ARMARX_CHECK_EQUAL(shape.at(2), 4) <<
"Expected Depth32 image to have 4 bytes per pixel.";
658 const int rows = shape.at(0);
659 const int cols = shape.at(1);
663 QImage image(cols, rows, QImage::Format::Format_RGB32);
664 const float*
data =
reinterpret_cast<float*
>(
aron.getData());
666 auto updateLimits = [](
float value,
Limits& limits)
670 limits.min = std::min(limits.min, value);
672 limits.max = std::max(limits.max, value);
679 const float* sourceRow =
data;
680 for (
int row = 0; row < rows; ++row)
682 for (
int col = 0; col < cols; ++col)
684 float value = sourceRow[col];
685 updateLimits(value, limits);
703 cmap.set_vlimits(softMin.getSoftMin(), softMax.getSoftMax());
708 const float* sourceRow =
data;
710 const int bytesPerLine = image.bytesPerLine();
711 uchar* targetRow = image.bits();
713 for (
int row = 0; row < rows; ++row)
715 for (
int col = 0; col < cols; ++col)
717 float value = sourceRow[col];
718 simox::Color color = value <= 0 ? simox::Color::white() :
cmap(value);
719 targetRow[col * 4 + 0] = color.b;
720 targetRow[col * 4 + 1] = color.g;
721 targetRow[col * 4 + 2] = color.r;
722 targetRow[col * 4 + 3] = color.a;
724 updateLimits(value, limits);
727 targetRow += bytesPerLine;
766 catch (
const armarx::LocalException&)
773 NDArray::PointerType imageData = NDArray::DynamicCast(element);
777 simox::meta::get_type_name(element));
781 const std::vector<int> shape = imageData->getShape();
782 if (shape.size() != 3)
785 NDArray::DimensionsToString(shape));
788 const int rows = shape.at(0);
789 const int cols = shape.at(1);
791 using aron::type::image::PixelType;
792 std::optional<PixelType> pixelType;
799 pixelType = (imageData->getType() ==
"5" ? PixelType::DEPTH32 : PixelType::RGB24);
805 bool clearLimitsHistory =
true;
806 std::optional<QImage> image;
809 switch (pixelType.value())
811 case PixelType::RGB24:
813 <<
"Expected Rgb24 image to have 3 bytes per pixel.";
814 image = QImage(imageData->getData(), cols, rows, QImage::Format::Format_RGB888);
817 case PixelType::DEPTH32:
818 image =
imageView->convertDepth32ToRGB32(*imageData);
819 clearLimitsHistory =
false;
825 QImage::Format format = QImage::Format_Invalid;
829 format = QImage::Format::Format_Grayscale8;
833 format = QImage::Format::Format_RGB888;
837 showErrorMessage(
"Expected 1 or 3 elements in last dimension, but got shape: " +
838 NDArray::DimensionsToString(shape));
841 image = QImage(imageData->getData(), cols, rows, format);
846 std::stringstream title;
847 title <<
"Image element '" <<
imageView->elementPath.toString()
849 imageView->setTitle(QString::fromStdString(title.str()));
850 imageView->view->setImage(image.value());
852 if (clearLimitsHistory)
861 setLayout(
new QHBoxLayout());
863 layout()->setContentsMargins(margin, margin, margin, margin);
866 QFont font = this->font();
867 font.setPointSizeF(font.pointSize() * 0.75);
872 layout()->addWidget(
view);
void setTag(const LogTag &tag)
static std::vector< std::pair< std::string, double > > decomposeNDArray(const aron::data::NDArray &array, int maxSize=16)
Decompose a numeric NDArray (vector, matrix, pose) into scalar datafields.
Streams selected memory values to the DebugObserver for live plotting.
void setColumns(int key, int value, int type)
void updateTree(QTreeWidgetItem *parent, const aron::data::DictPtr &data)
const size_t limitsHistoryMaxSize
In this context, n.
instance::ImageView * view
QImage convertDepth32ToRGB32(const aron::data::NDArray &aron)
simox::ColorMap cmap
Color map to visualize depth images.
std::deque< Limits > limitsHistory
History over first n extremal depth values used to calibrate the colormap.
void setUseTypeInfo(bool enable)
void memoryIdResolutionRequested(const MemoryID &id)
void setLivePlotController(controller::LivePlotController *controller)
void addDataView(DataView *dataView)
controller::LivePlotController * _livePlotController
QAction * makeActionCopyMemoryID(const MemoryID &id)
void actionsMenuRequested(const MemoryID &memoryID, QWidget *parent, const QPoint &pos, QMenu *menu)
virtual void updateData(const aron::data::DictPtr &data, aron::type::ObjectPtr aronType=nullptr)
virtual aron::data::DictPtr getData()=0
void showErrorMessage(const std::string &message)
virtual std::optional< MemoryID > getCurrentEntityID() const
static std::optional< aron::Path > getElementPath(const QTreeWidgetItem *item)
void updateImageView(const aron::data::DictPtr &data)
void showImageView(const aron::Path &elementPath)
aron::type::ObjectPtr currentAronType
virtual QMenu * buildActionsMenu(const QPoint &pos)
void setStatusLabel(QLabel *statusLabel)
QTreeWidgetItem * treeItemData
void applyLivePlotHighlights()
void useTypeInfoChanged(bool enable)
std::optional< MemoryID > getElementMemoryID(const aron::Path &elementPath)
virtual void prepareTreeContextMenu(const QPoint &pos)
A widget drawing an image in itself.
void updateTree(QTreeWidgetItem *parent, const aron::type::Dict &type, const aron::data::Dict &data)
std::string toString() const
static nlohmann::json ConvertToNlohmannJSON(const data::VariantPtr &)
static PointerType DynamicCast(const VariantPtr &n)
A base class for aron exceptions.
#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_WARNING
The logging level for unexpected behaviour, but not a serious problem.
aron::Path deserializePath(const QStringList &qpath)
std::string sanitizeTypeName(const std::string &typeName)
const std::string sanitizedMemoryIDTypeName
void fromAron(const arondto::MemoryID &dto, MemoryID &bo)
std::shared_ptr< Dict > DictPtr
std::shared_ptr< Variant > VariantPtr
void visitRecursive(RecursiveVisitorImplementation &v, typename RecursiveVisitorImplementation::Input &o)
std::shared_ptr< Object > ObjectPtr
std::shared_ptr< Variant > VariantPtr
void clearItem(QTreeWidgetItem *item)
Clear a tree widget item.
int toInt(const std::string &input)