use crate::{allocator::ImageAllocator, error::ImageError};
use kornia_tensor::{Tensor, Tensor2, Tensor3};
#[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
)
}
}
impl From<[usize; 2]> for ImageSize {
fn from(size: [usize; 2]) -> Self {
ImageSize {
width: size[0],
height: size[1],
}
}
}
impl From<ImageSize> for [u32; 2] {
fn from(size: ImageSize) -> Self {
[size.width as u32, size.height as u32]
}
}
#[derive(Clone)]
pub struct Image<T, const C: usize, A: ImageAllocator>(pub Tensor3<T, A>);
impl<T, const C: usize, A: ImageAllocator> std::ops::Deref for Image<T, C, A> {
type Target = Tensor3<T, A>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T, const C: usize, A: ImageAllocator> std::ops::DerefMut for Image<T, C, A> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<T, const C: usize, A: ImageAllocator> Image<T, C, A> {
pub fn new(size: ImageSize, data: Vec<T>, alloc: A) -> Result<Self, ImageError>
where
T: Clone, {
if data.len() != size.width * size.height * C {
return Err(ImageError::InvalidChannelShape(
data.len(),
size.width * size.height * C,
));
}
Ok(Self(Tensor3::from_shape_vec(
[size.height, size.width, C],
data,
alloc,
)?))
}
pub fn from_size_val(size: ImageSize, val: T, alloc: A) -> Result<Self, ImageError>
where
T: Clone + Default,
{
let data = vec![val; size.width * size.height * C];
let image = Image::new(size, data, alloc)?;
Ok(image)
}
pub unsafe fn from_raw_parts(
size: ImageSize,
data: *const T,
len: usize,
alloc: A,
) -> Result<Self, ImageError>
where
T: Clone,
{
Tensor::from_raw_parts([size.height, size.width, C], data, len, alloc)?.try_into()
}
pub fn from_size_slice(size: ImageSize, data: &[T], alloc: A) -> Result<Self, ImageError>
where
T: Clone,
{
let tensor: Tensor3<T, A> =
Tensor::from_shape_slice([size.height, size.width, C], data, alloc)?;
Image::try_from(tensor)
}
pub fn map<U>(&self, f: impl Fn(&T) -> U) -> Result<Image<U, C, A>, ImageError>
where
U: Clone,
{
let data = self.as_slice().iter().map(f).collect::<Vec<U>>();
let alloc = self.storage.alloc();
Image::<U, C, A>::new(self.size(), data, alloc.clone())
}
pub fn cast<U>(&self) -> Result<Image<U, C, A>, ImageError>
where
U: num_traits::NumCast + Clone + Copy, T: num_traits::NumCast + Clone + Copy, {
let casted_data = self
.as_slice()
.iter()
.map(|&x| {
let xu = U::from(x).ok_or(ImageError::CastError)?;
Ok(xu)
})
.collect::<Result<Vec<U>, ImageError>>()?;
let alloc = self.storage.alloc();
Image::new(self.size(), casted_data, alloc.clone())
}
pub fn channel(&self, channel: usize) -> Result<Image<T, 1, A>, ImageError>
where
T: Clone,
{
if channel >= C {
return Err(ImageError::ChannelIndexOutOfBounds(channel, C));
}
let channel_data = self
.as_slice()
.iter()
.skip(channel)
.step_by(C)
.cloned()
.collect();
let alloc = self.storage.alloc();
Image::new(self.size(), channel_data, alloc.clone())
}
pub fn split_channels(&self) -> Result<Vec<Image<T, 1, A>>, ImageError>
where
T: Clone + Copy, {
let mut channels = Vec::with_capacity(C);
for i in 0..C {
channels.push(self.channel(i)?);
}
Ok(channels)
}
pub fn size(&self) -> ImageSize {
ImageSize {
width: self.shape[1],
height: self.shape[0],
}
}
pub fn cols(&self) -> usize {
self.shape[1]
}
pub fn rows(&self) -> usize {
self.shape[0]
}
pub fn width(&self) -> usize {
self.cols()
}
pub fn height(&self) -> usize {
self.rows()
}
pub fn num_channels(&self) -> usize {
C
}
pub fn cast_and_scale<U>(self, scale: U) -> Result<Image<U, C, A>, ImageError>
where
U: num_traits::NumCast + std::ops::Mul<Output = U> + Clone + Copy,
T: num_traits::NumCast + Clone + Copy,
{
let casted_data = self
.as_slice()
.iter()
.map(|&x| {
let xu = U::from(x).ok_or(ImageError::CastError)?;
Ok(xu * scale)
})
.collect::<Result<Vec<U>, ImageError>>()?;
let alloc = self.storage.alloc();
Image::new(self.size(), casted_data, alloc.clone())
}
pub fn scale_and_cast<U>(&self, scale: T) -> Result<Image<U, C, A>, ImageError>
where
U: num_traits::NumCast + Clone + Copy,
T: num_traits::NumCast + std::ops::Mul<Output = T> + Clone + Copy,
{
let casted_data = self
.as_slice()
.iter()
.map(|&x| {
let xu = U::from(x * scale).ok_or(ImageError::CastError)?;
Ok(xu)
})
.collect::<Result<Vec<U>, ImageError>>()?;
let alloc = self.storage.alloc();
Image::new(self.size(), casted_data, alloc.clone())
}
pub fn get_pixel(&self, x: usize, y: usize, ch: usize) -> Result<&T, ImageError> {
if x >= self.width() || y >= self.height() {
return Err(ImageError::PixelIndexOutOfBounds(
x,
y,
self.width(),
self.height(),
));
}
if ch >= C {
return Err(ImageError::ChannelIndexOutOfBounds(ch, C));
}
let val = match self.get([y, x, ch]) {
Some(v) => v,
None => return Err(ImageError::ImageDataNotContiguous),
};
Ok(val)
}
pub fn set_pixel(&mut self, x: usize, y: usize, ch: usize, val: T) -> Result<(), ImageError> {
if x >= self.width() || y >= self.height() {
return Err(ImageError::PixelIndexOutOfBounds(
x,
y,
self.width(),
self.height(),
));
}
if ch >= C {
return Err(ImageError::ChannelIndexOutOfBounds(ch, C));
}
let idx = y * self.width() * C + x * C + ch;
self.as_slice_mut()[idx] = val;
Ok(())
}
pub fn into_vec(self) -> Vec<T> {
self.0.into_vec()
}
pub fn to_vec(&self) -> Vec<T>
where
T: Clone,
{
self.as_slice().to_vec()
}
}
impl<T, A: ImageAllocator> TryFrom<Tensor2<T, A>> for Image<T, 1, A>
where
T: Clone,
{
type Error = ImageError;
fn try_from(value: Tensor2<T, A>) -> Result<Self, Self::Error> {
let alloc = value.storage.alloc();
Self::from_size_slice(
ImageSize {
width: value.shape[1],
height: value.shape[0],
},
value.as_slice(),
alloc.clone(),
)
}
}
impl<T, const C: usize, A: ImageAllocator> TryFrom<Tensor3<T, A>> for Image<T, C, A> {
type Error = ImageError;
fn try_from(value: Tensor3<T, A>) -> Result<Self, Self::Error> {
if value.shape[2] != C {
return Err(ImageError::InvalidChannelShape(value.shape[2], C));
}
Ok(Self(value))
}
}
impl<T, const C: usize, A: ImageAllocator> TryInto<Tensor3<T, A>> for Image<T, C, A> {
type Error = ImageError;
fn try_into(self) -> Result<Tensor3<T, A>, Self::Error> {
Ok(self.0)
}
}
#[cfg(test)]
mod tests {
use crate::image::{Image, ImageError, ImageSize};
use kornia_tensor::{CpuAllocator, Tensor};
#[test]
fn test_image_size() {
let image_size = ImageSize {
width: 10,
height: 20,
};
assert_eq!(image_size.width, 10);
assert_eq!(image_size.height, 20);
}
#[test]
fn test_image_smoke() -> Result<(), ImageError> {
let image = Image::<u8, 3, CpuAllocator>::new(
ImageSize {
width: 10,
height: 20,
},
vec![0u8; 10 * 20 * 3],
CpuAllocator,
)?;
assert_eq!(image.size().width, 10);
assert_eq!(image.size().height, 20);
assert_eq!(image.num_channels(), 3);
Ok(())
}
#[test]
fn test_image_from_vec() -> Result<(), ImageError> {
let image: Image<f32, 3, CpuAllocator> = Image::new(
ImageSize {
height: 3,
width: 2,
},
vec![0.0; 3 * 2 * 3],
CpuAllocator,
)?;
assert_eq!(image.size().width, 2);
assert_eq!(image.size().height, 3);
assert_eq!(image.num_channels(), 3);
Ok(())
}
#[test]
fn test_image_cast() -> Result<(), ImageError> {
let data = vec![0, 1, 2, 3, 4, 5];
let image_u8 = Image::<_, 3, CpuAllocator>::new(
ImageSize {
height: 2,
width: 1,
},
data,
CpuAllocator,
)?;
assert_eq!(image_u8.get([1, 0, 2]), Some(&5u8));
let image_i32: Image<i32, 3, CpuAllocator> = image_u8.cast()?;
assert_eq!(image_i32.get([1, 0, 2]), Some(&5i32));
Ok(())
}
#[test]
fn test_image_rgbd() -> Result<(), ImageError> {
let image = Image::<f32, 4, CpuAllocator>::new(
ImageSize {
height: 2,
width: 3,
},
vec![0f32; 2 * 3 * 4],
CpuAllocator,
)?;
assert_eq!(image.size().width, 3);
assert_eq!(image.size().height, 2);
assert_eq!(image.num_channels(), 4);
Ok(())
}
#[test]
fn test_image_channel() -> Result<(), ImageError> {
let image = Image::<f32, 3, CpuAllocator>::new(
ImageSize {
height: 2,
width: 1,
},
vec![0., 1., 2., 3., 4., 5.],
CpuAllocator,
)?;
let channel = image.channel(2)?;
assert_eq!(channel.get([1, 0, 0]), Some(&5.0f32));
Ok(())
}
#[test]
fn test_image_split_channels() -> Result<(), ImageError> {
let image = Image::<f32, 3, CpuAllocator>::new(
ImageSize {
height: 2,
width: 1,
},
vec![0., 1., 2., 3., 4., 5.],
CpuAllocator,
)
.unwrap();
let channels = image.split_channels()?;
assert_eq!(channels.len(), 3);
assert_eq!(channels[0].get([1, 0, 0]), Some(&3.0f32));
assert_eq!(channels[1].get([1, 0, 0]), Some(&4.0f32));
assert_eq!(channels[2].get([1, 0, 0]), Some(&5.0f32));
Ok(())
}
#[test]
fn test_scale_and_cast() -> Result<(), ImageError> {
let data = vec![0u8, 0, 255, 0, 0, 255];
let image_u8 = Image::<u8, 3, CpuAllocator>::new(
ImageSize {
height: 2,
width: 1,
},
data,
CpuAllocator,
)?;
let image_f32 = image_u8.cast_and_scale::<f32>(1. / 255.0)?;
assert_eq!(image_f32.get([1, 0, 2]), Some(&1.0f32));
Ok(())
}
#[test]
fn test_cast_and_scale() -> Result<(), ImageError> {
let data = vec![0u8, 0, 255, 0, 0, 255];
let image_u8 = Image::<u8, 3, CpuAllocator>::new(
ImageSize {
height: 2,
width: 1,
},
data,
CpuAllocator,
)?;
let image_f32 = image_u8.cast_and_scale::<f32>(1. / 255.0)?;
assert_eq!(image_f32.get([1, 0, 2]), Some(&1.0f32));
Ok(())
}
#[test]
fn test_image_from_tensor() -> Result<(), ImageError> {
let data = vec![0u8, 1, 2, 3, 4, 5];
let tensor = Tensor::<u8, 2, _>::from_shape_vec([2, 3], data, CpuAllocator)?;
let image = Image::<u8, 1, CpuAllocator>::try_from(tensor.clone())?;
assert_eq!(image.size().width, 3);
assert_eq!(image.size().height, 2);
assert_eq!(image.num_channels(), 1);
let image_2: Image<u8, 1, CpuAllocator> = tensor.try_into()?;
assert_eq!(image_2.size().width, 3);
assert_eq!(image_2.size().height, 2);
assert_eq!(image_2.num_channels(), 1);
Ok(())
}
#[test]
fn test_image_from_tensor_3d() -> Result<(), ImageError> {
let tensor = Tensor::<u8, 3, CpuAllocator>::from_shape_vec(
[2, 3, 4],
vec![0u8; 2 * 3 * 4],
CpuAllocator,
)?;
let image = Image::<u8, 4, CpuAllocator>::try_from(tensor.clone())?;
assert_eq!(image.size().width, 3);
assert_eq!(image.size().height, 2);
assert_eq!(image.num_channels(), 4);
let image_2: Image<u8, 4, CpuAllocator> = tensor.try_into()?;
assert_eq!(image_2.size().width, 3);
assert_eq!(image_2.size().height, 2);
assert_eq!(image_2.num_channels(), 4);
Ok(())
}
#[test]
fn test_image_from_raw_parts() -> Result<(), ImageError> {
let data = vec![0u8, 1, 2, 3, 4, 5];
let image = unsafe {
Image::<_, 1, CpuAllocator>::from_raw_parts(
[2, 3].into(),
data.as_ptr(),
data.len(),
CpuAllocator,
)?
};
std::mem::forget(data);
assert_eq!(image.size().width, 2);
assert_eq!(image.size().height, 3);
assert_eq!(image.num_channels(), 1);
Ok(())
}
#[test]
fn test_get_pixel() -> Result<(), ImageError> {
let image = Image::<u8, 3, CpuAllocator>::new(
ImageSize {
height: 2,
width: 1,
},
vec![1, 2, 5, 19, 255, 128],
CpuAllocator,
)?;
assert_eq!(image.get_pixel(0, 0, 0)?, &1);
assert_eq!(image.get_pixel(0, 0, 1)?, &2);
assert_eq!(image.get_pixel(0, 0, 2)?, &5);
assert_eq!(image.get_pixel(0, 1, 0)?, &19);
assert_eq!(image.get_pixel(0, 1, 1)?, &255);
assert_eq!(image.get_pixel(0, 1, 2)?, &128);
Ok(())
}
#[test]
fn test_set_pixel() -> Result<(), ImageError> {
let mut image = Image::<u8, 3, CpuAllocator>::new(
ImageSize {
height: 2,
width: 1,
},
vec![1, 2, 5, 19, 255, 128],
CpuAllocator,
)?;
image.set_pixel(0, 0, 0, 128)?;
image.set_pixel(0, 1, 1, 25)?;
assert_eq!(image.get_pixel(0, 0, 0)?, &128);
assert_eq!(image.get_pixel(0, 1, 1)?, &25);
Ok(())
}
#[test]
fn test_image_map() -> Result<(), ImageError> {
let image_u8 = Image::<u8, 1, CpuAllocator>::new(
ImageSize {
height: 2,
width: 1,
},
vec![0, 128],
CpuAllocator,
)?;
let image_f32 = image_u8.map(|x| (x + 2) as f32)?;
assert_eq!(image_f32.size().width, 1);
assert_eq!(image_f32.size().height, 2);
assert_eq!(image_f32.num_channels(), 1);
assert_eq!(image_f32.get([0, 0, 0]), Some(&2.0f32));
assert_eq!(image_f32.get([1, 0, 0]), Some(&130.0f32));
Ok(())
}
}