use anyhow::Result;
use num_traits::Float;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ImageSize {
pub width: usize,
pub height: usize,
}
impl std::fmt::Display for ImageSize {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"ImageSize {{ width: {}, height: {} }}",
self.width, self.height
)
}
}
pub trait ImageDtype: Copy + Default + Into<f32> + Send + Sync {
fn from_f32(x: f32) -> Self;
}
impl ImageDtype for f32 {
fn from_f32(x: f32) -> Self {
x
}
}
impl ImageDtype for u8 {
fn from_f32(x: f32) -> Self {
x.round().clamp(0.0, 255.0) as u8
}
}
#[derive(Clone)]
pub struct Image<T, const CHANNELS: usize> {
pub data: ndarray::Array<T, ndarray::Dim<[ndarray::Ix; 3]>>,
}
impl<T, const CHANNELS: usize> Image<T, CHANNELS> {
pub fn new(size: ImageSize, data: Vec<T>) -> Result<Self> {
if data.len() != size.width * size.height * CHANNELS {
return Err(anyhow::anyhow!(
"Data length ({}) does not match the image size ({})",
data.len(),
size.width * size.height * CHANNELS
));
}
let data =
ndarray::Array::<T, _>::from_shape_vec((size.height, size.width, CHANNELS), data)?;
Ok(Image { data })
}
pub fn from_size_val(size: ImageSize, val: T) -> Result<Self>
where
T: Clone + Default,
{
let data = vec![val; size.width * size.height * CHANNELS];
let image = Image::new(size, data)?;
Ok(image)
}
pub fn cast<U>(self) -> Result<Image<U, CHANNELS>>
where
U: Clone + Default + num_traits::NumCast + std::fmt::Debug,
T: Copy + num_traits::NumCast + std::fmt::Debug,
{
let casted_data = self
.data
.map(|&x| U::from(x).expect("Failed to cast image data"));
Ok(Image { data: casted_data })
}
pub fn cast_and_scale<U>(self, scale: U) -> Result<Image<U, CHANNELS>>
where
U: Copy
+ Clone
+ Default
+ num_traits::NumCast
+ std::fmt::Debug
+ std::ops::Mul<Output = U>,
T: Copy + num_traits::NumCast + std::fmt::Debug,
{
let casted_data = self.data.map(|&x| {
let xu = U::from(x).expect("Failed to cast image data");
xu * scale
});
Ok(Image { data: casted_data })
}
pub fn channel(&self, channel: usize) -> Result<Image<T, 1>>
where
T: Clone,
{
if channel >= CHANNELS {
return Err(anyhow::anyhow!(
"Channel index ({}) out of bounds ({}).",
channel,
CHANNELS
));
}
let channel_data = self.data.slice(ndarray::s![.., .., channel..channel + 1]);
Ok(Image {
data: channel_data.to_owned(),
})
}
pub fn split_channels(&self) -> Result<Vec<Image<T, 1>>>
where
T: Clone,
{
let mut channels = Vec::with_capacity(CHANNELS);
for i in 0..CHANNELS {
channels.push(self.channel(i)?);
}
Ok(channels)
}
pub fn mul(&self, scale: T) -> Self
where
T: Copy + std::ops::Mul<Output = T>,
{
let scaled_data = self.data.map(|&x| x * scale);
Image { data: scaled_data }
}
pub fn div(&self, scale: T) -> Self
where
T: Copy + std::ops::Div<Output = T>,
{
let scaled_data = self.data.map(|&x| x / scale);
Image { data: scaled_data }
}
pub fn sub(&self, other: &Self) -> Self
where
T: Copy + std::ops::Sub<Output = T>,
{
let diff = &self.data - &other.data;
Image { data: diff }
}
pub fn powi(&self, n: i32) -> Self
where
T: Copy + Float,
{
let powered_data = self.data.map(|&x| x.powi(n));
Image { data: powered_data }
}
pub fn mean(&self) -> T
where
T: Copy + Float,
{
self.data.fold(T::zero(), |acc, &x| acc + x) / T::from(self.data.len()).unwrap()
}
pub fn abs(&self) -> Self
where
T: Copy + Float,
{
let abs_data = self.data.map(|&x| x.abs());
Image { data: abs_data }
}
#[deprecated(since = "0.1.2", note = "Use `image.size()` instead")]
pub fn image_size(&self) -> ImageSize {
ImageSize {
width: self.width(),
height: self.height(),
}
}
pub fn size(&self) -> ImageSize {
ImageSize {
width: self.width(),
height: self.height(),
}
}
pub fn cols(&self) -> usize {
self.width()
}
pub fn rows(&self) -> usize {
self.height()
}
pub fn width(&self) -> usize {
self.data.shape()[1]
}
pub fn height(&self) -> usize {
self.data.shape()[0]
}
pub fn num_channels(&self) -> usize {
CHANNELS
}
pub fn to_tensor_nchw(self) -> ndarray::Array4<T> {
let data = self.data.insert_axis(ndarray::Axis(0));
data.permuted_axes([0, 3, 1, 2])
}
pub fn to_tensor_nhwc(self) -> ndarray::Array4<T> {
self.data.insert_axis(ndarray::Axis(0))
}
pub fn set_pixel(&mut self, x: usize, y: usize, ch: usize, val: T) -> Result<()>
where
T: Copy,
{
if x >= self.width() || y >= self.height() {
return Err(anyhow::anyhow!(
"Pixel coordinates ({}, {}) out of bounds ({}, {}).",
x,
y,
self.width(),
self.height()
));
}
if ch >= CHANNELS {
return Err(anyhow::anyhow!(
"Channel index ({}) out of bounds ({}).",
ch,
CHANNELS
));
}
self.data[[y, x, ch]] = val;
Ok(())
}
pub fn get_pixel(&self, x: usize, y: usize, ch: usize) -> Result<T>
where
T: Copy,
{
if x >= self.width() || y >= self.height() {
return Err(anyhow::anyhow!(
"Pixel coordinates ({}, {}) out of bounds ({}, {}).",
x,
y,
self.width(),
self.height()
));
}
if ch >= CHANNELS {
return Err(anyhow::anyhow!(
"Channel index ({}) out of bounds ({}).",
ch,
CHANNELS
));
}
Ok(self.data[[y, x, ch]])
}
}
#[cfg(test)]
mod tests {
use crate::image::{Image, ImageSize};
use anyhow::Result;
#[test]
fn image_size() {
let image_size = ImageSize {
width: 10,
height: 20,
};
assert_eq!(image_size.width, 10);
assert_eq!(image_size.height, 20);
}
#[test]
fn image_smoke() -> Result<()> {
let image = Image::<u8, 3>::new(
ImageSize {
width: 10,
height: 20,
},
vec![0u8; 10 * 20 * 3],
)?;
assert_eq!(image.size().width, 10);
assert_eq!(image.size().height, 20);
assert_eq!(image.num_channels(), 3);
Ok(())
}
#[test]
fn image_from_vec() -> Result<()> {
let image: Image<f32, 3> = Image::new(
ImageSize {
height: 3,
width: 2,
},
vec![0.0; 3 * 2 * 3],
)?;
assert_eq!(image.size().width, 2);
assert_eq!(image.size().height, 3);
assert_eq!(image.num_channels(), 3);
Ok(())
}
#[test]
fn image_cast() -> Result<()> {
let data = vec![0., 1., 2., 3., 4., 5.];
let image_f64 = Image::<f64, 3>::new(
ImageSize {
height: 2,
width: 1,
},
data,
)?;
assert_eq!(image_f64.data.get((1, 0, 2)).unwrap(), &5.0f64);
let image_u8 = image_f64.cast::<u8>()?;
assert_eq!(image_u8.data.get((1, 0, 2)).unwrap(), &5u8);
let image_i32: Image<i32, 3> = image_u8.cast()?;
assert_eq!(image_i32.data.get((1, 0, 2)).unwrap(), &5i32);
Ok(())
}
#[test]
fn image_rgbd() -> Result<()> {
let image = Image::<f32, 4>::new(
ImageSize {
height: 2,
width: 3,
},
vec![0f32; 2 * 3 * 4],
)?;
assert_eq!(image.size().width, 3);
assert_eq!(image.size().height, 2);
assert_eq!(image.num_channels(), 4);
Ok(())
}
#[test]
fn image_channel() -> Result<()> {
let image = Image::<f32, 3>::new(
ImageSize {
height: 2,
width: 1,
},
vec![0., 1., 2., 3., 4., 5.],
)?;
let channel = image.channel(2)?;
assert_eq!(channel.data.get((1, 0, 0)).unwrap(), &5.0f32);
Ok(())
}
#[test]
fn image_split_channels() -> Result<()> {
let image = Image::<f32, 3>::new(
ImageSize {
height: 2,
width: 1,
},
vec![0., 1., 2., 3., 4., 5.],
)
.unwrap();
let channels = image.split_channels()?;
assert_eq!(channels.len(), 3);
assert_eq!(channels[0].data.get((1, 0, 0)).unwrap(), &3.0f32);
assert_eq!(channels[1].data.get((1, 0, 0)).unwrap(), &4.0f32);
assert_eq!(channels[2].data.get((1, 0, 0)).unwrap(), &5.0f32);
Ok(())
}
#[test]
fn convert_to_tensor() -> Result<()> {
let image = Image::<f32, 3>::new(
ImageSize {
height: 2,
width: 1,
},
vec![0., 1., 2., 3., 4., 5.],
)?;
let tensor_nchw = image.clone().to_tensor_nchw();
assert_eq!(tensor_nchw.shape(), &[1, 3, 2, 1]);
assert_eq!(tensor_nchw[[0, 2, 1, 0]], 5.0f32);
let tensor_nhwc = image.to_tensor_nhwc();
assert_eq!(tensor_nhwc.shape(), &[1, 2, 1, 3]);
assert_eq!(tensor_nhwc[[0, 1, 0, 2]], 5.0f32);
Ok(())
}
}