How Open Apero Works#
Open Apero’s design on one page: the problem it solves, its vocabulary, who owns what, one planning cycle and how a YAML file becomes a running planner. The module pages then take each part in turn.
The Problem#
Open Apero plans for active perception: given a robot, a map and a measure of information gain, it repeatedly chooses where to move so that the robot gathers the most useful observations within a cost budget (the formulation). It works in a receding horizon: each cycle it grows a graph of candidate viewpoints around the robot, estimates each one’s gain by simulating the sensor in the current map, prices each move (such as in seconds of trajectory time), picks the best path by a selection objective (Path Planners and Objectives), executes only its first edge and plans again.
Glossary#
Term |
Meaning |
|---|---|
node |
A candidate viewpoint: a |
node cluster |
The nodes that share one sensor position, one per orientation. |
edge |
A traversable trajectory between two nodes, priced by the agent’s cost computer. |
path |
A chain of edges starting at the robot’s node. Only its first edge is executed. |
step |
The edge a layer offers in one cycle, with its start and target node. |
gain |
What a node is expected to observe; a sum of gain anchors ( |
frontier node |
A node that looks into enough unexplored space; |
episode |
One run from a start state until the budget ( |
Who Owns What#
flowchart TB
Agent["Agent"] --> Shared["map, robot,<br/>cost_computer"]
Agent --> Planning["Planning layer"]
Agent --> Backtracking["Backtracking layer"]
Planning --> Gen["generator"]
Planning --> Eval["evaluator<br/>+ sensor_model"]
Planning --> Path["path_planner"]
Backtracking --> Back["backtracker"]
The agent owns the map, the robot model and the cost computer, which every layer and plugin reaches through
AgentInterface; one cost computer prices every edge, so one budget covers graph planning and recovery alike. The
agent builds one layer per key of the layers dictionary, and each layer owns its plugins and its own candidates.
Every role except the agent is a plugin:
Role |
Responsibility |
Registered types |
Module page |
|---|---|---|---|
|
Occupancy, distance field, integrated frames |
|
|
|
Kinematic limits, traversability, orientation sampling, steering |
|
|
|
Edge cost, one per agent |
|
|
|
One planning behavior with its own candidates |
|
|
|
Candidate nodes and the edges between them |
|
|
|
Gain and frontier flag of a candidate |
|
|
|
Simulated view from a pose |
|
|
|
Next path over the evaluated graph |
|
|
|
Recovery trajectory |
|
The paper calls the path planner the selector, and the generators RRT and RRTStar RRAT and RRAT*.
One Planning Cycle#
flowchart LR
Settle["1. Settle"] --> Absorb["2. Absorb"] --> Grow["3. Grow"] --> Choose["4. Choose"] --> Settle
Each call of Agent::loopIteration() returns at once until the robot has settled, then finishes the cycle;
Agent and Execution has the details:
Settle. The sensor pose must be within
target_position_thresholdandtarget_angle_thresholdof the dispatched edge’s target and, withwait_for_map_updated(shippedtrue), the map must have integrated a frame there.Absorb. The executed edge goes into
HistoryData, every layer takes the reached state (PlanningLayermoves its start node) and the edge’s cost is charged to the budget. A spent budget or a goal node ends the episode.Grow. The robot model and every layer update against the new map, then each layer adds candidates (
PlanningLayerrunsmax_expansionsrounds of sample, connect, evaluate and price).Choose. The agent asks the layers for a step in descending priority, and the first offer is sent to the robot; no offer ends the episode.
PlanningLayersits at0,BacktrackingLayerat-1000, promoted to1000during its initial sweep or while the robot stands in untraversable space (Layers).
From YAML to a Running Planner#
Each concrete class registers itself with the PluginRegistry under a name, and the YAML block of its role selects
it with type:. Swapping a planner, a gain measure or a robot model is therefore a configuration change; adding one
is a class, a registration line and a build entry (Extending Open Apero). The block’s other scalar
keys are the plugin’s parameters, a nested block is a child plugin named after its role, and a <role>_args key
points that child at another namespace instead:
# abridged from config/planners/nbs.yaml
layers:
planning: # the layer's instanceName()
type: "PlanningLayer" # selects the class
max_expansions: 50 # a parameter
generator: # a child plugin
type: "RRAG"
evaluator_args: "/task_evaluator" # the task's evaluator, shared by every planner
A plugin warns about keys it did not read (strict_params: true stops the start instead). See
How Parameters Are Read and
Configuration Files.
Where Things Live#
In the ROS-free core, open_apero_core, each role’s plugins sit in plugin/<role>/, the loop in agent/, the
shared types in graph/, state/, geometry/ and gain/, and the run outputs in logging/. Around it sit
open_apero_voxblox (map), open_apero_ros (node, launch files, configs), open_apero_msgs and
open_apero_ros_bridge (messages), open_apero_habitat and open_apero_controllers (simulation),
open_apero_analysis (run loading) and open_apero_graph_benchmark (offline benchmark).