Source code for homcloud.plotly_3d

"""
This module provides helper functions for plotly (https://plotly.com/python/) for HomCloud.
"""

import plotly.graph_objects as go
import numpy as np


[docs] def PointCloud(array, color=None, size=1, name=""): """ Returns a plotly's trace object for a pointcloud. Args: array (np.array): 3D pointcloud color (string or None): The name of color size (int): The size of each point name (string): The name of pointcloud Returns: plotly.graph_objects.Scatter3d: Plotly's trace object for the visualization of the pointcloud """ assert array.ndim == 2 and array.shape[1] >= 3 if array.shape[1] == 4 and color is True: color = array[:, 3] return go.Scatter3d( x=array[:, 0], y=array[:, 1], z=array[:, 2], name=name, mode="markers", marker=dict(size=size, color=color) )
def SimplicesMesh(simplices, color=None, name=""): vertices = dict() coordinates = [] n = 0 for simplex in simplices: for vertex in simplex: v = tuple(vertex) if v not in vertices: vertices[v] = n coordinates.append(vertex) n += 1 simplices_by_vertex_index = np.array([[vertices[tuple(v)] for v in simplex] for simplex in simplices]) coordinates = np.array(coordinates) return go.Mesh3d( x=coordinates[:, 0], y=coordinates[:, 1], z=coordinates[:, 2], i=simplices_by_vertex_index[:, 0], j=simplices_by_vertex_index[:, 1], k=simplices_by_vertex_index[:, 2], name=name, color=color, ) def Simplices(simplices, color=None, width=1, name=""): d = len(simplices[0]) ij_pairs = [(i, j) for j in range(d) for i in range(j + 1, d)] xs = [] ys = [] zs = [] for simplex in simplices: for i, j in ij_pairs: xs.extend([simplex[i][0], simplex[j][0], None]) ys.extend([simplex[i][1], simplex[j][1], None]) zs.extend([simplex[i][2], simplex[j][2], None]) return go.Scatter3d( x=xs, y=ys, z=zs, mode="lines", name=name, showlegend=False, line=dict(color=color, width=width), ) def Loop(vertices, color=None, width=1, name=""): vs = np.concatenate([vertices, [vertices[0]]]) return go.Scatter3d( x=vs[:, 0], y=vs[:, 1], z=vs[:, 2], mode="lines", line=dict(color=color, width=width), name=name, showlegend=False, ) class VertexIndexer(object): def __init__(self): self.vertex2index = dict() self.index2vertex = list() self.last_index = 0 def index_of(self, vertex): if vertex in self.vertex2index: return self.vertex2index[vertex] self.vertex2index[vertex] = self.last_index self.index2vertex.append(vertex) self.last_index += 1 return self.last_index - 1 def coordinates(self): return np.array(self.index2vertex) class VoxelDrawer(object): def __init__(self, bitmap, color, name, offsets): self.bitmap = bitmap self.shape = bitmap.shape self.color = color self.offsets = offsets self.vertex_indexer = VertexIndexer() self.triangles = [] self.name = name def isface(self, x, y, z, dx, dy, dz): return self.at(x, y, z) != self.at(x + dx, y + dy, z + dz) def at(self, x, y, z): if x < 0 or y < 0 or z < 0: return False if x >= self.shape[2] or y >= self.shape[1] or z >= self.shape[0]: return False return self.bitmap[z, y, x] def putface(self, x, y, z, dx, dy, dz): vertices = self.square_vertices(x, y, z, dx, dy, dz) vertex_indices = [self.vertex_indexer.index_of(vertex) for vertex in vertices] self.triangles.append([vertex_indices[0], vertex_indices[1], vertex_indices[2]]) self.triangles.append([vertex_indices[1], vertex_indices[3], vertex_indices[2]]) def square_vertices(self, x, y, z, dx, dy, dz): z += self.offsets[0] y += self.offsets[1] x += self.offsets[2] if dx == 1: return ( (x + 0.5, y - 0.5, z - 0.5), (x + 0.5, y + 0.5, z - 0.5), (x + 0.5, y - 0.5, z + 0.5), (x + 0.5, y + 0.5, z + 0.5), ) if dy == 1: return ( (x - 0.5, y + 0.5, z - 0.5), (x + 0.5, y + 0.5, z - 0.5), (x - 0.5, y + 0.5, z + 0.5), (x + 0.5, y + 0.5, z + 0.5), ) if dz == 1: return ( (x - 0.5, y - 0.5, z + 0.5), (x + 0.5, y - 0.5, z + 0.5), (x - 0.5, y + 0.5, z + 0.5), (x + 0.5, y + 0.5, z + 0.5), ) def graph_object(self): for dx, dy, dz in [(1, 0, 0), (0, 1, 0), (0, 0, 1)]: for z in range(-1, self.shape[0] + 1): for y in range(-1, self.shape[1] + 1): for x in range(-1, self.shape[2] + 1): if self.isface(x, y, z, dx, dy, dz): self.putface(x, y, z, dx, dy, dz) coordinates = self.vertex_indexer.coordinates() triangles = np.array(self.triangles) return go.Mesh3d( x=coordinates[:, 0], y=coordinates[:, 1], z=coordinates[:, 2], i=triangles[:, 0], j=triangles[:, 1], k=triangles[:, 2], name=self.name, color=self.color, )
[docs] def Bitmap3d(bitmap, color=None, name="", offsets=[0, 0, 0]): """ Returns a plotly's trace object for a 3D bitmap. Args: bitmap (np.array): 3D boolean bitmap color (string or None): The name of color name (string): The name of pointcloud Returns: plotly.graph_objects.Mesh3d: Plotly's trace object for the visualization of the pointcloud """ return VoxelDrawer(bitmap, color, name, offsets).graph_object()
def Voxels(voxels, color=None, name=""): assert len(voxels[0]) == 3 zmax, ymax, xmax = np.max(voxels, axis=0) + 1 bitmap = np.zeros((zmax, ymax, xmax), bool) for v in voxels: bitmap[v[0], v[1], v[2]] = True return Bitmap3d(bitmap, color, name) def Cubes1d(cubes, color, width, name): zs = [] ys = [] xs = [] for cube, d in cubes: zs.extend([cube[0], cube[0] + d[0], None]) ys.extend([cube[1], cube[1] + d[1], None]) xs.extend([cube[2], cube[2] + d[2], None]) return go.Scatter3d( x=xs, y=ys, z=zs, mode="lines", name=name, showlegend=False, line=dict(color=color, width=width), ) def rotate3(lst, k): return lst[k % 3], lst[(k + 1) % 3], lst[(k + 2) % 3] def ndeg2rot(ndeg): return ndeg.index(0) def Cubes2d(cubes, color, width, name): xyzs = [[], [], []] for cube, d in cubes: cs1, cs2, cs3 = rotate3(xyzs, ndeg2rot(d)) c1, c2, c3 = rotate3(cube, ndeg2rot(d)) cs1.extend([c1, c1, c1, c1, c1, None]) cs2.extend([c2, c2 + 1, c2 + 1, c2, c2, None]) cs3.extend([c3, c3, c3 + 1, c3 + 1, c3, None]) return go.Scatter3d( x=xyzs[0], y=xyzs[1], z=xyzs[2], mode="lines", name=name, showlegend=False, line=dict(color=color, width=width), ) def Cubes(cubes, color=None, width=1, name=""): dim = sum(cubes[0][1]) if dim == 1: return Cubes1d(cubes, color, width, name) elif dim == 2: return Cubes2d(cubes, color, width, name) elif dim == 3: raise RuntimeError("Dim 3 cube is not visualizable by homcloud-plotly") class CubeMeshDrawer(object): def __init__(self, cubes, color, name): self.cubes = cubes self.color = color self.name = name self.vertex_indexer = VertexIndexer() UV = [ (np.array([0, 1, 0]), np.array([0, 0, 1])), (np.array([0, 0, 1]), np.array([1, 0, 0])), (np.array([1, 0, 0]), np.array([0, 1, 0])), ] def graph_object(self): triangles = [] for cube, ndeg in self.cubes: u, v = self.UV[ndeg.index(0)] p0 = self.vertex_indexer.index_of(tuple(cube)) p1 = self.vertex_indexer.index_of(tuple(u + cube)) p2 = self.vertex_indexer.index_of(tuple(u + v + cube)) p3 = self.vertex_indexer.index_of(tuple(v + cube)) triangles.append([p0, p1, p2]) triangles.append([p0, p2, p3]) coordinates = self.vertex_indexer.coordinates() triangles = np.array(triangles) return go.Mesh3d( x=coordinates[:, 0], y=coordinates[:, 1], z=coordinates[:, 2], i=triangles[:, 0], j=triangles[:, 1], k=triangles[:, 2], name=self.name, color=self.color, ) def CubesMesh(cubes, color=None, name=""): dim = sum(cubes[0][1]) assert dim == 2 return CubeMeshDrawer(cubes, color, name).graph_object()
[docs] def SimpleScene(xrange=None, yrange=None, zrange=None): """ Returns a plotly's `graph_object.layout.Scene` object to remove x/y/z axes. You can use `xrange`/`yrange`/`zrange` to cut off the visualization area Args: xrange ((float, float) or None): The range of X axis to be visualized yrange ((float, float) or None): The range of Y axis to be visualized zrange ((float, float) or None): The range of Z axis to be visualized Returns: plotly.graph_objects.layout.Scene: Plotly's scene object """ return go.layout.Scene( xaxis=dict(range=xrange, visible=False), yaxis=dict(range=yrange, visible=False), zaxis=dict(range=zrange, visible=False), )