MemoryToDebugObserver.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 RobotAPI::ArmarXObjects::MemoryToDebugObserver
17 * @author Rainer Kartmann ( rainer dot kartmann at kit dot edu )
18 * @date 2023
19 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
20 * GNU General Public License
21 */
22
24
25#include <array>
26#include <cstdint>
27#include <mutex>
28#include <optional>
29#include <string>
30#include <utility>
31#include <vector>
32
33#include <Eigen/Core>
34
35#include <SimoxUtility/algorithm/string/string_tools.h>
36#include <SimoxUtility/math/convert/mat4f_to_rpy.h>
37
44
46{
47
49 const Services& services) :
50 properties(properties), services(services)
51 {
52 services.debugObserver.setDebugObserverBatchModeEnabled(true);
53 }
54
56 {
57 public:
61
62 // void visitAronVariant(const data::DictPtr&) override;
63 // void visitAronVariant(const data::ListPtr&) override;
64 void
66 {
67 if (not v)
68 {
69 return;
70 }
71 for (const auto& [suffix, value] : MemoryToDebugObserver::decomposeNDArray(*v))
72 {
73 setDatafield(datafieldName + suffix, value);
74 }
75 }
76
77 void
79 {
80 setDatafield(v->getValue());
81 }
82
83 void
85 {
86 setDatafield(v->getValue());
87 }
88
89 void
91 {
92 setDatafield(v->getValue());
93 }
94
95 void
97 {
98 setDatafield(v->getValue());
99 }
100
101 void
103 {
104 setDatafield(v->getValue());
105 }
106
107 void
109 {
110 setDatafield(v->getValue());
111 }
112
113 template <class ValueT>
114 void
115 setDatafield(const std::string& datafield, const ValueT& value)
116 {
117 debugObserver.setDebugObserverDatafield(channelName, datafield, value);
118 pushed[channelName].insert(datafield);
119 ++count;
120 }
121
122 template <class ValueT>
123 void
124 setDatafield(const ValueT& value)
125 {
127 }
128
130 std::string channelName;
131 std::string datafieldName;
132
133 std::map<std::string, std::set<std::string>> pushed;
134
135 int count = 0;
136 };
137
138 void
140 {
141 pollOnce(properties.plottedValues);
142 }
143
144 std::map<std::string, std::set<std::string>>
145 MemoryToDebugObserver::pollOnce(const std::vector<MemoryValueID>& values)
146 {
147 Visitor visitor(services.debugObserver);
148
149 std::stringstream log;
150
151 // Group by memory segment to reduce number of queries.
152 std::map<MemoryID, std::vector<const MemoryValueID*>> valuesPerProviderSegment;
153 for (const MemoryValueID& valueId : values)
154 {
155 valuesPerProviderSegment[valueId.entityID.getProviderSegmentID()].push_back(&valueId);
156 }
157
158 for (const auto& [providerSegmentID, values] : valuesPerProviderSegment)
159 {
160 armem::client::Reader* reader = nullptr;
161 try
162 {
163 reader = getReader(providerSegmentID);
164 }
166 {
167 log << "\n" << e.what();
168 continue;
169 }
170 ARMARX_CHECK_NOT_NULL(reader);
171
172 const QueryResult result = reader->getLatestSnapshotsIn(providerSegmentID);
173 if (not result.success)
174 {
175 log << "Query to provider segment " << providerSegmentID
176 << " failed: " << result.errorMessage;
177 continue;
178 }
179
180 for (const MemoryValueID* valueId : values)
181 {
182 const wm::Entity* entity = result.memory.findEntity(valueId->entityID);
183 if (entity == nullptr)
184 {
185 log << "\nDid not find entity " << valueId->entityID << " in provider segment "
186 << providerSegmentID << ".";
187 continue;
188 }
189
190 // Use the non-throwing findLatestInstance() variant: an
191 // entity may exist but have no snapshots (e.g. just
192 // created, or all snapshots truncated by maxHistorySize),
193 // or its latest snapshot may carry no instances. In both
194 // cases getLatestInstance() throws NoSuchEntries, which
195 // would propagate uncaught out of pollOnce() and abort
196 // the polling thread.
197 const wm::EntityInstance* instance = entity->findLatestInstance();
198 if (instance == nullptr)
199 {
200 log << "\nEntity " << valueId->entityID
201 << " has no latest instance (yet) in provider segment " << providerSegmentID
202 << ".";
203 continue;
204 }
205
206 aron::data::VariantPtr valueVariant =
207 instance->data()->navigateAbsolute(valueId->aronPath);
208 if (not valueVariant)
209 {
210 log << "\nDid not find " << valueId->aronPath.toString()
211 << " in entity instance " << instance->id() << ".";
212 continue;
213 }
214
215 visitor.channelName = makeChannelName(valueId->entityID);
216 visitor.datafieldName = makeDatafieldName(valueId->aronPath);
217
218 aron::data::visit(visitor, valueVariant);
219 }
220 }
221
222 services.debugObserver.sendDebugObserverBatch();
223
224 if (not log.str().empty())
225 {
227 << "Encountered issues while sending memory values to the debug observer "
228 "for plotting: "
229 << log.str();
230 }
231
232 return visitor.pushed;
233 }
234
235 std::string
237 {
238 return simox::alg::replace_all(memoryID.str(), "/", ">");
239 }
240
241 std::string
243 {
244 // The first element is always "ARON->", which we can discard for readability.
245 std::string str = path.toString();
246 str = simox::alg::remove_prefix(str, path.getRootIdentifier() + path.getDelimeter());
247 str = simox::alg::replace_all(str, path.getDelimeter(), ">");
248 return str;
249 }
250
251 std::vector<std::pair<std::string, double>>
253 {
254 // Aron stores numeric arrays row-major with shape {rows, cols, sizeof(element)} (see
255 // aron::eigen::AronPosef and the aron code generation), so map them row-major like
256 // armem_gui's DataDisplayVisitor. AronEigenConverter maps column-major and would
257 // transpose them.
258 auto mapAsMatrix = [&array, maxSize](auto scalar) -> std::optional<Eigen::MatrixXd>
259 {
260 using ScalarT = decltype(scalar);
261 const std::vector<int> shape = array.getShape();
262 if (shape.size() != 3 or shape.back() != static_cast<int>(sizeof(ScalarT)))
263 {
264 return std::nullopt;
265 }
266 const int rows = shape.at(0);
267 const int cols = shape.at(1);
268 if (rows <= 0 or cols <= 0 or rows * cols > maxSize)
269 {
270 return std::nullopt;
271 }
272 using MapT = Eigen::Map<
274 return MapT(reinterpret_cast<const ScalarT*>(array.getData()), rows, cols)
275 .template cast<double>();
276 };
277
278 const std::string type = array.getType();
279 std::optional<Eigen::MatrixXd> matrix;
280 if (type == "float")
281 {
282 matrix = mapAsMatrix(float{});
283 }
284 else if (type == "double")
285 {
286 matrix = mapAsMatrix(double{});
287 }
288 else if (type == "int")
289 {
290 matrix = mapAsMatrix(int{});
291 }
292 else if (type == "long")
293 {
294 matrix = mapAsMatrix(std::int64_t{});
295 }
296 if (not matrix.has_value())
297 {
298 return {};
299 }
300 const Eigen::MatrixXd& m = matrix.value();
301
302 std::vector<std::pair<std::string, double>> components;
303
304 // 4x4 homogeneous transform (bottom row [0 0 0 1]) -> x, y, z, roll, pitch, yaw.
305 constexpr double bottomRowPrecision = 1e-4;
306 if (m.rows() == 4 and m.cols() == 4 and
307 m.row(3).isApprox(Eigen::RowVector4d(0, 0, 0, 1), bottomRowPrecision))
308 {
309 const Eigen::Matrix4f pose = m.cast<float>();
310 const Eigen::Vector3f rpy = simox::math::mat4f_to_rpy(pose);
311 components.emplace_back("_x", m(0, 3));
312 components.emplace_back("_y", m(1, 3));
313 components.emplace_back("_z", m(2, 3));
314 components.emplace_back("_roll", rpy(0));
315 components.emplace_back("_pitch", rpy(1));
316 components.emplace_back("_yaw", rpy(2));
317 return components;
318 }
319
320 if (m.size() == 1)
321 {
322 components.emplace_back("", m(0, 0));
323 return components;
324 }
325
326 // Vectors -> x/y/z/w (or indices for larger vectors).
327 if (m.rows() == 1 or m.cols() == 1)
328 {
329 static const std::array<std::string, 4> xyzw{"_x", "_y", "_z", "_w"};
330 for (int i = 0; i < m.size(); ++i)
331 {
332 components.emplace_back(m.size() <= 4 ? xyzw.at(i) : "_" + std::to_string(i), m(i));
333 }
334 return components;
335 }
336
337 // Generic matrix -> _row_col.
338 for (int r = 0; r < m.rows(); ++r)
339 {
340 for (int c = 0; c < m.cols(); ++c)
341 {
342 components.emplace_back("_" + std::to_string(r) + "_" + std::to_string(c), m(r, c));
343 }
344 }
345 return components;
346 }
347
348 Reader*
349 MemoryToDebugObserver::getReader(const MemoryID& memoryID)
350 {
351 auto it = memoryReaders.find(memoryID);
352 if (it != memoryReaders.end())
353 {
354 return &it->second;
355 }
356 else
357 {
358 armem::client::Reader reader = services.memoryNameSystem.getReader(memoryID);
359 auto [it, _] = memoryReaders.emplace(memoryID, reader);
360 return &it->second;
361 }
362 }
363
364 bool
365 operator==(const MemoryValueID& lhs, const MemoryValueID& rhs)
366 {
367 return lhs.entityID == rhs.entityID && lhs.aronPath.getPath() == rhs.aronPath.getPath();
368 }
369
370 bool
371 operator<(const MemoryValueID& lhs, const MemoryValueID& rhs)
372 {
373 if (lhs.entityID < rhs.entityID)
374 {
375 return true;
376 }
377 if (rhs.entityID < lhs.entityID)
378 {
379 return false;
380 }
381 return lhs.aronPath.getPath() < rhs.aronPath.getPath();
382 }
383
384} // namespace armarx::armem::client::util
385
386namespace armarx::armem::client
387{
388
389 void
390 util::to_json(simox::json::json& j, const MemoryValueID& id)
391 {
392 j["entityID"] = id.entityID.getItems();
393 j["aronPath"] = id.aronPath.getPath();
394 }
395
396 void
397 util::from_json(const simox::json::json& j, MemoryValueID& id)
398 {
399 id.entityID = MemoryID::fromItems(j.at("entityID").get<std::vector<std::string>>());
400 id.aronPath = {j.at("aronPath").get<std::vector<std::string>>()};
401 }
402
403 void
404 util::to_json(simox::json::json& j, const MemoryToDebugObserver::Properties& p)
405 {
406 j["plottedValues"] = p.plottedValues;
407 }
408
409 void
411 {
412 j.at("plottedValues").get_to(p.plottedValues);
413 }
414
415
416} // namespace armarx::armem::client
SpamFilterDataPtr deactivateSpam(SpamFilterDataPtr const &spamFilter, float deactivationDurationSec, const std::string &identifier, bool deactivate)
Definition Logging.cpp:75
constexpr T c
std::string str(const T &t)
Brief description of class DebugObserverHelper.
static MemoryID fromItems(const std::vector< std::string > &items)
Constructor memory ID from items as returned by getItems().
Definition MemoryID.cpp:194
std::string str(bool escapeDelimiters=true) const
Get a string representation of this memory ID.
Definition MemoryID.cpp:102
auto * findLatestInstance(int instanceIndex=0)
Definition EntityBase.h:356
Reader getReader(const MemoryID &memoryID)
Get a reader to the given memory name.
Reads data from a memory server.
Definition Reader.h:25
QueryResult getLatestSnapshotsIn(const MemoryID &id, armem::query::DataMode dataMode=armem::query::DataMode::WithData) const
Get the latest snapshots under the given memory ID.
Definition Reader.cpp:427
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.
void pollOnce()
Query values from the memory and send them to the debug observer.
static std::string makeChannelName(const armem::MemoryID &memoryID)
static std::string makeDatafieldName(const aron::Path &path)
MemoryToDebugObserver(const Properties &properties, const Services &services)
Constructor.
Visitor(armarx::DebugObserverHelper &debugObserver)
std::map< std::string, std::set< std::string > > pushed
armarx::DebugObserverHelper & debugObserver
void visitAronVariant(const aron::data::LongPtr &v) override
void visitAronVariant(const aron::data::IntPtr &v) override
void visitAronVariant(const aron::data::DoublePtr &v) override
void visitAronVariant(const aron::data::NDArrayPtr &v) override
void visitAronVariant(const aron::data::StringPtr &v) override
void setDatafield(const std::string &datafield, const ValueT &value)
void visitAronVariant(const aron::data::FloatPtr &v) override
void visitAronVariant(const aron::data::BoolPtr &v) override
Indicates that a query to the Memory Name System failed.
Definition mns.h:25
The Path class.
Definition Path.h:36
std::string toString() const
Definition Path.cpp:127
std::vector< std::string > getPath() const
Definition Path.cpp:87
std::string getDelimeter() const
Definition Path.cpp:75
std::string getRootIdentifier() const
Definition Path.cpp:63
std::string getType() const
Definition NDArray.cpp:165
unsigned char * getData() const
Definition NDArray.cpp:128
std::vector< int > getShape() const
Definition NDArray.cpp:147
#define ARMARX_CHECK_NOT_NULL(ptr)
This macro evaluates whether ptr is not null and if it turns out to be false it will throw an Express...
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181
bool operator<(const MemoryValueID &lhs, const MemoryValueID &rhs)
void from_json(const simox::json::json &j, MemoryValueID &id)
bool operator==(const MemoryValueID &lhs, const MemoryValueID &rhs)
void to_json(simox::json::json &j, const MemoryValueID &id)
This file is part of ArmarX.
armem::wm::EntityInstance EntityInstance
std::shared_ptr< Bool > BoolPtr
std::shared_ptr< Float > FloatPtr
std::shared_ptr< Variant > VariantPtr
std::shared_ptr< NDArray > NDArrayPtr
Definition NDArray.h:46
std::shared_ptr< Long > LongPtr
std::shared_ptr< Int > IntPtr
void visit(VisitorImplementation &v, typename VisitorImplementation::Input &o)
Definition Visitor.h:136
std::shared_ptr< Double > DoublePtr
std::shared_ptr< String > StringPtr
auto * findEntity(const MemoryID &entityID)
Find an entity.
Result of a QueryInput.
Definition Query.h:51
wm::Memory memory
The slice of the memory that matched the query.
Definition Query.h:58