Initial commit
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
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<String>,
|
||||
/// Study description text (if available)
|
||||
pub study_description: Option<String>,
|
||||
/// Acquisition date in ISO format (if available)
|
||||
pub acquisition_date: Option<String>,
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user