Skip to content

Commit c198a95

Browse files
committed
maxGapSplit
Signed-off-by: Tony Najjar <[email protected]>
1 parent 18f0810 commit c198a95

2 files changed

Lines changed: 60 additions & 10 deletions

File tree

nav2_mppi_controller/include/nav2_mppi_controller/tools/trajectory_visualizer.hpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,13 +108,15 @@ class TrajectoryVisualizer
108108
* @param trajectory_idx Row index into the trajectories arrays
109109
* @param trajectories Trajectory data
110110
* @param normalized_cost Cost value in [0, 1] range
111+
* @param is_outlier Whether this trajectory is above the percentile threshold
111112
* @param ns Marker namespace
112113
* @param stamp Timestamp
113114
*/
114115
void addCostColoredTrajectory(
115116
size_t trajectory_idx,
116117
const models::Trajectories & trajectories,
117118
float normalized_cost,
119+
bool is_outlier,
118120
const std::string & ns,
119121
const builtin_interfaces::msg::Time & stamp);
120122

@@ -125,6 +127,19 @@ class TrajectoryVisualizer
125127
*/
126128
static std_msgs::msg::ColorRGBA costToColor(float normalized);
127129

130+
/**
131+
* @brief Maximum gap clustering (k=2). Splits sorted 1D data into two groups
132+
* by finding the largest consecutive gap. Returns the upper bound of the
133+
* lower group and whether a significant split was found.
134+
* @param values Input array (will be copied and sorted internally)
135+
* @param gap_ratio_threshold Minimum gap-to-range ratio to consider significant
136+
* @param[out] upper_bound Upper bound of the lower (non-outlier) group
137+
* @return True if a significant gap was found
138+
*/
139+
static bool maxGapSplit(
140+
const Eigen::ArrayXf & values, float gap_ratio_threshold,
141+
float & upper_bound);
142+
128143
std::string frame_id_;
129144
nav2::Publisher<visualization_msgs::msg::MarkerArray>::SharedPtr
130145
trajectories_publisher_;

nav2_mppi_controller/src/trajectory_visualizer.cpp

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -123,20 +123,21 @@ void TrajectoryVisualizer::add(
123123
return;
124124
}
125125

126-
// Normalize total costs using Eigen vectorized ops
126+
// Normalize costs for visualization. Outliers (e.g. collision trajectories,
127+
// detected via max-gap clustering) render in dark purple instead of the gradient.
127128
auto addCostLayer = [&](const Eigen::ArrayXf & layer_costs, const std::string & ns) {
128129
float min_val = layer_costs.minCoeff();
129130
float max_val = layer_costs.maxCoeff();
130-
float range = max_val - min_val;
131-
Eigen::ArrayXf normalized;
132-
if (range > 0.0f) {
133-
normalized = (layer_costs - min_val) / range;
134-
} else {
135-
normalized = Eigen::ArrayXf::Zero(layer_costs.size());
136-
}
131+
132+
float upper_bound = max_val;
133+
bool has_outliers = maxGapSplit(layer_costs, 0.5f, upper_bound);
134+
float range = (has_outliers ? upper_bound : max_val) - min_val;
137135

138136
for (size_t i = 0; i < n_rows; i += trajectory_step_) {
139-
addCostColoredTrajectory(i, trajectories, normalized(i), ns, stamp);
137+
float norm = (range > 0.0f) ?
138+
(layer_costs(i) - min_val) / range : 0.0f;
139+
bool is_outlier = has_outliers && layer_costs(i) > upper_bound;
140+
addCostColoredTrajectory(i, trajectories, norm, is_outlier, ns, stamp);
140141
}
141142
};
142143

@@ -153,6 +154,7 @@ void TrajectoryVisualizer::addCostColoredTrajectory(
153154
size_t trajectory_idx,
154155
const models::Trajectories & trajectories,
155156
float normalized_cost,
157+
bool is_outlier,
156158
const std::string & ns,
157159
const builtin_interfaces::msg::Time & stamp)
158160
{
@@ -168,7 +170,9 @@ void TrajectoryVisualizer::addCostColoredTrajectory(
168170
marker.action = Marker::ADD;
169171
marker.pose.orientation.w = 1.0;
170172
marker.scale.x = 0.01; // line width
171-
marker.color = costToColor(normalized_cost);
173+
marker.color = is_outlier ?
174+
utils::createColor(0.5f, 0.0f, 0.5f, 0.6f) : // dark purple for outliers
175+
costToColor(normalized_cost);
172176

173177
marker.points.reserve(n_cols / time_step_ + 1);
174178
for (size_t j = 0; j < n_cols; j += time_step_) {
@@ -197,6 +201,37 @@ std_msgs::msg::ColorRGBA TrajectoryVisualizer::costToColor(float normalized)
197201
return utils::createColor(r, g, 0.0f, 0.8f);
198202
}
199203

204+
bool TrajectoryVisualizer::maxGapSplit(
205+
const Eigen::ArrayXf & values, float gap_ratio_threshold,
206+
float & upper_bound)
207+
{
208+
if (values.size() < 2) {
209+
return false;
210+
}
211+
Eigen::ArrayXf sorted = values;
212+
std::sort(sorted.data(), sorted.data() + sorted.size());
213+
float total_range = sorted(sorted.size() - 1) - sorted(0);
214+
if (total_range <= 0.0f) {
215+
return false;
216+
}
217+
218+
float largest_gap = 0.0f;
219+
Eigen::Index gap_idx = 0;
220+
for (Eigen::Index k = 1; k < sorted.size(); ++k) {
221+
float gap = sorted(k) - sorted(k - 1);
222+
if (gap > largest_gap) {
223+
largest_gap = gap;
224+
gap_idx = k;
225+
}
226+
}
227+
228+
if (largest_gap > gap_ratio_threshold * total_range) {
229+
upper_bound = sorted(gap_idx - 1);
230+
return true;
231+
}
232+
return false;
233+
}
234+
200235
void TrajectoryVisualizer::reset()
201236
{
202237
marker_id_ = 0;

0 commit comments

Comments
 (0)