From aec269f6b6caff6278b93b9fe4c3cb242950897a Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 17 May 2024 11:41:00 +0200 Subject: [PATCH] [geom,vis] Add Geometry.corners property Now uses dual dimensions to list corners Add unit test for Sphere --- phi/geom/_box.py | 3 ++- phi/geom/_geom.py | 14 ++++++++++++++ phi/geom/_sphere.py | 4 ++++ phi/vis/_dash/_plotly_plots.py | 4 ++-- phi/vis/_matplotlib/_matplotlib_plots.py | 2 +- tests/commit/geom/test__sphere.py | 6 ++++++ 6 files changed, 29 insertions(+), 4 deletions(-) diff --git a/phi/geom/_box.py b/phi/geom/_box.py index 36c43d172..3d4c0ce43 100644 --- a/phi/geom/_box.py +++ b/phi/geom/_box.py @@ -188,9 +188,10 @@ def face_areas(self) -> Tensor: def face_shape(self) -> Shape: return self.shape.without('vector') & dual(side='lower,upper') & dual(**self.shape['vector'].untyped_dict) + @property def corners(self): to_face = self.face_normals[{'~side': 'upper'}] * math.rename_dims(self.half_size, 'vector', dual) - lower_upper = math.meshgrid(math.instance, **{dim: [-1, 1] for dim in self.vector.item_names}, stack_dim=dual('vector')) # (x=2, y=2, ... vector=x,y,...) + 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') return self.center + to_corner diff --git a/phi/geom/_geom.py b/phi/geom/_geom.py index 7d5e3af54..3ae48c8ce 100644 --- a/phi/geom/_geom.py +++ b/phi/geom/_geom.py @@ -119,6 +119,16 @@ def face_shape(self) -> Shape: """ raise NotImplementedError(self.__class__) + @property + def corners(self) -> Tensor: + """ + Returns: + Corner locations as `phiml.math.Tensor`. + Corners belonging to one object or cell are listed along dual dimensions. + If the object has no corners, a size-0 tensor with the correct vector and instance dims is returned. + """ + raise NotImplementedError(self.__class__) + def integrate_surface(self, face_values: Tensor, divide_volume=False) -> Tensor: """ Multiplies `values´ by the corresponding face area, computes the sum over all faces and divides by the cell volume. @@ -716,6 +726,10 @@ def boundary_faces(self) -> Dict[str, Tuple[Dict[str, slice], Dict[str, slice]]] def face_shape(self) -> Shape: return self.shape + @property + def corners(self): + return self._location + def __getitem__(self, item): return Point(self._location[_keep_vector(slicing_dict(self, item))]) diff --git a/phi/geom/_sphere.py b/phi/geom/_sphere.py index 83e05f768..94ec9b1d1 100644 --- a/phi/geom/_sphere.py +++ b/phi/geom/_sphere.py @@ -162,3 +162,7 @@ def boundary_faces(self) -> Dict[str, Tuple[Dict[str, slice], Dict[str, slice]]] @property def face_shape(self) -> Shape: return self.shape.without('vector') & dual(shell=0) + + @property + def corners(self) -> Tensor: + return math.zeros(self.shape & dual(corners=0)) diff --git a/phi/vis/_dash/_plotly_plots.py b/phi/vis/_dash/_plotly_plots.py index 7e256756a..41c962240 100644 --- a/phi/vis/_dash/_plotly_plots.py +++ b/phi/vis/_dash/_plotly_plots.py @@ -240,8 +240,8 @@ def plot(self, data: Field, figure, subplot, space: Box, min_val: float, max_val for lxi, lyi, uxi, uyi, ci, a in zip(lower_x, lower_y, upper_x, upper_y, hex_color, alphas): figure.add_shape(type="rect", xref="x", yref="y", x0=lxi, y0=lyi, x1=uxi, y1=uyi, fillcolor=ci, line_width=.5, line_color='#FFFFFF') else: - corners = data.geometry.corners() - c4, c1, c3, c2 = reshaped_numpy(corners, [corners.shape.only(dims, reorder=True), non_channel(data), 'vector']) + corners = data.geometry.corners + c4, c1, c3, c2 = reshaped_numpy(corners, [corners.shape.only(['~'+d for d in dims], reorder=True), non_channel(data), 'vector']) for c1i, c2i, c3i, c4i, ci, a in zip(c1, c2, c3, c4, hex_color, alphas): path = f"M{c1i[0]},{c1i[1]} L{c2i[0]},{c2i[1]} L{c3i[0]},{c3i[1]} L{c4i[0]},{c4i[1]} Z" figure.add_shape(type="path", xref="x", yref="y", path=path, fillcolor=ci, line_width=.5, line_color='#FFFFFF') diff --git a/phi/vis/_matplotlib/_matplotlib_plots.py b/phi/vis/_matplotlib/_matplotlib_plots.py index a04ea143b..4313e3313 100644 --- a/phi/vis/_matplotlib/_matplotlib_plots.py +++ b/phi/vis/_matplotlib/_matplotlib_plots.py @@ -784,7 +784,7 @@ def plot(self, data: Field, figure, subplot, space: Box, min_val: float, max_val elif isinstance(data.geometry, BaseBox): a = alphas[0] c = mpl_colors[0] - cx, cy, cz = math.reshaped_numpy(data.geometry.corners(), ['vector', *dims]) + cx, cy, cz = math.reshaped_numpy(data.geometry.corners, ['vector', *['~'+d for d in dims]]) plot_surface(subplot, cx[:, :, 1], cy[:, :, 1], cz[:, :, 1], alpha=a, color=c) plot_surface(subplot, cx[:, :, 0], cy[:, :, 0], cz[:, :, 0], alpha=a, color=c) plot_surface(subplot, cx[:, 1, :], cy[:, 1, :], cz[:, 1, :], alpha=a, color=c) diff --git a/tests/commit/geom/test__sphere.py b/tests/commit/geom/test__sphere.py index f7ce75d6f..5dfda0cb7 100644 --- a/tests/commit/geom/test__sphere.py +++ b/tests/commit/geom/test__sphere.py @@ -63,3 +63,9 @@ def test_reshaping_const_radius(self): assert batch(bat=100) & instance(particles=50) & channel(vector='x,y') == s.shape s = flatten(s) assert batch(bat=100) & instance(flat=50) & channel(vector='x,y') == s.shape + + def test_sphere_no_corners(self): + s = Sphere(x=0, y=0, radius=1) + corners = s.corners + self.assertIn('vector', corners.shape) + self.assertEqual(0, corners.shape.volume)