Source code for dtcc_model.geometry.transform
# Copyright(C) 2023 Anders Logg
# Licensed under the MIT License
from dataclasses import dataclass, field
from typing import Union
import numpy as np
from dtcc_model.model import Model
from dtcc_model import proto
default_affine = np.eye(4)
[docs]
@dataclass
class Transform(Model):
"""Represents an affine transformation to a global coordinate system.
The affine transformation is represented by a 4x4 matrix, where the
upper-left 3x3 submatrix represents the rotation and scaling, and the last
column represents the translation. By applying the affine transformation to
the coordinates of a point in the local coordinate system, one obtains the
coordinates of the point in the global coordinate system specified by the
spatial reference system (SRS).
Attributes
----------
srs : str
Name of global coordinate system (Spatial Reference System).
affine: np.ndarray
4x4 affine transformation matrix.
"""
srs: str = ""
affine: np.ndarray = field(default_factory=lambda: default_affine)
[docs]
def __call__(self, points):
"""
Apply the affine transformation to a 3D point or an N x 3 array of points.
Parameters
----------
points: tuple/list or np.ndarray of shape (N, 3)
Points to be transformed.
Returns
-------
tuple/list or np.ndarray of shape (N, 3)
Transformed points.
"""
if isinstance(points, (list, tuple)):
augmented_point = np.array([points[0], points[1], points[2], 1])
transformed = np.dot(self.affine, augmented_point)
return transformed[0], transformed[1], transformed[2]
elif isinstance(points, np.ndarray):
assert points.shape[1] == 3, "Input array should be of shape N x 3"
augmented_points = np.hstack([points, np.ones((points.shape[0], 1))])
transformed = np.dot(augmented_points, self.affine.T)
return transformed[:, :3]
else:
raise ValueError(
"Input should be a tuple/list or a numpy array of shape (N, 3)."
)
[docs]
def set_translation(self, dx, dy, dz):
"""Set the translation part of the affine transform."""
self.affine[0, 3] = dx
self.affine[1, 3] = dy
self.affine[2, 3] = dz
[docs]
def set_rotation(self, rotation_matrix):
"""
Sets the rotation part of the affine transform.
:param rotation_matrix: a 3x3 numpy array representing the rotation.
"""
assert rotation_matrix.shape == (
3,
3,
), "Rotation matrix should be of shape (3, 3)"
self.affine[:3, :3] = rotation_matrix
[docs]
def to_proto(self) -> proto.City:
"""Return a protobuf representation of the Transform.
Returns
-------
proto.Transform
A protobuf representation of the Transform.
"""
pb = proto.Transform()
pb.srs = self.srs
pb.affine.extend(self.affine.flatten())
return pb
[docs]
def from_proto(self, pb: Union[proto.Transform, bytes]):
"""Initialize Transform from a protobuf representation.
Parameters
----------
pb: Union[proto.Transform, bytes]
The protobuf message or its serialized bytes representation.
"""
if isinstance(pb, bytes):
pb = proto.Transform.FromString(pb)
self.srs = pb.srs
self.affine = np.array(pb.affine).reshape((4, 4))