# SPDX-FileCopyrightText: 2021 Division of Intelligent Medical Systems, DKFZ
# SPDX-FileCopyrightText: 2021 Janek Groehl
# SPDX-License-Identifier: MIT
import torch
from simpa.utils import Tags
from simpa.utils.libraries.molecule_library import MolecularComposition
from simpa.utils.libraries.structure_library.StructureBase import GeometricalStructure
from simpa.log import Logger
logger = Logger()
[docs]class HorizontalLayerStructure(GeometricalStructure):
"""
Defines a Layer structure which spans the xy-plane in the SIMPA axis convention. The thickness of the layer is
defined along the z-axis. This layer can be deformed by the simpa.utils.deformation_manager.
Example usage:
# single_structure_settings initialization
structure = Settings()
structure[Tags.PRIORITY] = 10
structure[Tags.STRUCTURE_START_MM] = [0, 0, 0]
structure[Tags.STRUCTURE_END_MM] = [0, 0, 100]
structure[Tags.MOLECULE_COMPOSITION] = TISSUE_LIBRARY.epidermis()
structure[Tags.CONSIDER_PARTIAL_VOLUME] = True
structure[Tags.ADHERE_TO_DEFORMATION] = True
structure[Tags.STRUCTURE_TYPE] = Tags.HORIZONTAL_LAYER_STRUCTURE
"""
[docs] def get_params_from_settings(self, single_structure_settings):
params = (single_structure_settings[Tags.STRUCTURE_START_MM],
single_structure_settings[Tags.STRUCTURE_END_MM],
single_structure_settings[Tags.CONSIDER_PARTIAL_VOLUME])
return params
[docs] def to_settings(self):
settings = super().to_settings()
settings[Tags.STRUCTURE_START_MM] = self.params[0]
settings[Tags.STRUCTURE_END_MM] = self.params[1]
return settings
[docs] def get_enclosed_indices(self):
start_mm = torch.tensor(self.params[0], dtype=torch.float).to(self.torch_device)
end_mm = torch.tensor(self.params[1], dtype=torch.float).to(self.torch_device)
partial_volume = self.params[2]
start_voxels = start_mm / self.voxel_spacing
direction_mm = end_mm - start_mm
depth_voxels = direction_mm[2] / self.voxel_spacing
if direction_mm[0] != 0 or direction_mm[1] != 0 or direction_mm[2] == 0:
raise ValueError("Horizontal Layer structure needs a start and end vector in the form of [0, 0, n].")
target_vector_voxels = torch.stack(torch.meshgrid(torch.arange(self.volume_dimensions_voxels[0], dtype=torch.float, device=self.torch_device),
torch.arange(
self.volume_dimensions_voxels[1], dtype=torch.float, device=self.torch_device),
torch.arange(
self.volume_dimensions_voxels[2], dtype=torch.float, device=self.torch_device),
indexing='ij'), dim=-1)
target_vector_voxels -= start_voxels
target_vector_voxels = target_vector_voxels[:, :, :, 2]
if self.do_deformation:
# the deformation functional needs mm as inputs and returns the result in reverse indexing order...
eval_points = torch.meshgrid(torch.arange(self.volume_dimensions_voxels[0], dtype=torch.float) * self.voxel_spacing,
torch.arange(self.volume_dimensions_voxels[1], dtype=torch.float) * self.voxel_spacing, indexing='ij')
deformation_values_mm = self.deformation_functional_mm(eval_points)
target_vector_voxels = (target_vector_voxels + torch.from_numpy(deformation_values_mm.reshape(
self.volume_dimensions_voxels[0],
self.volume_dimensions_voxels[1], 1)).to(self.torch_device) / self.voxel_spacing).float()
volume_fractions = torch.zeros(tuple(self.volume_dimensions_voxels),
dtype=torch.float, device=self.torch_device)
if partial_volume:
bools_first_layer = ((target_vector_voxels >= -1) & (target_vector_voxels < 0))
volume_fractions[bools_first_layer] = 1 - torch.abs(target_vector_voxels[bools_first_layer])
initial_fractions = torch.max(volume_fractions, dim=2, keepdims=True)[0]
floored_depth_voxels = torch.floor(depth_voxels - initial_fractions)
bools_fully_filled_layers = ((target_vector_voxels >= 0) & (target_vector_voxels < floored_depth_voxels))
bools_last_layer = ((target_vector_voxels >= 0) & (target_vector_voxels >= floored_depth_voxels) &
(target_vector_voxels <= floored_depth_voxels + 1))
volume_fractions[bools_last_layer] = depth_voxels - target_vector_voxels[bools_last_layer]
volume_fractions[volume_fractions > depth_voxels] = depth_voxels
volume_fractions[volume_fractions < 0] = 0
bools_all_layers = bools_first_layer | bools_last_layer | bools_fully_filled_layers
else:
bools_fully_filled_layers = ((target_vector_voxels >= -0.5) & (target_vector_voxels < depth_voxels - 0.5))
bools_all_layers = bools_fully_filled_layers
volume_fractions[bools_fully_filled_layers] = 1
return bools_all_layers.cpu().numpy(), volume_fractions[bools_all_layers].cpu().numpy()
[docs] def update_molecule_volume_fractions(self, single_structure_settings):
for molecule in self.molecule_composition:
old_vol = getattr(molecule, "volume_fraction")
if isinstance(old_vol, torch.Tensor):
structure_start_voxels = (torch.tensor(single_structure_settings[Tags.STRUCTURE_START_MM]) /
self.voxel_spacing)
structure_end_voxels = (torch.tensor(single_structure_settings[Tags.STRUCTURE_END_MM]) /
self.voxel_spacing)
structure_size = structure_end_voxels - structure_start_voxels
if self.volume_dimensions_voxels[2] != old_vol.shape[2]:
if self.volume_dimensions_voxels[2] == structure_size[2]:
pad_start = structure_start_voxels.flip(dims=[0])
pad_end = ((torch.from_numpy(self.volume_dimensions_voxels) - structure_end_voxels)
.flip(dims=[0]))
for count, structure_end in enumerate(structure_end_voxels):
if structure_end == 0:
pad_end[2 - count] = 0
if (pad_start > 1e-6).any() or (pad_end > 1e-6).any():
padding_list = torch.flatten(torch.stack((pad_start, pad_end), 1)).tolist()
padding = tuple(map(int, padding_list))
padded_vol = torch.nn.functional.pad(old_vol, padding, mode='constant', value=0)
setattr(molecule, "volume_fraction", padded_vol)
else:
logger.critical("Tensor does not have the same dimensionality as the area it should fill" +
"{} or the dimensions of the entire ".format(old_vol.shape) +
"simulation volume{}! Please change this.".format(
self.volume_dimensions_voxels.shape))
[docs]def define_horizontal_layer_structure_settings(molecular_composition: MolecularComposition,
z_start_mm: float = 0, thickness_mm: float = 0, priority: int = 10,
consider_partial_volume: bool = False,
adhere_to_deformation: bool = False):
"""
TODO
"""
return {
Tags.STRUCTURE_START_MM: [0, 0, z_start_mm],
Tags.STRUCTURE_END_MM: [0, 0, z_start_mm+thickness_mm],
Tags.PRIORITY: priority,
Tags.MOLECULE_COMPOSITION: molecular_composition,
Tags.CONSIDER_PARTIAL_VOLUME: consider_partial_volume,
Tags.ADHERE_TO_DEFORMATION: adhere_to_deformation,
Tags.STRUCTURE_TYPE: Tags.HORIZONTAL_LAYER_STRUCTURE
}