Files
rustytorch/crates/specialized/rtx-digital-twin/src/tissue.rs
T
2026-03-04 00:08:42 +00:00

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);
}
}