def test_meshio_interface(triangle_mesh_2d): meshio_mesh = triangle_mesh_2d.to_meshio() new_mesh = TriangleMesh.from_meshio(meshio_mesh) np.testing.assert_allclose(new_mesh.points, triangle_mesh_2d.points) np.testing.assert_allclose(new_mesh.cells, triangle_mesh_2d.cells) np.testing.assert_allclose(new_mesh.labels, triangle_mesh_2d.labels)
def generate_contour( self, level: float = None, ): """Generate contours using marching cubes algorithm. Also generates an envelope around the entire data volume corresponding to the bounding box. The bounding box equals the dimensions of the data volume. Parameters ---------- level : float, optional Contour value to search for isosurfaces (i.e. the threshold value). By default takes the average of the min and max value. Can be ignored if a binary image is passed to `Mesher3D`. """ from nanomesh.mesh import TriangleMesh points, cells, *_ = measure.marching_cubes( self.image, level=level, allow_degenerate=False, ) mesh = TriangleMesh(points=points, cells=cells) bbox = BoundingBox.from_shape(self.image.shape) mesh = generate_envelope(mesh, bbox=bbox) logger.info(f'Generated contour with {len(mesh.cells)} cells') self.contour = mesh
def test_compare_with_mesh(): image = np.zeros([5, 5]) points = np.array([ [0, 0], [0, 2], [2, 2], [2, 0], [3, 3], [3, 4], [4, 4], [4, 3], ]) cells = np.array([ [0, 1, 2], [0, 3, 2], [4, 5, 6], [4, 7, 6], ]) # 1: small square, 0: big square labels = np.array([0, 0, 1, 1]) mesh = TriangleMesh(points=points, cells=cells, labels=labels) compare_mesh_with_image(image, mesh)
def simple_mesh(): return TriangleMesh(points=np.array([ [0., 0.], [0., 1.], [1., 1.], [1., 0.], ]), cells=np.array([[1, 0, 3], [3, 2, 1]]))
def mesh(): """Box triangle mesh.""" points = np.array([[0., 0., 0.], [10., 0., 0.], [0., 20., 0.], [10., 20., 0.], [0., 0., 30.], [10., 0., 30.], [0., 20., 30.], [10., 20., 30.]]) cells = np.array([[0, 4, 6], [0, 6, 2], [5, 1, 3], [5, 3, 7], [0, 1, 5], [0, 5, 4], [6, 7, 3], [6, 3, 2], [1, 0, 2], [1, 2, 3], [4, 5, 7], [4, 7, 6]]) return TriangleMesh(points=points, cells=cells)
def add_corner_points(mesh: TriangleMesh, bbox: BoundingBox) -> None: """Add corner points from bounding box to mesh points. Parameters ---------- mesh : TriangleMesh Mesh to add corner points to. bbox : BoundingBox Container for the bounding box coordinates. """ corners = bbox.to_points() mesh.points = np.vstack([mesh.points, corners])
def compare_mesh_with_image(image: np.ndarray, mesh: TriangleMesh): """Compare mesh with image. Parameters ---------- image : 2D array Image to compare mesh with mesh : TriangleMesh Triangle mesh to plot on image Returns ------- ax : matplotlib.Axes """ fig, ax = plt.subplots() mesh.plot_mpl(ax=ax) ax.imshow(image) ax.axis('image') ax.set_xticks([]) ax.set_yticks([]) return ax
def close_side(mesh: TriangleMesh, *, side: str, bbox: BoundingBox, ax: plt.Axes = None): """Fill a side of the bounding box with triangles. Parameters ---------- mesh : TriangleMesh Input contour mesh. side : str Side of the volume to close. Must be one of `left`, `right`, `top`, `bottom`, `front`, `back`. bbox : BoundingBox Coordinates of the bounding box. ax : plt.Axes, optional Plot the generated side on a matplotlib axis. Returns ------- mesh : TriangleMesh Triangle mesh with the given side closed. Raises ------ ValueError When the value of `side` is invalid. """ from nanomesh.mesh import TriangleMesh all_points = mesh.points if side == 'top': edge_col = 2 edge_value = bbox.zmin elif side == 'bottom': edge_col = 2 edge_value = bbox.zmax elif side == 'left': edge_col = 1 edge_value = bbox.ymin elif side == 'right': edge_col = 1 edge_value = bbox.ymax elif side == 'front': edge_col = 0 edge_value = bbox.xmin elif side == 'back': edge_col = 0 edge_value = bbox.xmax else: raise ValueError('Side must be one of `right`, `left`, `bottom`' f'`top`, `front`, `back`. Got {side=}') keep_cols = [col for col in (0, 1, 2) if col != edge_col] is_edge = all_points[:, edge_col] == edge_value coords = all_points[is_edge][:, keep_cols] edge_mesh = simple_triangulate(points=coords, opts='') cells = edge_mesh.cells_dict['triangle'].copy() shape = cells.shape new_cells = cells.ravel() mesh_edge_index = np.argwhere(is_edge).flatten() new_edge_index = np.arange(len(mesh_edge_index)) mapping = np.vstack([new_edge_index, mesh_edge_index]) mask = np.in1d(new_cells, mapping[0, :]) new_cells[mask] = mapping[1, np.searchsorted(mapping[0, :], new_cells[mask])] new_cells = new_cells.reshape(shape) new_labels = np.ones(len(new_cells)) points = all_points cells = np.vstack([mesh.cells, new_cells]) labels = np.hstack([mesh.labels, new_labels]) mesh = TriangleMesh(points=points, cells=cells, labels=labels) if ax: edge_mesh.plot(ax=ax) ax.set_title(side) return mesh
def pad(mesh: TriangleMesh, *, side: str, width: int, label: int = None) -> TriangleMesh: """Pad a triangle mesh (3D). Parameters ---------- mesh : TriangleMesh The mesh to pad. side : str Side to pad, must be one of `left`, `right`, `top`, `bottom`, `back`, `front`. width : int Width of the padded area. label : int, optional The label to assign to the padded area. If not defined, generates the next unique label based on the existing ones. Returns ------- new_mesh : TriangleMesh Padded tetrahedral mesh. Raises ------ ValueError When the value of `side` is invalid. """ if label is None: label = mesh.labels.max() + 1 if width == 0: return mesh bbox = BoundingBox.from_points(mesh.points) if side == 'top': edge_col = 2 edge_value = bbox.zmax extra_coords = np.array([ [bbox.xmin, bbox.ymin, bbox.zmax + width], [bbox.xmin, bbox.ymax, bbox.zmax + width], [bbox.xmax, bbox.ymin, bbox.zmax + width], [bbox.xmax, bbox.ymax, bbox.zmax + width], ]) column_order = (0, 1, 1, 0) elif side == 'bottom': edge_col = 2 edge_value = bbox.zmin extra_coords = np.array([ [bbox.xmin, bbox.ymin, bbox.zmin - width], [bbox.xmin, bbox.ymax, bbox.zmin - width], [bbox.xmax, bbox.ymin, bbox.zmin - width], [bbox.xmax, bbox.ymax, bbox.zmin - width], ]) column_order = (0, 1, 1, 0) elif side == 'left': edge_col = 1 edge_value = bbox.ymin extra_coords = np.array([ [bbox.xmin, bbox.ymin - width, bbox.zmin], [bbox.xmin, bbox.ymin - width, bbox.zmax], [bbox.xmax, bbox.ymin - width, bbox.zmin], [bbox.xmax, bbox.ymin - width, bbox.zmax], ]) column_order = (0, 2, 2, 0) elif side == 'right': edge_col = 1 edge_value = bbox.ymax extra_coords = np.array([ [bbox.xmin, bbox.ymax + width, bbox.zmin], [bbox.xmin, bbox.ymax + width, bbox.zmax], [bbox.xmax, bbox.ymax + width, bbox.zmin], [bbox.xmax, bbox.ymax + width, bbox.zmax], ]) column_order = (0, 2, 2, 0) elif side == 'front': edge_col = 0 edge_value = bbox.xmin extra_coords = np.array([ [bbox.xmin - width, bbox.ymin, bbox.zmin], [bbox.xmin - width, bbox.ymin, bbox.zmax], [bbox.xmin - width, bbox.ymax, bbox.zmin], [bbox.xmin - width, bbox.ymax, bbox.zmax], ]) column_order = (1, 2, 2, 1) elif side == 'back': edge_col = 0 edge_value = bbox.xmax extra_coords = np.array([ [bbox.xmax + width, bbox.ymin, bbox.zmin], [bbox.xmax + width, bbox.ymin, bbox.zmax], [bbox.xmax + width, bbox.ymax, bbox.zmin], [bbox.xmax + width, bbox.ymax, bbox.zmax], ]) column_order = (1, 2, 2, 1) else: raise ValueError('Side must be one of `right`, `left`, `bottom`' f'`top`, `front`, `back`. Got {side=}') n_points = len(mesh.points) points = np.vstack([mesh.points, extra_coords]) new_triangles = [ np.array((0, 1, 2)) + n_points, np.array((3, 1, 2)) + n_points, ] for corner, col in zip(extra_coords, column_order): connect_to = np.argwhere((points[:, edge_col] == edge_value) & (points[:, col] == corner[col])) additional_points = np.argwhere( extra_coords[:, col] == corner[col]) + n_points first, last = additional_points first_point = points[first] sorted_by_distance = np.argsort( np.linalg.norm(first_point - points[connect_to].squeeze(), axis=1)) connect_to = connect_to[sorted_by_distance] connect_to = np.vstack([connect_to, last]).squeeze() for pair in pairwise(connect_to): tri = np.hstack((first, pair)) new_triangles.append(tri) new_triangles = np.array(new_triangles).squeeze() cells = np.vstack([mesh.cells, new_triangles]) new_mesh = mesh.__class__( points=points, cells=cells, region_markers=mesh.region_markers, ) # add marker for new region center = extra_coords.mean(axis=0) center[col] = (center[col] + edge_value) / 2 new_mesh.add_region_marker(RegionMarker(label, center)) return new_mesh
def triangle_mesh(): """Generate mesh. This mesh is a cube with a rectangular 'pore' through the middle. """ points = np.array([ # cube [0.0, 0.0, 0.0], # A [4.0, 0.0, 0.0], # B [4.0, 4.0, 0.0], # C [0.0, 4.0, 0.0], # D [0.0, 0.0, 4.0], # E [4.0, 0.0, 4.0], # F [4.0, 4.0, 4.0], # G [0.0, 4.0, 4.0], # H # inside rectangle ('pore') [1.0, 1.0, 0.0], # a [3.0, 1.0, 0.0], # b [3.0, 3.0, 0.0], # c [1.0, 3.0, 0.0], # d [1.0, 1.0, 4.0], # e [3.0, 1.0, 4.0], # f [3.0, 3.0, 4.0], # g [1.0, 3.0, 4.0], # h ]) cells = np.array([ # top face [0, 11, 8], [1, 8, 9], [2, 9, 10], [3, 10, 11], [0, 8, 1], [1, 9, 2], [2, 10, 3], [3, 11, 0], # side faces [0, 1, 5], [5, 4, 0], [1, 2, 6], [6, 5, 1], [3, 2, 6], [6, 7, 3], [0, 3, 7], [7, 4, 0], # bottom face [4, 15, 12], [5, 12, 13], [6, 13, 14], [7, 14, 15], [4, 12, 5], [5, 13, 6], [6, 14, 7], [7, 15, 4], # inside rectangle ('pore') [8, 9, 10], [10, 11, 8], [8, 9, 13], [13, 12, 8], [9, 10, 14], [14, 13, 9], [11, 10, 14], [14, 15, 11], [8, 11, 15], [15, 12, 8], [13, 14, 15], [15, 12, 13], ]) mesh = TriangleMesh(points=points, cells=cells) return mesh