Sampling.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
17#include "Sampling.h"
18
19#include <cmath>
20#include <limits>
21
22#include <Eigen/Geometry>
23
25
27{
28 namespace
29 {
31 poseAt(const Eigen::Vector2f& position, const float yaw)
32 {
33 core::Pose pose = core::Pose::Identity();
34 pose.translation() = Eigen::Vector3f{position.x(), position.y(), 0.F};
35 pose.linear() = Eigen::AngleAxisf{yaw, Eigen::Vector3f::UnitZ()}.toRotationMatrix();
36 return pose;
37 }
38
39 /// All cell centres whose clearance falls in [lo, hi]. Collected once and sampled from
40 /// by index: rejection sampling on a band this narrow would spend most of its attempts
41 /// missing, and the band is the whole point of the near-obstacle category.
42 std::vector<Eigen::Vector2f>
43 collectCells(const algorithms::Costmap& costmap, const float lo, const float hi)
44 {
45 std::vector<Eigen::Vector2f> cells;
46
47 for (int ix = 0; ix < costmap.getGrid().rows(); ix++)
48 {
49 for (int iy = 0; iy < costmap.getGrid().cols(); iy++)
50 {
52 if (not costmap.isValid(index))
53 {
54 continue;
55 }
56
57 const float value = costmap.getGrid()(ix, iy);
58 if (value >= lo and value <= hi)
59 {
60 cells.push_back(costmap.toPositionGlobal(index));
61 }
62 }
63 }
64
65 return cells;
66 }
67 } // namespace
68
69 std::string
71 {
72 switch (kind)
73 {
75 return "uniform";
77 return "near_obstacle";
79 return "location";
81 return "corridor_exit";
82 }
83
84 return "unknown";
85 }
86
87 std::vector<Request>
89 const int count,
90 const algorithms::Costmap& costmap,
91 const RoomScene& room,
92 const SamplingParameters& parameters,
93 std::mt19937& rng)
94 {
95 std::vector<Request> requests;
96 requests.reserve(static_cast<std::size_t>(count));
97
98 std::uniform_real_distribution<float> yawDistribution{-M_PIf32, M_PIf32};
99
100 if (kind == RequestKind::Location)
101 {
102 // Several named locations are manipulation poses set right at a worktop, so the
103 // robot's own footprint puts them inside an obstacle in the *planning* costmap.
104 // Those are requests for the recovery path, not for the planner proper, and
105 // mixing them in would report recovery behaviour as a planning failure rate.
106 std::vector<NamedPose> reachable;
107 for (const NamedPose& location : room.locations)
108 {
109 const Eigen::Vector2f position = location.pose.translation().head<2>();
110 const auto clearance = costmap.value(position);
111
112 if (clearance.has_value() and clearance.value() > 0.F)
113 {
114 reachable.push_back(location);
115 }
116 else
117 {
118 ARMARX_WARNING << "Location `" << location.name
119 << "` is inside an obstacle for a robot of this radius; "
120 "excluded from the location category.";
121 }
122 }
123
124 if (reachable.size() < 2)
125 {
126 ARMARX_WARNING << "Fewer than two reachable named locations; skipping this "
127 "category.";
128 return requests;
129 }
130
131 std::uniform_int_distribution<std::size_t> pick{0, reachable.size() - 1};
132
133 for (int index = 0; index < count; index++)
134 {
135 std::size_t from = pick(rng);
136 std::size_t to = pick(rng);
137 for (int attempt = 0; attempt < parameters.maxAttempts and to == from; attempt++)
138 {
139 to = pick(rng);
140 }
141
142 // The graph's own poses, orientation included -- resampling the yaw would
143 // discard the approach direction that makes these requests realistic.
144 requests.push_back({.kind = kind,
145 .index = index,
146 .start = reachable.at(from).pose,
147 .goal = reachable.at(to).pose,
148 .startName = reachable.at(from).name,
149 .goalName = reachable.at(to).name});
150 }
151
152 return requests;
153 }
154
155 if (kind == RequestKind::CorridorExit)
156 {
157 if (not room.corridor.has_value())
158 {
159 ARMARX_WARNING << "No corridor in this scene; skipping the corridor-exit "
160 "category.";
161 return requests;
162 }
163
164 const auto& corridor = room.corridor.value();
165
166 // Anything inside the corridor the robot actually fits in, and anything outside it
167 // that is clear. The corridor's own cells are what makes this hard: in a 900 mm
168 // channel a 350 mm robot has 100 mm to either side, so the start is pinned with
169 // almost no room.
170 std::vector<Eigen::Vector2f> inside;
171 std::vector<Eigen::Vector2f> outside;
172
173 for (int ix = 0; ix < costmap.getGrid().rows(); ix++)
174 {
175 for (int iy = 0; iy < costmap.getGrid().cols(); iy++)
176 {
177 const algorithms::Costmap::Index index{ix, iy};
178 if (not costmap.isValid(index) or costmap.getGrid()(ix, iy) <= 0.F)
179 {
180 continue;
181 }
182
183 const Eigen::Vector2f p = costmap.toPositionGlobal(index);
184
185 if (contains(corridor, p))
186 {
187 inside.push_back(p);
188 }
189 else if (p.y() > corridor.max.y() + 500.F)
190 {
191 // Clear of the mouth, so the request is a genuine exit rather than a
192 // step across the threshold.
193 outside.push_back(p);
194 }
195 }
196 }
197
198 if (inside.empty() or outside.empty())
199 {
200 ARMARX_WARNING << "Corridor holds " << inside.size() << " free cell(s) and "
201 << outside.size()
202 << " outside it; a robot of this radius does not fit. Skipping "
203 "the corridor-exit category.";
204 return requests;
205 }
206
207 ARMARX_INFO << "corridor_exit: " << inside.size() << " cells inside the corridor, "
208 << outside.size() << " outside.";
209
210 std::uniform_int_distribution<std::size_t> pickInside{0, inside.size() - 1};
211 std::uniform_int_distribution<std::size_t> pickOutside{0, outside.size() - 1};
212
213 for (int index = 0; index < count; index++)
214 {
215 const Eigen::Vector2f start = inside.at(pickInside(rng));
216 const Eigen::Vector2f goal = outside.at(pickOutside(rng));
217
218 // Start heading points along the corridor, as it would if the robot had driven
219 // in: a start facing a wall would make this a test of the orientation stage.
220 const float startYaw = std::atan2(goal.y() - start.y(), goal.x() - start.x());
221
222 requests.push_back({.kind = kind,
223 .index = index,
224 .start = poseAt(start, startYaw),
225 .goal = poseAt(goal, yawDistribution(rng)),
226 .startName = {},
227 .goalName = {}});
228 }
229
230 return requests;
231 }
232
233 const float lo = (kind == RequestKind::NearObstacle) ? parameters.nearObstacleMinClearance
234 : parameters.nearObstacleMaxClearance;
235 const float hi = (kind == RequestKind::NearObstacle) ? parameters.nearObstacleMaxClearance
236 : std::numeric_limits<float>::max();
237
238 const std::vector<Eigen::Vector2f> cells = collectCells(costmap, lo, hi);
239
240 if (cells.size() < 2)
241 {
242 ARMARX_WARNING << "Only " << cells.size() << " cell(s) in the clearance band [" << lo
243 << ", " << hi << "] mm; skipping the " << toString(kind) << " category.";
244 return requests;
245 }
246
247 ARMARX_INFO << toString(kind) << ": sampling from " << cells.size()
248 << " cells with clearance in [" << lo << ", " << hi << "] mm.";
249
250 std::uniform_int_distribution<std::size_t> pick{0, cells.size() - 1};
251
252 for (int index = 0; index < count; index++)
253 {
254 bool found = false;
255
256 for (int attempt = 0; attempt < parameters.maxAttempts and not found; attempt++)
257 {
258 const Eigen::Vector2f start = cells.at(pick(rng));
259 const Eigen::Vector2f goal = cells.at(pick(rng));
260
261 if ((goal - start).norm() < parameters.minSeparation)
262 {
263 continue;
264 }
265
266 requests.push_back({.kind = kind,
267 .index = index,
268 .start = poseAt(start, yawDistribution(rng)),
269 .goal = poseAt(goal, yawDistribution(rng)),
270 .startName = {},
271 .goalName = {}});
272 found = true;
273 }
274
275 if (not found)
276 {
277 ARMARX_WARNING << "Gave up drawing a " << toString(kind) << " request at least "
278 << parameters.minSeparation << " mm long after "
279 << parameters.maxAttempts << " attempts.";
280 break;
281 }
282 }
283
284 return requests;
285 }
286
287} // namespace armarx::navigation::evaluation
#define lo(x)
#define hi(x)
uint8_t index
Position toPositionGlobal(const Index &index) const
Definition Costmap.cpp:75
bool isValid(const Index &index) const noexcept
checks whether the cell is masked out
Definition Costmap.cpp:82
std::optional< float > value(const Index &index) const
Definition Costmap.cpp:541
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:179
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:191
Eigen::Isometry3f Pose
Definition basic_types.h:31
This file is part of ArmarX.
Definition Metrics.cpp:30
std::vector< Request > sampleRequests(const RequestKind kind, const int count, const algorithms::Costmap &costmap, const RoomScene &room, const SamplingParameters &parameters, std::mt19937 &rng)
Draw count requests of the given kind.
Definition Sampling.cpp:88
std::string toString(const RequestKind kind)
Definition Sampling.cpp:70
bool contains(const algorithms::SceneBounds &bounds, const Eigen::Vector2f &point)
Whether a point lies within the bounds, edges included.
Definition RoomScene.cpp:99
@ NearObstacle
Start and goal drawn from cells whose clearance sits just above zero, so the poses hug obstacle edges...
Definition Sampling.h:40
@ CorridorExit
Start inside a narrow corridor, goal out in the open.
Definition Sampling.h:55
@ Location
Pairs of poses from the room's navigation graph – the requests the robot really receives here.
Definition Sampling.h:44
@ Uniform
Start and goal drawn uniformly from the cells in open space, i.e.
Definition Sampling.h:36
This file is part of ArmarX.
Definition constants.cpp:4
std::shared_ptr< Value > value()
Definition cxxopts.hpp:855
double norm(const Point &a)
Definition point.hpp:102
A pose from the room's navigation graph, kept with its name so a failing request can be reported as s...
Definition RoomScene.h:38
The room as the planner sees it, assembled from PriorKnowledgeData.
Definition RoomScene.h:49
std::vector< NamedPose > locations
Poses from navigation-graphs/<room>/locations.json.
Definition RoomScene.h:55
std::optional< algorithms::SceneBounds > corridor
Set when the scene is a corridor, so requests can be drawn as exits from it.
Definition RoomScene.h:62
float minSeparation
Minimum straight-line start-to-goal distance [mm].
Definition Sampling.h:95
int maxAttempts
Attempts per request before giving up on a category.
Definition Sampling.h:98
float nearObstacleMinClearance
Clearance band [mm] a cell must fall in to count as "near an obstacle".
Definition Sampling.h:88
float nearObstacleMaxClearance
Upper bound of that band, and lower bound of the Uniform category.
Definition Sampling.h:91