diff options
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | Cargo.lock | 406 | ||||
-rw-r--r-- | lri-rs/src/block.rs | 209 | ||||
-rw-r--r-- | lri-rs/src/lib.rs | 436 | ||||
-rw-r--r-- | prism/src/main.rs | 70 | ||||
-rw-r--r-- | prism/src/rotate.rs | 11 |
6 files changed, 873 insertions, 262 deletions
diff --git a/.gitignore b/.gitignore index 54466f5..0036efe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target - +*.png +lri-study \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 253ba71..c91bb1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -42,6 +51,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] +name = "bytemuck" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] name = "bytes" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -72,12 +93,66 @@ dependencies = [ ] [[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + +[[package]] name = "either" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] +name = "enumn" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2ad8cef1d801a4686bfd8919f0b30eac4c8e48968c437a6405ded4fb5272d2b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.31", +] + +[[package]] name = "errno" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -124,12 +199,35 @@ dependencies = [ ] [[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + +[[package]] name = "home" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -149,6 +247,12 @@ dependencies = [ ] [[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] name = "libc" version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -182,12 +286,31 @@ dependencies = [ ] [[package]] +name = "matrixmultiply" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090126dc04f95dc0d1c1c91f61bdd474b3930ca064c1edc8a849da2c6cbe1e77" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[package]] name = "memchr" version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" [[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] name = "miniz_oxide" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -198,12 +321,94 @@ dependencies = [ ] [[package]] +name = "nalgebra" +version = "0.31.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20bd243ab3dbb395b39ee730402d2e5405e448c75133ec49cc977762c4cba3d1" +dependencies = [ + "approx", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational", + "num-traits", + "simba", + "typenum", +] + +[[package]] +name = "nalgebra-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01fcc0b8149b4632adc89ac3b7b31a12fb6099a0317a4eb2ebff574ef7de7218" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "num-complex" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] name = "once_cell" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] name = "png" version = "0.17.10" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -217,11 +422,20 @@ dependencies = [ ] [[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] name = "prism" version = "0.1.0" dependencies = [ "lri-rs", + "nalgebra", "png", + "rawloader", + "rawproc", ] [[package]] @@ -295,6 +509,91 @@ dependencies = [ ] [[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rawloader" +version = "0.37.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d8c6f168c492ffd326537b3aa5a8d5fe07f0d8a3970c5957f286bcd13f888aa" +dependencies = [ + "byteorder", + "enumn", + "glob", + "lazy_static", + "rayon", + "rustc_version", + "toml", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "rawproc" +version = "0.1.0" +source = "git+https://github.com/eclecticnybles/gaze#47dbf3f29edd932bb8e363146e7a5d1505d90c22" +dependencies = [ + "nalgebra", + "num-traits", + "rand", + "rawloader", + "thiserror", +] + +[[package]] +name = "rayon" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + +[[package]] name = "redox_syscall" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -333,6 +632,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] name = "rustix" version = "0.38.12" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -346,6 +654,60 @@ dependencies = [ ] [[package]] +name = "safe_arch" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f398075ce1e6a179b46f51bd88d0598b92b00d3551f1a2d4ac49e771b56ac354" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" + +[[package]] +name = "serde" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.31", +] + +[[package]] +name = "simba" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3fd720c48c53cace224ae62bef1bbff363a70c68c4802a78b5cc6159618176" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + +[[package]] name = "simd-adler32" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -353,6 +715,17 @@ checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" version = "2.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" @@ -392,16 +765,37 @@ checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.31", ] [[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] name = "unicode-ident" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] name = "which" version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -414,6 +808,16 @@ dependencies = [ ] [[package]] +name = "wide" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa469ffa65ef7e0ba0f164183697b89b854253fd31aeb92358b7b6155177d62f" +dependencies = [ + "bytemuck", + "safe_arch", +] + +[[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/lri-rs/src/block.rs b/lri-rs/src/block.rs new file mode 100644 index 0000000..2dc921b --- /dev/null +++ b/lri-rs/src/block.rs @@ -0,0 +1,209 @@ +use lri_proto::{ + gps_data::GPSData, lightheader::LightHeader, matrix3x3f::Matrix3x3F, + view_preferences::ViewPreferences, Message as PbMessage, +}; + +use crate::{CameraId, CameraInfo, ColorInfo, RawImage, SensorModel}; + +pub(crate) struct Block<'lri> { + pub header: Header, + /// This includes the 32 bytes that make up the header. + pub data: &'lri [u8], +} + +impl<'lri> Block<'lri> { + pub fn body(&self) -> &[u8] { + &self.data[32..] + } + + pub fn message_data(&self) -> &[u8] { + let end = self.header.message_offset + self.header.message_length; + &self.data[self.header.message_offset..end] + } + + pub fn message(&self) -> Message { + match self.header.kind { + BlockType::LightHeader => { + Message::LightHeader(LightHeader::parse_from_bytes(self.message_data()).unwrap()) + } + BlockType::ViewPreferences => Message::ViewPreferences( + ViewPreferences::parse_from_bytes(self.message_data()).unwrap(), + ), + BlockType::GPSData => { + Message::Gps(GPSData::parse_from_bytes(self.message_data()).unwrap()) + } + } + } + + pub fn extract_meaningful_data( + &self, + images: &mut Vec<RawImage<'lri>>, + colors: &mut Vec<ColorInfo>, + infos: &mut Vec<CameraInfo>, + ) -> ExtractedData { + let mut ext = ExtractedData { + reference_camera: None, + }; + + let LightHeader { + mut hw_info, + mut module_calibration, + mut modules, + mut image_reference_camera, + .. + } = if let Message::LightHeader(lh) = self.message() { + lh + } else { + return ext; + }; + + // Form the CameraInfo struct for mapping CameraId to SensorType + if let Some(hw_info) = hw_info.take() { + for info in hw_info.camera { + let info = CameraInfo { + camera: info.id().into(), + sensor: info.sensor().into(), + }; + + infos.push(info); + } + } + + // Color information for the Camera moduels. + for mcal in module_calibration { + let camera = mcal.camera_id().into(); + + for mut color in mcal.color { + let whitepoint = color.type_().into(); + let forward_matrix = match color.forward_matrix.take() { + Some(fw) => Self::deconstruct_matrix3x3(fw), + // The forward matrix is like, what we want! If we don't get it, don't bother + // with the struct + None => continue, + }; + let color_matrix = match color.color_matrix.take() { + None => [0.0; 9], + Some(cm) => Self::deconstruct_matrix3x3(cm), + }; + + let rg = color.rg_ratio(); + let bg = color.bg_ratio(); + + colors.push(ColorInfo { + camera, + whitepoint, + forward_matrix, + color_matrix, + rg, + bg, + }) + } + } + + // The images themselves + for mut module in modules { + let camera = module.id().into(); + let mut surface = match module.sensor_data_surface.take() { + Some(sur) => sur, + // The surface is what we're after here. Don't bother with anything lacking it + None => continue, + }; + + let size = surface.size.take().unwrap(); + let width = size.x() as usize; + let height = size.y() as usize; + + let offset = surface.data_offset() as usize; + let data_length = surface.row_stride() as usize * height; + + let format = surface.format().into(); + let image_data = &self.data[offset..offset + data_length]; + + let sbro = module.sensor_bayer_red_override.clone().unwrap(); + + images.push(RawImage { + camera, + // Populated after all the blocks are processed + sensor: SensorModel::Unknown, + width, + height, + format, + data: image_data, + sbro: (sbro.x(), sbro.y()), + // Populated after all the blocks are processed + color: vec![], + }); + } + + if let Some(Ok(irc)) = image_reference_camera.map(|ev| ev.enum_value()) { + ext.reference_camera = Some(irc.into()); + } + + ext + } + + // It kept making my neat little array very, very tall + #[rustfmt::skip] + fn deconstruct_matrix3x3(mat: Matrix3x3F) -> [f32; 9] { + [ + mat.x00(), mat.x01(), mat.x02(), + mat.x10(), mat.x11(), mat.x12(), + mat.x20(), mat.x21(), mat.x22(), + ] + } +} + +pub(crate) struct ExtractedData { + pub reference_camera: Option<CameraId>, +} + +pub enum Message { + LightHeader(LightHeader), + ViewPreferences(ViewPreferences), + Gps(GPSData), +} + +pub struct Header { + /// The length of this header plus the data after it. + pub block_length: usize, + /// An offset from the start of the header to the block's protobuf message + pub message_offset: usize, + /// block's protobuf message length + pub message_length: usize, + /// The kind of protobuf message in the block + pub kind: BlockType, +} + +impl Header { + pub fn ingest(data: &[u8]) -> Self { + let magic = b"LELR"; + + if &data[0..4] != magic { + panic!("Magic nubmer is wrong"); + } + + let combined_length = u64::from_le_bytes(data[4..12].try_into().unwrap()) as usize; + let message_offset = u64::from_le_bytes(data[12..20].try_into().unwrap()) as usize; + let message_length = u32::from_le_bytes(data[20..24].try_into().unwrap()) as usize; + + let kind = match data[24] { + 0 => BlockType::LightHeader, + 1 => BlockType::ViewPreferences, + 2 => BlockType::GPSData, + t => panic!("block type {t} is unknown"), + }; + + Header { + block_length: combined_length, + message_offset, + message_length, + kind, + } + } +} + +pub enum BlockType { + LightHeader, + ViewPreferences, + GPSData, +} diff --git a/lri-rs/src/lib.rs b/lri-rs/src/lib.rs index 2b5d8fb..44b2466 100644 --- a/lri-rs/src/lib.rs +++ b/lri-rs/src/lib.rs @@ -1,287 +1,200 @@ -use std::{fmt, vec::IntoIter}; +use std::fmt; -use lri_proto::Message as PbMessage; +use block::{Block, ExtractedData, Header}; use lri_proto::{ - camera_id::CameraID, - camera_module::{camera_module::surface::FormatType, CameraModule}, + camera_id::CameraID as PbCameraID, camera_module::camera_module::surface::FormatType, color_calibration::color_calibration::IlluminantType, - gps_data::GPSData, - lightheader::LightHeader, - view_preferences::ViewPreferences, }; -pub struct LriFile { - pub blocks: Vec<Block>, - pub models: Vec<SensorModel>, +mod block; + +pub struct LriFile<'lri> { + pub image_reference_camera: Option<CameraId>, + pub images: Vec<RawImage<'lri>>, + pub colors: Vec<ColorInfo>, + pub camera_infos: Vec<CameraInfo>, } -impl LriFile { +impl<'lri> LriFile<'lri> { /// Read - pub fn decode(mut data: Vec<u8>) -> Self { - let mut blocks = vec![]; + pub fn decode(mut data: &'lri [u8]) -> Self { + let mut reference = None; + let mut images = vec![]; + let mut colors = vec![]; + let mut camera_infos = vec![]; + // Read data blocks and extract informtion we care about loop { - let header = Header::ingest(&data[..]); - let end = header.combined_length as usize; - - if end == data.len() { - blocks.push(Block { header, data }); + if data.len() == 0 { break; - } else { - let remain = data.split_off(end); - blocks.push(Block { header, data }); - data = remain; } - } - let models = Self::grab_sensor_models(&blocks); + let header = Header::ingest(&data[..]); + let end = header.block_length as usize; - Self { blocks, models } - } + let block_data = &data[..end]; + data = &data[end..]; - fn grab_sensor_models(blocks: &[Block]) -> Vec<SensorModel> { - let mut models = vec![]; - - for blk in blocks { - match blk.message() { - Message::LightHeader(LightHeader { - module_calibration, .. - }) => { - for mcal in module_calibration { - let id = mcal.camera_id().into(); - let color = match mcal.color.first() { - None => continue, - Some(c) => c, - }; - let whitepoint = color.type_().into(); - let forward = color.forward_matrix.clone().unwrap(); - let our_forward = [ - forward.x00(), - forward.x01(), - forward.x02(), - forward.x10(), - forward.x11(), - forward.x12(), - forward.x20(), - forward.x21(), - forward.x22(), - ]; - - let forward = color.color_matrix.clone().unwrap(); - let our_color = [ - forward.x00(), - forward.x01(), - forward.x02(), - forward.x10(), - forward.x11(), - forward.x12(), - forward.x20(), - forward.x21(), - forward.x22(), - ]; - - let rg = color.rg_ratio(); - let bg = color.bg_ratio(); - - let model = SensorModel { - id, - whitepoint, - forward_matrix: our_forward, - color_matrix: our_color, - rg, - bg, - }; - models.push(model); - } + let block = Block { + header, + data: block_data, + }; + + match block.extract_meaningful_data(&mut images, &mut colors, &mut camera_infos) { + ExtractedData { + reference_camera: Some(irc), + } => { + reference = Some(irc); } _ => (), } } - models - } - - pub fn image_count(&self) -> usize { - let mut count = 0; - - for block in &self.blocks { - match block.message() { - Message::LightHeader(LightHeader { modules, .. }) => { - for cam in modules { - count += 1; - } - } - _ => (), + // Further fill in the RawImage's we extracted + for img in images.iter_mut() { + if let Some(info) = camera_infos.iter().find(|i| i.camera == img.camera) { + img.sensor = info.sensor; } + + let profiles = colors + .iter() + .filter(|c| c.camera == img.camera) + .map(<_>::clone) + .collect(); + + img.color = profiles; } - count + LriFile { + image_reference_camera: reference, + images, + colors, + camera_infos, + } } - pub fn images(&self) -> ImageIterator { - ImageIterator { - blocks: &self.blocks, - modules: None, - } + /// Number of images present in the file + pub fn image_count(&self) -> usize { + self.images.len() } - pub fn color_models(&self, cameraid: SensorId) -> Vec<&SensorModel> { - self.models.iter().filter(|sm| sm.id == cameraid).collect() + /// Iterator over the images + pub fn images(&self) -> std::slice::Iter<'_, RawImage> { + self.images.iter() } -} -pub struct ImageIterator<'lri> { - blocks: &'lri [Block], - modules: Option<(&'lri Block, IntoIter<CameraModule>)>, + /// Get the image the camera showed in the viewfinder, if it's been + /// recorded in the file. + pub fn reference_image(&self) -> Option<&RawImage> { + self.image_reference_camera + .map(|irc| self.images().find(|ri| ri.camera == irc)) + .flatten() + } } -impl<'lri> Iterator for ImageIterator<'lri> { - type Item = RawImage<'lri>; +pub struct RawImage<'img> { + /// Camera that captured this image + pub camera: CameraId, + /// The model of the sensor of the camera + pub sensor: SensorModel, - fn next(&mut self) -> Option<Self::Item> { - loop { - match self.modules.as_mut() { - None => match self.blocks.first() { - None => return None, - Some(block) => { - self.blocks = &self.blocks[1..]; - - if let Message::LightHeader(lh) = block.message() { - let mod_iter = lh.modules.into_iter(); - self.modules = Some((block, mod_iter)); - } else { - continue; - } - } - }, - Some((block, mods)) => { - for module in mods { - let sensor_id = module.id().into(); - let mut surface = module.sensor_data_surface.unwrap(); - let size = surface.size.take().unwrap(); - let offset = surface.data_offset() as usize; - let data_length = surface.row_stride() as usize * size.y() as usize; - - let data = &block.data[offset..offset + data_length]; - - return Some(RawImage { - sensor_id, - width: size.x() as usize, - height: size.y() as usize, - format: surface.format().into(), - data, - }); - } - - self.modules = None; - } - } - } - } -} + pub width: usize, + pub height: usize, -pub struct Block { - pub header: Header, - /// This includes the 32 bytes that make up the header. - pub data: Vec<u8>, + /// How the image data is encoded in the file + pub format: DataFormat, + /// Image data + pub data: &'img [u8], + /// "sensor bayer red offset" + pub sbro: (i32, i32), + /// All color information associated with this [CameraId] for different [Whitepoint]s + pub color: Vec<ColorInfo>, } -impl Block { - pub fn body(&self) -> &[u8] { - &self.data[32..] +impl<'img> RawImage<'img> { + /// Get the color profile for noon daylight. First looks for F7 and, if it can't find that, D65 + pub fn daylight(&self) -> Option<&ColorInfo> { + self.color + .iter() + .find(|c| c.whitepoint == Whitepoint::F7) + .or_else(|| self.color.iter().find(|c| c.whitepoint == Whitepoint::D65)) } - pub fn message_data(&self) -> &[u8] { - let end = self.header.message_offset + self.header.message_length; - &self.data[self.header.message_offset..end] + /// Get a color profile matching the provided Whitepoint + pub fn color_info(&self, whitepoint: Whitepoint) -> Option<&ColorInfo> { + self.color.iter().find(|c| c.whitepoint == whitepoint) } - pub fn message(&self) -> Message { - match self.header.kind { - BlockType::LightHeader => { - Message::LightHeader(LightHeader::parse_from_bytes(self.message_data()).unwrap()) - } - BlockType::ViewPreferences => Message::ViewPreferences( - ViewPreferences::parse_from_bytes(self.message_data()).unwrap(), - ), - BlockType::GPSData => { - Message::Gps(GPSData::parse_from_bytes(self.message_data()).unwrap()) - } + pub fn cfa_string(&self) -> Option<&'static str> { + match self.sensor { + SensorModel::Ar1335Mono => None, + SensorModel::Ar1335 => self.cfa_string_ar1335(), + _ => unimplemented!(), } } -} - -pub enum Message { - LightHeader(LightHeader), - ViewPreferences(ViewPreferences), - Gps(GPSData), -} -pub struct Header { - /// The length of this header and it's associated block - pub combined_length: usize, - /// An offset from the start of the header to the block's protobuf message - pub message_offset: usize, - /// block's protobuf message length - pub message_length: usize, - /// The kind of protobuf message in the block - pub kind: BlockType, -} - -impl Header { - pub fn ingest(data: &[u8]) -> Self { - let magic = b"LELR"; - - if &data[0..4] != magic { - panic!("Magic nubmer is wrong"); + // The AR1335 seems to be BGGR, which was weird. + fn cfa_string_ar1335(&self) -> Option<&'static str> { + match self.sbro { + (-1, -1) => None, + (0, 0) => Some("BGGR"), + (1, 0) => Some("GRBG"), + (0, 1) => Some("GBRG"), + (1, 1) => Some("RGGB"), + _ => unreachable!(), } + } - let combined_length = u64::from_le_bytes(data[4..12].try_into().unwrap()) as usize; - let message_offset = u64::from_le_bytes(data[12..20].try_into().unwrap()) as usize; - let message_length = u32::from_le_bytes(data[20..24].try_into().unwrap()) as usize; - - let kind = match data[24] { - 0 => BlockType::LightHeader, - 1 => BlockType::ViewPreferences, - 2 => BlockType::GPSData, - t => panic!("block type {t} is unknown"), - }; - - Header { - combined_length, - message_offset, - message_length, - kind, + /// Uses the [SensorModel] to determine if the image's [ColorType]. + /// If the sensor model is unknown, [SensorModel::Unknown], then [ColorType::Grayscale] is returned + pub fn color_type(&self) -> ColorType { + match self.sensor { + SensorModel::Ar1335 | SensorModel::Ar835 | SensorModel::Imx386 => ColorType::Rgb, + SensorModel::Ar1335Mono | SensorModel::Imx386Mono | SensorModel::Unknown => { + ColorType::Grayscale + } } } } -pub enum BlockType { - LightHeader, - ViewPreferences, - GPSData, +pub enum ColorType { + Rgb, + Grayscale, } -pub struct RawImage<'img> { - pub sensor_id: SensorId, - pub width: usize, - pub height: usize, - pub format: ImageFormat, - pub data: &'img [u8], -} +#[derive(Copy, Clone, Debug)] +/// Colour information about the camera. Used to correct the image +pub struct ColorInfo { + /// Which specific colour this image was taken by + pub camera: CameraId, -pub struct SensorModel { - pub id: SensorId, + /// The whitepoint that the forward matrix corresponds to. pub whitepoint: Whitepoint, - /// From the camera debayered data to XYZ in the given whitepoint + + /// Camera RGB -> XYZ conversion matrix. pub forward_matrix: [f32; 9], - /// ??? is this cam -> sRGB? + + /// A 3x3 Matrix with unclear usage. + /// + /// If you know what this is or think you have information, PRs are accepted. + /// Or emails, if you'd rather. gen@nyble.dev pub color_matrix: [f32; 9], + + /// Red-green ratio. pub rg: f32, + /// Blue-green ratio. pub bg: f32, } -pub enum ImageFormat { +#[derive(Copy, Clone, Debug)] +pub struct CameraInfo { + camera: CameraId, + sensor: SensorModel, +} + +#[derive(Copy, Clone, Debug)] +/// The representation of the raw data in the LRI file +pub enum DataFormat { // I'm not sure what this is?? Do we ever see it??? BayerJpeg, Packed10bpp, @@ -289,7 +202,7 @@ pub enum ImageFormat { Packed14bpp, } -impl fmt::Display for ImageFormat { +impl fmt::Display for DataFormat { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let str = match self { Self::BayerJpeg => "BayerJpeg", @@ -302,7 +215,7 @@ impl fmt::Display for ImageFormat { } } -impl From<FormatType> for ImageFormat { +impl From<FormatType> for DataFormat { fn from(proto: FormatType) -> Self { match proto { FormatType::RAW_BAYER_JPEG => Self::BayerJpeg, @@ -320,7 +233,7 @@ impl From<FormatType> for ImageFormat { } #[derive(Copy, Clone, Debug, PartialEq)] -pub enum SensorId { +pub enum CameraId { A1, A2, A3, @@ -339,37 +252,37 @@ pub enum SensorId { C6, } -impl From<CameraID> for SensorId { - fn from(pbid: CameraID) -> Self { +impl From<PbCameraID> for CameraId { + fn from(pbid: PbCameraID) -> Self { match pbid { - CameraID::A1 => Self::A1, - CameraID::A2 => Self::A2, - CameraID::A3 => Self::A3, - CameraID::A4 => Self::A4, - CameraID::A5 => Self::A5, - CameraID::B1 => Self::B1, - CameraID::B2 => Self::B2, - CameraID::B3 => Self::B3, - CameraID::B4 => Self::B4, - CameraID::B5 => Self::B5, - CameraID::C1 => Self::C1, - CameraID::C2 => Self::C2, - CameraID::C3 => Self::C3, - CameraID::C4 => Self::C4, - CameraID::C5 => Self::C5, - CameraID::C6 => Self::C6, + PbCameraID::A1 => Self::A1, + PbCameraID::A2 => Self::A2, + PbCameraID::A3 => Self::A3, + PbCameraID::A4 => Self::A4, + PbCameraID::A5 => Self::A5, + PbCameraID::B1 => Self::B1, + PbCameraID::B2 => Self::B2, + PbCameraID::B3 => Self::B3, + PbCameraID::B4 => Self::B4, + PbCameraID::B5 => Self::B5, + PbCameraID::C1 => Self::C1, + PbCameraID::C2 => Self::C2, + PbCameraID::C3 => Self::C3, + PbCameraID::C4 => Self::C4, + PbCameraID::C5 => Self::C5, + PbCameraID::C6 => Self::C6, } } } -impl fmt::Display for SensorId { +impl fmt::Display for CameraId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // this is good; i write good code write!(f, "{self:?}") } } -#[derive(Debug)] +#[derive(Copy, Clone, Debug, PartialEq)] pub enum Whitepoint { A, D50, @@ -396,3 +309,28 @@ impl From<IlluminantType> for Whitepoint { } } } + +#[derive(Copy, Clone, Debug)] +pub enum SensorModel { + Unknown, + Ar835, + Ar1335, + Ar1335Mono, + Imx386, + Imx386Mono, +} + +impl From<lri_proto::sensor_type::SensorType> for SensorModel { + fn from(pbst: lri_proto::sensor_type::SensorType) -> Self { + use lri_proto::sensor_type::SensorType as ProtoSt; + + match pbst { + ProtoSt::SENSOR_UNKNOWN => Self::Unknown, + ProtoSt::SENSOR_AR835 => Self::Ar835, + ProtoSt::SENSOR_AR1335 => Self::Ar1335, + ProtoSt::SENSOR_AR1335_MONO => Self::Ar1335Mono, + ProtoSt::SENSOR_IMX386 => Self::Imx386, + ProtoSt::SENSOR_IMX386_MONO => Self::Imx386Mono, + } + } +} diff --git a/prism/src/main.rs b/prism/src/main.rs index 37b2ff6..5f4af1c 100644 --- a/prism/src/main.rs +++ b/prism/src/main.rs @@ -1,6 +1,7 @@ use lri_rs::{LriFile, RawImage, Whitepoint}; use nalgebra::{Matrix3, Matrix3x1}; +mod rotate; mod unpack; fn main() { @@ -10,17 +11,33 @@ fn main() { println!("{} images", lri.image_count()); + lri.reference_image() + .map(|raw| make(raw, String::from("reference.png"))); + for (idx, img) in lri.images().enumerate() { + for color in &img.color { + println!( + "{:?} rg = {} bg = {}", + color.whitepoint, color.rg, color.bg + ); + + let white = + Matrix3::from_row_slice(&color.forward_matrix) * Matrix3x1::new(1.0, 1.0, 1.0); + + let white_x = white[0] / (white[0] + white[1] + white[2]); + let white_y = white[1] / (white[0] + white[1] + white[2]); + let white_z = 1.0 - white_x - white_y; + + println!("\twhite: x = {} y = {} z = {}", white_x, white_y, white_z); + + println!("\t{:?}", color.forward_matrix); + } + //std::process::exit(0); + make(img, format!("image_{idx}.png")); } } -// R G R G -// G B G B -// R G R G - -const CFAS: &[&'static str] = &["RGGB", "GRBG", "GBRG", "BGGR"]; - fn make(img: &RawImage, path: String) { use rawproc::image::RawMetadata; use rawproc::{colorspace::BayerRgb, image::Image}; @@ -72,7 +89,7 @@ fn make(img: &RawImage, path: String) { // C5 - RO // C6 - -1:-1 - let (rgb, color_format) = match img.cfa_string() { + let (mut rgb, color_format) = match img.cfa_string() { Some(cfa_string) => { let rawimg: Image<u16, BayerRgb> = Image::from_raw_parts( 4160, @@ -94,21 +111,42 @@ fn make(img: &RawImage, path: String) { None => (ten_data, png::ColorType::Grayscale), }; + rotate::rotate_180(rgb.as_mut_slice()); + let mut floats: Vec<f32> = rgb.into_iter().map(|p| p as f32 / 1023.0).collect(); print!("\t"); color.iter().for_each(|c| print!("{:?} ", c.whitepoint)); println!(); - match img.daylight() { + match img.color_info(Whitepoint::F11) { Some(c) => { //println!("\tApplying color profile: {:?}", c.color_matrix); let to_xyz = Matrix3::from_row_slice(&c.forward_matrix); let to_srgb = Matrix3::from_row_slice(&BRUCE_XYZ_RGB_D65); - //let color = Matrix3::from_row_slice(&c.color_matrix); + let color = Matrix3::from_row_slice(&c.color_matrix); + let d50_d65 = Matrix3::from_row_slice(&BRADFORD_D50_D65); + + let xyz_d65 = to_xyz * d50_d65; + + println!("{color}"); + + let white = xyz_d65 * Matrix3x1::new(1.0, 1.0, 1.0); + + let white_x = white[0] / (white[0] + white[1] + white[2]); + let white_y = white[1] / (white[0] + white[1] + white[2]); + let white_z = 1.0 - white_x - white_y; + + println!( + "\t{:?} ||| white: x = {} y = {} z = {}", + c.whitepoint, white_x, white_y, white_z + ); let premul = to_xyz * to_srgb; + let prenorm = premul.normalize(); + println!("{prenorm}"); + for chnk in floats.chunks_mut(3) { let r = chnk[0] * (1.0 / c.rg); let g = chnk[1]; @@ -117,7 +155,10 @@ fn make(img: &RawImage, path: String) { let px = Matrix3x1::new(r, g, b); //let rgb = premul * px; + //let px = color * px; let xyz = to_xyz * px; + //let xyz = d50_d65 * xyz; + //let xyz_white = color * xyz; let rgb = to_srgb * xyz; chnk[0] = srgb_gamma(rgb[0]) * 255.0; @@ -147,9 +188,16 @@ const BRUCE_XYZ_RGB_D50: [f32; 9] = [ #[rustfmt::skip] const BRUCE_XYZ_RGB_D65: [f32; 9] = [ - 3.2404542, -1.5371385, -0.4985314, + 3.2404542, -1.5371385, -0.4985314, -0.9692660, 1.8760108, 0.0415560, - 0.0556434, -0.2040259, 1.0572252 + 0.0556434, -0.2040259, 1.0572252 +]; + +#[rustfmt::skip] +const BRADFORD_D50_D65: [f32; 9] = [ + 0.9555766, -0.0230393, 0.0631636, + -0.0282895, 1.0099416, 0.0210077, + 0.0122982, -0.0204830, 1.3299098, ]; #[inline] diff --git a/prism/src/rotate.rs b/prism/src/rotate.rs new file mode 100644 index 0000000..82f29d1 --- /dev/null +++ b/prism/src/rotate.rs @@ -0,0 +1,11 @@ +pub fn rotate_180<T: Copy>(data: &mut [T]) { + let mut rat = vec![data[0]; data.len()]; + + for (idx, px) in data.chunks(3).rev().enumerate() { + rat[idx * 3] = px[0]; + rat[idx * 3 + 1] = px[1]; + rat[idx * 3 + 2] = px[2]; + } + + data.copy_from_slice(&rat); +} |