3434 * -------------------------------------------------------------------------- */
3535#pragma once
3636
37+ #include < hydra/common/output_sink.h>
3738#include < kimera_pgmo/mesh_delta.h>
3839#include < spark_dsg/dynamic_scene_graph.h>
3940
4041#include < memory>
42+ #include < optional>
4143
4244#include " hydra/active_window/active_window_output.h"
4345#include " hydra/places/traversability_layer.h"
4446#include " hydra/reconstruction/voxel_types.h"
4547
4648namespace hydra ::places {
4749
50+ struct GradientInfo {
51+ float gradient = 0 .0f ; // mean gradient magnitude
52+ float confidence = 0 .0f ; // num_neighbors / 8.0
53+ };
54+
55+ using HeightMap = Index2DMap<float >;
56+ using GradientMap = Index2DMap<GradientInfo>;
57+
58+ // Scan one vertical column for the highest surface voxel within [min_z, max_z].
59+ // Uses block/local index access to avoid the signed/unsigned division bug in
60+ // spatial_hash::blockIndexFromGlobalIndex for negative world coordinates.
61+ // Returns the voxel height if found (weight >= min_weight && distance < voxel_size).
62+ std::optional<float > extractSurfaceHeight (const TsdfLayer& layer,
63+ const BlockIndex& block_2d_index,
64+ const VoxelIndex& local_2d,
65+ float min_z,
66+ float max_z,
67+ float min_weight);
68+
69+ // Extract terrain surface heights from TSDF within a vertical window around robot_z.
70+ HeightMap extractHeightMap (const TsdfLayer& layer,
71+ const BlockIndexSet& blocks_2d,
72+ float robot_z,
73+ float height_below,
74+ float height_above,
75+ float min_weight);
76+
77+ // Box-filter smoothing to reduce projective TSDF radial bias (ripple artifact).
78+ HeightMap smoothHeightMap (const HeightMap& height_map);
79+
80+ // Compute mean 8-way gradient magnitude and neighbor confidence for each cell.
81+ GradientMap computeGradientMap (const HeightMap& height_map, float voxel_size);
82+
83+ // Linear interpolation: 1.0 at gradient=0, 0.0 at gradient=threshold.
84+ float computeTraversabilityFromGradient (float gradient, float gradient_threshold);
85+
4886class TraversabilityEstimator {
4987 public:
5088 using Ptr = std::shared_ptr<TraversabilityEstimator>;
5189 using ConstPtr = std::shared_ptr<const TraversabilityEstimator>;
90+ struct Config {
91+ // ! @brief Minimum confidence for a voxel to be considered observed.
92+ float min_confidence = 1 .0f ;
5293
53- TraversabilityEstimator () = default ;
94+ // ! @brief Minimum traversability for a voxel to be considered traversable.
95+ float min_traversability = 1 .0f ;
96+
97+ // ! @brief If true, mark voxels as intraversable if they do not meet the
98+ // ! min_traversability threshold, even if the confidence is below min_confidence. If
99+ // ! false, mark these voxels as unknown instead.
100+ bool pessimistic = true ;
101+ } const config;
102+
103+ explicit TraversabilityEstimator (const Config& config) : config(config) {}
54104 virtual ~TraversabilityEstimator () = default ;
55105
56106 /* *
@@ -64,10 +114,20 @@ class TraversabilityEstimator {
64114 return *traversability_layer_;
65115 }
66116
117+ /* *
118+ * @brief Classify a traversability voxel based on its confidence and traversability.
119+ * @note Default implementation uses min_confidence_, min_traversability_, and
120+ * pessimistic_ set by derived class constructors. Subclasses may override for custom
121+ * logic.
122+ */
123+ virtual void classifyTraversabilityVoxel (TraversabilityVoxel& voxel) const ;
124+
67125 protected:
68126 std::unique_ptr<TraversabilityLayer> traversability_layer_;
69127};
70128
129+ void declare_config (TraversabilityEstimator::Config& config);
130+
71131/* *
72132 * @brief Simple traversability estimator which checks a specified volume in the TSDF
73133 * map for obstacles.
@@ -76,23 +136,12 @@ class TraversabilityEstimator {
76136 */
77137class HeightTraversabilityEstimator : public TraversabilityEstimator {
78138 public:
79- struct Config {
139+ struct Config : public TraversabilityEstimator ::Config {
80140 // ! @brief The height above the robot body to consider for traversability in meters.
81141 float height_above = 0 .5f ;
82142
83143 // ! @brief The height below the robot body to consider for traversability in meters.
84144 float height_below = 0 .5f ;
85-
86- // ! @brief Minimum confidence for a voxel to be considered observed.
87- float min_confidence = 1 .0f ;
88-
89- // ! @brief Minimum traversability for a voxel to be considered traversable.
90- float min_traversability = 1 .0f ;
91-
92- // ! @brief If true, mark voxels as intraversable if they do not meet the
93- // ! min_traversability threshold, even if the confidence is below min_confidence. If
94- // ! false, mark these voxels as unknown instead.
95- bool pessimistic = true ;
96145 };
97146
98147 HeightTraversabilityEstimator (const Config& config);
@@ -108,7 +157,6 @@ class HeightTraversabilityEstimator : public TraversabilityEstimator {
108157 // Processing steps.
109158 void updateTsdf (const ActiveWindowOutput& msg);
110159 void computeTraversability (const ActiveWindowOutput& msg);
111- void classifyTraversabilityVoxel (TraversabilityVoxel& voxel) const ;
112160
113161 // Helper functions.
114162 BlockIndexSet get2DBlockIndices (const BlockIndices& blocks) const ;
@@ -124,7 +172,12 @@ void declare_config(HeightTraversabilityEstimator::Config& config);
124172 */
125173class GradientTraversabilityEstimator : public TraversabilityEstimator {
126174 public:
127- struct Config {
175+ using Sink = hydra::OutputSink<const HeightMap&,
176+ const GradientMap&,
177+ const ActiveWindowOutput&,
178+ const TsdfLayer&>;
179+
180+ struct Config : public TraversabilityEstimator ::Config {
128181 // ! @brief Maximum traversable gradient (m/m). Gradient >= threshold →
129182 // ! traversability = 0. Gradient = 0 → traversability = 1. Linear interpolation
130183 // ! between.
@@ -139,20 +192,12 @@ class GradientTraversabilityEstimator : public TraversabilityEstimator {
139192 // ! @brief Minimum TSDF weight to consider voxel observed.
140193 float min_weight = 1 .0e-6f ;
141194
142- // ! @brief Minimum confidence for a voxel to be considered observed.
143- float min_confidence = 0 .5f ;
144-
145- // ! @brief Minimum traversability for a voxel to be considered traversable.
146- float min_traversability = 0 .5f ;
147-
148- // ! @brief If true, mark voxels as intraversable if they do not meet the
149- // ! min_traversability threshold, even if the confidence is below min_confidence. If
150- // ! false, mark these voxels as unknown instead.
151- bool pessimistic = true ;
152-
153195 // ! @brief If true, smooth the height map with a box filter before computing
154196 // ! gradients, reducing the ripple artifact caused by projective TSDF radial bias.
155197 bool smoothing = true ;
198+
199+ // ! @brief Downstream consumers of the height map and gradient map.
200+ std::vector<Sink::Factory> sinks;
156201 };
157202
158203 GradientTraversabilityEstimator (const Config& config);
@@ -161,29 +206,14 @@ class GradientTraversabilityEstimator : public TraversabilityEstimator {
161206 void updateTraversability (const ActiveWindowOutput& msg) override ;
162207
163208 const Config config;
209+ Sink::List sinks_;
164210
165211 protected:
166212 TsdfLayer::Ptr tsdf_layer_;
167213
168214 // Processing steps (reuse updateTsdf from HeightTraversabilityEstimator).
169215 void updateTsdf (const ActiveWindowOutput& msg);
170216 void computeTraversability (const ActiveWindowOutput& msg);
171- void classifyTraversabilityVoxel (TraversabilityVoxel& voxel) const ;
172-
173- // Helper functions.
174- BlockIndexSet get2DBlockIndices (const BlockIndices& blocks) const ;
175-
176- std::optional<float > extractSurfaceHeight (const BlockIndex& block_2d_index,
177- const VoxelIndex& local_2d,
178- float robot_z) const ;
179-
180- float computeHorizontalDistance (const Index2D& offset) const ;
181-
182- float computeTraversabilityFromGradient (float gradient) const ;
183-
184- private:
185- // 8-way neighbor offsets.
186- static const std::array<Index2D, 8 > kNeighborOffsets ;
187217};
188218
189219void declare_config (GradientTraversabilityEstimator::Config& config);
0 commit comments