Skip to content

Commit

Permalink
[geom] Refactor: move Tensor functions to _functions.py, Box.rotation…
Browse files Browse the repository at this point in the history
…_matrix
  • Loading branch information
Philipp Holl authored and holl- committed Dec 14, 2024
1 parent 4f3586f commit 276a0f8
Show file tree
Hide file tree
Showing 9 changed files with 560 additions and 192 deletions.
4 changes: 2 additions & 2 deletions phi/geom/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@

# --- Low-level functions ---
from ._geom import Geometry, GeometryException, Point, assert_same_rank, invert, sample_function
from ._functions import normal_from_slope, clip_length, cross
from ._transform import scale, rotate, rotation_matrix, rotation_angles, rotation_matrix_from_axis_and_angle, rotation_matrix_from_directions
from ._functions import normal_from_slope, clip_length, cross, rotation_matrix, rotation_angles, rotation_matrix_from_axis_and_angle, rotation_matrix_from_directions
from ._transform import scale, rotate

# --- Geometry types ---
from ._box import Box, BaseBox, Cuboid, bounding_box
Expand Down
43 changes: 24 additions & 19 deletions phi/geom/_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ def upper(self) -> Tensor:
raise NotImplementedError(self)

@property
def rotation_matrix(self) -> Optional[Tensor]:
raise NotImplementedError(self)
def rotation_matrix(self) -> Tensor:
return rotation_matrix(self._rot_or_none, self.shape['vector'], none_to_unit=True)

@property
def is_size_variable(self):
Expand Down Expand Up @@ -75,7 +75,7 @@ def global_to_local(self, global_position: Tensor, scale=True, origin='lower') -
assert origin in ['lower', 'center', 'upper']
origin_loc = getattr(self, origin)
pos = global_position if math.always_close(origin_loc, 0) else global_position - origin_loc
pos = rotate(pos, self.rotation_matrix, invert=True)
pos = rotate(pos, self._rot_or_none, invert=True)
if scale:
pos /= (self.half_size if origin == 'center' else self.size)
return pos
Expand All @@ -84,7 +84,11 @@ def local_to_global(self, local_position, scale=True, origin='lower'):
assert origin in ['lower', 'center', 'upper']
origin_loc = getattr(self, origin)
pos = local_position * (self.half_size if origin == 'center' else self.size) if scale else local_position
return rotate(pos, self.rotation_matrix) + origin_loc
return rotate(pos, self._rot_or_none) + origin_loc

@property
def _rot_or_none(self) -> Optional[Tensor]:
raise NotImplementedError

def largest(self, dim: DimFilter) -> 'BaseBox':
dim = self.shape.without('vector').only(dim)
Expand All @@ -99,7 +103,7 @@ def smallest(self, dim: DimFilter) -> 'BaseBox':
return Box(math.max(self.lower, dim), math.min(self.upper, dim))

def lies_inside(self, location: Tensor):
assert self.rotation_matrix is None, f"Please override lies_inside() for rotated boxes"
assert self._rot_or_none is None, f"Please override lies_inside() for rotated boxes"
bool_inside = (location >= self.lower) & (location <= self.upper)
bool_inside = math.all(bool_inside, 'vector')
bool_inside = math.any(bool_inside, self.shape.instance - instance(location)) # union for instance dimensions
Expand Down Expand Up @@ -152,8 +156,7 @@ def approximate_closest_surface(self, location: Tensor) -> Tuple[Tensor, Tensor,
max_sgn_dist = math.max(sgn_surf_delta, 'vector')
normal_axis = max_sgn_dist == sgn_surf_delta # ToDo only one if inside
normal = math.vec_normalize(normal_axis * math.sign(loc_to_center))
if self.rotation_matrix is not None:
normal = rotate(normal, self.rotation_matrix)
normal = rotate(normal, self._rot_or_none)
surf_to_center = math.where(normal_axis, math.sign(loc_to_center) * self.half_size, loc_to_center)
closest_to_center = math.clip(surf_to_center, -self.half_size, self.half_size)
surface_pos = self.local_to_global(closest_to_center, scale=False, origin='center')
Expand Down Expand Up @@ -190,14 +193,16 @@ def sample_uniform_surface(self, *shape: Shape) -> Tensor:
return samples

def corner_representation(self) -> 'Box':
assert self.rotation_matrix is None, f"corner_representation does not support rotations"
assert self._rot_or_none is None, f"corner_representation does not support rotations"
return Box(self.lower, self.upper)

box = corner_representation

def center_representation(self, size_variable=True) -> 'Cuboid':
return Cuboid(self.center, self.half_size, size_variable=size_variable)

cuboid = center_representation

def contains(self, other: 'BaseBox'):
""" Tests if the other box lies fully inside this box. """
return np.all(other.lower >= self.lower) and np.all(other.upper <= self.upper)
Expand All @@ -215,7 +220,7 @@ def boundary_faces(self) -> Dict[Any, Dict[str, slice]]:

@property
def faces(self) -> 'Geometry':
return Cuboid(self.face_centers, self._half_size, self._rotation_matrix, size_variable=False)
return Cuboid(self.face_centers, self.half_size, self._rot_or_none, size_variable=False)

@property
def face_centers(self) -> Tensor:
Expand All @@ -224,7 +229,7 @@ def face_centers(self) -> Tensor:
@property
def face_normals(self) -> Tensor:
unit_vectors = math.to_float(math.range(self.shape['vector']) == math.range(dual(**self.shape['vector'].untyped_dict)))
vectors = rotate(unit_vectors, self.rotation_matrix)
vectors = rotate(unit_vectors, self._rot_or_none)
return vectors * math.vec(dual('side'), lower=-1, upper=1)

@property
Expand All @@ -238,7 +243,7 @@ def face_shape(self) -> Shape:
return self.shape.without('vector') & dual(side='lower,upper') & dual(**self.shape['vector'].untyped_dict)

@property
def corners(self):
def corners(self) -> Tensor:
to_face = self.face_normals[{'~side': 'upper'}] * math.rename_dims(self.half_size, 'vector', dual)
lower_upper = math.meshgrid(math.dual, **{dim: [-1, 1] for dim in self.vector.item_names}, stack_dim=dual('vector')) # (x=2, y=2, ... vector=x,y,...)
to_corner = math.sum(lower_upper * to_face, '~vector')
Expand Down Expand Up @@ -349,6 +354,10 @@ def __variable_attrs__(self):
def __value_attrs__(self):
return '_lower', '_upper'

@property
def _rot_or_none(self):
return None

@property
def shape(self):
if self._lower is None or self._upper is None:
Expand All @@ -375,16 +384,12 @@ def center(self):
def half_size(self):
return self.size * 0.5

@property
def rotation_matrix(self) -> Optional[Tensor]:
return None

@property
def is_size_variable(self):
raise False
return False

def at(self, center: Tensor) -> 'BaseBox':
return Cuboid(center, self.half_size, self.rotation_matrix)
return Cuboid(center, self.half_size, None)

def shifted(self, delta, **delta_by_dim) -> 'Box':
return Box(self.lower + delta, self.upper + delta)
Expand Down Expand Up @@ -505,15 +510,15 @@ def upper(self):
return self._center + self._half_size

@property
def rotation_matrix(self) -> Optional[Tensor]:
def _rot_or_none(self):
return self._rotation_matrix

@property
def is_size_variable(self):
return self._size_variable

def at(self, center: Tensor) -> 'Cuboid':
return Cuboid(center, self.half_size, self.rotation_matrix, size_variable=self._size_variable)
return Cuboid(center, self.half_size, self._rotation_matrix, size_variable=self._size_variable)

def rotated(self, angle) -> 'Cuboid':
if self._rotation_matrix is None:
Expand Down
18 changes: 12 additions & 6 deletions phi/geom/_convert.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import numpy as np

from phiml import math
from phiml.math import wrap, instance, batch, DimFilter, Tensor, spatial, pack_dims, dual, stack
from phiml.math import wrap, instance, batch, DimFilter, Tensor, spatial, pack_dims, dual, stack, to_int32, maximum
from ._box import Cuboid, BaseBox
from ._sphere import Sphere
from ._functions import plane_sgn_dist
from ._geom import Geometry, NoGeometry
from ._mesh import Mesh, mesh_from_numpy, mesh
from ._sdf import SDF, numpy_sdf
from ._sdf_grid import sample_sdf, SDFGrid
from ._spline_solid import SplineSolid


def as_sdf(geo: Geometry, bounds=None, rel_margin=None, abs_margin=0., separate: DimFilter = None, method='auto') -> SDF:
Expand Down Expand Up @@ -100,6 +101,12 @@ def surface_mesh(geo: Geometry,
raise NotImplementedError("Only 3D SDF currently supported")
if isinstance(geo, NoGeometry):
return mesh_from_numpy([], [], element_rank=2)
# --- Determine resolution ---
if rel_dx is None and abs_dx is None:
rel_dx = 0.005
rel_dx = None if rel_dx is None else rel_dx * geo.bounding_box().size.max
dx = math.minimum(rel_dx, abs_dx, allow_none=True)
# --- Check special cases ---
if method == 'auto' and isinstance(geo, BaseBox):
assert rel_dx is None and abs_dx is None, f"When method='auto', boxes will always use their corners as vertices. Leave rel_dx,abs_dx unspecified or pass 'lewiner' or 'lorensen' as method"
vertices = pack_dims(geo.corners, dual, instance('vertices'))
Expand All @@ -114,19 +121,18 @@ def surface_mesh(geo: Geometry,
return mesh(vertices, faces, element_rank=2)
elif method == 'auto' and isinstance(geo, Sphere):
pass # ToDo analytic solution
elif method == 'auto' and isinstance(geo, SplineSolid):
return geo.surface_mesh() # ToDo resolution from dx
# --- Build mesh from SDF ---
if isinstance(geo, SDFGrid):
assert rel_dx is None and abs_dx is None, f"When creating a surface mesh from an SDF grid, rel_dx and abs_dx are determined from the grid and must be specified as None"
sdf_grid = geo
else:
if rel_dx is None and abs_dx is None:
rel_dx = 0.005
rel_dx = None if rel_dx is None else rel_dx * geo.bounding_radius() * 2
dx = math.minimum(rel_dx, abs_dx, allow_none=True)
if isinstance(geo, SDF):
sdf = geo
else:
sdf = as_sdf(geo, rel_margin=0, abs_margin=dx)
resolution = math.to_int32(math.round(sdf.bounds.size / dx))
resolution = maximum(1, to_int32(math.round(sdf.bounds.size / dx)))
resolution = spatial(**resolution.vector)
sdf_grid = sample_sdf(sdf, sdf.bounds, resolution)
from skimage.measure import marching_cubes
Expand Down
2 changes: 1 addition & 1 deletion phi/geom/_cylinder.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from phiml.math._magic_ops import all_attributes
from phiml.dataclasses import replace, sliceable
from ._geom import Geometry
from ._transform import rotate, rotation_matrix, rotation_matrix_from_directions
from ._functions import rotate_vector as rotate, rotation_matrix, rotation_matrix_from_directions
from ._sphere import Sphere


Expand Down
Loading

0 comments on commit 276a0f8

Please sign in to comment.