From 59c60b1f115e85c21a901979f32e441b6b1a963a Mon Sep 17 00:00:00 2001 From: xd009642 Date: Tue, 26 Nov 2019 23:08:22 +0000 Subject: [PATCH 1/9] WIP for the grand refactor Types types types. What type of trouble do I make for myself --- src/core/colour_models.rs | 232 +++++++++++++++------- src/core/image.rs | 28 ++- src/core/padding.rs | 8 +- src/enhancement/histogram_equalisation.rs | 10 +- src/format/mod.rs | 15 +- src/format/netpbm.rs | 26 ++- src/processing/canny.rs | 10 +- src/processing/conv.rs | 25 ++- src/processing/filter.rs | 19 +- src/processing/sobel.rs | 39 +++- src/processing/threshold.rs | 35 ++-- src/transform/mod.rs | 14 +- 12 files changed, 312 insertions(+), 149 deletions(-) diff --git a/src/core/colour_models.rs b/src/core/colour_models.rs index 36e795a..5260e51 100644 --- a/src/core/colour_models.rs +++ b/src/core/colour_models.rs @@ -1,6 +1,6 @@ use crate::core::traits::*; use crate::core::{normalise_pixel_value, Image}; -use ndarray::{prelude::*, s, Zip}; +use ndarray::{prelude::*, s, Data, OwnedRepr, Zip}; use num_traits::cast::{FromPrimitive, NumCast}; use num_traits::{Num, NumAssignOps}; use std::convert::From; @@ -182,8 +182,9 @@ where (red, green, blue) } -impl From> for Image +impl From> for Image where + U: Data, T: Copy + Clone + FromPrimitive @@ -194,7 +195,7 @@ where + Display + PixelBound, { - fn from(image: Image) -> Self { + fn from(image: Image) -> Self { let mut res = Array3::::zeros((image.rows(), image.cols(), HSV::channels())); let window = image.data.windows((1, 1, image.channels())); @@ -210,8 +211,9 @@ where } } -impl From> for Image +impl From> for Image where + U: Data, T: Copy + Clone + FromPrimitive @@ -222,7 +224,7 @@ where + Display + PixelBound, { - fn from(image: Image) -> Self { + fn from(image: Image) -> Self { let mut res = Array3::::zeros((image.rows(), image.cols(), RGB::channels())); let window = image.data.windows((1, 1, image.channels())); @@ -238,8 +240,9 @@ where } } -impl From> for Image +impl From> for Image where + U: Data, T: Copy + Clone + FromPrimitive @@ -250,7 +253,7 @@ where + Display + PixelBound, { - fn from(image: Image) -> Self { + fn from(image: Image) -> Self { let mut res = Array3::::zeros((image.rows(), image.cols(), Gray::channels())); let window = image.data.windows((1, 1, image.channels())); @@ -268,8 +271,9 @@ where } } -impl From> for Image +impl From> for Image where + U: Data, T: Copy + Clone + FromPrimitive @@ -280,7 +284,7 @@ where + Display + PixelBound, { - fn from(image: Image) -> Self { + fn from(image: Image) -> Self { let mut res = Array3::::zeros((image.rows(), image.cols(), RGB::channels())); let window = image.data.windows((1, 1, image.channels())); @@ -294,8 +298,9 @@ where } } -impl From> for Image +impl From> for Image where + U: Data, T: Copy + Clone + FromPrimitive @@ -306,7 +311,7 @@ where + Display + PixelBound, { - fn from(image: Image) -> Self { + fn from(image: Image) -> Self { let mut res = Array3::::zeros((image.rows(), image.cols(), CIEXYZ::channels())); let window = image.data.windows((1, 1, image.channels())); @@ -332,8 +337,9 @@ where } } -impl From> for Image +impl From> for Image where + U: Data, T: Copy + Clone + FromPrimitive @@ -344,7 +350,7 @@ where + Display + PixelBound, { - fn from(image: Image) -> Self { + fn from(image: Image) -> Self { let mut res = Array3::::zeros((image.rows(), image.cols(), RGB::channels())); let window = image.data.windows((1, 1, image.channels())); @@ -370,241 +376,312 @@ where } } -impl From> for Image { +impl From> for Image +where + T: Data, +{ fn from(image: Image) -> Self { Self::from_data(image.data) } } -impl From> for Image { +impl From> for Image +where + T: Data, +{ fn from(image: Image) -> Self { Self::from_data(image.data) } } -impl From> for Image { +impl From> for Image +where + T: Data, +{ fn from(image: Image) -> Self { Self::from_data(image.data) } } -impl From> for Image { +impl From> for Image +where + T: Data, +{ fn from(image: Image) -> Self { Self::from_data(image.data) } } -impl From> for Image { +impl From> for Image +where + T: Data, +{ fn from(image: Image) -> Self { Self::from_data(image.data) } } -impl From> for Image { +impl From> for Image +where + T: Data, +{ fn from(image: Image) -> Self { Self::from_data(image.data) } } -impl From> for Image { +impl From> for Image +where + T: Data, +{ fn from(image: Image) -> Self { Self::from_data(image.data) } } -impl From> for Image { +impl From> for Image +where + T: Data, +{ fn from(image: Image) -> Self { Self::from_data(image.data) } } -impl From> for Image { +impl From> for Image +where + T: Data, +{ fn from(image: Image) -> Self { Self::from_data(image.data) } } -impl From> for Image { +impl From> for Image +where + T: Data, +{ fn from(image: Image) -> Self { Self::from_data(image.data) } } -impl From> for Image { +impl From> for Image +where + T: Data, +{ fn from(image: Image) -> Self { Self::from_data(image.data) } } -impl From> for Image { +impl From> for Image +where + T: Data, +{ fn from(image: Image) -> Self { Self::from_data(image.data) } } -impl From> for Image { +impl From> for Image +where + T: Data, +{ fn from(image: Image) -> Self { Self::from_data(image.data) } } -impl From> for Image { +impl From> for Image +where + T: Data, +{ fn from(image: Image) -> Self { Self::from_data(image.data) } } -impl From> for Image { +impl From> for Image +where + T: Data, +{ fn from(image: Image) -> Self { Self::from_data(image.data) } } -impl From> for Image { +impl From> for Image +where + T: Data, +{ fn from(image: Image) -> Self { Self::from_data(image.data) } } -impl From> for Image { +impl From> for Image +where + T: Data, +{ fn from(image: Image) -> Self { Self::from_data(image.data) } } -impl From> for Image { +impl From> for Image +where + T: Data, +{ fn from(image: Image) -> Self { Self::from_data(image.data) } } -impl From> for Image { +impl From> for Image +where + T: Data, +{ fn from(image: Image) -> Self { Self::from_data(image.data) } } -impl From> for Image { +impl From> for Image +where + T: Data, +{ fn from(image: Image) -> Self { Self::from_data(image.data) } } -impl From> for Image +impl From> for Image where + U: Data, T: Copy, { - fn from(image: Image) -> Self { + fn from(image: Image) -> Self { let shape = (image.rows(), image.cols(), Generic4::channels()); let data = Array3::from_shape_fn(shape, |(i, j, k)| image.data[[i, j, k]]); Self::from_data(data) } } -impl From> for Image +impl From> for Image where + U: Data, T: Copy, { - fn from(image: Image) -> Self { + fn from(image: Image) -> Self { let shape = (image.rows(), image.cols(), Generic3::channels()); let data = Array3::from_shape_fn(shape, |(i, j, k)| image.data[[i, j, k]]); Self::from_data(data) } } -impl From> for Image +impl From> for Image where + U: Data, T: Copy, { - fn from(image: Image) -> Self { + fn from(image: Image) -> Self { let shape = (image.rows(), image.cols(), Generic2::channels()); let data = Array3::from_shape_fn(shape, |(i, j, k)| image.data[[i, j, k]]); Self::from_data(data) } } -impl From> for Image +impl From> for Image where + U: Data, T: Copy, { - fn from(image: Image) -> Self { + fn from(image: Image) -> Self { let shape = (image.rows(), image.cols(), Generic1::channels()); let data = Array3::from_shape_fn(shape, |(i, j, k)| image.data[[i, j, k]]); Self::from_data(data) } } -impl From> for Image +impl From> for Image where + U: Data, T: Copy, { - fn from(image: Image) -> Self { + fn from(image: Image) -> Self { let shape = (image.rows(), image.cols(), Generic3::channels()); let data = Array3::from_shape_fn(shape, |(i, j, k)| image.data[[i, j, k]]); Self::from_data(data) } } -impl From> for Image +impl From> for Image where + U: Data, T: Copy, { - fn from(image: Image) -> Self { + fn from(image: Image) -> Self { let shape = (image.rows(), image.cols(), Generic2::channels()); let data = Array3::from_shape_fn(shape, |(i, j, k)| image.data[[i, j, k]]); Self::from_data(data) } } -impl From> for Image +impl From> for Image, Generic1> where + U: Data, T: Copy, { - fn from(image: Image) -> Self { + fn from(image: Image) -> Self { let shape = (image.rows(), image.cols(), Generic1::channels()); let data = Array3::from_shape_fn(shape, |(i, j, k)| image.data[[i, j, k]]); Self::from_data(data) } } -impl From> for Image +impl From> for Image, Generic2> where + U: Data, T: Copy, { - fn from(image: Image) -> Self { + fn from(image: Image) -> Self { let shape = (image.rows(), image.cols(), Generic2::channels()); let data = Array3::from_shape_fn(shape, |(i, j, k)| image.data[[i, j, k]]); Self::from_data(data) } } -impl From> for Image +impl From> for Image, Generic1> where + U: Data, T: Copy, { - fn from(image: Image) -> Self { + fn from(image: Image) -> Self { let shape = (image.rows(), image.cols(), Generic1::channels()); let data = Array3::from_shape_fn(shape, |(i, j, k)| image.data[[i, j, k]]); Self::from_data(data) } } -impl From> for Image +impl From> for Image, Generic1> where + U: Data, T: Copy, { - fn from(image: Image) -> Self { + fn from(image: Image) -> Self { let shape = (image.rows(), image.cols(), Generic1::channels()); let data = Array3::from_shape_fn(shape, |(i, j, k)| image.data[[i, j, k]]); Self::from_data(data) } } -impl From> for Image +impl From> for Image, Generic5> where + U: Data, T: Copy + Num, { - fn from(image: Image) -> Self { + fn from(image: Image) -> Self { let shape = (image.rows(), image.cols(), Generic5::channels()); let mut data = Array3::zeros(shape); data.slice_mut(s![.., .., 0..Generic1::channels()]) @@ -613,11 +690,12 @@ where } } -impl From> for Image +impl From> for Image, Generic5> where + U: Data, T: Copy + Num, { - fn from(image: Image) -> Self { + fn from(image: Image) -> Self { let shape = (image.rows(), image.cols(), Generic5::channels()); let mut data = Array3::zeros(shape); data.slice_mut(s![.., .., 0..Generic2::channels()]) @@ -626,11 +704,12 @@ where } } -impl From> for Image +impl From> for Image where + U: Data, T: Copy + Num, { - fn from(image: Image) -> Self { + fn from(image: Image) -> Self { let shape = (image.rows(), image.cols(), Generic5::channels()); let mut data = Array3::zeros(shape); data.slice_mut(s![.., .., 0..Generic3::channels()]) @@ -639,11 +718,12 @@ where } } -impl From> for Image +impl From> for Image where + U: Data, T: Copy + Num, { - fn from(image: Image) -> Self { + fn from(image: Image) -> Self { let shape = (image.rows(), image.cols(), Generic5::channels()); let mut data = Array3::zeros(shape); data.slice_mut(s![.., .., 0..Generic4::channels()]) @@ -652,11 +732,12 @@ where } } -impl From> for Image +impl From> for Image where + U: Data, T: Copy + Num, { - fn from(image: Image) -> Self { + fn from(image: Image) -> Self { let shape = (image.rows(), image.cols(), Generic4::channels()); let mut data = Array3::zeros(shape); data.slice_mut(s![.., .., 0..Generic1::channels()]) @@ -665,11 +746,12 @@ where } } -impl From> for Image +impl From> for Image where + U: Data, T: Copy + Num, { - fn from(image: Image) -> Self { + fn from(image: Image) -> Self { let shape = (image.rows(), image.cols(), Generic4::channels()); let mut data = Array3::zeros(shape); data.slice_mut(s![.., .., 0..Generic2::channels()]) @@ -678,11 +760,12 @@ where } } -impl From> for Image +impl From> for Image where + U: Data, T: Copy + Num, { - fn from(image: Image) -> Self { + fn from(image: Image) -> Self { let shape = (image.rows(), image.cols(), Generic4::channels()); let mut data = Array3::zeros(shape); data.slice_mut(s![.., .., 0..Generic3::channels()]) @@ -691,11 +774,12 @@ where } } -impl From> for Image +impl From> for Image where + U: Data, T: Copy + Num, { - fn from(image: Image) -> Self { + fn from(image: Image) -> Self { let shape = (image.rows(), image.cols(), Generic3::channels()); let mut data = Array3::zeros(shape); data.slice_mut(s![.., .., 0..Generic1::channels()]) @@ -704,11 +788,12 @@ where } } -impl From> for Image +impl From> for Image where + U: Data, T: Copy + Num, { - fn from(image: Image) -> Self { + fn from(image: Image) -> Self { let shape = (image.rows(), image.cols(), Generic3::channels()); let mut data = Array3::zeros(shape); data.slice_mut(s![.., .., 0..Generic2::channels()]) @@ -717,11 +802,12 @@ where } } -impl From> for Image +impl From> for Image where + U: Data, T: Copy + Num, { - fn from(image: Image) -> Self { + fn from(image: Image) -> Self { let shape = (image.rows(), image.cols(), Generic2::channels()); let mut data = Array3::zeros(shape); data.slice_mut(s![.., .., 0..Generic1::channels()]) diff --git a/src/core/image.rs b/src/core/image.rs index c01f75d..84f6081 100644 --- a/src/core/image.rs +++ b/src/core/image.rs @@ -1,7 +1,7 @@ use crate::core::colour_models::*; use crate::core::traits::PixelBound; use ndarray::prelude::*; -use ndarray::s; +use ndarray::{s, Data}; use num_traits::cast::{FromPrimitive, NumCast}; use num_traits::Num; use std::marker::PhantomData; @@ -11,6 +11,7 @@ use std::marker::PhantomData; pub struct Image where C: ColourModel, + T: Data, { /// Images are always going to be 3D to handle rows, columns and colour /// channels @@ -20,19 +21,21 @@ where /// number of channels in an image as this may cause other functionality to /// perform incorrectly. Use conversions to one of the `Generic` colour models /// instead. - pub data: Array3, + pub data: ArrayBase, /// Representation of how colour is encoded in the image pub(crate) model: PhantomData, } -impl Image +impl Image where + U: Data, T: Copy + Clone + FromPrimitive + Num + NumCast + PixelBound, C: ColourModel, { /// Converts image into a different type - doesn't scale to new pixel bounds - pub fn into_type(self) -> Image + pub fn into_type(self) -> Image where + U2: Data, T2: Copy + Clone + FromPrimitive + Num + NumCast + PixelBound, { let rescale = |x: &T| { @@ -43,12 +46,13 @@ where T2::from_f64(scaled).unwrap_or_else(T2::zero) + T2::min_pixel() }; let data = self.data.map(rescale); - Image::::from_data(data) + Image::::from_data(data) } } -impl Image +impl Image where + U: Data, T: Clone + Num, C: ColourModel, { @@ -73,10 +77,22 @@ where model: PhantomData, } } + + /// Create an image given an existing ndarray + pub fn from_array(data: ArrayBase) -> Self + where + V: Data, + { + Image { + data, + model: PhantomData, + } + } } impl Image where + T: Data, C: ColourModel, { /// Construct the image from a given Array3 diff --git a/src/core/padding.rs b/src/core/padding.rs index f9a8e40..257a5f8 100644 --- a/src/core/padding.rs +++ b/src/core/padding.rs @@ -1,5 +1,5 @@ use crate::core::{ColourModel, Image}; -use ndarray::{prelude::*, s}; +use ndarray::{prelude::*, s, Data}; use num_traits::identities::Zero; use std::marker::PhantomData; @@ -80,8 +80,9 @@ pub trait PaddingExt { fn pad(&self, padding: (usize, usize), strategy: &dyn PaddingStrategy) -> Self; } -impl PaddingExt for Array3 +impl PaddingExt for ArrayBase where + U: Data, T: Copy, { type Data = T; @@ -91,8 +92,9 @@ where } } -impl PaddingExt for Image +impl PaddingExt for Image where + U: Data, T: Copy, C: ColourModel, { diff --git a/src/enhancement/histogram_equalisation.rs b/src/enhancement/histogram_equalisation.rs index e2e172a..fe2d4e8 100644 --- a/src/enhancement/histogram_equalisation.rs +++ b/src/enhancement/histogram_equalisation.rs @@ -1,5 +1,5 @@ use crate::core::*; -use ndarray::prelude::*; +use ndarray::{prelude::*, Data}; use ndarray_stats::{histogram::Grid, HistogramExt}; use num_traits::cast::{FromPrimitive, ToPrimitive}; use num_traits::{Num, NumAssignOps}; @@ -19,8 +19,9 @@ where fn equalise_hist_inplace(&mut self, grid: Grid); } -impl HistogramEqExt for Array3 +impl HistogramEqExt for ArrayBase where + U: Data, T: Copy + Clone + Ord + Num + NumAssignOps + ToPrimitive + FromPrimitive + PixelBound, { fn equalise_hist(&self, grid: Grid) -> Self { @@ -71,9 +72,10 @@ where } } -impl HistogramEqExt for Image +impl HistogramEqExt for Image where - Image: Clone, + U: Data, + Image: Clone, T: Copy + Clone + Ord + Num + NumAssignOps + ToPrimitive + FromPrimitive + PixelBound, C: ColourModel, { diff --git a/src/format/mod.rs b/src/format/mod.rs index 55a1dd3..d5dd1fc 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -1,5 +1,6 @@ use crate::core::traits::PixelBound; use crate::core::*; +use ndarray::Data; use num_traits::cast::{FromPrimitive, NumCast}; use num_traits::{Num, NumAssignOps}; use std::fmt::Display; @@ -8,17 +9,18 @@ use std::io::prelude::*; use std::path::Path; /// Trait for an image encoder -pub trait Encoder +pub trait Encoder where + U: Data, T: Copy + Clone + Num + NumAssignOps + NumCast + PartialOrd + Display + PixelBound, C: ColourModel, { /// Encode an image into a sequence of bytes for the given format - fn encode(&self, image: &Image) -> Vec; + fn encode(&self, image: &Image) -> Vec; /// Encode an image saving it to the file at filename. This function shouldn't /// add an extension preferring the user to do that instead. - fn encode_file>(&self, image: &Image, filename: P) -> std::io::Result<()> { + fn encode_file>(&self, image: &Image, filename: P) -> std::io::Result<()> { let mut file = File::create(filename)?; file.write_all(&self.encode(image))?; Ok(()) @@ -26,8 +28,9 @@ where } /// Trait for an image decoder, use this to get an image from a byte stream -pub trait Decoder +pub trait Decoder where + U: Data, T: Copy + Clone + FromPrimitive @@ -41,9 +44,9 @@ where { /// From the bytes decode an image, will perform any scaling or conversions /// required to represent elements with type T. - fn decode(&self, bytes: &[u8]) -> std::io::Result>; + fn decode(&self, bytes: &[u8]) -> std::io::Result>; /// Given a filename decode an image performing any necessary conversions. - fn decode_file>(&self, filename: P) -> std::io::Result> { + fn decode_file>(&self, filename: P) -> std::io::Result> { let bytes = read(filename)?; self.decode(&bytes) } diff --git a/src/format/netpbm.rs b/src/format/netpbm.rs index 3f540af..4672e3b 100644 --- a/src/format/netpbm.rs +++ b/src/format/netpbm.rs @@ -1,5 +1,6 @@ use crate::core::{normalise_pixel_value, Image, PixelBound, RGB}; use crate::format::{Decoder, Encoder}; +use ndarray::Data; use num_traits::cast::{FromPrimitive, NumCast}; use num_traits::{Num, NumAssignOps}; use std::fmt::Display; @@ -32,8 +33,9 @@ impl Default for PpmEncoder { /// The ColourModel type argument is locked to RGB - this prevents calling /// RGB::into::() unnecessarily which is unavoidable until trait specialisation is /// stabilised. -impl Encoder for PpmEncoder +impl Encoder for PpmEncoder where + U: Data, T: Copy + Clone + Num @@ -44,7 +46,7 @@ where + PixelBound + FromPrimitive, { - fn encode(&self, image: &Image) -> Vec { + fn encode(&self, image: &Image) -> Vec { use EncodingType::*; match self.encoding { Plaintext => self.encode_plaintext(image), @@ -71,8 +73,9 @@ impl PpmEncoder { /// Gets the maximum pixel value in the image across all channels. This is /// used in the PPM header - fn get_max_value(image: &Image) -> Option + fn get_max_value(image: &Image) -> Option where + U: Data, T: Copy + Clone + Num + NumAssignOps + NumCast + PartialOrd + Display + PixelBound, { image @@ -92,8 +95,9 @@ impl PpmEncoder { } /// Encode the image into the binary PPM format (P6) returning the bytes - fn encode_binary(self, image: &Image) -> Vec + fn encode_binary(self, image: &Image) -> Vec where + U: Data, T: Copy + Clone + Num + NumAssignOps + NumCast + PartialOrd + Display + PixelBound, { let max_val = Self::get_max_value(image).unwrap_or_else(|| 255); @@ -113,8 +117,9 @@ impl PpmEncoder { /// Encode the image into the plaintext PPM format (P3) returning the text as /// an array of bytes - fn encode_plaintext(self, image: &Image) -> Vec + fn encode_plaintext(self, image: &Image) -> Vec where + U: Data, T: Copy + Clone + Num + NumAssignOps + NumCast + PartialOrd + Display + PixelBound, { let max_val = 255; @@ -150,8 +155,9 @@ impl PpmEncoder { /// The ColourModel type argument is locked to RGB - this prevents calling /// RGB::into::() unnecessarily which is unavoidable until trait specialisation is /// stabilised. -impl Decoder for PpmDecoder +impl Decoder for PpmDecoder where + U: Data, T: Copy + Clone + Num @@ -162,7 +168,7 @@ where + PixelBound + FromPrimitive, { - fn decode(&self, bytes: &[u8]) -> std::io::Result> { + fn decode(&self, bytes: &[u8]) -> std::io::Result> { if bytes.len() < 9 { Err(Error::new( ErrorKind::InvalidData, @@ -220,8 +226,9 @@ impl PpmDecoder { } } - fn decode_binary(bytes: &[u8]) -> std::io::Result> + fn decode_binary(bytes: &[u8]) -> std::io::Result> where + U: Data, T: Copy + Clone + Num @@ -273,8 +280,9 @@ impl PpmDecoder { } } - fn decode_plaintext(bytes: &[u8]) -> std::io::Result> + fn decode_plaintext(bytes: &[u8]) -> std::io::Result> where + U: Data, T: Copy + Clone + Num diff --git a/src/processing/canny.rs b/src/processing/canny.rs index 8841a6d..61218c1 100644 --- a/src/processing/canny.rs +++ b/src/processing/canny.rs @@ -1,7 +1,7 @@ use crate::core::{ColourModel, Image}; use crate::processing::*; use ndarray::prelude::*; -use ndarray::IntoDimension; +use ndarray::{Data, IntoDimension, OwnedRepr}; use num_traits::{cast::FromPrimitive, real::Real, Num, NumAssignOps}; use std::collections::HashSet; use std::marker::PhantomData; @@ -38,12 +38,13 @@ pub struct CannyParameters { pub t2: T, } -impl CannyEdgeDetectorExt for Image +impl CannyEdgeDetectorExt for Image where + U: Data, T: Copy + Clone + FromPrimitive + Real + Num + NumAssignOps, C: ColourModel, { - type Output = Image; + type Output = Image, C>; fn canny_edge_detector(&self, params: CannyParameters) -> Result { let data = self.data.canny_edge_detector(params)?; @@ -54,8 +55,9 @@ where } } -impl CannyEdgeDetectorExt for Array3 +impl CannyEdgeDetectorExt for ArrayBase where + U: Data, T: Copy + Clone + FromPrimitive + Real + Num + NumAssignOps, { type Output = Array3; diff --git a/src/processing/conv.rs b/src/processing/conv.rs index 18cd90b..32a2a7e 100644 --- a/src/processing/conv.rs +++ b/src/processing/conv.rs @@ -2,7 +2,7 @@ use crate::core::padding::*; use crate::core::{ColourModel, Image}; use crate::processing::Error; use ndarray::prelude::*; -use ndarray::{s, Zip}; +use ndarray::{s, Data, OwnedRepr, Zip}; use num_traits::{Num, NumAssignOps}; use std::marker::PhantomData; use std::marker::Sized; @@ -14,10 +14,12 @@ where { /// Underlying data type to perform the convolution on type Data; + /// Type for the output as data will have to be allocated + type Output; /// Perform a convolution returning the resultant data /// applies the default padding of zero padding - fn conv2d(&self, kernel: ArrayView3) -> Result; + fn conv2d(&self, kernel: ArrayView3) -> Result; /// Performs the convolution inplace mutating the containers data /// applies the default padding of zero padding fn conv2d_inplace(&mut self, kernel: ArrayView3) -> Result<(), Error>; @@ -27,7 +29,7 @@ where &self, kernel: ArrayView3, strategy: &dyn PaddingStrategy, - ) -> Result; + ) -> Result; /// Performs the convolution inplace mutating the containers data /// applies the default padding of zero padding fn conv2d_inplace_with_padding( @@ -43,13 +45,15 @@ fn kernel_centre(rows: usize, cols: usize) -> (usize, usize) { (row_offset, col_offset) } -impl ConvolutionExt for Array3 +impl ConvolutionExt for ArrayBase where + U: Data, T: Copy + Clone + Num + NumAssignOps, { type Data = T; + type Output = ArrayBase, Ix3>; - fn conv2d(&self, kernel: ArrayView3) -> Result { + fn conv2d(&self, kernel: ArrayView3) -> Result { self.conv2d_with_padding(kernel, &ZeroPadding {}) } @@ -62,7 +66,7 @@ where &self, kernel: ArrayView3, strategy: &dyn PaddingStrategy, - ) -> Result { + ) -> Result { if self.shape()[2] != kernel.shape()[2] { Err(Error::ChannelDimensionMismatch) } else { @@ -73,7 +77,7 @@ where let shape = (self.shape()[0], self.shape()[1], self.shape()[2]); if shape.0 > 0 && shape.1 > 0 { - let mut result = Self::zeros(shape); + let mut result = Self::Output::zeros(shape); let tmp = self.pad((row_offset, col_offset), strategy); Zip::indexed(tmp.windows(kernel.dim())).apply(|(i, j, _), window| { @@ -98,12 +102,15 @@ where } } -impl ConvolutionExt for Image +impl ConvolutionExt for Image where + U: Data, T: Copy + Clone + Num + NumAssignOps, C: ColourModel, { type Data = T; + type Output = Image, C>; + fn conv2d(&self, kernel: ArrayView3) -> Result { let data = self.data.conv2d(kernel)?; Ok(Self { @@ -120,7 +127,7 @@ where &self, kernel: ArrayView3, strategy: &dyn PaddingStrategy, - ) -> Result { + ) -> Result { let data = self.data.conv2d_with_padding(kernel, strategy)?; Ok(Self { data, diff --git a/src/processing/filter.rs b/src/processing/filter.rs index f0a3f40..ab78e90 100644 --- a/src/processing/filter.rs +++ b/src/processing/filter.rs @@ -1,6 +1,6 @@ use crate::core::{ColourModel, Image}; use ndarray::prelude::*; -use ndarray::{IntoDimension, Zip}; +use ndarray::{Data, IntoDimension, OwnedRepr, Zip}; use ndarray_stats::interpolate::*; use ndarray_stats::Quantile1dExt; use noisy_float::types::n64; @@ -11,18 +11,22 @@ use std::marker::PhantomData; /// Median filter, given a region to move over the image, each pixel is given /// the median value of itself and it's neighbours pub trait MedianFilterExt { + type Output; /// Run the median filter given the region. Median is assumed to be calculated /// independently for each channel. - fn median_filter(&self, region: E) -> Self + fn median_filter(&self, region: E) -> Self::Output where E: IntoDimension; } -impl MedianFilterExt for Array3 +impl MedianFilterExt for ArrayBase where + U: Data, T: Copy + Clone + FromPrimitive + ToPrimitive + Num + Ord, { - fn median_filter(&self, region: E) -> Self + type Output = ArrayBase, Ix3>; + + fn median_filter(&self, region: E) -> Self::Output where E: IntoDimension, { @@ -43,12 +47,15 @@ where } } -impl MedianFilterExt for Image +impl MedianFilterExt for Image where + U: Data, T: Copy + Clone + FromPrimitive + ToPrimitive + Num + Ord, C: ColourModel, { - fn median_filter(&self, region: E) -> Self + type Output = Image, C>; + + fn median_filter(&self, region: E) -> Self::Output where E: IntoDimension, { diff --git a/src/processing/sobel.rs b/src/processing/sobel.rs index a4d6ecf..7b82824 100644 --- a/src/processing/sobel.rs +++ b/src/processing/sobel.rs @@ -1,7 +1,7 @@ use crate::core::*; use crate::processing::*; use core::ops::Neg; -use ndarray::prelude::*; +use ndarray::{prelude::*, Data, OwnedRepr}; use num_traits::{cast::FromPrimitive, real::Real, Num, NumAssignOps}; use std::marker::Sized; @@ -13,15 +13,25 @@ where /// Type to output type Output; /// Returns the magnitude output of the sobel - an image of only lines - fn apply_sobel(&self) -> Result; + fn apply_sobel(&self) -> Result; +} +pub trait FullSobelExt +where + Self: Sized, +{ + /// Type to output + type Output; /// Returns the magntitude and rotation outputs for use in other algorithms /// like the Canny edge detector. Rotation is in radians fn full_sobel(&self) -> Result<(Self::Output, Self::Output), Error>; } -fn get_edge_images(mat: &Array3) -> Result<(Array3, Array3), Error> +fn get_edge_images( + mat: &ArrayBase, +) -> Result<(ArrayBase, Ix3>, ArrayBase, Ix3>), Error> where + U: Data, T: Copy + Clone + Num + NumAssignOps + Neg + FromPrimitive + Real, { let v_temp: Array3 = SobelFilter::build_with_params(Orientation::Vertical).unwrap(); @@ -40,9 +50,9 @@ impl SobelExt for Array3 where T: Copy + Clone + Num + NumAssignOps + Neg + FromPrimitive + Real, { - type Output = Self; + type Output = ArrayBase, Ix3>; - fn apply_sobel(&self) -> Result { + fn apply_sobel(&self) -> Result { let (h_deriv, v_deriv) = get_edge_images(self)?; let h_deriv = h_deriv.mapv(|x| x.powi(2)); @@ -56,7 +66,12 @@ where Ok(result) } +} +impl FullSobelExt for Array3 +where + T: Copy + Clone + Num + NumAssignOps + Neg + FromPrimitive + Real, +{ fn full_sobel(&self) -> Result<(Self::Output, Self::Output), Error> { let (h_deriv, v_deriv) = get_edge_images(self)?; @@ -71,17 +86,27 @@ where } } -impl SobelExt for Image +impl SobelExt for Image where + U: Data, T: Copy + Clone + Num + NumAssignOps + Neg + FromPrimitive + Real, C: ColourModel, { - type Output = Array3; + type Output = Image, C>; fn apply_sobel(&self) -> Result { let data = self.data.apply_sobel()?; Ok(Image::from_data(data)) } +} + +impl FullSobelExt for Image +where + U: Data, + T: Copy + Clone + Num + NumAssignOps + Neg + FromPrimitive + Real, + C: ColourModel, +{ + type Output = ArrayBase, Ix3>; fn full_sobel(&self) -> Result<(Self::Output, Self::Output), Error> { self.data.full_sobel() diff --git a/src/processing/threshold.rs b/src/processing/threshold.rs index 49304f0..d92b88d 100644 --- a/src/processing/threshold.rs +++ b/src/processing/threshold.rs @@ -1,7 +1,7 @@ use crate::core::PixelBound; use crate::core::{ColourModel, Image}; use crate::processing::*; -use ndarray::prelude::*; +use ndarray::{prelude::*, Data, OwnedRepr}; use ndarray_stats::histogram::{Bins, Edges, Grid}; use ndarray_stats::HistogramExt; use ndarray_stats::QuantileExt; @@ -41,13 +41,14 @@ pub trait ThresholdMeanExt { fn threshold_mean(&self) -> Result; } -impl ThresholdOtsuExt for Image +impl ThresholdOtsuExt for Image where - Image: Clone, + U: Data, + Image: Clone, T: Copy + Clone + Ord + Num + NumAssignOps + ToPrimitive + FromPrimitive + PixelBound, C: ColourModel, { - type Output = Image; + type Output = Image, C>; fn threshold_otsu(&self) -> Result { let data = self.data.threshold_otsu()?; @@ -58,8 +59,9 @@ where } } -impl ThresholdOtsuExt for Array3 +impl ThresholdOtsuExt for ArrayBase where + U: Data, T: Copy + Clone + Ord + Num + NumAssignOps + ToPrimitive + FromPrimitive, { type Output = Array3; @@ -68,7 +70,7 @@ where if self.shape()[2] > 1 { Err(Error::ChannelDimensionMismatch) } else { - let value = calculate_threshold_otsu(&self)?; + let value = calculate_threshold_otsu(self)?; let mask = apply_threshold(self, value); Ok(mask) } @@ -82,8 +84,9 @@ where /// i.e. single channel; otherwise we need to output all 3 threshold values). /// Todo: Add optional nbins /// -fn calculate_threshold_otsu(mat: &Array3) -> Result +fn calculate_threshold_otsu(mat: &ArrayBase) -> Result where + U: Data, T: Copy + Clone + Ord + Num + NumAssignOps + ToPrimitive + FromPrimitive, { let mut threshold = 0.0; @@ -132,13 +135,14 @@ where Ok(threshold) } -impl ThresholdMeanExt for Image +impl ThresholdMeanExt for Image where - Image: Clone, + U: Data, + Image: Clone, T: Copy + Clone + Ord + Num + NumAssignOps + ToPrimitive + FromPrimitive + PixelBound, C: ColourModel, { - type Output = Image; + type Output = Image, C>; fn threshold_mean(&self) -> Result { let data = self.data.threshold_mean()?; @@ -149,8 +153,9 @@ where } } -impl ThresholdMeanExt for Array3 +impl ThresholdMeanExt for ArrayBase where + U: Data, T: Copy + Clone + Ord + Num + NumAssignOps + ToPrimitive + FromPrimitive, { type Output = Array3; @@ -159,22 +164,24 @@ where if self.shape()[2] > 1 { Err(Error::ChannelDimensionMismatch) } else { - let value = calculate_threshold_mean(&self)?; + let value = calculate_threshold_mean(self)?; let mask = apply_threshold(self, value); Ok(mask) } } } -fn calculate_threshold_mean(array: &Array3) -> Result +fn calculate_threshold_mean(array: &ArrayBase) -> Result where + U: Data, T: Copy + Clone + Num + NumAssignOps + ToPrimitive + FromPrimitive, { Ok(array.sum().to_f64().unwrap() / array.len() as f64) } -fn apply_threshold(data: &Array3, threshold: f64) -> Array3 +fn apply_threshold(data: &ArrayBase, threshold: f64) -> Array3 where + U: Data, T: Copy + Clone + Num + NumAssignOps + ToPrimitive + FromPrimitive, { let result = data.mapv(|x| x.to_f64().unwrap() >= threshold); diff --git a/src/transform/mod.rs b/src/transform/mod.rs index 9db811c..a5ac068 100644 --- a/src/transform/mod.rs +++ b/src/transform/mod.rs @@ -1,6 +1,6 @@ use crate::core::{ColourModel, Image}; use crate::transform::affine::translation; -use ndarray::{array, prelude::*, s, Data}; +use ndarray::{array, prelude::*, s, Data, OwnedRepr}; use ndarray_linalg::solve::Inverse; use num_traits::{Num, NumAssignOps}; use std::cmp::{max, min}; @@ -88,7 +88,7 @@ where T: Copy + Clone + Num + NumAssignOps, U: Data, { - type Output = Array3; + type Output = ArrayBase, Ix3>; fn transform( &self, @@ -139,12 +139,13 @@ where } } -impl TransformExt for Image +impl TransformExt for Image where + U: Data, T: Copy + Clone + Num + NumAssignOps, C: ColourModel, { - type Output = Self; + type Output = Image, C>; fn transform( &self, @@ -152,10 +153,7 @@ where output_size: Option<(usize, usize)>, ) -> Result { let data = self.data.transform(transform, output_size)?; - Ok(Self { - data, - model: PhantomData, - }) + Ok(Self::from_array(data)) } } From 0a92f707ebc58b1f145cdf4aa33c3f196a1ba5bf Mon Sep 17 00:00:00 2001 From: xd009642 Date: Tue, 26 Nov 2019 23:59:17 +0000 Subject: [PATCH 2/9] More progress fixing type errors --- src/core/colour_models.rs | 56 +++++++++++------------ src/core/image.rs | 15 ++++-- src/enhancement/histogram_equalisation.rs | 4 +- src/processing/conv.rs | 2 +- src/processing/sobel.rs | 8 +++- src/transform/mod.rs | 1 - 6 files changed, 49 insertions(+), 37 deletions(-) diff --git a/src/core/colour_models.rs b/src/core/colour_models.rs index 5260e51..13fe75d 100644 --- a/src/core/colour_models.rs +++ b/src/core/colour_models.rs @@ -182,7 +182,7 @@ where (red, green, blue) } -impl From> for Image +impl From> for Image, HSV> where U: Data, T: Copy @@ -196,7 +196,7 @@ where + PixelBound, { fn from(image: Image) -> Self { - let mut res = Array3::::zeros((image.rows(), image.cols(), HSV::channels())); + let mut res = Array3::<_>::zeros((image.rows(), image.cols(), HSV::channels())); let window = image.data.windows((1, 1, image.channels())); Zip::indexed(window).apply(|(i, j, _), pix| { @@ -211,7 +211,7 @@ where } } -impl From> for Image +impl From> for Image, RGB> where U: Data, T: Copy @@ -240,7 +240,7 @@ where } } -impl From> for Image +impl From> for Image, Gray> where U: Data, T: Copy @@ -271,7 +271,7 @@ where } } -impl From> for Image +impl From> for Image, RGB> where U: Data, T: Copy @@ -298,7 +298,7 @@ where } } -impl From> for Image +impl From> for Image, CIEXYZ> where U: Data, T: Copy @@ -337,7 +337,7 @@ where } } -impl From> for Image +impl From> for Image, RGB> where U: Data, T: Copy @@ -376,27 +376,27 @@ where } } -impl From> for Image +impl From> for Image, RGB> where - T: Data, + T: Data, { fn from(image: Image) -> Self { - Self::from_data(image.data) + image.into_type_raw() } } -impl From> for Image +impl From> for Image where - T: Data, + T: Data, { fn from(image: Image) -> Self { Self::from_data(image.data) } } -impl From> for Image +impl From> for Image, HSI> where - T: Data, + T: Data, { fn from(image: Image) -> Self { Self::from_data(image.data) @@ -475,63 +475,63 @@ where } } -impl From> for Image +impl From> for Image, Generic3> where - T: Data, + T: Data, { fn from(image: Image) -> Self { Self::from_data(image.data) } } -impl From> for Image +impl From> for Image, Generic3> where - T: Data, + T: Data, { fn from(image: Image) -> Self { Self::from_data(image.data) } } -impl From> for Image +impl From> for Image, Generic3> where - T: Data, + T: Data, { fn from(image: Image) -> Self { Self::from_data(image.data) } } -impl From> for Image +impl From> for Image, Generic3> where - T: Data, + T: Data, { fn from(image: Image) -> Self { Self::from_data(image.data) } } -impl From> for Image +impl From> for Image, Generic3> where - T: Data, + T: Data, { fn from(image: Image) -> Self { Self::from_data(image.data) } } -impl From> for Image +impl From> for Image, Generic3> where - T: Data, + T: Data, { fn from(image: Image) -> Self { Self::from_data(image.data) } } -impl From> for Image +impl From> for Image, Generic3> where - T: Data, + T: Data, { fn from(image: Image) -> Self { Self::from_data(image.data) diff --git a/src/core/image.rs b/src/core/image.rs index 84f6081..1cb6734 100644 --- a/src/core/image.rs +++ b/src/core/image.rs @@ -90,13 +90,13 @@ where } } -impl Image +impl Image where - T: Data, + T: Data, C: ColourModel, { /// Construct the image from a given Array3 - pub fn from_data(data: Array3) -> Self { + pub fn from_data(data: ArrayBase) -> Self { Image { data, model: PhantomData, @@ -125,6 +125,15 @@ where pub fn pixel_mut(&mut self, row: usize, col: usize) -> ArrayViewMut { self.data.slice_mut(s![row, col, ..]) } + + pub fn into_type_raw(self) -> Image + where + U2: Data, + C2: ColourModel, + { + assert_eq!(C2::channels(), C::channels()); + Image::::from_data(self.data) + } } /// Returns a normalised pixel value or 0 if it can't convert the types. diff --git a/src/enhancement/histogram_equalisation.rs b/src/enhancement/histogram_equalisation.rs index fe2d4e8..6e97e78 100644 --- a/src/enhancement/histogram_equalisation.rs +++ b/src/enhancement/histogram_equalisation.rs @@ -1,5 +1,5 @@ use crate::core::*; -use ndarray::{prelude::*, Data}; +use ndarray::{prelude::*, Data, DataMut}; use ndarray_stats::{histogram::Grid, HistogramExt}; use num_traits::cast::{FromPrimitive, ToPrimitive}; use num_traits::{Num, NumAssignOps}; @@ -21,7 +21,7 @@ where impl HistogramEqExt for ArrayBase where - U: Data, + U: Data + DataMut, T: Copy + Clone + Ord + Num + NumAssignOps + ToPrimitive + FromPrimitive + PixelBound, { fn equalise_hist(&self, grid: Grid) -> Self { diff --git a/src/processing/conv.rs b/src/processing/conv.rs index 32a2a7e..9a4e0ff 100644 --- a/src/processing/conv.rs +++ b/src/processing/conv.rs @@ -111,7 +111,7 @@ where type Data = T; type Output = Image, C>; - fn conv2d(&self, kernel: ArrayView3) -> Result { + fn conv2d(&self, kernel: ArrayView3) -> Result { let data = self.data.conv2d(kernel)?; Ok(Self { data, diff --git a/src/processing/sobel.rs b/src/processing/sobel.rs index 7b82824..47d61ef 100644 --- a/src/processing/sobel.rs +++ b/src/processing/sobel.rs @@ -46,8 +46,9 @@ where Ok((h_deriv, v_deriv)) } -impl SobelExt for Array3 +impl SobelExt for ArrayBase where + U: Data, T: Copy + Clone + Num + NumAssignOps + Neg + FromPrimitive + Real, { type Output = ArrayBase, Ix3>; @@ -68,10 +69,13 @@ where } } -impl FullSobelExt for Array3 +impl FullSobelExt for ArrayBase where + U: Data, T: Copy + Clone + Num + NumAssignOps + Neg + FromPrimitive + Real, { + type Output = ArrayBase, Ix3>; + fn full_sobel(&self) -> Result<(Self::Output, Self::Output), Error> { let (h_deriv, v_deriv) = get_edge_images(self)?; diff --git a/src/transform/mod.rs b/src/transform/mod.rs index a5ac068..ac0bb48 100644 --- a/src/transform/mod.rs +++ b/src/transform/mod.rs @@ -4,7 +4,6 @@ use ndarray::{array, prelude::*, s, Data, OwnedRepr}; use ndarray_linalg::solve::Inverse; use num_traits::{Num, NumAssignOps}; use std::cmp::{max, min}; -use std::marker::PhantomData; pub mod affine; From 46a5188a26427d2767d04f846ee5f8ac4c8f32d9 Mon Sep 17 00:00:00 2001 From: xd009642 Date: Sat, 30 Nov 2019 00:14:37 +0000 Subject: [PATCH 3/9] Move to ImageBase, Image, ImageView Look familiar? Found it was the most ergonomic way to handle all these types --- src/core/colour_models.rs | 208 +++++++++++++++++++------------------- src/core/image.rs | 62 +++++++----- src/core/padding.rs | 50 +++++---- 3 files changed, 172 insertions(+), 148 deletions(-) diff --git a/src/core/colour_models.rs b/src/core/colour_models.rs index 13fe75d..13c33b5 100644 --- a/src/core/colour_models.rs +++ b/src/core/colour_models.rs @@ -1,6 +1,6 @@ use crate::core::traits::*; -use crate::core::{normalise_pixel_value, Image}; -use ndarray::{prelude::*, s, Data, OwnedRepr, Zip}; +use crate::core::*; +use ndarray::{prelude::*, s, Data, Zip}; use num_traits::cast::{FromPrimitive, NumCast}; use num_traits::{Num, NumAssignOps}; use std::convert::From; @@ -182,7 +182,7 @@ where (red, green, blue) } -impl From> for Image, HSV> +impl From> for Image where U: Data, T: Copy @@ -195,7 +195,7 @@ where + Display + PixelBound, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { let mut res = Array3::<_>::zeros((image.rows(), image.cols(), HSV::channels())); let window = image.data.windows((1, 1, image.channels())); @@ -211,7 +211,7 @@ where } } -impl From> for Image, RGB> +impl From> for Image where U: Data, T: Copy @@ -224,7 +224,7 @@ where + Display + PixelBound, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { let mut res = Array3::::zeros((image.rows(), image.cols(), RGB::channels())); let window = image.data.windows((1, 1, image.channels())); @@ -240,7 +240,7 @@ where } } -impl From> for Image, Gray> +impl From> for Image where U: Data, T: Copy @@ -253,7 +253,7 @@ where + Display + PixelBound, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { let mut res = Array3::::zeros((image.rows(), image.cols(), Gray::channels())); let window = image.data.windows((1, 1, image.channels())); @@ -271,7 +271,7 @@ where } } -impl From> for Image, RGB> +impl From> for Image where U: Data, T: Copy @@ -284,7 +284,7 @@ where + Display + PixelBound, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { let mut res = Array3::::zeros((image.rows(), image.cols(), RGB::channels())); let window = image.data.windows((1, 1, image.channels())); @@ -298,7 +298,7 @@ where } } -impl From> for Image, CIEXYZ> +impl From> for Image where U: Data, T: Copy @@ -311,7 +311,7 @@ where + Display + PixelBound, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { let mut res = Array3::::zeros((image.rows(), image.cols(), CIEXYZ::channels())); let window = image.data.windows((1, 1, image.channels())); @@ -337,7 +337,7 @@ where } } -impl From> for Image, RGB> +impl From> for Image where U: Data, T: Copy @@ -350,7 +350,7 @@ where + Display + PixelBound, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { let mut res = Array3::::zeros((image.rows(), image.cols(), RGB::channels())); let window = image.data.windows((1, 1, image.channels())); @@ -376,312 +376,312 @@ where } } -impl From> for Image, RGB> +impl From> for ImageBase where - T: Data, + T: Data, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { image.into_type_raw() } } -impl From> for Image +impl From> for ImageBase where - T: Data, + T: Data, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { Self::from_data(image.data) } } -impl From> for Image, HSI> +impl From> for ImageBase where - T: Data, + T: Data, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { Self::from_data(image.data) } } -impl From> for Image +impl From> for ImageBase where T: Data, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { Self::from_data(image.data) } } -impl From> for Image +impl From> for ImageBase where T: Data, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { Self::from_data(image.data) } } -impl From> for Image +impl From> for ImageBase where T: Data, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { Self::from_data(image.data) } } -impl From> for Image +impl From> for ImageBase where T: Data, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { Self::from_data(image.data) } } -impl From> for Image +impl From> for ImageBase where T: Data, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { Self::from_data(image.data) } } -impl From> for Image +impl From> for ImageBase where T: Data, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { Self::from_data(image.data) } } -impl From> for Image +impl From> for ImageBase where T: Data, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { Self::from_data(image.data) } } -impl From> for Image +impl From> for ImageBase where T: Data, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { Self::from_data(image.data) } } -impl From> for Image, Generic3> +impl From> for ImageBase where - T: Data, + T: Data, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { Self::from_data(image.data) } } -impl From> for Image, Generic3> +impl From> for ImageBase where - T: Data, + T: Data, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { Self::from_data(image.data) } } -impl From> for Image, Generic3> +impl From> for ImageBase where - T: Data, + T: Data, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { Self::from_data(image.data) } } -impl From> for Image, Generic3> +impl From> for ImageBase where - T: Data, + T: Data, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { Self::from_data(image.data) } } -impl From> for Image, Generic3> +impl From> for ImageBase where - T: Data, + T: Data, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { Self::from_data(image.data) } } -impl From> for Image, Generic3> +impl From> for ImageBase where - T: Data, + T: Data, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { Self::from_data(image.data) } } -impl From> for Image, Generic3> +impl From> for ImageBase where - T: Data, + T: Data, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { Self::from_data(image.data) } } -impl From> for Image +impl From> for ImageBase where T: Data, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { Self::from_data(image.data) } } -impl From> for Image +impl From> for ImageBase where T: Data, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { Self::from_data(image.data) } } -impl From> for Image +impl From> for Image where U: Data, T: Copy, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { let shape = (image.rows(), image.cols(), Generic4::channels()); let data = Array3::from_shape_fn(shape, |(i, j, k)| image.data[[i, j, k]]); Self::from_data(data) } } -impl From> for Image +impl From> for Image where U: Data, T: Copy, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { let shape = (image.rows(), image.cols(), Generic3::channels()); let data = Array3::from_shape_fn(shape, |(i, j, k)| image.data[[i, j, k]]); Self::from_data(data) } } -impl From> for Image +impl From> for Image where U: Data, T: Copy, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { let shape = (image.rows(), image.cols(), Generic2::channels()); let data = Array3::from_shape_fn(shape, |(i, j, k)| image.data[[i, j, k]]); Self::from_data(data) } } -impl From> for Image +impl From> for Image where U: Data, T: Copy, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { let shape = (image.rows(), image.cols(), Generic1::channels()); let data = Array3::from_shape_fn(shape, |(i, j, k)| image.data[[i, j, k]]); Self::from_data(data) } } -impl From> for Image +impl From> for Image where U: Data, T: Copy, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { let shape = (image.rows(), image.cols(), Generic3::channels()); let data = Array3::from_shape_fn(shape, |(i, j, k)| image.data[[i, j, k]]); Self::from_data(data) } } -impl From> for Image +impl From> for Image where U: Data, T: Copy, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { let shape = (image.rows(), image.cols(), Generic2::channels()); let data = Array3::from_shape_fn(shape, |(i, j, k)| image.data[[i, j, k]]); Self::from_data(data) } } -impl From> for Image, Generic1> +impl From> for Image where U: Data, T: Copy, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { let shape = (image.rows(), image.cols(), Generic1::channels()); let data = Array3::from_shape_fn(shape, |(i, j, k)| image.data[[i, j, k]]); Self::from_data(data) } } -impl From> for Image, Generic2> +impl From> for Image where U: Data, T: Copy, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { let shape = (image.rows(), image.cols(), Generic2::channels()); let data = Array3::from_shape_fn(shape, |(i, j, k)| image.data[[i, j, k]]); Self::from_data(data) } } -impl From> for Image, Generic1> +impl From> for Image where U: Data, T: Copy, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { let shape = (image.rows(), image.cols(), Generic1::channels()); let data = Array3::from_shape_fn(shape, |(i, j, k)| image.data[[i, j, k]]); Self::from_data(data) } } -impl From> for Image, Generic1> +impl From> for Image where U: Data, T: Copy, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { let shape = (image.rows(), image.cols(), Generic1::channels()); let data = Array3::from_shape_fn(shape, |(i, j, k)| image.data[[i, j, k]]); Self::from_data(data) } } -impl From> for Image, Generic5> +impl From> for Image where U: Data, T: Copy + Num, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { let shape = (image.rows(), image.cols(), Generic5::channels()); let mut data = Array3::zeros(shape); data.slice_mut(s![.., .., 0..Generic1::channels()]) @@ -690,12 +690,12 @@ where } } -impl From> for Image, Generic5> +impl From> for Image where U: Data, T: Copy + Num, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { let shape = (image.rows(), image.cols(), Generic5::channels()); let mut data = Array3::zeros(shape); data.slice_mut(s![.., .., 0..Generic2::channels()]) @@ -704,12 +704,12 @@ where } } -impl From> for Image +impl From> for Image where U: Data, T: Copy + Num, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { let shape = (image.rows(), image.cols(), Generic5::channels()); let mut data = Array3::zeros(shape); data.slice_mut(s![.., .., 0..Generic3::channels()]) @@ -718,12 +718,12 @@ where } } -impl From> for Image +impl From> for Image where U: Data, T: Copy + Num, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { let shape = (image.rows(), image.cols(), Generic5::channels()); let mut data = Array3::zeros(shape); data.slice_mut(s![.., .., 0..Generic4::channels()]) @@ -732,12 +732,12 @@ where } } -impl From> for Image +impl From> for Image where U: Data, T: Copy + Num, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { let shape = (image.rows(), image.cols(), Generic4::channels()); let mut data = Array3::zeros(shape); data.slice_mut(s![.., .., 0..Generic1::channels()]) @@ -746,12 +746,12 @@ where } } -impl From> for Image +impl From> for Image where U: Data, T: Copy + Num, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { let shape = (image.rows(), image.cols(), Generic4::channels()); let mut data = Array3::zeros(shape); data.slice_mut(s![.., .., 0..Generic2::channels()]) @@ -760,12 +760,12 @@ where } } -impl From> for Image +impl From> for Image where U: Data, T: Copy + Num, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { let shape = (image.rows(), image.cols(), Generic4::channels()); let mut data = Array3::zeros(shape); data.slice_mut(s![.., .., 0..Generic3::channels()]) @@ -774,12 +774,12 @@ where } } -impl From> for Image +impl From> for Image where U: Data, T: Copy + Num, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { let shape = (image.rows(), image.cols(), Generic3::channels()); let mut data = Array3::zeros(shape); data.slice_mut(s![.., .., 0..Generic1::channels()]) @@ -788,12 +788,12 @@ where } } -impl From> for Image +impl From> for Image where U: Data, T: Copy + Num, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { let shape = (image.rows(), image.cols(), Generic3::channels()); let mut data = Array3::zeros(shape); data.slice_mut(s![.., .., 0..Generic2::channels()]) @@ -802,12 +802,12 @@ where } } -impl From> for Image +impl From> for Image where U: Data, T: Copy + Num, { - fn from(image: Image) -> Self { + fn from(image: ImageBase) -> Self { let shape = (image.rows(), image.cols(), Generic2::channels()); let mut data = Array3::zeros(shape); data.slice_mut(s![.., .., 0..Generic1::channels()]) diff --git a/src/core/image.rs b/src/core/image.rs index 1cb6734..0642673 100644 --- a/src/core/image.rs +++ b/src/core/image.rs @@ -1,14 +1,16 @@ use crate::core::colour_models::*; use crate::core::traits::PixelBound; use ndarray::prelude::*; -use ndarray::{s, Data}; +use ndarray::{s, Data, DataMut, OwnedRepr, ViewRepr}; use num_traits::cast::{FromPrimitive, NumCast}; use num_traits::Num; use std::marker::PhantomData; +pub type Image = ImageBase, C>; +pub type ImageView<'a, T, C> = ImageBase, C>; + /// Basic structure containing an image. -#[derive(Clone, Eq, PartialEq, Hash, Debug)] -pub struct Image +pub struct ImageBase where C: ColourModel, T: Data, @@ -26,16 +28,15 @@ where pub(crate) model: PhantomData, } -impl Image +impl ImageBase where U: Data, T: Copy + Clone + FromPrimitive + Num + NumCast + PixelBound, C: ColourModel, { /// Converts image into a different type - doesn't scale to new pixel bounds - pub fn into_type(self) -> Image + pub fn into_type(self) -> Image where - U2: Data, T2: Copy + Clone + FromPrimitive + Num + NumCast + PixelBound, { let rescale = |x: &T| { @@ -46,13 +47,12 @@ where T2::from_f64(scaled).unwrap_or_else(T2::zero) + T2::min_pixel() }; let data = self.data.map(rescale); - Image::::from_data(data) + Image::<_, C>::from_data(data) } } -impl Image +impl Image where - U: Data, T: Clone + Num, C: ColourModel, { @@ -60,7 +60,7 @@ where /// a colour model pub fn new(rows: usize, columns: usize) -> Self { Image { - data: Array3::::zeros((rows, columns, C::channels())), + data: Array3::zeros((rows, columns, C::channels())), model: PhantomData, } } @@ -69,7 +69,7 @@ where /// the data sizes don't match a zero filled image will be returned instead /// of panicking pub fn from_shape_data(rows: usize, cols: usize, data: Vec) -> Self { - let data = Array3::::from_shape_vec((rows, cols, C::channels()), data) + let data = Array3::from_shape_vec((rows, cols, C::channels()), data) .unwrap_or_else(|_| Array3::::zeros((rows, cols, C::channels()))); Image { @@ -77,27 +77,30 @@ where model: PhantomData, } } +} +impl ImageBase +where + T: Data, + C: ColourModel, +{ /// Create an image given an existing ndarray - pub fn from_array(data: ArrayBase) -> Self - where - V: Data, - { - Image { + pub fn from_array(data: ArrayBase) -> Self { + Self { data, model: PhantomData, } } } -impl Image +impl ImageBase where T: Data, C: ColourModel, { /// Construct the image from a given Array3 pub fn from_data(data: ArrayBase) -> Self { - Image { + Self { data, model: PhantomData, } @@ -117,22 +120,27 @@ where } /// Get a view of all colour channels at a pixels location - pub fn pixel(&self, row: usize, col: usize) -> ArrayView { + pub fn pixel(&self, row: usize, col: usize) -> ArrayView { self.data.slice(s![row, col, ..]) } - /// Get a mutable view of a pixels colour channels given a location - pub fn pixel_mut(&mut self, row: usize, col: usize) -> ArrayViewMut { - self.data.slice_mut(s![row, col, ..]) - } - - pub fn into_type_raw(self) -> Image + pub fn into_type_raw(self) -> ImageBase where - U2: Data, C2: ColourModel, { assert_eq!(C2::channels(), C::channels()); - Image::::from_data(self.data) + ImageBase::::from_data(self.data) + } +} + +impl ImageBase +where + T: DataMut, + C: ColourModel, +{ + /// Get a mutable view of a pixels colour channels given a location + pub fn pixel_mut(&mut self, row: usize, col: usize) -> ArrayViewMut { + self.data.slice_mut(s![row, col, ..]) } } diff --git a/src/core/padding.rs b/src/core/padding.rs index 257a5f8..28c8350 100644 --- a/src/core/padding.rs +++ b/src/core/padding.rs @@ -1,5 +1,5 @@ -use crate::core::{ColourModel, Image}; -use ndarray::{prelude::*, s, Data}; +use crate::core::{ColourModel, Image, ImageBase}; +use ndarray::{prelude::*, s, Data, OwnedRepr}; use num_traits::identities::Zero; use std::marker::PhantomData; @@ -11,7 +11,11 @@ where { /// Taking in the image data and the margin to apply to rows and columns /// returns a padded image - fn pad(&self, image: ArrayView3, padding: (usize, usize)) -> Array3; + fn pad( + &self, + image: ArrayView, + padding: (usize, usize), + ) -> ArrayBase, Ix3>; } /// Doesn't apply any padding to the image returning it unaltered regardless @@ -33,7 +37,11 @@ impl PaddingStrategy for NoPadding where T: Copy, { - fn pad(&self, image: ArrayView3, _padding: (usize, usize)) -> Array3 { + fn pad( + &self, + image: ArrayView, + _padding: (usize, usize), + ) -> ArrayBase, Ix3> { image.to_owned() } } @@ -42,7 +50,11 @@ impl PaddingStrategy for ConstantPadding where T: Copy, { - fn pad(&self, image: ArrayView3, padding: (usize, usize)) -> Array3 { + fn pad( + &self, + image: ArrayView, + padding: (usize, usize), + ) -> ArrayBase, Ix3> { let shape = ( image.shape()[0] + padding.0 * 2, image.shape()[1] + padding.1 * 2, @@ -66,42 +78,46 @@ impl PaddingStrategy for ZeroPadding where T: Copy + Zero, { - fn pad(&self, image: ArrayView3, padding: (usize, usize)) -> Array3 { + fn pad( + &self, + image: ArrayView, + padding: (usize, usize), + ) -> ArrayBase, Ix3> { let padder = ConstantPadding(T::zero()); padder.pad(image, padding) } } /// Padding extension for images -pub trait PaddingExt { - /// Data type for container - type Data; +pub trait PaddingExt { + /// Type of the output image + type Output; /// Pad the object with the given padding and strategy - fn pad(&self, padding: (usize, usize), strategy: &dyn PaddingStrategy) -> Self; + fn pad(&self, padding: (usize, usize), strategy: &dyn PaddingStrategy) -> Self::Output; } -impl PaddingExt for ArrayBase +impl PaddingExt for ArrayBase where U: Data, T: Copy, { - type Data = T; + type Output = ArrayBase, Ix3>; - fn pad(&self, padding: (usize, usize), strategy: &dyn PaddingStrategy) -> Self { + fn pad(&self, padding: (usize, usize), strategy: &dyn PaddingStrategy) -> Self::Output { strategy.pad(self.view(), padding) } } -impl PaddingExt for Image +impl PaddingExt for ImageBase where U: Data, T: Copy, C: ColourModel, { - type Data = T; + type Output = Image; - fn pad(&self, padding: (usize, usize), strategy: &dyn PaddingStrategy) -> Self { - Self { + fn pad(&self, padding: (usize, usize), strategy: &dyn PaddingStrategy) -> Self::Output { + Self::Output { data: strategy.pad(self.data.view(), padding), model: PhantomData, } From 3f0ff8d1c81fca4290a8e0a5ffcbb0d180c16796 Mon Sep 17 00:00:00 2001 From: xd009642 Date: Sat, 30 Nov 2019 12:20:39 +0000 Subject: [PATCH 4/9] Got histogram eq working with new traits --- src/core/image.rs | 14 ++++++++++++++ src/enhancement/histogram_equalisation.rs | 20 ++++++++++++-------- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/core/image.rs b/src/core/image.rs index 0642673..16b5ea3 100644 --- a/src/core/image.rs +++ b/src/core/image.rs @@ -51,6 +51,20 @@ where } } +impl ImageBase +where + S: Data, + T: Clone, + C: ColourModel, +{ + pub fn to_owned(&self) -> Image { + Image { + data: self.data.to_owned(), + model: PhantomData, + } + } +} + impl Image where T: Clone + Num, diff --git a/src/enhancement/histogram_equalisation.rs b/src/enhancement/histogram_equalisation.rs index 6e97e78..d1e454e 100644 --- a/src/enhancement/histogram_equalisation.rs +++ b/src/enhancement/histogram_equalisation.rs @@ -10,9 +10,10 @@ pub trait HistogramEqExt where A: Ord, { + type Output; /// Equalises an image histogram returning a new image. /// Grids should be for a 1xN image as the image is flattened during processing - fn equalise_hist(&self, grid: Grid) -> Self; + fn equalise_hist(&self, grid: Grid) -> Self::Output; /// Equalises an image histogram inplace /// Grids should be for a 1xN image as the image is flattened during processing @@ -24,8 +25,10 @@ where U: Data + DataMut, T: Copy + Clone + Ord + Num + NumAssignOps + ToPrimitive + FromPrimitive + PixelBound, { - fn equalise_hist(&self, grid: Grid) -> Self { - let mut result = self.clone(); + type Output = Array; + + fn equalise_hist(&self, grid: Grid) -> Self::Output { + let mut result = self.to_owned(); result.equalise_hist_inplace(grid); result } @@ -72,15 +75,16 @@ where } } -impl HistogramEqExt for Image +impl HistogramEqExt for ImageBase where - U: Data, - Image: Clone, + U: Data + DataMut, T: Copy + Clone + Ord + Num + NumAssignOps + ToPrimitive + FromPrimitive + PixelBound, C: ColourModel, { - fn equalise_hist(&self, grid: Grid) -> Self { - let mut result = self.clone(); + type Output = Image; + + fn equalise_hist(&self, grid: Grid) -> Self::Output { + let mut result = self.to_owned(); result.equalise_hist_inplace(grid); result } From 03cfadf34bd49b860dc0a6972738d6d81be9115a Mon Sep 17 00:00:00 2001 From: xd009642 Date: Sat, 30 Nov 2019 15:59:50 +0000 Subject: [PATCH 5/9] Update processing module --- src/processing/canny.rs | 12 +++++------ src/processing/conv.rs | 22 +++++++++---------- src/processing/filter.rs | 6 +++--- src/processing/sobel.rs | 42 +++++++++---------------------------- src/processing/threshold.rs | 12 +++++------ 5 files changed, 36 insertions(+), 58 deletions(-) diff --git a/src/processing/canny.rs b/src/processing/canny.rs index 61218c1..a2d5a45 100644 --- a/src/processing/canny.rs +++ b/src/processing/canny.rs @@ -1,7 +1,7 @@ -use crate::core::{ColourModel, Image}; +use crate::core::{ColourModel, Image, ImageBase}; use crate::processing::*; use ndarray::prelude::*; -use ndarray::{Data, IntoDimension, OwnedRepr}; +use ndarray::{Data, DataMut, IntoDimension}; use num_traits::{cast::FromPrimitive, real::Real, Num, NumAssignOps}; use std::collections::HashSet; use std::marker::PhantomData; @@ -38,13 +38,13 @@ pub struct CannyParameters { pub t2: T, } -impl CannyEdgeDetectorExt for Image +impl CannyEdgeDetectorExt for ImageBase where - U: Data, + U: Data + DataMut, T: Copy + Clone + FromPrimitive + Real + Num + NumAssignOps, C: ColourModel, { - type Output = Image, C>; + type Output = Image; fn canny_edge_detector(&self, params: CannyParameters) -> Result { let data = self.data.canny_edge_detector(params)?; @@ -57,7 +57,7 @@ where impl CannyEdgeDetectorExt for ArrayBase where - U: Data, + U: Data + DataMut, T: Copy + Clone + FromPrimitive + Real + Num + NumAssignOps, { type Output = Array3; diff --git a/src/processing/conv.rs b/src/processing/conv.rs index 9a4e0ff..4794127 100644 --- a/src/processing/conv.rs +++ b/src/processing/conv.rs @@ -1,8 +1,8 @@ use crate::core::padding::*; -use crate::core::{ColourModel, Image}; +use crate::core::{ColourModel, Image, ImageBase}; use crate::processing::Error; use ndarray::prelude::*; -use ndarray::{s, Data, OwnedRepr, Zip}; +use ndarray::{s, Data, DataMut, Zip}; use num_traits::{Num, NumAssignOps}; use std::marker::PhantomData; use std::marker::Sized; @@ -47,18 +47,18 @@ fn kernel_centre(rows: usize, cols: usize) -> (usize, usize) { impl ConvolutionExt for ArrayBase where - U: Data, + U: Data + DataMut, T: Copy + Clone + Num + NumAssignOps, { type Data = T; - type Output = ArrayBase, Ix3>; + type Output = Array; fn conv2d(&self, kernel: ArrayView3) -> Result { self.conv2d_with_padding(kernel, &ZeroPadding {}) } fn conv2d_inplace(&mut self, kernel: ArrayView3) -> Result<(), Error> { - *self = self.conv2d_with_padding(kernel, &ZeroPadding {})?; + self.assign(&self.conv2d_with_padding(kernel, &ZeroPadding {})?); Ok(()) } @@ -97,23 +97,23 @@ where kernel: ArrayView3, strategy: &dyn PaddingStrategy, ) -> Result<(), Error> { - *self = self.conv2d_with_padding(kernel, strategy)?; + self.assign(&self.conv2d_with_padding(kernel, strategy)?); Ok(()) } } -impl ConvolutionExt for Image +impl ConvolutionExt for ImageBase where - U: Data, + U: Data + DataMut, T: Copy + Clone + Num + NumAssignOps, C: ColourModel, { type Data = T; - type Output = Image, C>; + type Output = Image; fn conv2d(&self, kernel: ArrayView3) -> Result { let data = self.data.conv2d(kernel)?; - Ok(Self { + Ok(Self::Output { data, model: PhantomData, }) @@ -129,7 +129,7 @@ where strategy: &dyn PaddingStrategy, ) -> Result { let data = self.data.conv2d_with_padding(kernel, strategy)?; - Ok(Self { + Ok(Self::Output { data, model: PhantomData, }) diff --git a/src/processing/filter.rs b/src/processing/filter.rs index ab78e90..3c97ec6 100644 --- a/src/processing/filter.rs +++ b/src/processing/filter.rs @@ -1,4 +1,4 @@ -use crate::core::{ColourModel, Image}; +use crate::core::{ColourModel, Image, ImageBase}; use ndarray::prelude::*; use ndarray::{Data, IntoDimension, OwnedRepr, Zip}; use ndarray_stats::interpolate::*; @@ -47,13 +47,13 @@ where } } -impl MedianFilterExt for Image +impl MedianFilterExt for ImageBase where U: Data, T: Copy + Clone + FromPrimitive + ToPrimitive + Num + Ord, C: ColourModel, { - type Output = Image, C>; + type Output = Image; fn median_filter(&self, region: E) -> Self::Output where diff --git a/src/processing/sobel.rs b/src/processing/sobel.rs index 47d61ef..812756b 100644 --- a/src/processing/sobel.rs +++ b/src/processing/sobel.rs @@ -1,7 +1,7 @@ use crate::core::*; use crate::processing::*; use core::ops::Neg; -use ndarray::{prelude::*, Data, OwnedRepr}; +use ndarray::{prelude::*, Data, DataMut, OwnedRepr}; use num_traits::{cast::FromPrimitive, real::Real, Num, NumAssignOps}; use std::marker::Sized; @@ -14,14 +14,7 @@ where type Output; /// Returns the magnitude output of the sobel - an image of only lines fn apply_sobel(&self) -> Result; -} -pub trait FullSobelExt -where - Self: Sized, -{ - /// Type to output - type Output; /// Returns the magntitude and rotation outputs for use in other algorithms /// like the Canny edge detector. Rotation is in radians fn full_sobel(&self) -> Result<(Self::Output, Self::Output), Error>; @@ -31,7 +24,7 @@ fn get_edge_images( mat: &ArrayBase, ) -> Result<(ArrayBase, Ix3>, ArrayBase, Ix3>), Error> where - U: Data, + U: Data + DataMut, T: Copy + Clone + Num + NumAssignOps + Neg + FromPrimitive + Real, { let v_temp: Array3 = SobelFilter::build_with_params(Orientation::Vertical).unwrap(); @@ -48,7 +41,7 @@ where impl SobelExt for ArrayBase where - U: Data, + U: Data + DataMut, T: Copy + Clone + Num + NumAssignOps + Neg + FromPrimitive + Real, { type Output = ArrayBase, Ix3>; @@ -67,14 +60,6 @@ where Ok(result) } -} - -impl FullSobelExt for ArrayBase -where - U: Data, - T: Copy + Clone + Num + NumAssignOps + Neg + FromPrimitive + Real, -{ - type Output = ArrayBase, Ix3>; fn full_sobel(&self) -> Result<(Self::Output, Self::Output), Error> { let (h_deriv, v_deriv) = get_edge_images(self)?; @@ -90,29 +75,22 @@ where } } -impl SobelExt for Image +impl SobelExt for ImageBase where - U: Data, + U: Data + DataMut, T: Copy + Clone + Num + NumAssignOps + Neg + FromPrimitive + Real, C: ColourModel, { - type Output = Image, C>; + type Output = Image; - fn apply_sobel(&self) -> Result { + fn apply_sobel(&self) -> Result { let data = self.data.apply_sobel()?; Ok(Image::from_data(data)) } -} - -impl FullSobelExt for Image -where - U: Data, - T: Copy + Clone + Num + NumAssignOps + Neg + FromPrimitive + Real, - C: ColourModel, -{ - type Output = ArrayBase, Ix3>; fn full_sobel(&self) -> Result<(Self::Output, Self::Output), Error> { - self.data.full_sobel() + self.data + .full_sobel() + .map(|(m, r)| (Image::from_data(m), Image::from_data(r))) } } diff --git a/src/processing/threshold.rs b/src/processing/threshold.rs index d92b88d..f4ea498 100644 --- a/src/processing/threshold.rs +++ b/src/processing/threshold.rs @@ -1,7 +1,7 @@ use crate::core::PixelBound; -use crate::core::{ColourModel, Image}; +use crate::core::{ColourModel, Image, ImageBase}; use crate::processing::*; -use ndarray::{prelude::*, Data, OwnedRepr}; +use ndarray::{prelude::*, Data}; use ndarray_stats::histogram::{Bins, Edges, Grid}; use ndarray_stats::HistogramExt; use ndarray_stats::QuantileExt; @@ -41,14 +41,14 @@ pub trait ThresholdMeanExt { fn threshold_mean(&self) -> Result; } -impl ThresholdOtsuExt for Image +impl ThresholdOtsuExt for ImageBase where U: Data, Image: Clone, T: Copy + Clone + Ord + Num + NumAssignOps + ToPrimitive + FromPrimitive + PixelBound, C: ColourModel, { - type Output = Image, C>; + type Output = Image; fn threshold_otsu(&self) -> Result { let data = self.data.threshold_otsu()?; @@ -135,14 +135,14 @@ where Ok(threshold) } -impl ThresholdMeanExt for Image +impl ThresholdMeanExt for ImageBase where U: Data, Image: Clone, T: Copy + Clone + Ord + Num + NumAssignOps + ToPrimitive + FromPrimitive + PixelBound, C: ColourModel, { - type Output = Image, C>; + type Output = Image; fn threshold_mean(&self) -> Result { let data = self.data.threshold_mean()?; From 08caed1d32fe52c75f3b9d206831cabf04e303e3 Mon Sep 17 00:00:00 2001 From: xd009642 Date: Sat, 30 Nov 2019 16:12:55 +0000 Subject: [PATCH 6/9] Update rest of the library code Now just to refactor the tests and the new types will be ready for review --- src/format/mod.rs | 13 ++++++++----- src/format/netpbm.rs | 18 ++++++++---------- src/transform/mod.rs | 13 +++++++------ 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index d5dd1fc..c496ec4 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -16,11 +16,15 @@ where C: ColourModel, { /// Encode an image into a sequence of bytes for the given format - fn encode(&self, image: &Image) -> Vec; + fn encode(&self, image: &ImageBase) -> Vec; /// Encode an image saving it to the file at filename. This function shouldn't /// add an extension preferring the user to do that instead. - fn encode_file>(&self, image: &Image, filename: P) -> std::io::Result<()> { + fn encode_file>( + &self, + image: &ImageBase, + filename: P, + ) -> std::io::Result<()> { let mut file = File::create(filename)?; file.write_all(&self.encode(image))?; Ok(()) @@ -30,7 +34,6 @@ where /// Trait for an image decoder, use this to get an image from a byte stream pub trait Decoder where - U: Data, T: Copy + Clone + FromPrimitive @@ -44,9 +47,9 @@ where { /// From the bytes decode an image, will perform any scaling or conversions /// required to represent elements with type T. - fn decode(&self, bytes: &[u8]) -> std::io::Result>; + fn decode(&self, bytes: &[u8]) -> std::io::Result>; /// Given a filename decode an image performing any necessary conversions. - fn decode_file>(&self, filename: P) -> std::io::Result> { + fn decode_file>(&self, filename: P) -> std::io::Result> { let bytes = read(filename)?; self.decode(&bytes) } diff --git a/src/format/netpbm.rs b/src/format/netpbm.rs index 4672e3b..1da8bea 100644 --- a/src/format/netpbm.rs +++ b/src/format/netpbm.rs @@ -1,4 +1,4 @@ -use crate::core::{normalise_pixel_value, Image, PixelBound, RGB}; +use crate::core::{normalise_pixel_value, Image, ImageBase, PixelBound, RGB}; use crate::format::{Decoder, Encoder}; use ndarray::Data; use num_traits::cast::{FromPrimitive, NumCast}; @@ -46,7 +46,7 @@ where + PixelBound + FromPrimitive, { - fn encode(&self, image: &Image) -> Vec { + fn encode(&self, image: &ImageBase) -> Vec { use EncodingType::*; match self.encoding { Plaintext => self.encode_plaintext(image), @@ -73,7 +73,7 @@ impl PpmEncoder { /// Gets the maximum pixel value in the image across all channels. This is /// used in the PPM header - fn get_max_value(image: &Image) -> Option + fn get_max_value(image: &ImageBase) -> Option where U: Data, T: Copy + Clone + Num + NumAssignOps + NumCast + PartialOrd + Display + PixelBound, @@ -95,7 +95,7 @@ impl PpmEncoder { } /// Encode the image into the binary PPM format (P6) returning the bytes - fn encode_binary(self, image: &Image) -> Vec + fn encode_binary(self, image: &ImageBase) -> Vec where U: Data, T: Copy + Clone + Num + NumAssignOps + NumCast + PartialOrd + Display + PixelBound, @@ -117,7 +117,7 @@ impl PpmEncoder { /// Encode the image into the plaintext PPM format (P3) returning the text as /// an array of bytes - fn encode_plaintext(self, image: &Image) -> Vec + fn encode_plaintext(self, image: &ImageBase) -> Vec where U: Data, T: Copy + Clone + Num + NumAssignOps + NumCast + PartialOrd + Display + PixelBound, @@ -168,7 +168,7 @@ where + PixelBound + FromPrimitive, { - fn decode(&self, bytes: &[u8]) -> std::io::Result> { + fn decode(&self, bytes: &[u8]) -> std::io::Result> { if bytes.len() < 9 { Err(Error::new( ErrorKind::InvalidData, @@ -226,9 +226,8 @@ impl PpmDecoder { } } - fn decode_binary(bytes: &[u8]) -> std::io::Result> + fn decode_binary(bytes: &[u8]) -> std::io::Result> where - U: Data, T: Copy + Clone + Num @@ -280,9 +279,8 @@ impl PpmDecoder { } } - fn decode_plaintext(bytes: &[u8]) -> std::io::Result> + fn decode_plaintext(bytes: &[u8]) -> std::io::Result> where - U: Data, T: Copy + Clone + Num diff --git a/src/transform/mod.rs b/src/transform/mod.rs index ac0bb48..c97789d 100644 --- a/src/transform/mod.rs +++ b/src/transform/mod.rs @@ -1,6 +1,6 @@ -use crate::core::{ColourModel, Image}; +use crate::core::{ColourModel, Image, ImageBase}; use crate::transform::affine::translation; -use ndarray::{array, prelude::*, s, Data, OwnedRepr}; +use ndarray::{array, prelude::*, s, Data}; use ndarray_linalg::solve::Inverse; use num_traits::{Num, NumAssignOps}; use std::cmp::{max, min}; @@ -87,7 +87,7 @@ where T: Copy + Clone + Num + NumAssignOps, U: Data, { - type Output = ArrayBase, Ix3>; + type Output = Array; fn transform( &self, @@ -138,13 +138,13 @@ where } } -impl TransformExt for Image +impl TransformExt for ImageBase where U: Data, T: Copy + Clone + Num + NumAssignOps, C: ColourModel, { - type Output = Image, C>; + type Output = Image; fn transform( &self, @@ -152,7 +152,8 @@ where output_size: Option<(usize, usize)>, ) -> Result { let data = self.data.transform(transform, output_size)?; - Ok(Self::from_array(data)) + let result = Self::Output::from_array(data).to_owned(); + Ok(result) } } From 2c3961a9f214245c6b3ef96a51be9a94a878b973 Mon Sep 17 00:00:00 2001 From: xd009642 Date: Sat, 30 Nov 2019 16:36:00 +0000 Subject: [PATCH 7/9] Update traits for tests --- src/core/image.rs | 56 +++++++++++++++++++++++++++++++++++++-- src/format/mod.rs | 2 +- src/format/netpbm.rs | 3 +-- src/processing/kernels.rs | 6 ++--- 4 files changed, 59 insertions(+), 8 deletions(-) diff --git a/src/core/image.rs b/src/core/image.rs index 16b5ea3..3c9c4f2 100644 --- a/src/core/image.rs +++ b/src/core/image.rs @@ -1,10 +1,10 @@ use crate::core::colour_models::*; use crate::core::traits::PixelBound; use ndarray::prelude::*; -use ndarray::{s, Data, DataMut, OwnedRepr, ViewRepr}; +use ndarray::{s, Data, DataMut, OwnedRepr, RawDataClone, ViewRepr}; use num_traits::cast::{FromPrimitive, NumCast}; use num_traits::Num; -use std::marker::PhantomData; +use std::{fmt, hash, marker::PhantomData}; pub type Image = ImageBase, C>; pub type ImageView<'a, T, C> = ImageBase, C>; @@ -158,6 +158,58 @@ where } } +impl fmt::Debug for ImageBase +where + U: Data, + T: fmt::Debug, + C: ColourModel, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "ColourModel={:?} Data={:?}", self.model, self.data)?; + Ok(()) + } +} + +impl PartialEq> for ImageBase +where + U: Data, + T: PartialEq, + C: ColourModel, +{ + fn eq(&self, other: &Self) -> bool { + self.model == other.model && self.data == other.data + } +} + +impl Clone for ImageBase +where + S: RawDataClone + Data, + C: ColourModel, +{ + fn clone(&self) -> Self { + Self { + data: self.data.clone(), + model: PhantomData, + } + } + + fn clone_from(&mut self, other: &Self) { + self.data.clone_from(&other.data) + } +} + +impl<'a, S, C> hash::Hash for ImageBase +where + S: Data, + S::Elem: hash::Hash, + C: ColourModel, +{ + fn hash(&self, state: &mut H) { + self.model.hash(state); + self.data.hash(state); + } +} + /// Returns a normalised pixel value or 0 if it can't convert the types. /// This should never fail if your types are good. pub fn normalise_pixel_value(t: T) -> f64 diff --git a/src/format/mod.rs b/src/format/mod.rs index c496ec4..f3c5c8a 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -32,7 +32,7 @@ where } /// Trait for an image decoder, use this to get an image from a byte stream -pub trait Decoder +pub trait Decoder where T: Copy + Clone diff --git a/src/format/netpbm.rs b/src/format/netpbm.rs index 1da8bea..a87e6f5 100644 --- a/src/format/netpbm.rs +++ b/src/format/netpbm.rs @@ -155,9 +155,8 @@ impl PpmEncoder { /// The ColourModel type argument is locked to RGB - this prevents calling /// RGB::into::() unnecessarily which is unavoidable until trait specialisation is /// stabilised. -impl Decoder for PpmDecoder +impl Decoder for PpmDecoder where - U: Data, T: Copy + Clone + Num diff --git a/src/processing/kernels.rs b/src/processing/kernels.rs index 19d9faf..f7ccf63 100644 --- a/src/processing/kernels.rs +++ b/src/processing/kernels.rs @@ -44,7 +44,7 @@ pub struct LaplaceFilter; #[derive(Copy, Clone, Eq, PartialEq, Hash)] pub enum LaplaceType { /// Standard filter and the default parameter choice, for a 3x3x1 matrix it is: - /// ```ignore + /// ```text /// [0, -1, 0] /// [-1, 4, -1] /// [0, -1, 0] @@ -52,7 +52,7 @@ pub enum LaplaceType { Standard, /// The diagonal filter also contains derivatives for diagonal lines and /// for a 3x3x1 matrix is given by: - /// ```ignore + /// ```text /// [-1, -1, -1] /// [-1, 8, -1] /// [-1, -1, -1] @@ -102,7 +102,7 @@ where { /// The parameter for the Gaussian filter is the horizontal and vertical /// covariances to form the covariance matrix. - /// ```ignore + /// ```text /// [ Params[0], 0] /// [ 0, Params[1]] /// ``` From 65c268c8558f958e0177ecf18d1509037555d66c Mon Sep 17 00:00:00 2001 From: xd009642 Date: Sat, 30 Nov 2019 16:38:26 +0000 Subject: [PATCH 8/9] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92048cd..7a4c7ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,14 @@ * Padding strategies (`NoPadding`, `ConstantPadding`, `ZeroPadding`) * Threshold module with Otsu and Mean threshold algorithms * Image transformations and functions to create affine transform matrices +* Type alias `Image` for `ImageBase, _>` replicated old `Image` type +* Type alias `ImageView` for `ImageBase, _>` ### Changed * Integrated Padding strategies into convolutions * Updated `ndarray-stats` to 0.2.0 adding `noisy_float` for median change * [INTERNAL] Disabled code coverage due to issues with tarpaulin and native libraries +* Renamed `Image` to `ImageBase` which can take any implementor of the ndaray `Data` trait ### Removed From 52b7fb8992485eab3c7b27e588b8236f3c1fb9d9 Mon Sep 17 00:00:00 2001 From: xd009642 Date: Sat, 7 Dec 2019 11:02:53 +0000 Subject: [PATCH 9/9] Remove unnecessary Data Also changed some types to be more idiomatic --- src/enhancement/histogram_equalisation.rs | 6 +++--- src/processing/canny.rs | 6 +++--- src/processing/conv.rs | 6 +++--- src/processing/sobel.rs | 12 +++++------- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/enhancement/histogram_equalisation.rs b/src/enhancement/histogram_equalisation.rs index d1e454e..dae2bdf 100644 --- a/src/enhancement/histogram_equalisation.rs +++ b/src/enhancement/histogram_equalisation.rs @@ -1,5 +1,5 @@ use crate::core::*; -use ndarray::{prelude::*, Data, DataMut}; +use ndarray::{prelude::*, DataMut}; use ndarray_stats::{histogram::Grid, HistogramExt}; use num_traits::cast::{FromPrimitive, ToPrimitive}; use num_traits::{Num, NumAssignOps}; @@ -22,7 +22,7 @@ where impl HistogramEqExt for ArrayBase where - U: Data + DataMut, + U: DataMut, T: Copy + Clone + Ord + Num + NumAssignOps + ToPrimitive + FromPrimitive + PixelBound, { type Output = Array; @@ -77,7 +77,7 @@ where impl HistogramEqExt for ImageBase where - U: Data + DataMut, + U: DataMut, T: Copy + Clone + Ord + Num + NumAssignOps + ToPrimitive + FromPrimitive + PixelBound, C: ColourModel, { diff --git a/src/processing/canny.rs b/src/processing/canny.rs index a2d5a45..bf56327 100644 --- a/src/processing/canny.rs +++ b/src/processing/canny.rs @@ -1,7 +1,7 @@ use crate::core::{ColourModel, Image, ImageBase}; use crate::processing::*; use ndarray::prelude::*; -use ndarray::{Data, DataMut, IntoDimension}; +use ndarray::{DataMut, IntoDimension}; use num_traits::{cast::FromPrimitive, real::Real, Num, NumAssignOps}; use std::collections::HashSet; use std::marker::PhantomData; @@ -40,7 +40,7 @@ pub struct CannyParameters { impl CannyEdgeDetectorExt for ImageBase where - U: Data + DataMut, + U: DataMut, T: Copy + Clone + FromPrimitive + Real + Num + NumAssignOps, C: ColourModel, { @@ -57,7 +57,7 @@ where impl CannyEdgeDetectorExt for ArrayBase where - U: Data + DataMut, + U: DataMut, T: Copy + Clone + FromPrimitive + Real + Num + NumAssignOps, { type Output = Array3; diff --git a/src/processing/conv.rs b/src/processing/conv.rs index 4794127..14f53dd 100644 --- a/src/processing/conv.rs +++ b/src/processing/conv.rs @@ -2,7 +2,7 @@ use crate::core::padding::*; use crate::core::{ColourModel, Image, ImageBase}; use crate::processing::Error; use ndarray::prelude::*; -use ndarray::{s, Data, DataMut, Zip}; +use ndarray::{s, DataMut, Zip}; use num_traits::{Num, NumAssignOps}; use std::marker::PhantomData; use std::marker::Sized; @@ -47,7 +47,7 @@ fn kernel_centre(rows: usize, cols: usize) -> (usize, usize) { impl ConvolutionExt for ArrayBase where - U: Data + DataMut, + U: DataMut, T: Copy + Clone + Num + NumAssignOps, { type Data = T; @@ -104,7 +104,7 @@ where impl ConvolutionExt for ImageBase where - U: Data + DataMut, + U: DataMut, T: Copy + Clone + Num + NumAssignOps, C: ColourModel, { diff --git a/src/processing/sobel.rs b/src/processing/sobel.rs index 812756b..0842fc4 100644 --- a/src/processing/sobel.rs +++ b/src/processing/sobel.rs @@ -1,7 +1,7 @@ use crate::core::*; use crate::processing::*; use core::ops::Neg; -use ndarray::{prelude::*, Data, DataMut, OwnedRepr}; +use ndarray::{prelude::*, DataMut, OwnedRepr}; use num_traits::{cast::FromPrimitive, real::Real, Num, NumAssignOps}; use std::marker::Sized; @@ -20,11 +20,9 @@ where fn full_sobel(&self) -> Result<(Self::Output, Self::Output), Error>; } -fn get_edge_images( - mat: &ArrayBase, -) -> Result<(ArrayBase, Ix3>, ArrayBase, Ix3>), Error> +fn get_edge_images(mat: &ArrayBase) -> Result<(Array3, Array3), Error> where - U: Data + DataMut, + U: DataMut, T: Copy + Clone + Num + NumAssignOps + Neg + FromPrimitive + Real, { let v_temp: Array3 = SobelFilter::build_with_params(Orientation::Vertical).unwrap(); @@ -41,7 +39,7 @@ where impl SobelExt for ArrayBase where - U: Data + DataMut, + U: DataMut, T: Copy + Clone + Num + NumAssignOps + Neg + FromPrimitive + Real, { type Output = ArrayBase, Ix3>; @@ -77,7 +75,7 @@ where impl SobelExt for ImageBase where - U: Data + DataMut, + U: DataMut, T: Copy + Clone + Num + NumAssignOps + Neg + FromPrimitive + Real, C: ColourModel, {