use serde::{Deserialize, Serialize}; /// Spatial orientation codes following neuroimaging conventions #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum Orientation { /// Right to Left RightToLeft, /// Left to Right LeftToRight, /// Posterior to Anterior PosteriorToAnterior, /// Anterior to Posterior AnteriorToPosterior, /// Inferior to Superior InferiorToSuperior, /// Superior to Inferior SuperiorToInferior, } /// Voxel spacing in millimeters #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct VoxelSpacing { /// X-axis spacing in millimeters pub x: f32, /// Y-axis spacing in millimeters pub y: f32, /// Z-axis spacing in millimeters pub z: f32, } impl VoxelSpacing { /// Create new voxel spacing pub fn new(x: f32, y: f32, z: f32) -> Self { Self { x, y, z } } /// Create isotropic voxel spacing pub fn isotropic(spacing: f32) -> Self { Self::new(spacing, spacing, spacing) } /// Check if voxel spacing is isotropic pub fn is_isotropic(&self) -> bool { (self.x - self.y).abs() < f32::EPSILON && (self.y - self.z).abs() < f32::EPSILON } } /// Medical image metadata #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MedicalMetadata { /// Image dimensions (width, height, depth) in voxels pub dimensions: (usize, usize, usize), /// Voxel spacing in millimeters (physical size of each voxel) pub voxel_spacing: VoxelSpacing, /// Patient identifier (if available in DICOM/NIfTI metadata) pub patient_id: Option, /// Study description text (if available) pub study_description: Option, /// Acquisition date in ISO format (if available) pub acquisition_date: Option, } impl MedicalMetadata { /// Create new metadata with required fields pub fn new(dimensions: (usize, usize, usize), voxel_spacing: VoxelSpacing) -> Self { Self { dimensions, voxel_spacing, patient_id: None, study_description: None, acquisition_date: None, } } /// Set patient ID pub fn with_patient_id(mut self, patient_id: String) -> Self { self.patient_id = Some(patient_id); self } /// Set study description pub fn with_study_description(mut self, description: String) -> Self { self.study_description = Some(description); self } /// Set acquisition date pub fn with_acquisition_date(mut self, date: String) -> Self { self.acquisition_date = Some(date); self } /// Calculate total number of voxels pub fn voxel_count(&self) -> usize { self.dimensions.0 * self.dimensions.1 * self.dimensions.2 } /// Calculate volume in cubic millimeters pub fn volume_mm3(&self) -> f32 { let (w, h, d) = self.dimensions; (w as f32 * self.voxel_spacing.x) * (h as f32 * self.voxel_spacing.y) * (d as f32 * self.voxel_spacing.z) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_voxel_spacing_new() { let spacing = VoxelSpacing::new(1.0, 2.0, 3.0); assert_eq!(spacing.x, 1.0); assert_eq!(spacing.y, 2.0); assert_eq!(spacing.z, 3.0); } #[test] fn test_voxel_spacing_isotropic() { let spacing = VoxelSpacing::isotropic(1.5); assert_eq!(spacing.x, 1.5); assert_eq!(spacing.y, 1.5); assert_eq!(spacing.z, 1.5); assert!(spacing.is_isotropic()); } #[test] fn test_voxel_spacing_is_isotropic_true() { let spacing = VoxelSpacing::new(1.0, 1.0, 1.0); assert!(spacing.is_isotropic()); } #[test] fn test_voxel_spacing_is_isotropic_false() { let spacing = VoxelSpacing::new(1.0, 2.0, 1.0); assert!(!spacing.is_isotropic()); } #[test] fn test_medical_metadata_new() { let spacing = VoxelSpacing::isotropic(1.0); let metadata = MedicalMetadata::new((256, 256, 128), spacing); assert_eq!(metadata.dimensions, (256, 256, 128)); assert!(metadata.patient_id.is_none()); assert!(metadata.study_description.is_none()); assert!(metadata.acquisition_date.is_none()); } #[test] fn test_medical_metadata_with_patient_id() { let spacing = VoxelSpacing::isotropic(1.0); let metadata = MedicalMetadata::new((256, 256, 128), spacing).with_patient_id("PT001".to_string()); assert_eq!(metadata.patient_id, Some("PT001".to_string())); } #[test] fn test_medical_metadata_with_study_description() { let spacing = VoxelSpacing::isotropic(1.0); let metadata = MedicalMetadata::new((256, 256, 128), spacing) .with_study_description("Brain MRI".to_string()); assert_eq!(metadata.study_description, Some("Brain MRI".to_string())); } #[test] fn test_medical_metadata_with_acquisition_date() { let spacing = VoxelSpacing::isotropic(1.0); let metadata = MedicalMetadata::new((256, 256, 128), spacing) .with_acquisition_date("2024-01-15".to_string()); assert_eq!(metadata.acquisition_date, Some("2024-01-15".to_string())); } #[test] fn test_medical_metadata_builder_chain() { let spacing = VoxelSpacing::isotropic(1.0); let metadata = MedicalMetadata::new((256, 256, 128), spacing) .with_patient_id("PT001".to_string()) .with_study_description("Brain MRI".to_string()) .with_acquisition_date("2024-01-15".to_string()); assert_eq!(metadata.patient_id, Some("PT001".to_string())); assert_eq!(metadata.study_description, Some("Brain MRI".to_string())); assert_eq!(metadata.acquisition_date, Some("2024-01-15".to_string())); } #[test] fn test_voxel_count() { let spacing = VoxelSpacing::isotropic(1.0); let metadata = MedicalMetadata::new((256, 256, 128), spacing); assert_eq!(metadata.voxel_count(), 256 * 256 * 128); } #[test] fn test_voxel_count_small() { let spacing = VoxelSpacing::isotropic(1.0); let metadata = MedicalMetadata::new((10, 10, 10), spacing); assert_eq!(metadata.voxel_count(), 1000); } #[test] fn test_volume_mm3_isotropic() { let spacing = VoxelSpacing::isotropic(1.0); let metadata = MedicalMetadata::new((10, 10, 10), spacing); assert_eq!(metadata.volume_mm3(), 1000.0); } #[test] fn test_volume_mm3_anisotropic() { let spacing = VoxelSpacing::new(1.0, 1.0, 2.0); let metadata = MedicalMetadata::new((10, 10, 10), spacing); assert_eq!(metadata.volume_mm3(), 2000.0); } #[test] fn test_orientation_equality() { assert_eq!(Orientation::RightToLeft, Orientation::RightToLeft); assert_ne!(Orientation::RightToLeft, Orientation::LeftToRight); } #[test] fn test_voxel_spacing_clone() { let spacing1 = VoxelSpacing::isotropic(1.0); let spacing2 = spacing1.clone(); assert_eq!(spacing1, spacing2); } #[test] fn test_medical_metadata_clone() { let spacing = VoxelSpacing::isotropic(1.0); let metadata1 = MedicalMetadata::new((256, 256, 128), spacing); let metadata2 = metadata1.clone(); assert_eq!(metadata1.dimensions, metadata2.dimensions); assert_eq!(metadata1.voxel_spacing, metadata2.voxel_spacing); } #[test] fn test_serde_voxel_spacing() { let spacing = VoxelSpacing::isotropic(1.5); let json = serde_json::to_string(&spacing).unwrap(); let deserialized: VoxelSpacing = serde_json::from_str(&json).unwrap(); assert_eq!(spacing, deserialized); } #[test] fn test_serde_medical_metadata() { let spacing = VoxelSpacing::isotropic(1.0); let metadata = MedicalMetadata::new((256, 256, 128), spacing).with_patient_id("PT001".to_string()); let json = serde_json::to_string(&metadata).unwrap(); let deserialized: MedicalMetadata = serde_json::from_str(&json).unwrap(); assert_eq!(metadata.dimensions, deserialized.dimensions); assert_eq!(metadata.patient_id, deserialized.patient_id); } }