use clap::ValueEnum;
use image;
use imageproc::contours;
use imageproc::drawing;
use ndarray::{s, ArrayViewMut, ShapeBuilder};
use super::Mask;
use crate::coco::object_detection;
use crate::errors::MaskError;
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
pub enum Segmentation {
Polygons,
Rle,
CocoRle,
}
pub fn convert_coco_segmentation(
dataset: &mut object_detection::HashmapDataset,
target_segmentation: Segmentation,
) -> Result<(), MaskError> {
use object_detection::Segmentation::{CocoRle, Polygons, PolygonsRS, Rle};
use Segmentation as S;
for ann in dataset.anns.values_mut() {
let converted_segmentation = match &ann.segmentation {
Rle(rle) => match target_segmentation {
S::Rle => Rle(rle.clone()),
S::CocoRle => CocoRle(object_detection::CocoRle::try_from(rle)?),
S::Polygons => Polygons(object_detection::Polygons::from(rle)),
},
CocoRle(coco_rle) => match target_segmentation {
S::Rle => Rle(object_detection::Rle::from(coco_rle)),
S::CocoRle => CocoRle(coco_rle.clone()),
S::Polygons => Polygons(object_detection::Polygons::from(
&object_detection::Rle::from(coco_rle),
)),
},
PolygonsRS(poly) => match target_segmentation {
S::Rle => Rle(object_detection::Rle::try_from(poly)?),
S::CocoRle => CocoRle(object_detection::CocoRle::try_from(poly)?),
S::Polygons => Polygons(poly.counts.clone()),
},
Polygons(_) => unimplemented!(),
};
ann.segmentation = converted_segmentation;
}
Ok(())
}
impl TryFrom<&object_detection::PolygonsRS> for object_detection::Rle {
type Error = MaskError;
fn try_from(poly: &object_detection::PolygonsRS) -> Result<Self, Self::Error> {
Ok(Self::from(&Mask::try_from(poly)?))
}
}
#[allow(clippy::cast_possible_truncation)]
impl From<&Mask> for object_detection::Rle {
fn from(mask: &Mask) -> Self {
let mut previous_value = 0;
let mut count = 0;
let mut counts = Vec::new();
for value in mask.clone().reversed_axes().iter() {
if *value != previous_value {
counts.push(count);
previous_value = *value;
count = 0;
}
count += 1;
}
counts.push(count);
Self {
size: vec![mask.nrows() as u32, mask.ncols() as u32],
counts,
}
}
}
#[allow(clippy::cast_sign_loss, clippy::cast_possible_wrap)]
impl From<&object_detection::CocoRle> for object_detection::Rle {
fn from(coco_rle: &object_detection::CocoRle) -> Self {
assert!(
coco_rle.counts.is_ascii(),
"COCO RLE is not in valid ascii."
);
let bytes_rle = coco_rle.counts.as_bytes();
let mut current_count_idx: usize = 0;
let mut current_byte_idx: usize = 0;
let mut counts: Vec<u32> = vec![0; coco_rle.counts.len()];
while current_byte_idx < bytes_rle.len() {
let mut continuous_pixels: i32 = 0;
let mut shift = 0;
let mut high_order_bit = 1;
while high_order_bit != 0 {
let byte = bytes_rle[current_byte_idx] - 48;
continuous_pixels |= (i32::from(byte) & 31) << shift;
high_order_bit = byte & 32;
current_byte_idx += 1;
shift += 5;
if high_order_bit == 0 && (byte & 16 != 0) {
continuous_pixels |= !0 << shift;
}
}
if current_count_idx > 2 {
continuous_pixels += counts[current_count_idx - 2] as i32;
}
counts[current_count_idx] = continuous_pixels as u32;
current_count_idx += 1;
}
while let Some(last) = counts.last() {
if *last == 0 {
counts.pop();
} else {
break;
}
}
Self {
size: coco_rle.size.clone(),
counts,
}
}
}
impl TryFrom<&object_detection::Rle> for object_detection::CocoRle {
type Error = MaskError;
fn try_from(rle: &object_detection::Rle) -> Result<Self, Self::Error> {
let mut high_order_bit: bool;
let mut byte: u8;
let mut coco_counts: Vec<u8> = Vec::new();
for i in 0..rle.counts.len() {
let mut continuous_pixels = i64::from(rle.counts[i]);
if i > 2 {
continuous_pixels -= i64::from(rle.counts[i - 2]);
}
high_order_bit = true;
while high_order_bit {
byte = u8::try_from(continuous_pixels & 0x1f)
.map_err(|err| MaskError::IntConversion(err, continuous_pixels & 0x1f))?;
continuous_pixels >>= 5;
high_order_bit = if byte & 0x10 == 0 {
continuous_pixels != 0
} else {
continuous_pixels != -1
};
if high_order_bit {
byte |= 0x20;
};
byte += 48;
coco_counts.push(byte);
}
}
Ok(Self {
size: rle.size.clone(),
counts: std::str::from_utf8(&coco_counts)
.map_err(|err| MaskError::StrConversion(err, coco_counts.clone()))?
.to_string(),
})
}
}
impl TryFrom<&object_detection::PolygonsRS> for object_detection::CocoRle {
type Error = MaskError;
fn try_from(poly: &object_detection::PolygonsRS) -> Result<Self, Self::Error> {
Self::try_from(&object_detection::Rle::from(&Mask::try_from(poly)?))
}
}
#[allow(clippy::cast_possible_truncation)]
impl TryFrom<&Mask> for object_detection::CocoRle {
type Error = MaskError;
fn try_from(mask: &Mask) -> Result<Self, Self::Error> {
Self::try_from(&object_detection::Rle::from(mask))
}
}
#[allow(clippy::expect_used)]
impl From<&object_detection::Rle> for object_detection::Polygons {
fn from(rle: &object_detection::Rle) -> Self {
let mask = Mask::from(rle);
let mask_img = mask
.as_slice_memory_order()
.map(|slice| {
image::GrayImage::from_raw(rle.size[1], rle.size[0], slice.to_owned()).expect(
"Buffer already contains a mask created using the rle sizes and is threfore big enough."
)
})
.expect("The mask is created just above and should therefore be continuous in memory.");
let contours = contours::find_contours::<u32>(&mask_img);
let mut counts: Self = Self::new();
let mut prev_prev_x: u32;
let mut prev_prev_y: u32;
let mut prev_x: u32;
let mut prev_y: u32;
for (i, contour) in contours.iter().enumerate() {
if contour.points.len() > 3 {
counts.push(Vec::with_capacity(2 * contour.points.len()));
counts[i].push(f64::from(contour.points[0].y));
counts[i].push(f64::from(contour.points[0].x));
prev_prev_x = contour.points[0].x;
prev_prev_y = contour.points[0].y;
prev_x = contour.points[1].x;
prev_y = contour.points[1].y;
for point in &contour.points {
if !((prev_prev_x == prev_x && prev_x == point.x)
|| (prev_prev_y == prev_y && prev_y == point.y))
{
counts[i].push(f64::from(prev_y));
counts[i].push(f64::from(prev_x));
}
prev_prev_x = prev_x;
prev_prev_y = prev_y;
prev_x = point.x;
prev_y = point.y;
}
if !((prev_prev_x == prev_x && prev_x == contour.points[0].x)
|| (prev_prev_y == prev_y && prev_y == contour.points[0].y))
{
counts[i].push(f64::from(prev_y));
counts[i].push(f64::from(prev_x));
}
}
}
counts
}
}
#[allow(clippy::cast_possible_truncation)]
impl From<&Mask> for object_detection::PolygonsRS {
fn from(mask: &Mask) -> Self {
Self {
size: vec![mask.shape()[0] as u32, mask.shape()[1] as u32],
counts: object_detection::Polygons::from(&object_detection::Rle::from(mask)),
}
}
}
#[allow(clippy::expect_used)]
impl From<&object_detection::Rle> for Mask {
#[allow(clippy::cast_possible_truncation)]
fn from(rle: &object_detection::Rle) -> Self {
let width = rle.size[1] as usize;
let height = rle.size[0] as usize;
let mut mask: Self = Self::zeros((height, width).f());
let mut mask_1d = ArrayViewMut::from_shape(
(height * width).f(),
mask.as_slice_memory_order_mut().expect("The mask array is created just above, there shouldn't be any error when creating a view of it"),
)
.expect("The mask array is created just above, there shouldn't be any error when creating a view of it");
let mut current_value = 0u8;
let mut current_position = 0usize;
for nb_pixels in &rle.counts {
mask_1d
.slice_mut(s![current_position..current_position + *nb_pixels as usize])
.fill(current_value);
current_value = u8::from(current_value == 0);
current_position += *nb_pixels as usize;
}
mask
}
}
impl TryFrom<&object_detection::Segmentation> for Mask {
type Error = MaskError;
fn try_from(coco_segmentation: &object_detection::Segmentation) -> Result<Self, Self::Error> {
let mask = match coco_segmentation {
object_detection::Segmentation::Rle(rle) => Self::from(rle),
object_detection::Segmentation::CocoRle(coco_rle) => {
Self::from(&object_detection::Rle::from(coco_rle))
}
object_detection::Segmentation::PolygonsRS(poly) => Self::try_from(poly)?,
object_detection::Segmentation::Polygons(_) => {
unimplemented!("Use the 'mask_from_poly' function.")
}
};
Ok(mask)
}
}
#[allow(clippy::cast_possible_truncation)]
impl TryFrom<&object_detection::PolygonsRS> for Mask {
type Error = MaskError;
fn try_from(poly_ann: &object_detection::PolygonsRS) -> Result<Self, Self::Error> {
let mut mask = image::GrayImage::new(poly_ann.size[1], poly_ann.size[0]);
for poly in &poly_ann.counts {
let mut points_poly: Vec<imageproc::point::Point<i32>> = Vec::new();
for i in (0..poly.len()).step_by(2) {
points_poly.push(imageproc::point::Point::new(
poly[i] as i32,
poly[i + 1] as i32,
));
}
while let Some(last_point) = points_poly.last() {
if points_poly[0].x == last_point.x && points_poly[0].y == last_point.y {
points_poly.pop();
} else {
break;
}
}
drawing::draw_polygon_mut(&mut mask, &points_poly, image::Luma([1u8]));
}
Self::from_shape_vec(
(poly_ann.size[1] as usize, poly_ann.size[0] as usize),
mask.into_raw(),
)
.map_err(MaskError::ImageToNDArrayConversion)
}
}
#[allow(clippy::cast_possible_truncation, clippy::module_name_repetitions)]
pub fn mask_from_poly(
poly: &object_detection::Polygons,
width: u32,
height: u32,
) -> Result<Mask, MaskError> {
let mut points_poly: Vec<imageproc::point::Point<i32>> = Vec::new();
for i in (0..poly[0].len()).step_by(2) {
points_poly.push(imageproc::point::Point::new(
poly[0][i] as i32,
poly[0][i + 1] as i32,
));
}
let mut mask = image::GrayImage::new(width, height);
drawing::draw_polygon_mut(&mut mask, &points_poly, image::Luma([1u8]));
Mask::from_shape_vec((height as usize, width as usize), mask.into_raw())
.map_err(MaskError::ImageToNDArrayConversion)
}
#[allow(clippy::cast_possible_truncation, clippy::module_name_repetitions)]
#[must_use]
pub fn poly_from_mask(mask: &Mask) -> object_detection::Polygons {
object_detection::Polygons::from(&object_detection::Rle::from(mask))
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::object_detection::{CocoRle, Polygons, PolygonsRS, Rle};
use super::*;
use ndarray::array;
use proptest::prelude::*;
use rstest::rstest;
prop_compose! {
#[allow(clippy::unwrap_used)]
fn generate_rle(max_value: u32, max_elts: usize)
(counts in prop::collection::vec(1..max_value, 2..max_elts))
(width in 1..counts.iter().sum(), sum in Just(counts.iter().sum::<u32>()), mut counts in Just(counts))
-> Rle {
let height = sum / width + 1;
*counts.last_mut().unwrap() += width * height - sum;
Rle { counts,
size: vec![width, height]
}
}
}
prop_compose! {
fn generate_mask(max_ncols: usize, max_nrows: usize)
(ncols in 2..max_ncols, nrows in 2..max_nrows)
(ncols in Just(ncols),
nrows in Just(nrows),
mask_data in prop::collection::vec(0..=1u8, ncols * nrows),
) -> Mask {
Mask::from_shape_vec((nrows, ncols), mask_data).unwrap()
}
}
proptest! {
#[test]
fn rle_decode_inverts_encode(rle in generate_rle(50, 20)){
let coco_rle = CocoRle::try_from(&rle).unwrap();
let decoded_rle = Rle::from(&coco_rle);
prop_assert_eq!(decoded_rle, rle);
}
}
proptest! {
#[test]
fn mask_to_rle_to_mask(mask in generate_mask(100, 100)){
let rle = Rle::from(&mask);
let decoded_mask = Mask::from(&rle);
prop_assert_eq!(decoded_mask, mask);
}
}
#[rstest]
#[case::square(&Rle {size: vec![4, 4], counts: vec![5, 2, 2, 2, 5]})]
#[case::thick_horizontal_line(&Rle { size: vec![7, 7], counts: vec![9, 3, 4, 3, 4, 3, 4, 3, 4, 3, 9] })]
#[case::vertical_line(&Rle { size: vec![7, 7], counts: vec![15, 5, 2, 5, 2, 5, 15] })]
fn rle_to_poly_to_rle(#[case] rle: &Rle) {
let poly = Polygons::from(rle);
let mask = mask_from_poly(&poly, rle.size[1], rle.size[0]).unwrap();
let result_rle = Rle::try_from(&mask).unwrap();
assert_eq!(&result_rle, rle);
}
#[rstest]
#[case::horizontal_thick_line(
&PolygonsRS {size: vec![7, 7], counts: vec![vec![1.0, 2.0, 1.0, 4.0, 5.0, 4.0, 5.0, 2.0]]},
&array![[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 1, 1, 1, 0],
[0, 1, 1, 1, 1, 1, 0],
[0, 1, 1, 1, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0]],
)]
fn poly_rs_to_mask(#[case] poly: &PolygonsRS, #[case] expected_mask: &Mask) {
let mask = Mask::try_from(poly).unwrap();
assert_eq!(&mask, expected_mask);
}
#[rstest]
#[case::square(
&Rle {size: vec![4, 4], counts: vec![5, 2, 2, 2, 5]},
&PolygonsRS {size: vec![4, 4], counts: vec![vec![1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 2.0, 1.0]] }
)]
#[case::horizontal_thick_line(
&Rle {size: vec![7, 7], counts: vec![9, 3, 4, 3, 4, 3, 4, 3, 4, 3, 9]},
&PolygonsRS {size: vec![7, 7], counts: vec![vec![1.0, 2.0, 1.0, 4.0, 5.0, 4.0, 5.0, 2.0]]}
)]
#[case::vertical_thick_line(
&Rle {size: vec![7, 7], counts: vec![15, 5, 2, 5, 2, 5, 15]},
&PolygonsRS {size: vec![7, 7], counts: vec![vec![2.0, 1.0, 2.0, 5.0, 4.0, 5.0, 4.0, 1.0]]}
)]
fn rle_to_poly(#[case] rle: &Rle, #[case] expected_polygon: &PolygonsRS) {
let poly = PolygonsRS {
size: rle.size.clone(),
counts: Polygons::from(rle),
};
assert_eq!(&poly, expected_polygon);
}
#[rstest]
#[case::square(
&array![[0, 0, 0, 0],
[0, 1, 1, 0],
[0, 1, 1, 0],
[0, 0, 0, 0]],
&Rle {size: vec![4, 4], counts: vec![5, 2, 2, 2, 5]})]
#[case::horizontal_line(
&array![[0, 0, 0, 0, 0],
[1, 1, 1, 1, 1],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]],
&Rle {size: vec![4, 5], counts: vec![1, 1, 3, 1, 3, 1, 3, 1, 3, 1, 2]})]
#[case::vertical_line(
&array![[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0]],
&Rle {size: vec![4, 5], counts: vec![8, 4, 8]})]
#[case::thick_horizontal_line(
&array![[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 1, 1, 1, 0],
[0, 1, 1, 1, 1, 1, 0],
[0, 1, 1, 1, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0]],
&Rle { size: vec![7, 7], counts: vec![9, 3, 4, 3, 4, 3, 4, 3, 4, 3, 9] })]
#[case::thick_vertical_line(
&array![[0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0]],
&Rle { size: vec![7, 7], counts: vec![15, 5, 2, 5, 2, 5, 15] })]
fn mask_to_rle(#[case] mask: &Mask, #[case] expected_rle: &Rle) {
let rle = Rle::from(mask);
assert_eq!(&rle, expected_rle);
}
#[rstest]
#[case::square(
&array![[0, 0, 0, 0],
[0, 1, 1, 0],
[0, 1, 1, 0],
[0, 0, 0, 0]],
&Rle {size: vec![4, 4], counts: vec![5, 2, 2, 2, 5]})]
#[case::horizontal_line(
&array![[0, 0, 0, 0, 0],
[1, 1, 1, 1, 1],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]],
&Rle {size: vec![4, 5], counts: vec![1, 1, 3, 1, 3, 1, 3, 1, 3, 1, 2]})]
#[case::vertical_line(
&array![[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0]],
&Rle {size: vec![4, 5], counts: vec![8, 4, 8]})]
fn rle_to_mask(#[case] expected_mask: &Mask, #[case] rle: &Rle) {
let mask = Mask::from(rle);
assert_eq!(&mask, expected_mask);
}
#[rstest]
#[case::square(
&Rle {size: vec![4, 4], counts: vec![5, 2, 2, 2, 5]},
&CocoRle { size: vec![4, 4], counts: "52203".to_string() })]
#[case::square2(
&Rle {counts: vec![6, 1, 40, 4, 5, 4, 5, 4, 21], size: vec![9, 10]},
&CocoRle {size: vec![9, 10], counts: "61X13mN000`0".to_string()})]
#[case::test1(
&Rle {counts: vec![245, 5, 35, 5, 35, 5, 35, 5, 35, 5, 1190], size: vec![40, 40]},
&CocoRle {size: vec![40, 40], counts: "e75S10000000ST1".to_string()})]
fn encode_rle(#[case] rle: &Rle, #[case] expected_coco_rle: &CocoRle) {
let coco_rle = CocoRle::try_from(rle).unwrap();
assert_eq!(&coco_rle, expected_coco_rle);
}
}