Skip to content

Commit bb5ae85

Browse files
authored
Clean up gradient based traversability estimation (#149)
* optionally add height information to traversability voxel for visualization * clean up by factor out some helper functions. * factor out common traversability classification. * Add base config to traversability * use some strcuture form hydra_ros to merge the two gradient computation. * Pass additional tsdf_layer_ to the sink to let it figure out the active window size. * remove from protected
1 parent d9a5302 commit bb5ae85

3 files changed

Lines changed: 268 additions & 241 deletions

File tree

include/hydra/places/traversability_estimator.h

Lines changed: 72 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -34,23 +34,73 @@
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

4648
namespace 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+
4886
class 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
*/
77137
class 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
*/
125173
class 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

189219
void declare_config(GradientTraversabilityEstimator::Config& config);

include/hydra/places/traversability_layer.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ struct TraversabilityVoxel {
5252
//! @brief Confidence in the traversability value in [0, 1].
5353
float confidence = 0.0f;
5454

55+
//! @brief The height of the surface in meters in global coordinate, used for
56+
//! debugging and visualization.
57+
std::optional<float> height = 0.0f;
58+
5559
//! @brief Discrete traversability state for of the voxel, computed as a function of
5660
// traversability and confidence.
5761
spark_dsg::TraversabilityState state = spark_dsg::TraversabilityState::UNKNOWN;

0 commit comments

Comments
 (0)