27#include <boost/program_options.hpp>
49 toLogLevel(
const std::string& name)
53 if (name ==
"verbose")
57 if (name ==
"important")
64 throw std::invalid_argument{
"Unknown log level `" + name +
"`."};
68 resolvePriorKnowledge(
const std::string&
override)
70 if (not
override.
empty())
83 shapeScene(
const po::variables_map& vm,
84 const SmoothingParams& smoothingParams,
91 if (
const float corridorWidth = vm[
"corridor"].as<float>(); corridorWidth > 0.F)
94 .width = corridorWidth, .length = vm[
"corridor-length"].as<
float>()};
98 const float clearance = 0.5F * corridorWidth - robotRadius;
100 ARMARX_IMPORTANT <<
"Corridor: " << corridorWidth <<
" mm wide, " << parameters.length
101 <<
" mm long, leaving a " << robotRadius <<
" mm robot " << clearance
102 <<
" mm either side.";
104 if (clearance <=
static_cast<float>(smoothingParams.min_clearance))
106 ARMARX_WARNING <<
"That is at or below the " << smoothingParams.min_clearance
107 <<
" mm the trajectory checker enforces, so no path along the "
108 "corridor can be accepted however well it is smoothed. Use a "
110 << 2.F * (robotRadius +
111 static_cast<float>(smoothingParams.min_clearance))
116 if (
const float passageWidth = vm[
"serpentine"].as<float>(); passageWidth > 0.F)
119 .passageWidth = passageWidth, .walls = vm[
"serpentine-walls"].as<
int>()};
121 const float clearance =
124 ARMARX_IMPORTANT <<
"Serpentine: " << parameters.walls <<
" walls, " << passageWidth
125 <<
" mm openings, tightest clearance " << clearance <<
" mm.";
127 if (clearance <=
static_cast<float>(smoothingParams.clearance))
129 ARMARX_WARNING <<
"That is at or below the " << smoothingParams.clearance
130 <<
" mm the trajectory checker demands, so every path through "
131 "an opening is rejected however well it is smoothed. This "
132 "sweep would measure the scene, not the planner. Use openings "
135 (robotRadius +
static_cast<float>(smoothingParams.clearance))
140 if (
const int clutter = vm[
"clutter"].as<int>(); clutter > 0)
143 .minGap = vm[
"clutter-gap"].as<
float>()};
149 const float required =
150 2.F * (robotRadius +
static_cast<float>(smoothingParams.clearance));
152 if (parameters.minGap <= required)
155 <<
"clutter-gap is " << parameters.minGap <<
" mm, which leaves a "
156 << robotRadius <<
" mm robot only " << (0.5F * parameters.minGap - robotRadius)
157 <<
" mm of clearance -- below the " << smoothingParams.clearance
158 <<
" mm the trajectory checker demands. Every path through a clutter passage "
159 "will be rejected regardless of smoothing quality, so the rejection rate "
160 "from this sweep says nothing about the smoother. Use more than "
161 << required <<
" mm.";
167 <<
" boxes at a minimum gap of " << parameters.minGap <<
" mm."
168 << (placed < clutter ?
" The room saturated before the rest fit."
175 samplingParameters(
const po::variables_map& vm,
const SmoothingParams& smoothingParams)
181 const float bandMin = vm[
"endpoint-clearance-min"].as<
float>();
183 bandMin >= 0.F ? bandMin :
static_cast<float>(smoothingParams.clearance) + 5.F;
185 const float bandMax = vm[
"endpoint-clearance-max"].as<
float>();
196 PathDump(
const analysis::Scene& scene,
const int maxPaths) : maxPaths_{maxPaths}
198 nlohmann::json obstacles = nlohmann::json::array();
199 for (
const analysis::Box& box : scene.
obstacles)
201 obstacles.push_back({{
"min", {box.
min.x(), box.
min.y()}},
202 {
"max", {box.
max.x(), box.
max.y()}},
206 dump_[
"obstacles"] = obstacles;
209 dump_[
"paths"] = nlohmann::json::array();
214 add(
const evaluation::Request& request,
const global_planning::GlobalPlannerResult& result)
216 if (
static_cast<int>(dump_[
"paths"].size()) >= maxPaths_)
221 nlohmann::json points = nlohmann::json::array();
225 {point.waypoint.pose.translation().x(), point.waypoint.pose.translation().y()});
228 dump_[
"paths"].push_back({{
"kind",
toString(request.
kind)},
229 {
"index", request.
index},
231 {
"points", points}});
235 write(
const std::string& path)
const
237 std::ofstream file{path};
238 file << dump_.dump() << std::endl;
239 ARMARX_IMPORTANT <<
"Wrote scene and " << dump_[
"paths"].size() <<
" paths to `" << path
246 nlohmann::json dump_;
254 po::options_description desc(
"Offline sweep of planning requests against a real room.");
258 (
"help,h",
"produce help message")
259 (
"prior-knowledge", po::value<std::string>()->default_value(
""),
260 "Path to PriorKnowledgeData's data directory. Resolved from the package if omitted.")
261 (
"room", po::value<std::string>()->default_value(
"50_19/R007"),
262 "Room within the scene collections.")
263 (
"scenes", po::value<std::vector<std::string>>()->multitoken()
264 ->default_value({
"static_scene",
"mobile_kitchen"},
"static_scene mobile_kitchen"),
265 "Scene descriptions to merge.")
266 (
"uniform", po::value<int>()->default_value(100),
"Uniform free-space requests.")
267 (
"near-obstacle", po::value<int>()->default_value(200),
"Near-obstacle requests.")
268 (
"locations", po::value<int>()->default_value(100),
"Named-location requests.")
269 (
"seed", po::value<unsigned>()->default_value(1),
"RNG seed; a sweep is reproducible.")
270 (
"corridor", po::value<float>()->default_value(0.F),
271 "Replace the room's contents with a dead-end corridor of this clear width [mm] "
272 "opening into the rest of the room. Requests then start inside it and finish outside, "
273 "which is the case a start pinned in tight space produces and no other scene here "
274 "does. 0 leaves the room as furnished.")
275 (
"corridor-length", po::value<float>()->default_value(5000.F),
276 "How far the corridor runs into the room [mm].")
277 (
"corridor-exits", po::value<int>()->default_value(0),
278 "Corridor-exit requests to draw.")
279 (
"serpentine", po::value<float>()->default_value(0.F),
280 "Replace the room's contents with transverse walls whose openings alternate sides, "
281 "each opening this wide [mm]. Forces a path to thread tight passages and reverse "
282 "direction repeatedly. 0 leaves the room as furnished.")
283 (
"serpentine-walls", po::value<int>()->default_value(6),
284 "Number of transverse walls in the serpentine scene.")
285 (
"endpoint-clearance-min", po::value<float>()->default_value(-1.F),
286 "Lower bound [mm] of the clearance band near-obstacle endpoints are drawn from. "
287 "Negative ties it to the smoother's target, which excludes endpoints the planner "
288 "cannot satisfy; set it lower to include them and see how the rejection rate depends "
289 "on how close to furniture the robot is asked to start and finish.")
290 (
"endpoint-clearance-max", po::value<float>()->default_value(-1.F),
291 "Upper bound [mm] of that band. Negative uses the lower bound plus 300.")
292 (
"no-smoothing", po::bool_switch(),
293 "Bypass the smoothing stage entirely, so the sweep measures the raw resampled path.")
294 (
"dump-scene", po::value<std::string>()->default_value(
""),
295 "Write the scene geometry and the first few planned paths here, for plotting.")
296 (
"dump-count", po::value<int>()->default_value(6),
"How many paths to dump.")
297 (
"resample", po::value<float>()->default_value(-1.F),
298 "Waypoint spacing [mm] before smoothing. Negative uses the planner's default.")
299 (
"clutter", po::value<int>()->default_value(0),
300 "Extra boxes scattered through the room to create tight passages. 0 leaves the room "
302 (
"clutter-gap", po::value<float>()->default_value(1000.F),
303 "Gap [mm] clutter must leave to every other obstacle. Must exceed "
304 "2 * (robot radius + smoothing clearance) or the passages it creates cannot be "
305 "traversed at all; a warning says so.")
306 (
"cell-size", po::value<float>()->default_value(100.F),
"Costmap cell size [mm].")
307 (
"robot-radius", po::value<float>()->default_value(350.F),
308 "Robot radius [mm]. ARMAR-7 is circular; the static costmap inflates the 3D model "
310 (
"log-level", po::value<std::string>()->default_value(
"important"),
311 "debug|verbose|info|important|warn|error. A sweep provokes planning failures by "
312 "design, and each one logs a backtrace at error level.")
313 (
"out", po::value<std::string>()->default_value(
"evaluation.jsonl"),
314 "Per-request records (JSON lines).")
318 po::variables_map vm;
319 po::store(po::parse_command_line(argc, argv, desc), vm);
321 if (vm.count(
"help") != 0u)
323 std::cout << desc <<
"\n";
331 const std::filesystem::path priorKnowledge =
332 resolvePriorKnowledge(vm[
"prior-knowledge"].as<std::string>());
333 const std::string room = vm[
"room"].as<std::string>();
335 ARMARX_INFO <<
"Reading `" << room <<
"` from `" << priorKnowledge.string() <<
"`.";
338 priorKnowledge, room, vm[
"scenes"].as<std::vector<std::string>>());
341 <<
"included), " << roomScene.
locations.size() <<
" named locations.";
355 std::mt19937 rng{vm[
"seed"].as<
unsigned>()};
359 shapeScene(vm, smoothingParams, config, roomScene, rng);
369 ARMARX_IMPORTANT <<
"Smoothing disabled: measuring the raw resampled path.";
380 std::vector<evaluation::Request> requests;
381 for (
const auto& [kind, count] :
392 const std::vector<evaluation::Request> sampled =
394 requests.insert(requests.end(), sampled.begin(), sampled.end());
399 const std::filesystem::path outFilename = vm[
"out"].as<std::string>();
400 std::ofstream out{outFilename};
403 throw std::runtime_error{
"Cannot write to `" + outFilename.string() +
"`."};
406 const std::string dumpPath = vm[
"dump-scene"].as<std::string>();
407 std::optional<PathDump>
dump;
408 if (not dumpPath.empty())
410 dump.emplace(config.
scene, vm[
"dump-count"].as<
int>());
413 std::vector<evaluation::Record> records;
414 records.reserve(requests.size());
418 const auto before = std::chrono::steady_clock::now();
420 std::optional<global_planning::GlobalPlannerResult> result;
425 catch (
const std::exception& e)
430 <<
") threw: " << e.what();
433 const float seconds =
434 std::chrono::duration<float>(std::chrono::steady_clock::now() - before).count();
436 if (
dump.has_value() and result.has_value())
438 dump->add(request, result.value());
443 records.push_back(std::move(record));
448 if (
dump.has_value())
450 dump->write(dumpPath);
453 ARMARX_IMPORTANT <<
"Wrote " << records.size() <<
" records to `" << outFilename.string()
458 std::cout << evaluation::summarize(records, static_cast<float>(smoothingParams.min_clearance))
463catch (
const std::exception& e)
static void SetGlobalMinimumLoggingLevel(MessageTypeT level)
With setGlobalMinimumLoggingLevel the minimum verbosity-level of log-messages can be set for the whol...
static std::filesystem::path toSystemPath(const data::PackagePath &pp)
const std::vector< GlobalTrajectoryPoint > & points() const
std::optional< GlobalPlannerResult > plan(const core::Pose &start, const core::Pose &goal)
void dump(vec_point_2d &vec)
#define ARMARX_INFO
The normal logging level.
#define ARMARX_IMPORTANT
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
void write(WriterT &aron_w, const Eigen::Matrix< EigenT, rows, cols, options > &input, typename WriterT::ReturnType &ret, const armarx::aron::Path &aron_p=armarx::aron::Path())
SmoothingParams loadSmoothingParams(const std::string &filePath)
algorithms::Costmap buildCostmap(const Config &config)
Build a costmap holding, per cell, the distance to the closest obstacle reduced by the robot radius (...
std::vector< Request > sampleRequests(const RequestKind kind, const int count, const algorithms::Costmap &costmap, const RoomScene &room, const SamplingParameters ¶meters, std::mt19937 &rng)
Draw count requests of the given kind.
Record measure(const Request &request, const std::optional< global_planning::GlobalPlannerResult > &result, const algorithms::Costmap &costmap, const float plannedSeconds)
Measure a planning result against the costmap.
algorithms::SceneBounds makeCorridor(analysis::Scene &scene, const CorridorParameters ¶meters)
A dead-end corridor opening into the rest of the room.
float makeSerpentine(analysis::Scene &scene, const SerpentineParameters ¶meters, const float robotRadius)
Replace the room's contents with transverse walls whose openings alternate sides.
void writeRecord(std::ostream &out, const Record &record)
One JSON object per line, so a sweep streams to disk and can be diffed across revisions.
@ NearObstacle
Start and goal drawn from cells whose clearance sits just above zero, so the poses hug obstacle edges...
@ CorridorExit
Start inside a narrow corridor, goal out in the open.
@ Location
Pairs of poses from the room's navigation graph – the requests the robot really receives here.
@ Uniform
Start and goal drawn uniformly from the cells in open space, i.e.
RoomScene buildRoomScene(const std::filesystem::path &priorKnowledge, const std::string &room, const std::vector< std::string > &sceneFiles, const float wallThickness)
Build the scene for a room from PriorKnowledgeData.
int addClutter(analysis::Scene &scene, const ClutterParameters ¶meters, std::mt19937 &rng)
Scatter boxes through the room to create tight passages.
This file is part of ArmarX.
const char * toString(InteractionFeedbackType type)
bool empty(const std::string &s)
float cellSize
How big each cell is in the uniform grid.
float yaw
Rotation about the box centre [rad].
Everything the application needs, as read from the scene description file.
core::GeneralConfig generalConfig
global_planning::SPFAParams plannerParams
algorithms::Costmap::Parameters costmapParams
algorithms::SceneBounds bounds
std::vector< Box > obstacles
What one planning request produced.
int index
Index within the requests of the same kind, so a record can be traced back to its inputs.
The room as the planner sees it, assembled from PriorKnowledgeData.
std::vector< NamedPose > locations
Poses from navigation-graphs/<room>/locations.json.
std::optional< algorithms::SceneBounds > corridor
Set when the scene is a corridor, so requests can be drawn as exits from it.
analysis::Scene scene
The first roomWallCount obstacles are always the room walls, which is what lets the synthetic scenes ...
std::vector< std::string > importWarnings
Import problems: objects and locations dropped, and objects kept with an approximated footprint.
float nearObstacleMinClearance
Clearance band [mm] a cell must fall in to count as "near an obstacle".
float nearObstacleMaxClearance
Upper bound of that band, and lower bound of the Uniform category.
bool positionSmoothingApplied
Whether the position smoothing stage ran and its result was adopted.
core::GlobalTrajectory trajectory
float resampleDistance
Spacing [mm] the planned path is resampled to before smoothing.
bool enablePositionSmoothing
Diagnostic switches for the post-processing stages.