178 lines
5.0 KiB
Rust
178 lines
5.0 KiB
Rust
//! Geometry types for MRE tissue domains and phantoms
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// 2D point with vector operations
|
|
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq)]
|
|
pub struct Point2D {
|
|
pub x: f32,
|
|
pub y: f32,
|
|
}
|
|
|
|
impl Point2D {
|
|
/// Create a new point
|
|
#[must_use]
|
|
pub const fn new(x: f32, y: f32) -> Self {
|
|
Self { x, y }
|
|
}
|
|
|
|
/// Distance to another point
|
|
#[must_use]
|
|
pub fn distance(&self, other: &Self) -> f32 {
|
|
let dx = self.x - other.x;
|
|
let dy = self.y - other.y;
|
|
(dx * dx + dy * dy).sqrt()
|
|
}
|
|
|
|
/// Squared distance (faster, no sqrt)
|
|
#[must_use]
|
|
pub fn distance_sq(&self, other: &Self) -> f32 {
|
|
let dx = self.x - other.x;
|
|
let dy = self.y - other.y;
|
|
dx * dx + dy * dy
|
|
}
|
|
}
|
|
|
|
/// A circular tissue region with specific stiffness
|
|
/// Used to define tumors, lesions, or other inclusions
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct TissueRegion {
|
|
/// Center of the circular region
|
|
pub center: Point2D,
|
|
/// Radius in meters
|
|
pub radius: f32,
|
|
/// Stiffness in kiloPascals (kPa)
|
|
/// Typical values:
|
|
/// - Healthy liver: 2-6 kPa
|
|
/// - Fibrotic liver: 8-12 kPa
|
|
/// - Tumor/HCC: 15-75 kPa
|
|
pub stiffness_kpa: f32,
|
|
}
|
|
|
|
impl TissueRegion {
|
|
/// Create a tumor-like inclusion
|
|
#[must_use]
|
|
pub fn tumor(center: Point2D, radius: f32) -> Self {
|
|
Self {
|
|
center,
|
|
radius,
|
|
stiffness_kpa: 15.0, // Typical tumor stiffness
|
|
}
|
|
}
|
|
|
|
/// Create a fibrotic region
|
|
#[must_use]
|
|
pub fn fibrotic(center: Point2D, radius: f32) -> Self {
|
|
Self {
|
|
center,
|
|
radius,
|
|
stiffness_kpa: 10.0,
|
|
}
|
|
}
|
|
|
|
/// Check if a point is inside this region
|
|
#[must_use]
|
|
pub fn contains(&self, point: &Point2D) -> bool {
|
|
self.center.distance_sq(point) <= self.radius * self.radius
|
|
}
|
|
}
|
|
|
|
/// Configuration for generating synthetic phantoms
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct PhantomConfig {
|
|
/// Physical domain size in meters (width, height)
|
|
pub domain_size: (f32, f32),
|
|
/// Background tissue stiffness in kPa (healthy tissue)
|
|
pub background_stiffness: f32,
|
|
/// List of tissue inclusions (tumors, lesions, etc.)
|
|
pub inclusions: Vec<TissueRegion>,
|
|
/// Grid resolution for stiffness map (nx, ny)
|
|
pub resolution: (usize, usize),
|
|
}
|
|
|
|
impl Default for PhantomConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
domain_size: (0.1, 0.1), // 10cm x 10cm
|
|
background_stiffness: 3.0, // 3 kPa (healthy liver)
|
|
inclusions: vec![TissueRegion::tumor(Point2D::new(0.05, 0.05), 0.015)],
|
|
resolution: (64, 64),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PhantomConfig {
|
|
/// Create a simple phantom with a single centered tumor
|
|
#[must_use]
|
|
pub fn single_tumor() -> Self {
|
|
let (w, h) = (0.1, 0.1);
|
|
Self {
|
|
domain_size: (w, h),
|
|
background_stiffness: 3.0,
|
|
inclusions: vec![TissueRegion::tumor(Point2D::new(w / 2.0, h / 2.0), 0.015)],
|
|
resolution: (64, 64),
|
|
}
|
|
}
|
|
|
|
/// Create a phantom with multiple lesions
|
|
#[must_use]
|
|
pub fn multiple_lesions() -> Self {
|
|
let (w, h) = (0.1, 0.1);
|
|
Self {
|
|
domain_size: (w, h),
|
|
background_stiffness: 3.0,
|
|
inclusions: vec![
|
|
TissueRegion::tumor(Point2D::new(0.03, 0.05), 0.01),
|
|
TissueRegion::fibrotic(Point2D::new(0.07, 0.05), 0.012),
|
|
TissueRegion {
|
|
center: Point2D::new(0.05, 0.075),
|
|
radius: 0.008,
|
|
stiffness_kpa: 20.0, // Very stiff lesion
|
|
},
|
|
],
|
|
resolution: (64, 64),
|
|
}
|
|
}
|
|
|
|
/// Get stiffness at a point based on phantom configuration
|
|
#[must_use]
|
|
pub fn stiffness_at(&self, point: &Point2D) -> f32 {
|
|
// Check each inclusion (last one wins for overlaps)
|
|
for region in self.inclusions.iter().rev() {
|
|
if region.contains(point) {
|
|
return region.stiffness_kpa;
|
|
}
|
|
}
|
|
self.background_stiffness
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_point_distance() {
|
|
let p1 = Point2D::new(0.0, 0.0);
|
|
let p2 = Point2D::new(3.0, 4.0);
|
|
assert!((p1.distance(&p2) - 5.0).abs() < 1e-6);
|
|
}
|
|
|
|
#[test]
|
|
fn test_region_contains() {
|
|
let region = TissueRegion::tumor(Point2D::new(0.5, 0.5), 0.1);
|
|
assert!(region.contains(&Point2D::new(0.5, 0.5)));
|
|
assert!(region.contains(&Point2D::new(0.55, 0.55)));
|
|
assert!(!region.contains(&Point2D::new(0.0, 0.0)));
|
|
}
|
|
|
|
#[test]
|
|
fn test_phantom_stiffness() {
|
|
let config = PhantomConfig::single_tumor();
|
|
// Background
|
|
assert!((config.stiffness_at(&Point2D::new(0.01, 0.01)) - 3.0).abs() < 0.01);
|
|
// Inside tumor
|
|
assert!((config.stiffness_at(&Point2D::new(0.05, 0.05)) - 15.0).abs() < 0.01);
|
|
}
|
|
}
|