Skip to content

Commit

Permalink
Add support for RViz-like colormaps (#13)
Browse files Browse the repository at this point in the history
Go from intermediate occupancy grid message like state (PR #11)
to something meaningful on the display.

Relates to: #7
  • Loading branch information
MichaelGrupp authored Feb 17, 2025
1 parent b9688ee commit 660d06d
Show file tree
Hide file tree
Showing 20 changed files with 363 additions and 56 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ egui_kittest = { version = "0.31.0", features = ["wgpu", "snapshot", "eframe"] }
egui_tiles = "0.12.0"
env_logger = "0.11.5"
fast_image_resize = { version = "5.1.1", features = ["image"] }
lazy_static = "1.5.0"
image = { version = "0.25.5", features = ["jpeg", "png", "pnm"] }
imageproc = "0.25.0"
log = "0.4.22 "
Expand Down
1 change: 1 addition & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub use crate::app_impl::pose_edit::PoseEditOptions;
pub use crate::app_impl::tint_settings::TintOptions;
pub use crate::grid_options::GridOptions;
pub use crate::lens::LensOptions;
pub use crate::value_colormap::ColorMap;

use crate::map_state::MapState;
use crate::meta::Meta;
Expand Down
30 changes: 27 additions & 3 deletions src/app_impl/tint_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};

use crate::app::AppState;
use crate::app_impl::constants::SPACE;
use crate::value_colormap::ColorMap;
use crate::value_interpretation::{Mode, Quirks, ValueInterpretation};

use crate::texture_request::NO_TINT;
Expand All @@ -20,6 +21,8 @@ pub struct TintOptions {
pub use_value_interpretation_for_all: bool,
#[serde(default, skip)]
pub value_interpretation_for_all: ValueInterpretation,
#[serde(default, skip)]
pub colormap_for_all: ColorMap,
}

impl default::Default for TintOptions {
Expand All @@ -31,6 +34,7 @@ impl default::Default for TintOptions {
color_to_alpha_for_all: None,
use_value_interpretation_for_all: false,
value_interpretation_for_all: ValueInterpretation::default(),
colormap_for_all: ColorMap::default(),
}
}
}
Expand Down Expand Up @@ -158,6 +162,7 @@ fn pick(
);
ui.checkbox(edit_value_interpretation, "");
if *edit_value_interpretation {
ui.end_row();
ui.end_row();
pick_value_interpretation(ui, value_interpretation);
}
Expand Down Expand Up @@ -190,7 +195,7 @@ fn pick_quirks(ui: &mut egui::Ui, quirks: &mut Quirks) {
ui.horizontal(|ui| {
ui.selectable_value(quirks, Quirks::Ros1Wiki, "ROS 1 Wiki")
.on_hover_text("Interpret values as documented in ROS 1 Wiki.");
ui.selectable_value(quirks, Quirks::Ros1MapServer, "ROS 1/2 map_server")
ui.selectable_value(quirks, Quirks::Ros1MapServer, "ROS map_server")
.on_hover_text("ROS 1/2 map_server behaves slightly differently than the Wiki :(");
// ROS 2 is left out because I assume it behaves like ROS 1 map_server.
});
Expand All @@ -212,11 +217,26 @@ fn pick_mode(ui: &mut egui::Ui, mode: &mut Mode) {
});
}

fn pick_colormap(ui: &mut egui::Ui, colormap: &mut ColorMap) {
ui.label("Coloring")
.on_hover_text("Select a colormap for the visualization.");
egui::ComboBox::from_label("")
.selected_text(colormap.to_string())
.show_ui(ui, |ui| {
ui.selectable_value(colormap, ColorMap::RvizMap, "RViz \"Map\"")
.on_hover_text("Classic RViz map coloring.");
ui.selectable_value(colormap, ColorMap::RvizCostmap, "RViz \"Costmap\"")
.on_hover_text("Classic RViz costmap coloring.");
ui.selectable_value(colormap, ColorMap::Raw, "Raw")
.on_hover_text("No coloring.");
ui.selectable_value(colormap, ColorMap::CoolCostmap, "Cool costmap")
.on_hover_text("Alternative costmap coloring with less screaming colors.");
});
}

fn pick_value_interpretation(ui: &mut egui::Ui, value_interpretation: &mut ValueInterpretation) {
pick_mode(ui, &mut value_interpretation.mode);
ui.end_row();
pick_quirks(ui, &mut value_interpretation.quirks);
ui.end_row();
ui.label("Free threshold")
.on_hover_text("Threshold for free space interpretation.");
ui.add(egui::Slider::new(&mut value_interpretation.free, 0.0..=1.0));
Expand All @@ -231,4 +251,8 @@ fn pick_value_interpretation(ui: &mut egui::Ui, value_interpretation: &mut Value
ui.label("Negate")
.on_hover_text("Negate the pixel interpretation.");
ui.checkbox(&mut value_interpretation.negate, "");
ui.end_row();
pick_colormap(ui, &mut value_interpretation.colormap);
ui.end_row();
pick_quirks(ui, &mut value_interpretation.quirks);
}
6 changes: 3 additions & 3 deletions src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,10 @@ pub fn color_to_alpha(img: &mut image::DynamicImage, color: Option<egui::Color32
}
}

pub fn add_alpha_if_needed(img: image::DynamicImage) -> image::DynamicImage {
pub fn to_rgba8(img: image::DynamicImage) -> image::DynamicImage {
match img.color() {
image::ColorType::L8 => image::DynamicImage::from(img.to_luma_alpha8()),
image::ColorType::La8 => img,
image::ColorType::L8 => image::DynamicImage::from(img.to_rgba8()),
image::ColorType::La8 => image::DynamicImage::from(img.to_rgba8()),
image::ColorType::Rgb8 => image::DynamicImage::from(img.to_rgba8()),
image::ColorType::Rgba8 => img,
_ => panic!("Unsupported color type: {:?}", img.color()),
Expand Down
9 changes: 7 additions & 2 deletions src/image_pyramid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::collections::HashMap;
use eframe::egui;
use log::{debug, trace};

use crate::image::{add_alpha_if_needed, fit_image};
use crate::image::{fit_image, to_rgba8};

// Side lengths used for the image pyramid levels.
// These shall correspond roughly to zoom levels w.r.t. original images.
Expand All @@ -18,12 +18,16 @@ pub struct ImagePyramid {
levels_by_size: HashMap<u32, image::DynamicImage>,
aspect_ratio: f32,
original_size: egui::Vec2,
pub original_has_alpha: bool,
}

impl ImagePyramid {
pub fn new(original: image::DynamicImage) -> ImagePyramid {
// Always add an alpha channel, if not present, to support our image operations.
let original = add_alpha_if_needed(original);
// DynamicImage allows conversions, but we do it once here for performance reasons.
// Use always RGBA8 internally.
let original_has_alpha = original.color().has_alpha();
let original = to_rgba8(original);

let original_size = egui::Vec2::new(original.width() as f32, original.height() as f32);
ImagePyramid {
Expand All @@ -49,6 +53,7 @@ impl ImagePyramid {
original,
aspect_ratio: original_size.x / original_size.y,
original_size,
original_has_alpha,
}
}

Expand Down
5 changes: 4 additions & 1 deletion src/lens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,10 @@ impl<'a> Lens<'a> {
}
let mut cropped_image = original_image.crop_imm(min_x, min_y, max_x - min_x, max_y - min_y);
color_to_alpha(&mut cropped_image, map.color_to_alpha);
map.meta.value_interpretation.apply(&mut cropped_image);
map.meta.value_interpretation.apply(
&mut cropped_image,
texture_state.image_pyramid.original_has_alpha,
);
let cropped_size = egui::vec2(cropped_image.width() as f32, cropped_image.height() as f32);

let overlay_texture_handle = ui.ctx().load_texture(
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ mod texture_request;
mod texture_state;
mod tiles;
mod tiles_behavior;
mod value_colormap;
mod value_interpretation;
14 changes: 2 additions & 12 deletions src/meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,18 +81,8 @@ impl From<MetaYamlAnnotated> for Meta {
resolution: meta_yaml.resolution,
origin_xy: emath::Vec2::new(meta_yaml.origin[0], meta_yaml.origin[1]),
origin_theta: emath::Rot2::from_angle(emath::normalized_angle(meta_yaml.origin[2])),
value_interpretation: ValueInterpretation::new(
meta_yaml.free_thresh,
meta_yaml.occupied_thresh,
meta_yaml.negate != 0,
meta_yaml.mode,
),
original_value_interpretation: ValueInterpretation::new(
meta_yaml.free_thresh,
meta_yaml.occupied_thresh,
meta_yaml.negate != 0,
meta_yaml.mode,
),
value_interpretation: ValueInterpretation::from_meta_yaml(meta_yaml),
original_value_interpretation: ValueInterpretation::from_meta_yaml(meta_yaml),
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/texture_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ impl TextureState {
);
color_to_alpha(&mut image, request.color_to_alpha);
if let Some(thresholding) = &request.thresholding {
thresholding.apply(&mut image);
thresholding.apply(&mut image, self.image_pyramid.original_has_alpha);
}
ui.ctx().load_texture(
request.client.clone(),
Expand Down Expand Up @@ -115,7 +115,7 @@ impl TextureState {
}
color_to_alpha(&mut cropped_image, request.uncropped.color_to_alpha);
if let Some(thresholding) = &request.uncropped.thresholding {
thresholding.apply(&mut cropped_image);
thresholding.apply(&mut cropped_image, self.image_pyramid.original_has_alpha);
}

self.texture_handle = Some(ui.ctx().load_texture(
Expand Down
Loading

0 comments on commit 660d06d

Please sign in to comment.