Source code for simpa.utils.libraries.structure_library.StructureBase

# SPDX-FileCopyrightText: 2021 Division of Intelligent Medical Systems, DKFZ
# SPDX-FileCopyrightText: 2021 Janek Groehl
# SPDX-License-Identifier: MIT

from abc import abstractmethod

import numpy as np

from simpa.log import Logger
from simpa.utils import Settings, Tags, get_functional_from_deformation_settings, round_x5_away_from_zero
from simpa.utils.libraries.molecule_library import MolecularComposition
from simpa.utils.tissue_properties import TissueProperties
from simpa.utils.processing_device import get_processing_device


[docs]class GeometricalStructure: """ Base class for all model-based structures for ModelBasedVolumeCreator. A GeometricalStructure has an internal representation of its own geometry. This is represented by self.geometrical_volume which is a 3D array that defines for every voxel within the simulation volume if it is enclosed in the GeometricalStructure or if it is outside. Most of the GeometricalStructures implement a partial volume effect. So if a voxel has the value 1, it is completely enclosed by the GeometricalStructure. If a voxel has a value between 0 and 1, that fraction of the volume is occupied by the GeometricalStructure. If a voxel has the value 0, it is outside of the GeometricalStructure. """ def __init__(self, global_settings: Settings, single_structure_settings: Settings = None): self.torch_device = get_processing_device(global_settings) self.logger = Logger() self.voxel_spacing = global_settings[Tags.SPACING_MM] volume_x_dim = round_x5_away_from_zero(global_settings[Tags.DIM_VOLUME_X_MM] / self.voxel_spacing) volume_y_dim = round_x5_away_from_zero(global_settings[Tags.DIM_VOLUME_Y_MM] / self.voxel_spacing) volume_z_dim = round_x5_away_from_zero(global_settings[Tags.DIM_VOLUME_Z_MM] / self.voxel_spacing) self.volume_dimensions_voxels = np.asarray([volume_x_dim, volume_y_dim, volume_z_dim]) self.volume_dimensions_mm = self.volume_dimensions_voxels * self.voxel_spacing self.do_deformation = (Tags.SIMULATE_DEFORMED_LAYERS in global_settings.get_volume_creation_settings() and global_settings.get_volume_creation_settings()[Tags.SIMULATE_DEFORMED_LAYERS]) if (Tags.ADHERE_TO_DEFORMATION in single_structure_settings and not single_structure_settings[Tags.ADHERE_TO_DEFORMATION]): self.do_deformation = False self.logger.debug(f"This structure will simulate deformations: {self.do_deformation}") if self.do_deformation and Tags.DEFORMED_LAYERS_SETTINGS in global_settings.get_volume_creation_settings(): self.deformation_functional_mm = get_functional_from_deformation_settings( global_settings.get_volume_creation_settings()[Tags.DEFORMED_LAYERS_SETTINGS]) else: self.deformation_functional_mm = None self.logger.debug(f"This structure's deformation functional: {self.deformation_functional_mm}") if single_structure_settings is None: self.molecule_composition = MolecularComposition() self.priority = 0 return if Tags.PRIORITY in single_structure_settings: self.priority = single_structure_settings[Tags.PRIORITY] if Tags.CONSIDER_PARTIAL_VOLUME in single_structure_settings: self.partial_volume = single_structure_settings[Tags.CONSIDER_PARTIAL_VOLUME] else: self.partial_volume = False self.molecule_composition: MolecularComposition = single_structure_settings[Tags.MOLECULE_COMPOSITION] self.update_molecule_volume_fractions(single_structure_settings) self.molecule_composition.update_internal_properties(global_settings) self.geometrical_volume = np.zeros(self.volume_dimensions_voxels, dtype=np.float32) self.params = self.get_params_from_settings(single_structure_settings) self.fill_internal_volume() assert ((self.molecule_composition.internal_properties.volume_fraction[self.geometrical_volume != 0] - 1 < 1e-5) .all()), ("Invalid Molecular composition! The volume fractions of all molecules in the structure must" "be exactly 100%!")
[docs] def fill_internal_volume(self): """ Fills self.geometrical_volume of the GeometricalStructure. """ indices, values = self.get_enclosed_indices() self.geometrical_volume[indices] = values
[docs] def get_volume_fractions(self): """ Get the volume fraction this structure takes per voxel. """ return self.geometrical_volume
[docs] @abstractmethod def get_enclosed_indices(self): """ Gets indices of the voxels that are either entirely or partially occupied by the GeometricalStructure. :return: mask for a numpy array """ pass
[docs] @abstractmethod def get_params_from_settings(self, single_structure_settings: Settings): """ Gets all the parameters required for the specific GeometricalStructure. :param single_structure_settings: Settings which describe the specific GeometricalStructure. :return: Tuple of parameters """ pass
[docs] def properties_for_wavelength(self, settings, wavelength) -> TissueProperties: """ Returns the values corresponding to each optical/acoustic property used in SIMPA. :param settings: The global settings that contains the info on the volume dimensions. :param wavelength: Wavelength of the queried properties :return: optical/acoustic properties """ return self.molecule_composition.get_properties_for_wavelength(settings, wavelength)
[docs] def update_molecule_volume_fractions(self, single_structure_settings: Settings): """ In particular cases, only molecule volume fractions are determined by tensors that expand the structure. This method allows the tensor to only have the size of the structure, with the rest of the volume filled with volume fractions of 0. In the case where the tensors defined are such that they fill the volume, they will be left. Later, when using priority_sorted_structures the volume used will be that within the boundaries in the shape. :param single_structure_settings: Settings which describe the specific GeometricalStructure. """ pass
[docs] @abstractmethod def to_settings(self) -> Settings: """ Creates a Settings dictionary which contains all the parameters needed to create the same GeometricalStructure again. :return : A tuple containing the settings key and the needed entries """ settings_dict = Settings() settings_dict[Tags.PRIORITY] = self.priority settings_dict[Tags.STRUCTURE_TYPE] = self.__class__.__name__ settings_dict[Tags.CONSIDER_PARTIAL_VOLUME] = self.partial_volume settings_dict[Tags.MOLECULE_COMPOSITION] = self.molecule_composition return settings_dict