use serde::{Deserialize, Serialize};
const TRINARY_FREE: u8 = 0;
const TRINARY_OCCUPIED: u8 = 100;
const TRINARY_UNKNOWN: u8 = 255;
const MAP_SERVER_FREE_DEFAULT: f32 = 0.196;
const MAP_SERVER_OCCUPIED_DEFAULT: f32 = 0.65;
use image::{DynamicImage, Rgba};
use imageproc::{integral_image::ArrayData, map::map_colors_mut};
use crate::meta::MetaYaml;
use crate::value_colormap::ColorMap;
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Mode {
Raw,
#[default]
Trinary,
Scale,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
pub enum Quirks {
Ros1Wiki,
#[default]
Ros1MapServer,
Ros2MapServer,
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct ValueInterpretation {
pub free: f32,
pub occupied: f32,
pub negate: bool,
pub mode: Mode,
#[serde(default)]
pub explicit_mode: bool,
pub quirks: Quirks,
#[serde(default)]
pub colormap: ColorMap,
}
impl Default for ValueInterpretation {
fn default() -> Self {
ValueInterpretation {
free: MAP_SERVER_FREE_DEFAULT,
occupied: MAP_SERVER_OCCUPIED_DEFAULT,
negate: false,
mode: Mode::default(),
explicit_mode: false,
quirks: Quirks::default(),
colormap: ColorMap::default(),
}
}
}
impl ValueInterpretation {
pub fn new(free: f32, occupied: f32, negate: bool, mode: Option<Mode>) -> Self {
ValueInterpretation {
free,
occupied,
negate,
mode: mode.unwrap_or_default(),
explicit_mode: mode.is_some(),
quirks: Quirks::default(),
colormap: ColorMap::default(),
}
}
pub fn from_meta_yaml(meta: &MetaYaml) -> Self {
ValueInterpretation::new(
meta.free_thresh,
meta.occupied_thresh,
meta.negate != 0,
meta.mode,
)
}
pub fn with_quirks(mut self, quirks: Quirks) -> Self {
self.quirks = quirks;
self
}
pub fn with_colormap(mut self, colormap: ColorMap) -> Self {
self.colormap = colormap;
self
}
pub fn apply(&self, img: &mut DynamicImage, original_has_alpha: bool) {
match self.mode {
Mode::Raw => {
map_colors_mut(img, |mut c| {
if self.negate {
c[0] = 255 - c[0];
}
self.colormap.get().map(c[0])
});
}
Mode::Trinary | Mode::Scale => {
map_colors_mut(img, |c| {
self.colormap
.get()
.map(self.interpret(c, original_has_alpha)[0])
});
}
}
}
fn avg_float(&self, pixel: Rgba<u8>, has_alpha: bool) -> f32 {
let num_channels = match self.quirks {
Quirks::Ros1Wiki => 3,
Quirks::Ros1MapServer | Quirks::Ros2MapServer => {
if self.mode == Mode::Trinary && has_alpha {
4
} else {
3
}
}
};
let sum = pixel.data()[0..num_channels]
.iter()
.map(|&v| v as f32)
.sum::<f32>();
let avg = sum / num_channels as f32;
if self.negate {
return avg / 255.;
}
(255. - avg) / 255.
}
fn interpret(&self, pixel: Rgba<u8>, has_alpha: bool) -> Rgba<u8> {
let p = self.avg_float(pixel, has_alpha);
let alpha = pixel[3];
let scale_unknown = self.mode == Mode::Scale && alpha != 255;
if p > self.occupied && !scale_unknown {
Rgba([TRINARY_OCCUPIED, TRINARY_OCCUPIED, TRINARY_OCCUPIED, alpha])
} else if p < self.free && !scale_unknown {
Rgba([TRINARY_FREE, TRINARY_FREE, TRINARY_FREE, alpha])
} else if self.mode == Mode::Trinary || scale_unknown {
Rgba([TRINARY_UNKNOWN, TRINARY_UNKNOWN, TRINARY_UNKNOWN, alpha])
}
else {
let scaled = match self.quirks {
Quirks::Ros1Wiki => {
(99. * (p - self.free) / (self.occupied - self.free)) as u8
}
Quirks::Ros1MapServer | Quirks::Ros2MapServer => {
(1. + 98. * (p - self.free) / (self.occupied - self.free)) as u8
}
};
Rgba([scaled, scaled, scaled, alpha])
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use image::{GenericImage, GenericImageView};
const EPS: f32 = 1e-3;
#[test]
fn avg_float() {
let thresholding = ValueInterpretation::new(0.196, 0.65, false, None);
let pixel = Rgba([128, 128, 128, 255]);
assert!(thresholding.avg_float(pixel, false) - 0.5 < EPS);
let pixel = Rgba([255, 255, 255, 255]);
assert_eq!(thresholding.avg_float(pixel, false), 0.);
let pixel = Rgba([0, 0, 0, 255]);
assert_eq!(thresholding.avg_float(pixel, false), 1.);
}
#[test]
fn trinary_wiki() {
let thresholding = ValueInterpretation::new(0.196, 0.65, false, Some(Mode::Trinary))
.with_quirks(Quirks::Ros1Wiki)
.with_colormap(ColorMap::Raw);
let mut img = DynamicImage::new_rgba8(1, 1);
img.put_pixel(0, 0, Rgba([128, 128, 128, 255]));
thresholding.apply(&mut img, false);
assert_eq!(
img.get_pixel(0, 0),
Rgba([TRINARY_UNKNOWN, TRINARY_UNKNOWN, TRINARY_UNKNOWN, 255])
);
img.put_pixel(0, 0, Rgba([255, 255, 255, 255]));
thresholding.apply(&mut img, false);
assert_eq!(
img.get_pixel(0, 0),
Rgba([TRINARY_FREE, TRINARY_FREE, TRINARY_FREE, 255])
);
img.put_pixel(0, 0, Rgba([60, 60, 60, 255]));
thresholding.apply(&mut img, false);
assert_eq!(
img.get_pixel(0, 0),
Rgba([TRINARY_OCCUPIED, TRINARY_OCCUPIED, TRINARY_OCCUPIED, 255])
);
}
#[test]
fn scale_wiki() {
let thresholding = ValueInterpretation::new(0.196, 0.65, false, Some(Mode::Scale))
.with_quirks(Quirks::Ros1Wiki)
.with_colormap(ColorMap::Raw);
let mut img = DynamicImage::new_rgba8(1, 1);
img.put_pixel(0, 0, Rgba([128, 128, 128, 255]));
thresholding.apply(&mut img, true);
assert_eq!(img.get_pixel(0, 0), Rgba([65, 65, 65, 255]));
img.put_pixel(0, 0, Rgba([60, 60, 60, 255]));
thresholding.apply(&mut img, true);
assert_eq!(
img.get_pixel(0, 0),
Rgba([TRINARY_OCCUPIED, TRINARY_OCCUPIED, TRINARY_OCCUPIED, 255])
);
img.put_pixel(0, 0, Rgba([255, 255, 255, 255]));
thresholding.apply(&mut img, true);
assert_eq!(
img.get_pixel(0, 0),
Rgba([TRINARY_FREE, TRINARY_FREE, TRINARY_FREE, 255])
);
img.put_pixel(0, 0, Rgba([1, 2, 3, 100]));
thresholding.apply(&mut img, true);
assert_eq!(
img.get_pixel(0, 0),
Rgba([TRINARY_UNKNOWN, TRINARY_UNKNOWN, TRINARY_UNKNOWN, 255])
);
}
#[test]
fn scale_map_server_quirks() {
let thresholding = ValueInterpretation::new(0.196, 0.65, false, Some(Mode::Scale))
.with_quirks(Quirks::Ros1MapServer)
.with_colormap(ColorMap::Raw);
let mut img = DynamicImage::new_rgba8(1, 1);
img.put_pixel(0, 0, Rgba([128, 128, 128, 255]));
thresholding.apply(&mut img, true);
assert_eq!(img.get_pixel(0, 0), Rgba([66, 66, 66, 255]));
img.put_pixel(0, 0, Rgba([60, 60, 60, 255]));
thresholding.apply(&mut img, true);
assert_eq!(
img.get_pixel(0, 0),
Rgba([TRINARY_OCCUPIED, TRINARY_OCCUPIED, TRINARY_OCCUPIED, 255])
);
img.put_pixel(0, 0, Rgba([255, 255, 255, 255]));
thresholding.apply(&mut img, true);
assert_eq!(
img.get_pixel(0, 0),
Rgba([TRINARY_FREE, TRINARY_FREE, TRINARY_FREE, 255])
);
img.put_pixel(0, 0, Rgba([1, 2, 3, 100]));
thresholding.apply(&mut img, true);
assert_eq!(
img.get_pixel(0, 0),
Rgba([TRINARY_UNKNOWN, TRINARY_UNKNOWN, TRINARY_UNKNOWN, 255])
);
}
}