465 lines
14 KiB
Rust
465 lines
14 KiB
Rust
//! Tissue property database for medical digital twins.
|
|
//!
|
|
//! This module provides a database of tissue properties used in physics
|
|
//! simulations. Properties include thermal, mechanical, and electrical
|
|
//! characteristics based on published medical literature values.
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
|
|
/// Types of tissues in the human body.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
pub enum TissueType {
|
|
/// Background/air (outside body)
|
|
Air,
|
|
/// Skin tissue
|
|
Skin,
|
|
/// Subcutaneous fat
|
|
Fat,
|
|
/// Skeletal muscle
|
|
Muscle,
|
|
/// Cortical (compact) bone
|
|
CorticalBone,
|
|
/// Trabecular (spongy) bone
|
|
TrabecularBone,
|
|
/// Liver parenchyma
|
|
Liver,
|
|
/// Kidney cortex
|
|
Kidney,
|
|
/// Spleen tissue
|
|
Spleen,
|
|
/// Brain gray matter
|
|
BrainGrayMatter,
|
|
/// Brain white matter
|
|
BrainWhiteMatter,
|
|
/// Blood
|
|
Blood,
|
|
/// Lung tissue (average)
|
|
Lung,
|
|
/// Heart muscle (myocardium)
|
|
Heart,
|
|
/// Tumor (generic solid tumor)
|
|
Tumor,
|
|
/// Water (for calibration)
|
|
Water,
|
|
/// Custom tissue type with index
|
|
Custom(u8),
|
|
}
|
|
|
|
impl TissueType {
|
|
/// Get tissue name as string.
|
|
pub fn name(&self) -> &'static str {
|
|
match self {
|
|
Self::Air => "Air",
|
|
Self::Skin => "Skin",
|
|
Self::Fat => "Fat",
|
|
Self::Muscle => "Muscle",
|
|
Self::CorticalBone => "Cortical Bone",
|
|
Self::TrabecularBone => "Trabecular Bone",
|
|
Self::Liver => "Liver",
|
|
Self::Kidney => "Kidney",
|
|
Self::Spleen => "Spleen",
|
|
Self::BrainGrayMatter => "Brain Gray Matter",
|
|
Self::BrainWhiteMatter => "Brain White Matter",
|
|
Self::Blood => "Blood",
|
|
Self::Lung => "Lung",
|
|
Self::Heart => "Heart",
|
|
Self::Tumor => "Tumor",
|
|
Self::Water => "Water",
|
|
Self::Custom(_) => "Custom",
|
|
}
|
|
}
|
|
|
|
/// Get numeric label for segmentation.
|
|
pub fn label(&self) -> u8 {
|
|
match self {
|
|
Self::Air => 0,
|
|
Self::Skin => 1,
|
|
Self::Fat => 2,
|
|
Self::Muscle => 3,
|
|
Self::CorticalBone => 4,
|
|
Self::TrabecularBone => 5,
|
|
Self::Liver => 6,
|
|
Self::Kidney => 7,
|
|
Self::Spleen => 8,
|
|
Self::BrainGrayMatter => 9,
|
|
Self::BrainWhiteMatter => 10,
|
|
Self::Blood => 11,
|
|
Self::Lung => 12,
|
|
Self::Heart => 13,
|
|
Self::Tumor => 14,
|
|
Self::Water => 15,
|
|
Self::Custom(n) => 100 + n,
|
|
}
|
|
}
|
|
|
|
/// Create tissue type from segmentation label.
|
|
pub fn from_label(label: u8) -> Self {
|
|
match label {
|
|
0 => Self::Air,
|
|
1 => Self::Skin,
|
|
2 => Self::Fat,
|
|
3 => Self::Muscle,
|
|
4 => Self::CorticalBone,
|
|
5 => Self::TrabecularBone,
|
|
6 => Self::Liver,
|
|
7 => Self::Kidney,
|
|
8 => Self::Spleen,
|
|
9 => Self::BrainGrayMatter,
|
|
10 => Self::BrainWhiteMatter,
|
|
11 => Self::Blood,
|
|
12 => Self::Lung,
|
|
13 => Self::Heart,
|
|
14 => Self::Tumor,
|
|
15 => Self::Water,
|
|
n if n >= 100 => Self::Custom(n - 100),
|
|
_ => Self::Air, // Default to air for unknown labels
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Physical properties of a tissue type.
|
|
///
|
|
/// All values are in SI units unless otherwise noted.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct TissueProperties {
|
|
/// Tissue type identifier
|
|
pub tissue_type: TissueType,
|
|
|
|
// Thermal properties
|
|
/// Thermal conductivity [W/(m·K)]
|
|
pub thermal_conductivity: f32,
|
|
/// Specific heat capacity [J/(kg·K)]
|
|
pub specific_heat: f32,
|
|
/// Density [kg/m³]
|
|
pub density: f32,
|
|
/// Blood perfusion rate [1/s] (for Pennes bioheat equation)
|
|
pub perfusion_rate: f32,
|
|
/// Metabolic heat generation [W/m³]
|
|
pub metabolic_heat: f32,
|
|
|
|
// Mechanical properties
|
|
/// Young's modulus [Pa]
|
|
pub youngs_modulus: f32,
|
|
/// Poisson's ratio [-]
|
|
pub poisson_ratio: f32,
|
|
|
|
// Electrical properties
|
|
/// Electrical conductivity [S/m]
|
|
pub electrical_conductivity: f32,
|
|
/// Relative permittivity [-]
|
|
pub relative_permittivity: f32,
|
|
}
|
|
|
|
impl TissueProperties {
|
|
/// Create new tissue properties.
|
|
pub fn new(tissue_type: TissueType) -> Self {
|
|
Self {
|
|
tissue_type,
|
|
thermal_conductivity: 0.5,
|
|
specific_heat: 3500.0,
|
|
density: 1000.0,
|
|
perfusion_rate: 0.0,
|
|
metabolic_heat: 0.0,
|
|
youngs_modulus: 10000.0,
|
|
poisson_ratio: 0.3,
|
|
electrical_conductivity: 0.2,
|
|
relative_permittivity: 50.0,
|
|
}
|
|
}
|
|
|
|
/// Set thermal properties.
|
|
pub fn with_thermal(mut self, conductivity: f32, specific_heat: f32, density: f32) -> Self {
|
|
self.thermal_conductivity = conductivity;
|
|
self.specific_heat = specific_heat;
|
|
self.density = density;
|
|
self
|
|
}
|
|
|
|
/// Set perfusion and metabolic properties.
|
|
pub fn with_perfusion(mut self, perfusion_rate: f32, metabolic_heat: f32) -> Self {
|
|
self.perfusion_rate = perfusion_rate;
|
|
self.metabolic_heat = metabolic_heat;
|
|
self
|
|
}
|
|
|
|
/// Set mechanical properties.
|
|
pub fn with_mechanical(mut self, youngs_modulus: f32, poisson_ratio: f32) -> Self {
|
|
self.youngs_modulus = youngs_modulus;
|
|
self.poisson_ratio = poisson_ratio;
|
|
self
|
|
}
|
|
|
|
/// Set electrical properties.
|
|
pub fn with_electrical(mut self, conductivity: f32, permittivity: f32) -> Self {
|
|
self.electrical_conductivity = conductivity;
|
|
self.relative_permittivity = permittivity;
|
|
self
|
|
}
|
|
|
|
/// Calculate thermal diffusivity [m²/s].
|
|
pub fn thermal_diffusivity(&self) -> f32 {
|
|
self.thermal_conductivity / (self.density * self.specific_heat)
|
|
}
|
|
}
|
|
|
|
/// Database of tissue properties.
|
|
///
|
|
/// Contains lookup table for standard human tissue properties based on
|
|
/// published literature values.
|
|
#[derive(Debug, Clone)]
|
|
pub struct TissueDatabase {
|
|
properties: HashMap<TissueType, TissueProperties>,
|
|
}
|
|
|
|
impl TissueDatabase {
|
|
/// Create an empty tissue database.
|
|
pub fn new() -> Self {
|
|
Self {
|
|
properties: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
/// Create database with standard human tissue properties.
|
|
///
|
|
/// Values are based on:
|
|
/// - IT'IS Foundation tissue properties database
|
|
/// - Hasgall et al., "IT'IS Database for thermal and electromagnetic parameters"
|
|
/// - Duck, F.A., "Physical Properties of Tissues"
|
|
pub fn standard() -> Self {
|
|
let mut db = Self::new();
|
|
|
|
// Air
|
|
db.insert(
|
|
TissueProperties::new(TissueType::Air)
|
|
.with_thermal(0.026, 1005.0, 1.2)
|
|
.with_perfusion(0.0, 0.0)
|
|
.with_mechanical(0.0, 0.0)
|
|
.with_electrical(0.0, 1.0),
|
|
);
|
|
|
|
// Skin
|
|
db.insert(
|
|
TissueProperties::new(TissueType::Skin)
|
|
.with_thermal(0.37, 3391.0, 1109.0)
|
|
.with_perfusion(0.00196, 368.0)
|
|
.with_mechanical(10000.0, 0.48)
|
|
.with_electrical(0.0002, 1119.0),
|
|
);
|
|
|
|
// Fat
|
|
db.insert(
|
|
TissueProperties::new(TissueType::Fat)
|
|
.with_thermal(0.21, 2348.0, 911.0)
|
|
.with_perfusion(0.00036, 400.0)
|
|
.with_mechanical(2000.0, 0.49)
|
|
.with_electrical(0.024, 92.9),
|
|
);
|
|
|
|
// Muscle
|
|
db.insert(
|
|
TissueProperties::new(TissueType::Muscle)
|
|
.with_thermal(0.49, 3421.0, 1090.0)
|
|
.with_perfusion(0.00069, 684.0)
|
|
.with_mechanical(500000.0, 0.49)
|
|
.with_electrical(0.355, 6862.0),
|
|
);
|
|
|
|
// Cortical Bone
|
|
db.insert(
|
|
TissueProperties::new(TissueType::CorticalBone)
|
|
.with_thermal(0.32, 1313.0, 1908.0)
|
|
.with_perfusion(0.0, 0.0)
|
|
.with_mechanical(17.0e9, 0.3)
|
|
.with_electrical(0.02, 145.0),
|
|
);
|
|
|
|
// Trabecular Bone
|
|
db.insert(
|
|
TissueProperties::new(TissueType::TrabecularBone)
|
|
.with_thermal(0.31, 2274.0, 1178.0)
|
|
.with_perfusion(0.00025, 0.0)
|
|
.with_mechanical(0.5e9, 0.3)
|
|
.with_electrical(0.084, 528.0),
|
|
);
|
|
|
|
// Liver
|
|
db.insert(
|
|
TissueProperties::new(TissueType::Liver)
|
|
.with_thermal(0.52, 3540.0, 1079.0)
|
|
.with_perfusion(0.01117, 5880.0)
|
|
.with_mechanical(640.0, 0.45)
|
|
.with_electrical(0.0846, 5070.0),
|
|
);
|
|
|
|
// Kidney
|
|
db.insert(
|
|
TissueProperties::new(TissueType::Kidney)
|
|
.with_thermal(0.54, 3763.0, 1066.0)
|
|
.with_perfusion(0.0667, 6650.0)
|
|
.with_mechanical(25000.0, 0.45)
|
|
.with_electrical(0.127, 8570.0),
|
|
);
|
|
|
|
// Spleen
|
|
db.insert(
|
|
TissueProperties::new(TissueType::Spleen)
|
|
.with_thermal(0.54, 3591.0, 1054.0)
|
|
.with_perfusion(0.01117, 6310.0)
|
|
.with_mechanical(20000.0, 0.45)
|
|
.with_electrical(0.102, 7560.0),
|
|
);
|
|
|
|
// Brain Gray Matter
|
|
db.insert(
|
|
TissueProperties::new(TissueType::BrainGrayMatter)
|
|
.with_thermal(0.565, 3696.0, 1045.0)
|
|
.with_perfusion(0.01, 7100.0)
|
|
.with_mechanical(3000.0, 0.45)
|
|
.with_electrical(0.108, 4980.0),
|
|
);
|
|
|
|
// Brain White Matter
|
|
db.insert(
|
|
TissueProperties::new(TissueType::BrainWhiteMatter)
|
|
.with_thermal(0.503, 3583.0, 1041.0)
|
|
.with_perfusion(0.004, 4500.0)
|
|
.with_mechanical(3000.0, 0.45)
|
|
.with_electrical(0.0654, 2810.0),
|
|
);
|
|
|
|
// Blood
|
|
db.insert(
|
|
TissueProperties::new(TissueType::Blood)
|
|
.with_thermal(0.52, 3617.0, 1050.0)
|
|
.with_perfusion(0.0, 0.0) // Blood itself doesn't have perfusion
|
|
.with_mechanical(0.0, 0.5) // Liquid
|
|
.with_electrical(0.7, 5120.0),
|
|
);
|
|
|
|
// Lung
|
|
db.insert(
|
|
TissueProperties::new(TissueType::Lung)
|
|
.with_thermal(0.39, 3886.0, 394.0) // Lower density due to air
|
|
.with_perfusion(0.00667, 1200.0)
|
|
.with_mechanical(2500.0, 0.3)
|
|
.with_electrical(0.107, 2510.0),
|
|
);
|
|
|
|
// Heart (Myocardium)
|
|
db.insert(
|
|
TissueProperties::new(TissueType::Heart)
|
|
.with_thermal(0.56, 3686.0, 1081.0)
|
|
.with_perfusion(0.0167, 9150.0) // High perfusion
|
|
.with_mechanical(10000.0, 0.45)
|
|
.with_electrical(0.106, 9310.0),
|
|
);
|
|
|
|
// Tumor (generic solid tumor)
|
|
db.insert(
|
|
TissueProperties::new(TissueType::Tumor)
|
|
.with_thermal(0.55, 3800.0, 1040.0)
|
|
.with_perfusion(0.003, 5000.0) // Reduced perfusion
|
|
.with_mechanical(5000.0, 0.45)
|
|
.with_electrical(0.15, 3000.0),
|
|
);
|
|
|
|
// Water (calibration)
|
|
db.insert(
|
|
TissueProperties::new(TissueType::Water)
|
|
.with_thermal(0.60, 4180.0, 1000.0)
|
|
.with_perfusion(0.0, 0.0)
|
|
.with_mechanical(0.0, 0.5)
|
|
.with_electrical(0.0, 80.0),
|
|
);
|
|
|
|
db
|
|
}
|
|
|
|
/// Insert tissue properties into the database.
|
|
pub fn insert(&mut self, props: TissueProperties) {
|
|
self.properties.insert(props.tissue_type, props);
|
|
}
|
|
|
|
/// Get properties for a tissue type.
|
|
pub fn get(&self, tissue_type: TissueType) -> Option<&TissueProperties> {
|
|
self.properties.get(&tissue_type)
|
|
}
|
|
|
|
/// Get properties or default if not found.
|
|
pub fn get_or_default(&self, tissue_type: TissueType) -> TissueProperties {
|
|
self.properties
|
|
.get(&tissue_type)
|
|
.cloned()
|
|
.unwrap_or_else(|| TissueProperties::new(tissue_type))
|
|
}
|
|
|
|
/// Get all tissue types in the database.
|
|
pub fn tissue_types(&self) -> Vec<TissueType> {
|
|
self.properties.keys().copied().collect()
|
|
}
|
|
|
|
/// Get number of tissue types.
|
|
pub fn len(&self) -> usize {
|
|
self.properties.len()
|
|
}
|
|
|
|
/// Check if database is empty.
|
|
pub fn is_empty(&self) -> bool {
|
|
self.properties.is_empty()
|
|
}
|
|
}
|
|
|
|
impl Default for TissueDatabase {
|
|
fn default() -> Self {
|
|
Self::standard()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_tissue_type_labels() {
|
|
assert_eq!(TissueType::Air.label(), 0);
|
|
assert_eq!(TissueType::Liver.label(), 6);
|
|
assert_eq!(TissueType::from_label(6), TissueType::Liver);
|
|
}
|
|
|
|
#[test]
|
|
fn test_tissue_database_standard() {
|
|
let db = TissueDatabase::standard();
|
|
|
|
// Should have all standard tissues
|
|
assert!(db.len() >= 15);
|
|
|
|
// Check liver properties
|
|
let liver = db.get(TissueType::Liver).unwrap();
|
|
assert!(liver.thermal_conductivity > 0.5);
|
|
assert!(liver.perfusion_rate > 0.01);
|
|
}
|
|
|
|
#[test]
|
|
fn test_thermal_diffusivity() {
|
|
let props = TissueProperties::new(TissueType::Muscle).with_thermal(0.49, 3421.0, 1090.0);
|
|
|
|
let alpha = props.thermal_diffusivity();
|
|
// Should be approximately 1.3e-7 m²/s
|
|
assert!(alpha > 1e-8);
|
|
assert!(alpha < 1e-6);
|
|
}
|
|
|
|
#[test]
|
|
fn test_custom_tissue() {
|
|
let mut db = TissueDatabase::new();
|
|
|
|
let custom = TissueProperties::new(TissueType::Custom(0)).with_thermal(0.4, 3000.0, 1000.0);
|
|
|
|
db.insert(custom);
|
|
|
|
let retrieved = db.get(TissueType::Custom(0)).unwrap();
|
|
assert_eq!(retrieved.thermal_conductivity, 0.4);
|
|
}
|
|
}
|