From e8ca3c712ae594dd9fc6dcf29223bb43c63b11a8 Mon Sep 17 00:00:00 2001 From: Christopher Field Date: Thu, 29 Dec 2022 17:30:35 -0500 Subject: [PATCH 1/6] Add serde for ImageBase --- Cargo.toml | 3 ++- src/core_serde.rs | 21 +++++++++++++++++++++ src/lib.rs | 1 + 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 src/core_serde.rs diff --git a/Cargo.toml b/Cargo.toml index e3f6176..242fc07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,11 +22,12 @@ intel-mkl = ["ndarray-linalg/intel-mkl"] transform = ["ndarray-linalg"] [dependencies] -ndarray = { version = "0.15", default-features = false } +ndarray = { version = "0.15", default-features = false, features = ["serde"] } ndarray-stats = { version = "0.5", default-features = false } ndarray-linalg = { version = "0.14", default-features = false, optional = true } noisy_float = { version = "0.2", default-features = false } num-traits = { version = "0.2", default-features = false } +serde = { version = "1", default-features = false, features = ["alloc"] } [dev-dependencies] ndarray = { version = "0.15", features = ["approx"] } diff --git a/src/core_serde.rs b/src/core_serde.rs new file mode 100644 index 0000000..b8152f4 --- /dev/null +++ b/src/core_serde.rs @@ -0,0 +1,21 @@ +use crate::core::{ColourModel, ImageBase}; +use ndarray::Data; +use serde::ser::SerializeStruct; +use serde::{Serialize, Serializer}; + +impl Serialize for ImageBase +where + A: Serialize, + T: Data + Serialize, + C: ColourModel, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_struct("Image", 2)?; + state.serialize_field("data", &self.data)?; + state.serialize_field("model", &self.model)?; + state.end() + } +} diff --git a/src/lib.rs b/src/lib.rs index 538eaa1..fd20b3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,7 @@ /// The core of `ndarray-vision` contains the `Image` type and colour models pub mod core; +mod core_serde; /// Image enhancement intrinsics and algorithms #[cfg(feature = "enhancement")] pub mod enhancement; From 7eda8a611c437eba80f3d227a94445c95d4c1165 Mon Sep 17 00:00:00 2001 From: Christopher Field Date: Fri, 30 Dec 2022 11:14:25 -0500 Subject: [PATCH 2/6] Add `NAME` constant to trait This is needed to make (de)serialization easier. I don't know of a better way to handle this without defining the `Serialize` trait for each `C` of the `ImageBase` or handling PhantomData. --- src/core/colour_models.rs | 59 +++++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/src/core/colour_models.rs b/src/core/colour_models.rs index 41d34c5..91c5614 100644 --- a/src/core/colour_models.rs +++ b/src/core/colour_models.rs @@ -60,6 +60,8 @@ pub struct Generic5; /// ColourModel trait, this trait reports base parameters for different colour /// models pub trait ColourModel { + const NAME: &'static str; + /// Number of colour channels for a type. fn channels() -> usize { 3 @@ -816,48 +818,89 @@ where } } -impl ColourModel for RGB {} -impl ColourModel for HSV {} -impl ColourModel for HSI {} -impl ColourModel for HSL {} -impl ColourModel for YCrCb {} -impl ColourModel for CIEXYZ {} -impl ColourModel for CIELAB {} -impl ColourModel for CIELUV {} +impl ColourModel for RGB { + const NAME: &'static str = "RGB"; +} + +impl ColourModel for HSV { + const NAME: &'static str = "HSV"; +} + +impl ColourModel for HSI { + const NAME: &'static str = "HSI"; +} + +impl ColourModel for HSL { + const NAME: &'static str = "HSL"; +} + +impl ColourModel for YCrCb { + const NAME: &'static str = "YCrCb"; +} + +impl ColourModel for CIEXYZ { + const NAME: &'static str = "CIEXYZ"; +} + +impl ColourModel for CIELAB { + const NAME: &'static str = "CIELAB"; +} + +impl ColourModel for CIELUV { + const NAME: &'static str = "CIELUV"; +} impl ColourModel for Gray { + const NAME: &'static str = "Gray"; + fn channels() -> usize { 1 } } impl ColourModel for Generic1 { + const NAME: &'static str = "Generic1"; + fn channels() -> usize { 1 } } + impl ColourModel for Generic2 { + const NAME: &'static str = "Generic2"; + fn channels() -> usize { 2 } } + impl ColourModel for Generic3 { + const NAME: &'static str = "Generic3"; + fn channels() -> usize { 3 } } + impl ColourModel for Generic4 { + const NAME: &'static str = "Generic4"; + fn channels() -> usize { 4 } } + impl ColourModel for Generic5 { + const NAME: &'static str = "Generic5"; + fn channels() -> usize { 5 } } impl ColourModel for RGBA { + const NAME: &'static str = "RGBA"; + fn channels() -> usize { 4 } From 366e9132c4870018ca86dfc98d4a92b425e2a556 Mon Sep 17 00:00:00 2001 From: Christopher Field Date: Fri, 30 Dec 2022 11:15:43 -0500 Subject: [PATCH 3/6] Add dependency for creating unit tests --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 242fc07..413f955 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,3 +41,4 @@ ndarray-linalg = { version = "0.14", features = ["intel-mkl"] } # it is a dependency of intel-mkl-tool, we pin it to temporary solve # https://github.com/rust-math/intel-mkl-src/issues/68 anyhow = "<1.0.49" +serde_json = "1" From d989cad2080d4ed37f15e0038d8caaa61d52f4fe Mon Sep 17 00:00:00 2001 From: Christopher Field Date: Fri, 30 Dec 2022 11:16:05 -0500 Subject: [PATCH 4/6] Add unit test and minimum working implementation --- src/core_serde.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/core_serde.rs b/src/core_serde.rs index b8152f4..ab2e25e 100644 --- a/src/core_serde.rs +++ b/src/core_serde.rs @@ -6,16 +6,29 @@ use serde::{Serialize, Serializer}; impl Serialize for ImageBase where A: Serialize, - T: Data + Serialize, + T: Data, C: ColourModel, { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - let mut state = serializer.serialize_struct("Image", 2)?; + let mut state = serializer.serialize_struct("ImageBase", 2)?; state.serialize_field("data", &self.data)?; - state.serialize_field("model", &self.model)?; + state.serialize_field("model", C::NAME)?; state.end() } } + +#[cfg(test)] +mod tests { + use crate::core::{Image, RGB}; + + #[test] + fn serialize_image_base() { + const EXPECTED: &str = r#"{"data":{"v":1,"dim":[2,3,3],"data":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},"model":"RGB"}"#; + let i = Image::::new(2, 3); + let actual = serde_json::to_string(&i).expect("Serialized RGB image"); + assert_eq!(actual, EXPECTED); + } +} From 79fcc4743bd87b886171e647567268811fc230c1 Mon Sep 17 00:00:00 2001 From: Christopher Field Date: Fri, 30 Dec 2022 11:43:31 -0500 Subject: [PATCH 5/6] Add stub deserialization implementation --- src/core_serde.rs | 113 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 2 deletions(-) diff --git a/src/core_serde.rs b/src/core_serde.rs index ab2e25e..4cf8ea3 100644 --- a/src/core_serde.rs +++ b/src/core_serde.rs @@ -1,7 +1,10 @@ use crate::core::{ColourModel, ImageBase}; -use ndarray::Data; +use ndarray::{Data, DataOwned}; +use serde::de; use serde::ser::SerializeStruct; -use serde::{Serialize, Serializer}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::fmt; +use std::marker::PhantomData; impl Serialize for ImageBase where @@ -20,6 +23,112 @@ where } } +struct ImageBaseVisitor { + _marker_a: PhantomData, + _marker_b: PhantomData, +} + +enum ImageBaseField { + Data, + Model, +} + +impl ImageBaseVisitor { + pub fn new() -> Self { + ImageBaseVisitor { + _marker_a: PhantomData, + _marker_b: PhantomData, + } + } +} + +static IMAGE_BASE_FIELDS: &[&str] = &["data", "model"]; + +impl<'de, A, T, C> Deserialize<'de> for ImageBase +where + A: Deserialize<'de>, + T: DataOwned, + C: ColourModel, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_struct("ImageBase", IMAGE_BASE_FIELDS, ImageBaseVisitor::new()) + } +} + +impl<'de> Deserialize<'de> for ImageBaseField { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct ImageBaseFieldVisitor; + + impl<'de> de::Visitor<'de> for ImageBaseFieldVisitor { + type Value = ImageBaseField; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str(r#""data" or "model""#) + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + match value { + "data" => Ok(ImageBaseField::Data), + "model" => Ok(ImageBaseField::Model), + other => Err(de::Error::unknown_field(other, IMAGE_BASE_FIELDS)), + } + } + + fn visit_bytes(self, value: &[u8]) -> Result + where + E: de::Error, + { + match value { + b"data" => Ok(ImageBaseField::Data), + b"model" => Ok(ImageBaseField::Model), + other => Err(de::Error::unknown_field( + &format!("{:?}", other), + IMAGE_BASE_FIELDS, + )), + } + } + } + + deserializer.deserialize_identifier(ImageBaseFieldVisitor) + } +} + +impl<'de, A, T, C> de::Visitor<'de> for ImageBaseVisitor +where + A: Deserialize<'de>, + T: DataOwned, + C: ColourModel, +{ + type Value = ImageBase; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("ndarray-vision Image representation") + } + + fn visit_seq(self, mut _visitor: V) -> Result + where + V: de::SeqAccess<'de>, + { + unimplemented!() + } + + fn visit_map(self, mut _visitor: V) -> Result + where + V: de::MapAccess<'de>, + { + unimplemented!() + } +} + #[cfg(test)] mod tests { use crate::core::{Image, RGB}; From faf95e4922bfb5e9bd0cf7b6f5be1e5e7dac7191 Mon Sep 17 00:00:00 2001 From: Christopher Field Date: Fri, 30 Dec 2022 16:08:31 -0500 Subject: [PATCH 6/6] Add deserialize implementation --- src/core_serde.rs | 55 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/src/core_serde.rs b/src/core_serde.rs index 4cf8ea3..1309bd5 100644 --- a/src/core_serde.rs +++ b/src/core_serde.rs @@ -114,30 +114,75 @@ where formatter.write_str("ndarray-vision Image representation") } - fn visit_seq(self, mut _visitor: V) -> Result + fn visit_seq(self, mut visitor: V) -> Result where V: de::SeqAccess<'de>, { - unimplemented!() + let data = visitor + .next_element()? + .ok_or_else(|| de::Error::invalid_length(0, &self))?; + let model = visitor + .next_element()? + .ok_or_else(|| de::Error::invalid_length(1, &self))?; + Ok(ImageBase { data, model }) } - fn visit_map(self, mut _visitor: V) -> Result + fn visit_map(self, mut visitor: V) -> Result where V: de::MapAccess<'de>, { - unimplemented!() + let mut data = None; + let mut model: Option<&str> = None; + while let Some(key) = visitor.next_key()? { + match key { + ImageBaseField::Data => { + if data.is_some() { + return Err(de::Error::duplicate_field("data")); + } + data = Some(visitor.next_value()?); + } + ImageBaseField::Model => { + if model.is_some() { + return Err(de::Error::duplicate_field("model")); + } + model = Some(visitor.next_value()?); + } + } + } + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let model = model.ok_or_else(|| de::Error::missing_field("model"))?; + if model.to_lowercase() == C::NAME.to_lowercase() { + Ok(ImageBase { + data, + model: PhantomData, + }) + } else { + Err(de::Error::invalid_value( + de::Unexpected::Str(model), + &C::NAME, + )) + } } } #[cfg(test)] mod tests { + use std::marker::PhantomData; + use crate::core::{Image, RGB}; #[test] fn serialize_image_base() { const EXPECTED: &str = r#"{"data":{"v":1,"dim":[2,3,3],"data":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},"model":"RGB"}"#; let i = Image::::new(2, 3); - let actual = serde_json::to_string(&i).expect("Serialized RGB image"); + let actual = serde_json::to_string(&i).expect("Serialized image"); assert_eq!(actual, EXPECTED); } + + #[test] + fn deserialize_image_base() { + const EXPECTED: &str = r#"{"data":{"v":1,"dim":[2,3,3],"data":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},"model":"RGB"}"#; + let actual: Image = serde_json::from_str(EXPECTED).expect("Deserialized image"); + assert_eq!(actual.model, PhantomData); + } }