Source code for simpa.utils.libraries.tissue_library

# SPDX-FileCopyrightText: 2021 Division of Intelligent Medical Systems, DKFZ
# SPDX-FileCopyrightText: 2021 Janek Groehl
# SPDX-License-Identifier: MIT
import numpy as np
from typing import Union, List, Optional

from simpa.utils import OpticalTissueProperties, SegmentationClasses, StandardProperties, MolecularCompositionGenerator
from simpa.utils import Molecule
from simpa.utils import MOLECULE_LIBRARY
from simpa.utils import Spectrum
from simpa.utils.libraries.molecule_library import MolecularComposition
from simpa.utils.libraries.spectrum_library import AnisotropySpectrumLibrary, ScatteringSpectrumLibrary
from simpa.utils.calculate import randomize_uniform
from simpa.utils.libraries.spectrum_library import AbsorptionSpectrumLibrary


[docs]class TissueLibrary(object): """ A library, returning molecular compositions for various typical tissue segmentations. """
[docs] def get_blood_volume_fractions(self, oxygenation: Union[float, int, np.ndarray] = 1e-10, blood_volume_fraction: Union[float, int, np.ndarray] = 1e-10)\ -> List[Union[int, float, np.ndarray]]: """ A function that returns the volume fraction of the oxygenated and deoxygenated blood. :param oxygenation: The oxygenation level of the blood volume fraction (as a decimal). Default: 1e-10 :param blood_volume_fraction: The total blood volume fraction (including oxygenated and deoxygenated blood). Default: 1e-10 :return: the blood volume fraction of the oxygenated and deoxygenated blood separately. """ return [blood_volume_fraction*oxygenation, blood_volume_fraction*(1-oxygenation)]
[docs] def constant(self, mua: Union[float, int, np.ndarray] = 1e-10, mus: Union[float, int, np.ndarray] = 1e-10, g: Union[float, int, np.ndarray] = 0) -> MolecularComposition: """ A function returning a molecular composition as specified by the user. Typically intended for the use of wanting specific mua, mus and g values. :param mua: optical absorption coefficient Default: 1e-10 cm^⁻1 :param mus: optical scattering coefficient Default: 1e-10 cm^⁻1 :param g: optical scattering anisotropy Default: 0 :return: the molecular composition as specified by the user """ mua_as_spectrum = AbsorptionSpectrumLibrary().CONSTANT_ABSORBER_ARBITRARY(mua) mus_as_spectrum = ScatteringSpectrumLibrary.CONSTANT_SCATTERING_ARBITRARY(mus) g_as_spectrum = AnisotropySpectrumLibrary.CONSTANT_ANISOTROPY_ARBITRARY(g) return self.generic_tissue(mua_as_spectrum, mus_as_spectrum, g_as_spectrum, "constant_mua_mus_g")
[docs] def generic_tissue(self, mua: Spectrum = AbsorptionSpectrumLibrary().CONSTANT_ABSORBER_ARBITRARY(1e-10), mus: Spectrum = AbsorptionSpectrumLibrary().CONSTANT_ABSORBER_ARBITRARY(1e-10), g: Spectrum = AbsorptionSpectrumLibrary().CONSTANT_ABSORBER_ARBITRARY(1e-10), molecule_name: Optional[str] = "generic_tissue") -> MolecularComposition: """ Returns a generic issue defined by the provided optical parameters. :param mua: The absorption coefficient spectrum in cm^{-1}. :param mus: The scattering coefficient spectrum in cm^{-1}. :param g: The anisotropy spectrum. :param molecule_name: The molecule name. :returns: The molecular composition of the tissue. """ assert isinstance(mua, Spectrum), type(mua) assert isinstance(mus, Spectrum), type(mus) assert isinstance(g, Spectrum), type(g) assert isinstance(molecule_name, str) or molecule_name is None, type(molecule_name) return (MolecularCompositionGenerator().append(Molecule(name=molecule_name, absorption_spectrum=mua, volume_fraction=1.0, scattering_spectrum=mus, anisotropy_spectrum=g)) .get_molecular_composition(SegmentationClasses.GENERIC))
[docs] def muscle(self, oxygenation: Union[float, int, np.ndarray] = 0.175, blood_volume_fraction: Union[float, int, np.ndarray] = 0.06) -> MolecularComposition: """ Create a molecular composition mimicking that of muscle :param oxygenation: The oxygenation level of the blood volume fraction (as a decimal). Default: 0.175 :param blood_volume_fraction: The total blood volume fraction (including oxygenated and deoxygenated blood). Default: 0.06 :return: a settings dictionary containing all min and max parameters fitting for muscle tissue. """ [fraction_oxy, fraction_deoxy] = self.get_blood_volume_fractions(oxygenation, blood_volume_fraction) # Get the water volume fraction water_volume_fraction = OpticalTissueProperties.WATER_VOLUME_FRACTION_HUMAN_BODY if isinstance(blood_volume_fraction, np.ndarray): if (blood_volume_fraction + water_volume_fraction - 1 > 1e-5).any(): raise AssertionError(f"Blood volume fraction too large, must be less than {1 - water_volume_fraction}" f" everywhere to leave space for water") else: if blood_volume_fraction + water_volume_fraction - 1 > 1e-5: raise AssertionError(f"Blood volume fraction too large, must be less than {1 - water_volume_fraction}" f"everywhere to leave space for water") custom_water = MOLECULE_LIBRARY.water(water_volume_fraction) custom_water.anisotropy_spectrum = AnisotropySpectrumLibrary.CONSTANT_ANISOTROPY_ARBITRARY( OpticalTissueProperties.STANDARD_ANISOTROPY - 0.005) custom_water.alpha_coefficient = 1.58 custom_water.speed_of_sound = StandardProperties.SPEED_OF_SOUND_MUSCLE + 16 custom_water.density = StandardProperties.DENSITY_MUSCLE + 41 custom_water.mus500 = OpticalTissueProperties.MUS500_MUSCLE_TISSUE custom_water.b_mie = OpticalTissueProperties.BMIE_MUSCLE_TISSUE custom_water.f_ray = OpticalTissueProperties.FRAY_MUSCLE_TISSUE # generate the tissue dictionary return (MolecularCompositionGenerator() .append(MOLECULE_LIBRARY.oxyhemoglobin(fraction_oxy)) .append(MOLECULE_LIBRARY.deoxyhemoglobin(fraction_deoxy)) .append(value=MOLECULE_LIBRARY.muscle_scatterer( volume_fraction=1 - fraction_oxy - fraction_deoxy - water_volume_fraction), key="muscle_scatterers") .append(custom_water) .get_molecular_composition(SegmentationClasses.MUSCLE))
[docs] def soft_tissue(self, oxygenation: Union[float, int, np.ndarray] = OpticalTissueProperties.BACKGROUND_OXYGENATION, blood_volume_fraction: Union[float, int, np.ndarray] = OpticalTissueProperties.BLOOD_VOLUME_FRACTION_MUSCLE_TISSUE) -> MolecularComposition: """ IMPORTANT! This tissue is not tested and it is not based on a specific real tissue type. It is a mixture of muscle (mostly optical properties) and water (mostly acoustic properties). This tissue type roughly resembles the generic background tissue that we see in real PA images. :param oxygenation: The oxygenation level of the blood volume fraction (as a decimal). Default: OpticalTissueProperties.BACKGROUND_OXYGENATION :param blood_volume_fraction: The total blood volume fraction (including oxygenated and deoxygenated blood). Default: OpticalTissueProperties.BLOOD_VOLUME_FRACTION_MUSCLE_TISSUE :return: a settings dictionary containing all min and max parameters fitting for generic soft tissue. """ [fraction_oxy, fraction_deoxy] = self.get_blood_volume_fractions(oxygenation, blood_volume_fraction) # Get the water volume fraction water_volume_fraction = OpticalTissueProperties.WATER_VOLUME_FRACTION_HUMAN_BODY if isinstance(blood_volume_fraction, np.ndarray): if (blood_volume_fraction + water_volume_fraction - 1 > 1e-5).any(): raise AssertionError(f"Blood volume fraction too large, must be less than {1 - water_volume_fraction}" f"everywhere to leave space for water") else: if blood_volume_fraction + water_volume_fraction - 1 > 1e-5: raise AssertionError(f"Blood volume fraction too large, must be less than {1 - water_volume_fraction}" f"everywhere to leave space for water") custom_water = MOLECULE_LIBRARY.water(water_volume_fraction) custom_water.anisotropy_spectrum = AnisotropySpectrumLibrary.CONSTANT_ANISOTROPY_ARBITRARY( OpticalTissueProperties.STANDARD_ANISOTROPY - 0.005) custom_water.alpha_coefficient = 0.08 custom_water.speed_of_sound = StandardProperties.SPEED_OF_SOUND_WATER custom_water.density = StandardProperties.DENSITY_WATER custom_water.mus500 = OpticalTissueProperties.MUS500_MUSCLE_TISSUE custom_water.b_mie = OpticalTissueProperties.BMIE_MUSCLE_TISSUE custom_water.f_ray = OpticalTissueProperties.FRAY_MUSCLE_TISSUE # generate the tissue dictionary return (MolecularCompositionGenerator() .append(MOLECULE_LIBRARY.oxyhemoglobin(fraction_oxy)) .append(MOLECULE_LIBRARY.deoxyhemoglobin(fraction_deoxy)) .append(value=MOLECULE_LIBRARY.muscle_scatterer( volume_fraction=1 - fraction_oxy - fraction_deoxy - water_volume_fraction), key="muscle_scatterers") .append(custom_water) .get_molecular_composition(SegmentationClasses.SOFT_TISSUE))
[docs] def epidermis(self, melanin_volume_fraction: Union[float, int, np.ndarray] = 0.014) -> MolecularComposition: """ Create a molecular composition mimicking that of dermis :param melanin_volume_fraction: the total volume fraction of melanin :return: a settings dictionary containing all min and max parameters fitting for epidermis tissue. """ # generate the tissue dictionary return (MolecularCompositionGenerator() .append(MOLECULE_LIBRARY.melanin(melanin_volume_fraction)) .append(MOLECULE_LIBRARY.epidermal_scatterer(1 - melanin_volume_fraction)) .get_molecular_composition(SegmentationClasses.EPIDERMIS))
[docs] def dermis(self, oxygenation: Union[float, int, np.ndarray] = 0.5, blood_volume_fraction: Union[float, int, np.ndarray] = 0.002) -> MolecularComposition: """ Create a molecular composition mimicking that of dermis :param oxygenation: The oxygenation level of the blood volume fraction (as a decimal). Default: 0.5 :param blood_volume_fraction: The total blood volume fraction (including oxygenated and deoxygenated blood). Default: 0.002 :return: a settings dictionary containing all min and max parameters fitting for dermis tissue. """ # Get the blood volume fractions for oxyhemoglobin and deoxyhemoglobin [fraction_oxy, fraction_deoxy] = self.get_blood_volume_fractions(oxygenation, blood_volume_fraction) # generate the tissue dictionary return (MolecularCompositionGenerator() .append(MOLECULE_LIBRARY.oxyhemoglobin(fraction_oxy)) .append(MOLECULE_LIBRARY.deoxyhemoglobin(fraction_deoxy)) .append(MOLECULE_LIBRARY.dermal_scatterer(1.0 - blood_volume_fraction)) .get_molecular_composition(SegmentationClasses.DERMIS))
[docs] def subcutaneous_fat(self, oxygenation: Union[float, int, np.ndarray] = OpticalTissueProperties.BACKGROUND_OXYGENATION, blood_volume_fraction: Union[float, int, np.ndarray] = OpticalTissueProperties.BLOOD_VOLUME_FRACTION_MUSCLE_TISSUE) -> MolecularComposition: """ Create a molecular composition mimicking that of subcutaneous fat :param oxygenation: The oxygenation level of the blood volume fraction (as a decimal). Default: OpticalTissueProperties.BACKGROUND_OXYGENATION :param blood_volume_fraction: The total blood volume fraction (including oxygenated and deoxygenated blood). Default: OpticalTissueProperties.BLOOD_VOLUME_FRACTION_MUSCLE_TISSUE :return: a settings dictionary containing all min and max parameters fitting for subcutaneous fat tissue. """ # Get water volume fraction water_volume_fraction = OpticalTissueProperties.WATER_VOLUME_FRACTION_HUMAN_BODY # Get the blood volume fractions for oxyhemoglobin and deoxyhemoglobin [fraction_oxy, fraction_deoxy] = self.get_blood_volume_fractions( oxygenation, blood_volume_fraction) # Determine fat volume fraction fat_volume_fraction = randomize_uniform(0.2, 1 - (water_volume_fraction + fraction_oxy + fraction_deoxy)) # generate the tissue dictionary return (MolecularCompositionGenerator() .append(MOLECULE_LIBRARY.oxyhemoglobin(fraction_oxy)) .append(MOLECULE_LIBRARY.deoxyhemoglobin(fraction_deoxy)) .append(MOLECULE_LIBRARY.fat(fat_volume_fraction)) .append(MOLECULE_LIBRARY.soft_tissue_scatterer( 1 - (fat_volume_fraction + water_volume_fraction + fraction_oxy + fraction_deoxy))) .append(MOLECULE_LIBRARY.water(water_volume_fraction)) .get_molecular_composition(SegmentationClasses.FAT))
[docs] def blood(self, oxygenation: Union[float, int, np.ndarray, None] = None) -> MolecularComposition: """ Create a molecular composition mimicking that of blood :param oxygenation: The oxygenation level of the blood(as a decimal). Default: random oxygenation between 0 and 1. :return: a settings dictionary containing all min and max parameters fitting for full blood. """ # Get the blood volume fractions for oxyhemoglobin and deoxyhemoglobin if oxygenation is None: oxygenation = randomize_uniform(0.0, 1.0) # Get the blood volume fractions for oxyhemoglobin and deoxyhemoglobin [fraction_oxy, fraction_deoxy] = self.get_blood_volume_fractions(oxygenation, 1.0) # generate the tissue dictionary return (MolecularCompositionGenerator() .append(MOLECULE_LIBRARY.oxyhemoglobin(fraction_oxy)) .append(MOLECULE_LIBRARY.deoxyhemoglobin(fraction_deoxy)) .get_molecular_composition(SegmentationClasses.BLOOD))
[docs] def bone(self) -> MolecularComposition: """ Create a molecular composition mimicking that of bone :return: a settings dictionary fitting for bone. """ # Get water volume fraction water_volume_fraction = randomize_uniform(OpticalTissueProperties.WATER_VOLUME_FRACTION_BONE_MEAN - OpticalTissueProperties.WATER_VOLUME_FRACTION_BONE_STD, OpticalTissueProperties.WATER_VOLUME_FRACTION_BONE_MEAN + OpticalTissueProperties.WATER_VOLUME_FRACTION_BONE_STD ) # generate the tissue dictionary return (MolecularCompositionGenerator() .append(MOLECULE_LIBRARY.bone(1 - water_volume_fraction)) .append(MOLECULE_LIBRARY.water(water_volume_fraction)) .get_molecular_composition(SegmentationClasses.BONE))
[docs] def mediprene(self) -> MolecularComposition: """ Create a molecular composition mimicking that of mediprene :return: a settings dictionary fitting for mediprene. """ return (MolecularCompositionGenerator() .append(MOLECULE_LIBRARY.mediprene()) .get_molecular_composition(SegmentationClasses.MEDIPRENE))
[docs] def heavy_water(self) -> MolecularComposition: """ Create a molecular composition mimicking that of heavy water :return: a settings dictionary containing all min and max parameters fitting for heavy water. """ return (MolecularCompositionGenerator() .append(MOLECULE_LIBRARY.heavy_water()) .get_molecular_composition(SegmentationClasses.HEAVY_WATER))
[docs] def ultrasound_gel(self) -> MolecularComposition: """ Create a molecular composition mimicking that of ultrasound gel :return: a settings dictionary fitting for generic ultrasound gel. """ return (MolecularCompositionGenerator() .append(MOLECULE_LIBRARY.water()) .get_molecular_composition(SegmentationClasses.ULTRASOUND_GEL))
[docs] def lymph_node(self, oxygenation: Union[float, int, np.ndarray] = OpticalTissueProperties.LYMPH_NODE_OXYGENATION, blood_volume_fraction: Union[float, int, np.ndarray] = OpticalTissueProperties.BLOOD_VOLUME_FRACTION_LYMPH_NODE) -> MolecularComposition: """ IMPORTANT! This tissue is not tested and it is not based on a specific real tissue type. It is a mixture of oxyhemoglobin, deoxyhemoglobin, and lymph node customized water. :param oxygenation: The oxygenation level of the blood volume fraction (as a decimal). Default: 0.175 :param blood_volume_fraction: The total blood volume fraction (including oxygenated and deoxygenated blood). Default: 0.06 :return: a settings dictionary fitting for generic lymph node tissue. """ [fraction_oxy, fraction_deoxy] = self.get_blood_volume_fractions(oxygenation, blood_volume_fraction) # Get the water volume fraction # water_volume_fraction = OpticalTissueProperties.WATER_VOLUME_FRACTION_HUMAN_BODY lymphatic_fluid = MOLECULE_LIBRARY.water(1 - fraction_deoxy - fraction_oxy) lymphatic_fluid.speed_of_sound = StandardProperties.SPEED_OF_SOUND_LYMPH_NODE + 1.22 lymphatic_fluid.density = StandardProperties.DENSITY_LYMPH_NODE - 2.30 lymphatic_fluid.alpha_coefficient = StandardProperties.ALPHA_COEFF_LYMPH_NODE + 0.36 # generate the tissue dictionary return (MolecularCompositionGenerator() .append(MOLECULE_LIBRARY.oxyhemoglobin(fraction_oxy)) .append(MOLECULE_LIBRARY.deoxyhemoglobin(fraction_deoxy)) .append(lymphatic_fluid) .get_molecular_composition(SegmentationClasses.LYMPH_NODE))
TISSUE_LIBRARY = TissueLibrary()