filters¶
Role: Cluster a stream of TFs (or Poses) into a single stable output,
plus a handful of detection-projection / labelling nodes. Hosts the
cluster_tf action and service that every BT mission depends on. The
torpedo and bin trees both push their YOLO/template TFs through here to
get a stable map-frame target before approaching it.
The ROS package name is bb_filters (the repo is named filters).
Build & layout (upstream cleanup, 2026-05)¶
bb_filters was restructured from ament_cmake → ament_python and the
default branch was renamed master → main:
- All runtime nodes moved from
scripts/cluster/→bb_filters/nodes/cluster/. - Cluster helpers moved from
bb_filters/utils/cluster.py→bb_filters/utils/cluster/cluster.pyand gained aget_largest_clusterhelper (the old_get_idxs_in_largest_clusterprivate staticmethod is gone). - Legacy detection nodes (
detected_object_3d_filter.py,sort_3d.py, most of the RobotX detectors) are now underbb_filters/archive/and not installed. - C++ visualisation binaries (
detected_object_2d_vis, etc.) and theCMakeLists.txtare gone.
setup.py declares its entries via scripts=[…] (not console_scripts),
so the installed ROS-2 executable names are unchanged. Anything that
launches cluster_tf_action_server.py still works. Build type flipped
though, so a full clean before the first build after pulling:
rm -rf build/bb_filters install/bb_filters
colcon build --symlink-install --packages-select bb_filters
Entry points (installed scripts)¶
| Executable | Role |
|---|---|
cluster_tf_action_server.py |
Async action handler for ClusterTfAction (/<ns>/cluster_tf). |
cluster_tf_service_server.py |
Sync service handler for ClusterTfSrv (/<ns>/cluster_tfs_srv). |
cluster_tf_multi_action_server.py |
Multi-source action variant. |
cluster_tf_multi_service_server.py |
Multi-source service variant. |
cluster_slalom_tfs_node.py |
RobotX-style slalom clustering. |
cluster_poses_action_node.py / _node.py / _service_node.py |
New parallel Pose API (see below). |
detected_object_2d_filter_projection.py |
2D-detection projection cleanup. |
detected_object_3d_labelling.py |
Label 3D detections from a TF tree. |
The two clustering APIs¶
There are now two parallel cluster APIs in this package. Our BT missions use the TF-based one; the Pose-based one is new and not consumed by any BlueROV mission yet.
TF-based (existing. What BTs use)¶
- Action:
bb_perception_msgs/ClusterTfActionon<ns>/cluster_tf. - Service:
bb_perception_msgs/ClusterTfSrvon<ns>/cluster_tfs_srv. - Server polls the TF buffer at ~12 Hz for each
input_child_frame_id, buffers samples forcollection_duration, runs HDBSCAN on the positions, broadcasts the centroid of the largest cluster asparent → out_child_frame_id. - BlueROV missions build goals through a small helper that defaults
out_parents="map"(so clustered TFs land in map/ENU rather than NED).
Pose-based (new, 2026-05)¶
- Service:
bb_perception_msgs/ClusterPosesSrv. - Action:
bb_perception_msgs/ClusterPosesAction(rewritten to useClusterPosesRequest+ClusterPoseResultArray). - Consumes a stream of poses on a topic rather than TFs, with
max_detection_age_sfiltering. ClusterSpikeStatus.msgwas removed in the same cleanup.
ROS interfaces (used by our missions)¶
| Direction | Topic / Action / Service |
|---|---|
| Action server | /bluerov/cluster_tf (ClusterTfAction) |
| Service server | /bluerov/cluster_tfs_srv (ClusterTfSrv) |
| TF input (sub) | Whatever frames mission code passes as input_child_frame_ids. E.g. torpedo/yolo, Task04_Tagging_01_optical, bin/yolo, Task03_DropBRUVS_optical. |
| TF output (pub) | The clustered frames, e.g. torpedo_1, bin/yolo/clustered. |
The bin and torpedo BTs both go through these via
bluerov_cluster.launch.py. See the Architecture
page for where this sits in the stack.
Notes & gotchas¶
use_sim_time is required
The cluster_tf servers must be started with use_sim_time: True
(bluerov_cluster.launch.py already does this). Without it, the
server captures wall time at goal-start while incoming TFs are
stamped with Gazebo sim time. Every TF then looks "decades old"
and gets dropped as stale.
Source frame must actually exist
cluster_tf only reads the TF tree, never poses. If the BT hands
it an input_child_frame_id that has no broadcaster, you get a
tf2.LookupException loop and 0 valid transforms. Make sure a
pose estimator (e.g. points_pose_estimator_node, bin_pose_estimator_node,
torpedo_pose_estimator_node) is publishing the source frame before
the BT asks for it.
MultiThreadedExecutor removed
The 2026-05 cleanup ripped out MultiThreadedExecutors from the
cluster nodes. Single-threaded performs better in practice.
See also¶
- pose_estimator: broadcasts the source TFs consumed by cluster_tf.
- bb_msgs: owns
ClusterTfAction,ClusterTfSrv, and the newClusterPoses*messages. - Bin mission, Torpedo mission: primary consumers.