""" Module that contains functions to get required in the format """
__all__ = ['get_static_2d_centered_coordinates', "compute_orientation_matrix"]
from typing import Tuple, Optional
import numpy as np
from pint import Quantity
import tensorflow as tf
[docs]
def get_static_2d_centered_coordinates(
map_size: Tuple[int, int],
object_dimensions: Optional[Tuple[float, float]] = None,
return_2d: bool = True
)-> tf.Tensor:
""" Calculates relative coordinates for a dense map with coordinate origin in the center of the
map. If the dimension in map_size is even, the origin is shifted 1/2 with respect to grid
indices, if they are uneven, the origin coincides with a grid point.
Reshapes the output to a shape, used in cmrsim simulations. The singleton-axis is reserved
for repetitions contrasts.
:param map_size: Tuple[int, int], 2D grid dimensions used to calculate coordinates relative
to mid-point
:param object_dimensions: Optional Tuple[float, float] used to scale relative coordinates.
Defaults to (1., 1.)
:return: (X, Y, 1, 1, 3)
"""
if object_dimensions is None:
object_dimensions = (1., 1.)
map_size = tf.constant(map_size, dtype=tf.float32)
x, y = tf.meshgrid(tf.range(0., map_size[0]), tf.range(0., map_size[1]), indexing='xy')
grid_coordinates = tf.cast(tf.stack([x, y], 2), tf.float32)
offsets = (1 - tf.math.mod(map_size, 2)) / 2
cell_mid_shift_coords = grid_coordinates - (map_size - 1) / 2 - offsets
object_scaling_dr = tf.constant(object_dimensions) / map_size
voxel_center_coords = cell_mid_shift_coords * object_scaling_dr
# Append zero z coordinate
voxel_center_coords = tf.concat((voxel_center_coords, tf.zeros_like(x)[..., tf.newaxis]), -1)
# Reshape to desired shape
if return_2d:
voxel_center_coords = tf.reshape(voxel_center_coords, [map_size[0], map_size[1], 1, 1, 3])
else:
voxel_center_coords = tf.reshape(voxel_center_coords, [-1, 1, 1, 3])
return voxel_center_coords
[docs]
def compute_orientation_matrix(slice_normal: np.array, slice_position: Quantity,
readout_direction: np.array = None):
"""Computes the 4x4 transformation matrix :math:`A` that transform a positional vector
:math:`r_{s}` defined in slice-coordinates into the gobal coordinate system :math:`r_{g}`.
To apply the transformation, compute the matrix-vector product:
.. math::
(x_{g}, y_{g}, x_{g}, 1) = A \cdot (x_{s}, y_{s}, x_{s}, 1)
To apply the inverse transformation, transpose the rotation-part of the matrix:
.. code::
A_inv = A.copy()
A_inv[:3, :3] = A[:3, :3].T
:param slice_normal:
:param slice_position:
:param readout_direction:
:return: transformation matrix - A (4, 4)
"""
from scipy.spatial.transform import Rotation
slice_normal /= np.linalg.norm(slice_normal)
if readout_direction is None:
if np.allclose(slice_normal, np.array([1., 0., 0.]), atol=1e-3):
readout_direction = np.array([0., 1., 0.])
else:
readout_direction = np.array([1., 0., 0.])
else:
if np.isclose(
np.dot(slice_normal, readout_direction / np.linalg.norm(readout_direction)),
1., atol=1e-3):
raise ValueError("Slice normal and readout-direction are parallel")
readout_direction = readout_direction - np.dot(slice_normal,
readout_direction) * slice_normal
readout_direction /= np.linalg.norm(readout_direction)
pe_direction = np.cross(readout_direction, slice_normal)
pe_direction /= np.linalg.norm(pe_direction)
original_basis = np.eye(3, 3)
new_basis = np.stack([readout_direction, pe_direction, slice_normal])
rot, _ = Rotation.align_vectors(original_basis, new_basis)
total_transform = np.eye(4, 4)
total_transform[:3, :3] = rot.as_matrix()
total_transform[:3, 3] = slice_position.m_as("m")
return total_transform