diff --git a/examples/dummy-opencv-capture/main.rs b/examples/dummy-opencv-capture/main.rs index 954eab9..7a0e504 100644 --- a/examples/dummy-opencv-capture/main.rs +++ b/examples/dummy-opencv-capture/main.rs @@ -11,9 +11,9 @@ fn camera_read() -> ndarray::Array { flat_image.as_ptr() ); - let image = Image::new_bgr8(flat_image, 3, 3, None); + let image = Image::new_bgr8(flat_image, 3, 3, None).unwrap(); - return image.to_bgr8_ndarray().unwrap(); + return image.bgr8_to_ndarray().unwrap(); } fn image_show(_frame: ndarray::ArrayView) { @@ -37,10 +37,10 @@ fn main() { // Read OpenCV Camera, default is nd_array BGR8 let frame = camera_read(); - let image = Image::from_bgr8_ndarray(frame, Some("camera.left")); + let image = Image::bgr8_from_ndarray(frame, Some("camera.left")).unwrap(); // Convert to RGB8, apply some filter (Black and White). - let mut frame = image.to_rgb().unwrap().to_rgb8_ndarray().unwrap(); + let mut frame = image.to_rgb8().unwrap().rgb8_to_ndarray().unwrap(); for i in 0..frame.shape()[0] { for j in 0..frame.shape()[1] { @@ -59,10 +59,10 @@ fn main() { } } - let image = Image::from_rgb8_ndarray(frame, Some("camera.left.baw")); + let image = Image::rgb8_from_ndarray(frame, Some("camera.left.baw")).unwrap(); // Plot the image, you may only need a nd array view - image_show(image.to_rgb8_ndarray_view().unwrap()); + image_show(image.rgb8_to_ndarray_view().unwrap()); send_output(image.to_arrow().unwrap()); } diff --git a/src/image.rs b/src/image.rs index 619919b..6489d79 100644 --- a/src/image.rs +++ b/src/image.rs @@ -1,8 +1,10 @@ -use crate::arrow::{column_by_name, union_field, union_look_up_table}; +use eyre::{Report, Result}; -use std::{mem, sync::Arc}; +mod bgr8; +mod gray8; +mod rgb8; -use eyre::{Context, Report, Result}; +mod arrow; #[derive(Debug)] pub struct ImageData { @@ -29,34 +31,7 @@ impl Image { } } - pub fn new_bgr8(pixels: Vec, width: u32, height: u32, name: Option<&str>) -> Self { - Self::ImageBGR8(ImageData { - pixels, - width, - height, - name: name.map(|s| s.to_string()), - }) - } - - pub fn new_rgb8(pixels: Vec, width: u32, height: u32, name: Option<&str>) -> Self { - Self::ImageRGB8(ImageData { - pixels, - width, - height, - name: name.map(|s| s.to_string()), - }) - } - - pub fn new_gray8(pixels: Vec, width: u32, height: u32, name: Option<&str>) -> Self { - Self::ImageGray8(ImageData { - pixels, - width, - height, - name: name.map(|s| s.to_string()), - }) - } - - pub fn to_rgb(self) -> Result { + pub fn to_rgb8(self) -> Result { match self { Self::ImageBGR8(image) => { let mut pixels = image.pixels; @@ -73,11 +48,11 @@ impl Image { })) } Self::ImageRGB8(_) => Ok(self), - Self::ImageGray8(_) => Err(Report::msg("Can't convert grayscale image to RGB")), + _ => Err(Report::msg("Can't convert image to RGB8")), } } - pub fn to_bgr(self) -> Result { + pub fn to_bgr8(self) -> Result { match self { Self::ImageRGB8(image) => { let mut pixels = image.pixels; @@ -94,257 +69,29 @@ impl Image { })) } Self::ImageBGR8(_) => Ok(self), - Self::ImageGray8(_) => Err(Report::msg("Can't convert grayscale image to BGR")), - } - } - - pub fn from_rgb8_ndarray(array: ndarray::Array, name: Option<&str>) -> Self { - let width = array.shape()[1] as u32; - let height = array.shape()[0] as u32; - - let pixels = array.into_raw_vec(); - - Self::new_rgb8(pixels, width, height, name) - } - - pub fn from_bgr8_ndarray(array: ndarray::Array, name: Option<&str>) -> Self { - let width = array.shape()[1] as u32; - let height = array.shape()[0] as u32; - - let pixels = array.into_raw_vec(); - - Self::new_bgr8(pixels, width, height, name) - } - - pub fn from_gray8_ndarray(array: ndarray::Array, name: Option<&str>) -> Self { - let width = array.shape()[1] as u32; - let height = array.shape()[0] as u32; - - let pixels = array.into_raw_vec(); - - Self::new_gray8(pixels, width, height, name) - } - - pub fn to_rgb8_ndarray(self) -> Result> { - match self { - Self::ImageRGB8(image) => ndarray::Array::from_shape_vec( - (image.height as usize, image.width as usize, 3), - image.pixels, - ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and RGB8 encoding doesn't match pixels data length."), - _ => Err(Report::msg("Image is not in RGB8 format")), - } - } - - pub fn to_bgr8_ndarray(self) -> Result> { - match self { - Self::ImageBGR8(image) => ndarray::Array::from_shape_vec( - (image.height as usize, image.width as usize, 3), - image.pixels, - ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and BGR8 encoding doesn't match pixels data length."), - _ => Err(Report::msg("Image is not in BGR8 format")), - } - } - - pub fn to_gray8_ndarray(self) -> Result> { - match self { - Self::ImageGray8(image) => ndarray::Array::from_shape_vec( - (image.height as usize, image.width as usize), - image.pixels, - ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and Gray8 encoding doesn't match pixels data length."), - _ => Err(Report::msg("Image is not in Gray8 format")), - } - } - - pub fn to_rgb8_ndarray_view(&self) -> Result> { - match self { - Self::ImageRGB8(image) => ndarray::ArrayView::from_shape( - (image.height as usize, image.width as usize, 3), - &image.pixels, - ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and RGB8 encoding doesn't match pixels data length."), - _ => Err(Report::msg("Image is not in RGB8 format")), - } - } - - pub fn to_bgr8_ndarray_view(&self) -> Result> { - match self { - Self::ImageBGR8(image) => ndarray::ArrayView::from_shape( - (image.height as usize, image.width as usize, 3), - &image.pixels, - ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and BGR8 encoding doesn't match pixels data length."), - _ => Err(Report::msg("Image is not in BGR8 format")), - } - } - - pub fn to_gray8_ndarray_view(&self) -> Result> { - match self { - Self::ImageGray8(image) => ndarray::ArrayView::from_shape( - (image.height as usize, image.width as usize), - &image.pixels, - ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and Gray8 encoding doesn't match pixels data length."), - _ => Err(Report::msg("Image is not in Gray8 format")), - } - } - - pub fn to_rgb8_ndarray_view_mut(&mut self) -> Result> { - match self { - Self::ImageRGB8(image) => ndarray::ArrayViewMut::from_shape( - (image.height as usize, image.width as usize, 3), - &mut image.pixels, - ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and RGB8 encoding doesn't match pixels data length."), - _ => Err(Report::msg("Image is not in RGB8 format")), - } - } - - pub fn to_bgr8_ndarray_view_mut(&mut self) -> Result> { - match self { - Self::ImageBGR8(image) => ndarray::ArrayViewMut::from_shape( - (image.height as usize, image.width as usize, 3), - &mut image.pixels, - ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and BGR8 encoding doesn't match pixels data length."), - _ => Err(Report::msg("Image is not in BGR8 format")), - } - } - - pub fn to_gray8_ndarray_view_mut(&mut self) -> Result> { - match self { - Self::ImageGray8(image) => ndarray::ArrayViewMut::from_shape( - (image.height as usize, image.width as usize), - &mut image.pixels, - ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and Gray8 encoding doesn't match pixels data length."), - _ => Err(Report::msg("Image is not in Gray8 format")), - } - } - - pub fn from_arrow(array: arrow::array::UnionArray) -> Result { - use arrow::array::Array; - - let union_fields = match array.data_type() { - arrow::datatypes::DataType::Union(fields, ..) => fields, - _ => { - return Err(Report::msg("UnionArray has invalid data type.")); - } - }; - - let look_up_table = union_look_up_table(&union_fields); - - let width = - column_by_name::(&array, "width", &look_up_table)?.value(0); - let height = - column_by_name::(&array, "height", &look_up_table)?.value(0); - let encoding = - column_by_name::(&array, "encoding", &look_up_table)? - .value(0) - .to_string(); - - let name = column_by_name::(&array, "name", &look_up_table)?; - - let name = if name.is_null(0) { - None - } else { - Some(name.value(0).to_string()) - }; - - let name = name.as_ref().map(|s| s.as_str()); - - unsafe { - let array = mem::ManuallyDrop::new(array); - let pixels = match encoding.as_str() { - "RGB8" => { - column_by_name::(&array, "pixels", &look_up_table)? - } - "BGR8" => { - column_by_name::(&array, "pixels", &look_up_table)? - } - "GRAY8" => { - column_by_name::(&array, "pixels", &look_up_table)? - } - _ => { - return Err(Report::msg(format!("Invalid encoding: {}", encoding))); - } - }; - - let ptr = pixels.values().as_ptr(); - let len = pixels.len(); - - let pixels = Vec::from_raw_parts(ptr as *mut u8, len, len); - - return match encoding.as_str() { - "RGB8" => Ok(Self::new_rgb8(pixels, width, height, name)), - "BGR8" => Ok(Self::new_bgr8(pixels, width, height, name)), - "GRAY8" => Ok(Self::new_gray8(pixels, width, height, name)), - _ => Err(Report::msg(format!("Invalid encoding: {}", encoding))), - }; + _ => Err(Report::msg("Can't convert image to BGR8")), } } +} - fn get_image_details( - image: &ImageData, - ) -> ( - arrow::array::UInt32Array, - arrow::array::UInt32Array, - arrow::array::StringArray, - ) { - let width = arrow::array::UInt32Array::from(vec![image.width; 1]); - let height = arrow::array::UInt32Array::from(vec![image.height; 1]); +mod tests { + #[test] + fn test_rgb8_to_bgr8() { + use crate::image::Image; - let name = arrow::array::StringArray::from(vec![image.name.clone(); 1]); + let flat_image = (1..28).collect::>(); - (width, height, name) + let image = Image::new_rgb8(flat_image, 3, 3, Some("camera.test")).unwrap(); + image.to_bgr8().unwrap(); } - pub fn to_arrow(self) -> Result { - let ((width, height, name), encoding, pixels, datatype) = match self { - Image::ImageBGR8(image) => ( - Self::get_image_details(&image), - arrow::array::StringArray::from(vec!["BGR8".to_string(); 1]), - arrow::array::UInt8Array::from(image.pixels), - arrow::datatypes::DataType::UInt8, - ), - Image::ImageRGB8(image) => ( - Self::get_image_details(&image), - arrow::array::StringArray::from(vec!["RGB8".to_string(); 1]), - arrow::array::UInt8Array::from(image.pixels), - arrow::datatypes::DataType::UInt8, - ), - Image::ImageGray8(image) => ( - Self::get_image_details(&image), - arrow::array::StringArray::from(vec!["GRAY8".to_string(); 1]), - arrow::array::UInt8Array::from(image.pixels), - arrow::datatypes::DataType::UInt8, - ), - }; - - let type_ids = [].into_iter().collect::>(); - let offsets = [].into_iter().collect::>(); - - let union_fields = [ - union_field(0, "pixels", datatype, false), - union_field(1, "width", arrow::datatypes::DataType::UInt32, false), - union_field(2, "height", arrow::datatypes::DataType::UInt32, false), - union_field(3, "encoding", arrow::datatypes::DataType::Utf8, false), - union_field(4, "name", arrow::datatypes::DataType::Utf8, true), - ] - .into_iter() - .collect::(); + #[test] + fn test_bgr8_to_rgb8() { + use crate::image::Image; - let children: Vec> = vec![ - Arc::new(pixels), - Arc::new(width), - Arc::new(height), - Arc::new(encoding), - Arc::new(name), - ]; + let flat_image = (1..28).collect::>(); - arrow::array::UnionArray::try_new(union_fields, type_ids, Some(offsets), children) - .wrap_err("Failed to create UnionArray") + let image = Image::new_bgr8(flat_image, 3, 3, Some("camera.test")).unwrap(); + image.to_rgb8().unwrap(); } } diff --git a/src/image/arrow.rs b/src/image/arrow.rs new file mode 100644 index 0000000..135abf2 --- /dev/null +++ b/src/image/arrow.rs @@ -0,0 +1,153 @@ +use crate::arrow::{column_by_name, union_field, union_look_up_table}; + +use super::{Image, ImageData}; +use eyre::{Context, Report, Result}; + +use std::{mem, sync::Arc}; + +impl Image { + pub fn from_arrow(array: arrow::array::UnionArray) -> Result { + use arrow::array::Array; + + let union_fields = match array.data_type() { + arrow::datatypes::DataType::Union(fields, ..) => fields, + _ => { + return Err(Report::msg("UnionArray has invalid data type.")); + } + }; + + let look_up_table = union_look_up_table(&union_fields); + + let width = + column_by_name::(&array, "width", &look_up_table)?.value(0); + let height = + column_by_name::(&array, "height", &look_up_table)?.value(0); + let encoding = + column_by_name::(&array, "encoding", &look_up_table)? + .value(0) + .to_string(); + + let name = column_by_name::(&array, "name", &look_up_table)?; + + let name = if name.is_null(0) { + None + } else { + Some(name.value(0).to_string()) + }; + + let name = name.as_ref().map(|s| s.as_str()); + + unsafe { + let array = mem::ManuallyDrop::new(array); + let pixels = match encoding.as_str() { + "RGB8" => { + column_by_name::(&array, "pixels", &look_up_table)? + } + "BGR8" => { + column_by_name::(&array, "pixels", &look_up_table)? + } + "GRAY8" => { + column_by_name::(&array, "pixels", &look_up_table)? + } + _ => { + return Err(Report::msg(format!("Invalid encoding: {}", encoding))); + } + }; + + let ptr = pixels.values().as_ptr(); + let len = pixels.len(); + + let pixels = Vec::from_raw_parts(ptr as *mut u8, len, len); + + return match encoding.as_str() { + "RGB8" => Self::new_rgb8(pixels, width, height, name), + "BGR8" => Self::new_bgr8(pixels, width, height, name), + "GRAY8" => Self::new_gray8(pixels, width, height, name), + _ => Err(Report::msg(format!("Invalid encoding: {}", encoding))), + }; + } + } + + fn get_image_details( + image: &ImageData, + ) -> ( + arrow::array::UInt32Array, + arrow::array::UInt32Array, + arrow::array::StringArray, + ) { + let width = arrow::array::UInt32Array::from(vec![image.width; 1]); + let height = arrow::array::UInt32Array::from(vec![image.height; 1]); + + let name = arrow::array::StringArray::from(vec![image.name.clone(); 1]); + + (width, height, name) + } + + pub fn to_arrow(self) -> Result { + let ((width, height, name), encoding, pixels, datatype) = match self { + Image::ImageBGR8(image) => ( + Self::get_image_details(&image), + arrow::array::StringArray::from(vec!["BGR8".to_string(); 1]), + arrow::array::UInt8Array::from(image.pixels), + arrow::datatypes::DataType::UInt8, + ), + Image::ImageRGB8(image) => ( + Self::get_image_details(&image), + arrow::array::StringArray::from(vec!["RGB8".to_string(); 1]), + arrow::array::UInt8Array::from(image.pixels), + arrow::datatypes::DataType::UInt8, + ), + Image::ImageGray8(image) => ( + Self::get_image_details(&image), + arrow::array::StringArray::from(vec!["GRAY8".to_string(); 1]), + arrow::array::UInt8Array::from(image.pixels), + arrow::datatypes::DataType::UInt8, + ), + }; + + let type_ids = [].into_iter().collect::>(); + let offsets = [].into_iter().collect::>(); + + let union_fields = [ + union_field(0, "pixels", datatype, false), + union_field(1, "width", arrow::datatypes::DataType::UInt32, false), + union_field(2, "height", arrow::datatypes::DataType::UInt32, false), + union_field(3, "encoding", arrow::datatypes::DataType::Utf8, false), + union_field(4, "name", arrow::datatypes::DataType::Utf8, true), + ] + .into_iter() + .collect::(); + + let children: Vec> = vec![ + Arc::new(pixels), + Arc::new(width), + Arc::new(height), + Arc::new(encoding), + Arc::new(name), + ]; + + arrow::array::UnionArray::try_new(union_fields, type_ids, Some(offsets), children) + .wrap_err("Failed to create UnionArray width Image data.") + } +} + +mod tests { + #[test] + fn test_arrow_conversion() { + use crate::image::Image; + + let flat_image = (1..28).collect::>(); + let original_buffer_address = flat_image.as_ptr(); + + let bgr8_image = Image::new_bgr8(flat_image, 3, 3, None).unwrap(); + let image_buffer_address = bgr8_image.as_ptr(); + + let arrow_image = bgr8_image.to_arrow().unwrap(); + + let new_image = Image::from_arrow(arrow_image).unwrap(); + let final_image_buffer = new_image.as_ptr(); + + assert_eq!(original_buffer_address, image_buffer_address); + assert_eq!(image_buffer_address, final_image_buffer); + } +} diff --git a/src/image/bgr8.rs b/src/image/bgr8.rs new file mode 100644 index 0000000..184e59f --- /dev/null +++ b/src/image/bgr8.rs @@ -0,0 +1,141 @@ +use super::{Image, ImageData}; + +use eyre::{Context, Report, Result}; + +impl Image { + pub fn new_bgr8(pixels: Vec, width: u32, height: u32, name: Option<&str>) -> Result { + if width * height * 3 != pixels.len() as u32 { + return Err(Report::msg( + "Width, height and BGR8 encoding doesn't match pixels data length.", + )); + } + + Ok(Self::ImageBGR8(ImageData { + pixels, + width, + height, + name: name.map(|s| s.to_string()), + })) + } + + pub fn bgr8_from_ndarray( + array: ndarray::Array, + name: Option<&str>, + ) -> Result { + let width = array.shape()[1] as u32; + let height = array.shape()[0] as u32; + + let pixels = array.into_raw_vec(); + + Self::new_bgr8(pixels, width, height, name) + } + + pub fn bgr8_to_ndarray(self) -> Result> { + match self { + Self::ImageBGR8(image) => ndarray::Array::from_shape_vec( + (image.height as usize, image.width as usize, 3), + image.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and BGR8 encoding doesn't match pixels data length."), + _ => Err(Report::msg("Image is not in BGR8 format")), + } + } + + pub fn bgr8_to_ndarray_view(&self) -> Result> { + match self { + Self::ImageBGR8(image) => ndarray::ArrayView::from_shape( + (image.height as usize, image.width as usize, 3), + &image.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and BGR8 encoding doesn't match pixels data length."), + _ => Err(Report::msg("Image is not in BGR8 format")), + } + } + + pub fn bgr8_to_ndarray_view_mut(&mut self) -> Result> { + match self { + Self::ImageBGR8(image) => ndarray::ArrayViewMut::from_shape( + (image.height as usize, image.width as usize, 3), + &mut image.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and BGR8 encoding doesn't match pixels data length."), + _ => Err(Report::msg("Image is not in BGR8 format")), + } + } +} + +mod tests { + #[test] + fn test_bgr8_creation() { + use crate::image::Image; + + let flat_image = (1..28).collect::>(); + + Image::new_bgr8(flat_image, 3, 3, Some("camera.test")).unwrap(); + } + + #[test] + fn test_bgr8_from_ndarray() { + use ndarray::Array3; + + use crate::image::Image; + + let array = Array3::::zeros((3, 3, 3)); + + Image::bgr8_from_ndarray(array, Some("camera.test")).unwrap(); + } + + #[test] + fn test_bgr8_to_ndarray() { + use crate::image::Image; + + let flat_image = (1..28).collect::>(); + + let image = Image::new_bgr8(flat_image, 3, 3, Some("camera.test")).unwrap(); + + image.bgr8_to_ndarray().unwrap(); + } + + #[test] + fn test_bgr8_to_ndarray_view() { + use crate::image::Image; + + let flat_image = (1..28).collect::>(); + + let image = Image::new_bgr8(flat_image, 3, 3, Some("camera.test")).unwrap(); + + image.bgr8_to_ndarray_view().unwrap(); + } + + #[test] + fn test_bgr8_to_ndarray_view_mut() { + use crate::image::Image; + + let flat_image = (1..28).collect::>(); + + let mut image = Image::new_bgr8(flat_image, 3, 3, Some("camera.test")).unwrap(); + + image.bgr8_to_ndarray_view_mut().unwrap(); + } + + #[test] + fn test_bgr8_ndarray_zero_copy_conversion() { + use crate::image::Image; + + let flat_image = (1..28).collect::>(); + let original_buffer_address = flat_image.as_ptr(); + + let bgr8_image = Image::new_bgr8(flat_image, 3, 3, None).unwrap(); + let image_buffer_address = bgr8_image.as_ptr(); + + let bgr8_ndarray = bgr8_image.bgr8_to_ndarray().unwrap(); + let ndarray_buffer_address = bgr8_ndarray.as_ptr(); + + let final_image = Image::bgr8_from_ndarray(bgr8_ndarray, None).unwrap(); + let final_image_buffer_address = final_image.as_ptr(); + + assert_eq!(original_buffer_address, image_buffer_address); + assert_eq!(image_buffer_address, ndarray_buffer_address); + assert_eq!(ndarray_buffer_address, final_image_buffer_address); + } +} diff --git a/src/image/gray8.rs b/src/image/gray8.rs new file mode 100644 index 0000000..1bb38d3 --- /dev/null +++ b/src/image/gray8.rs @@ -0,0 +1,139 @@ +use super::{Image, ImageData}; + +use eyre::{Context, Report, Result}; + +impl Image { + pub fn new_gray8(pixels: Vec, width: u32, height: u32, name: Option<&str>) -> Result { + if pixels.len() != (width * height) as usize { + return Err(Report::msg("Invalid pixels data length.")); + } + + Ok(Self::ImageGray8(ImageData { + pixels, + width, + height, + name: name.map(|s| s.to_string()), + })) + } + + pub fn gray8_from_ndarray( + array: ndarray::Array, + name: Option<&str>, + ) -> Result { + let width = array.shape()[1] as u32; + let height = array.shape()[0] as u32; + + let pixels = array.into_raw_vec(); + + Self::new_gray8(pixels, width, height, name) + } + + pub fn gray8_to_ndarray(self) -> Result> { + match self { + Self::ImageGray8(image) => ndarray::Array::from_shape_vec( + (image.height as usize, image.width as usize), + image.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and Gray8 encoding doesn't match pixels data length."), + _ => Err(Report::msg("Image is not in Gray8 format")), + } + } + + pub fn gray8_to_ndarray_view(&self) -> Result> { + match self { + Self::ImageGray8(image) => ndarray::ArrayView::from_shape( + (image.height as usize, image.width as usize), + &image.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and Gray8 encoding doesn't match pixels data length."), + _ => Err(Report::msg("Image is not in Gray8 format")), + } + } + + pub fn gray8_to_ndarray_view_mut(&mut self) -> Result> { + match self { + Self::ImageGray8(image) => ndarray::ArrayViewMut::from_shape( + (image.height as usize, image.width as usize), + &mut image.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and Gray8 encoding doesn't match pixels data length."), + _ => Err(Report::msg("Image is not in Gray8 format")), + } + } +} + +mod test { + #[test] + fn test_gray8_creation() { + use crate::image::Image; + + let flat_image = (1..10).collect::>(); + + Image::new_gray8(flat_image, 3, 3, Some("camera.test")).unwrap(); + } + + #[test] + fn test_gray8_from_ndarray() { + use ndarray::Array2; + + use crate::image::Image; + + let array = Array2::::zeros((3, 3)); + + Image::gray8_from_ndarray(array, Some("camera.test")).unwrap(); + } + + #[test] + fn test_gray8_to_ndarray() { + use crate::image::Image; + + let flat_image = (1..10).collect::>(); + + let image = Image::new_gray8(flat_image, 3, 3, Some("camera.test")).unwrap(); + + image.gray8_to_ndarray().unwrap(); + } + + #[test] + fn test_gray8_to_ndarray_view() { + use crate::image::Image; + + let flat_image = (1..10).collect::>(); + + let image = Image::new_gray8(flat_image, 3, 3, Some("camera.test")).unwrap(); + + image.gray8_to_ndarray_view().unwrap(); + } + + #[test] + fn test_gray8_to_ndarray_view_mut() { + use crate::image::Image; + + let flat_image = (1..10).collect::>(); + + let mut image = Image::new_gray8(flat_image, 3, 3, Some("camera.test")).unwrap(); + + image.gray8_to_ndarray_view_mut().unwrap(); + } + + #[test] + fn test_gray8_ndarray_zero_copy_conversion() { + use crate::image::Image; + + let flat_image = (1..10).collect::>(); + let original_buffer_address = flat_image.as_ptr(); + + let gray8_image = Image::new_gray8(flat_image, 3, 3, None).unwrap(); + let image_buffer_address = gray8_image.as_ptr(); + + let gray8_ndarray = gray8_image.gray8_to_ndarray().unwrap(); + let ndarray_buffer_address = gray8_ndarray.as_ptr(); + + let final_image = Image::gray8_from_ndarray(gray8_ndarray, None).unwrap(); + let final_image_buffer_address = final_image.as_ptr(); + + assert_eq!(original_buffer_address, image_buffer_address); + assert_eq!(image_buffer_address, ndarray_buffer_address); + assert_eq!(ndarray_buffer_address, final_image_buffer_address); + } +} diff --git a/src/image/rgb8.rs b/src/image/rgb8.rs new file mode 100644 index 0000000..a69505c --- /dev/null +++ b/src/image/rgb8.rs @@ -0,0 +1,139 @@ +use super::{Image, ImageData}; + +use eyre::{Context, Report, Result}; + +impl Image { + pub fn new_rgb8(pixels: Vec, width: u32, height: u32, name: Option<&str>) -> Result { + if pixels.len() != (width * height * 3) as usize { + return Err(Report::msg("Invalid pixel data length.")); + } + + Ok(Self::ImageRGB8(ImageData { + pixels, + width, + height, + name: name.map(|s| s.to_string()), + })) + } + + pub fn rgb8_from_ndarray( + array: ndarray::Array, + name: Option<&str>, + ) -> Result { + let width = array.shape()[1] as u32; + let height = array.shape()[0] as u32; + + let pixels = array.into_raw_vec(); + + Self::new_rgb8(pixels, width, height, name) + } + + pub fn rgb8_to_ndarray(self) -> Result> { + match self { + Self::ImageRGB8(image) => ndarray::Array::from_shape_vec( + (image.height as usize, image.width as usize, 3), + image.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and RGB8 encoding doesn't match pixels data length."), + _ => Err(Report::msg("Image is not in RGB8 format")), + } + } + + pub fn rgb8_to_ndarray_view(&self) -> Result> { + match self { + Self::ImageRGB8(image) => ndarray::ArrayView::from_shape( + (image.height as usize, image.width as usize, 3), + &image.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and RGB8 encoding doesn't match pixels data length."), + _ => Err(Report::msg("Image is not in RGB8 format")), + } + } + + pub fn rgb8_to_ndarray_view_mut(&mut self) -> Result> { + match self { + Self::ImageRGB8(image) => ndarray::ArrayViewMut::from_shape( + (image.height as usize, image.width as usize, 3), + &mut image.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and RGB8 encoding doesn't match pixels data length."), + _ => Err(Report::msg("Image is not in RGB8 format")), + } + } +} + +mod tests { + #[test] + fn test_rgb8_creation() { + use crate::image::Image; + + let flat_image = (1..28).collect::>(); + + Image::new_rgb8(flat_image, 3, 3, Some("camera.test")).unwrap(); + } + + #[test] + fn test_rgb8_from_ndarray() { + use ndarray::Array3; + + use crate::image::Image; + + let array = Array3::::zeros((3, 3, 3)); + + Image::rgb8_from_ndarray(array, Some("camera.test")).unwrap(); + } + + #[test] + fn test_rgb8_to_ndarray() { + use crate::image::Image; + + let flat_image = (1..28).collect::>(); + + let image = Image::new_rgb8(flat_image, 3, 3, Some("camera.test")).unwrap(); + + image.rgb8_to_ndarray().unwrap(); + } + + #[test] + fn test_rgb8_to_ndarray_view() { + use crate::image::Image; + + let flat_image = (1..28).collect::>(); + + let image = Image::new_rgb8(flat_image, 3, 3, Some("camera.test")).unwrap(); + + image.rgb8_to_ndarray_view().unwrap(); + } + + #[test] + fn test_rgb8_to_ndarray_view_mut() { + use crate::image::Image; + + let flat_image = (1..28).collect::>(); + + let mut image = Image::new_rgb8(flat_image, 3, 3, Some("camera.test")).unwrap(); + + image.rgb8_to_ndarray_view_mut().unwrap(); + } + + #[test] + fn test_rgb8_ndarray_zero_copy_conversion() { + use crate::image::Image; + + let flat_image = (1..28).collect::>(); + let original_buffer_address = flat_image.as_ptr(); + + let rgb8_image = Image::new_rgb8(flat_image, 3, 3, None).unwrap(); + let image_buffer_address = rgb8_image.as_ptr(); + + let rgb8_ndarray = rgb8_image.rgb8_to_ndarray().unwrap(); + let ndarray_buffer_address = rgb8_ndarray.as_ptr(); + + let final_image = Image::rgb8_from_ndarray(rgb8_ndarray, None).unwrap(); + let final_image_buffer_address = final_image.as_ptr(); + + assert_eq!(original_buffer_address, image_buffer_address); + assert_eq!(image_buffer_address, ndarray_buffer_address); + assert_eq!(ndarray_buffer_address, final_image_buffer_address); + } +} diff --git a/src/lib.rs b/src/lib.rs index ac5ba7c..7419bd0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,45 +16,3 @@ fn fastformat(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(sum_as_string, m)?)?; Ok(()) } - -mod tests { - #[test] - fn test_ndarray_conversion() { - use crate::image::Image; - - let flat_image = (1..28).collect::>(); - let original_buffer_address = flat_image.as_ptr(); - - let bgr8_image = Image::new_bgr8(flat_image, 3, 3, None); - let image_buffer_address = bgr8_image.as_ptr(); - - let bgr8_ndarray = bgr8_image.to_bgr8_ndarray().unwrap(); - let ndarray_buffer_address = bgr8_ndarray.as_ptr(); - - let final_image = Image::from_bgr8_ndarray(bgr8_ndarray, None); - let final_image_buffer_address = final_image.as_ptr(); - - assert_eq!(original_buffer_address, image_buffer_address); - assert_eq!(image_buffer_address, ndarray_buffer_address); - assert_eq!(ndarray_buffer_address, final_image_buffer_address); - } - - #[test] - fn test_arrow_conversion() { - use crate::image::Image; - - let flat_image = (1..28).collect::>(); - let original_buffer_address = flat_image.as_ptr(); - - let bgr8_image = Image::new_bgr8(flat_image, 3, 3, None); - let image_buffer_address = bgr8_image.as_ptr(); - - let arrow_image = bgr8_image.to_arrow().unwrap(); - - let new_image = Image::from_arrow(arrow_image).unwrap(); - let final_image_buffer = new_image.as_ptr(); - - assert_eq!(original_buffer_address, image_buffer_address); - assert_eq!(image_buffer_address, final_image_buffer); - } -}