Goal
I want to plot a large number of cubes (arranged in a 3D grid) with different colors and opacities.
Current State and question
I have come up with a solution using vispy
, but the performance is very poor - drawing takes very long and the window is very unresponsive. Also, there seem to be some glitches in the visualization, but I could live with those.
Is there a more efficient/elegant way to implement that? I am open to using other packages (I have tried open3d
but found it difficult to specify colors and opacities - the documentation is not very verbose). However, I need to use python.
What I did so far
The first problem I had to solve with vispy
was that I was unable to create cubes at custom positions. I therefore wrote a subclass that can do that:
import vispy.visuals
from vispy.geometry import create_box
class PositionedCubeVisual(vispy.visuals.BoxVisual):
def __init__(self, size=1, position=(0, 0, 0), width_segments=1,
height_segments=1, depth_segments=1, planes=None,
vertex_colors=None, face_colors=None,
color=(0.5, 0.5, 1, 1), edge_color=None, **kwargs):
vertices, filled_indices, outline_indices = create_box(
size, size, size, width_segments, height_segments,
depth_segments, planes)
for column, pos in zip(vertices['position'].T, position):
column += pos
self._mesh = vispy.visuals.MeshVisual(vertices['position'], filled_indices,
vertex_colors, face_colors, color)
if edge_color:
self._border = vispy.visuals.MeshVisual(vertices['position'], outline_indices,
color=edge_color, mode='lines')
else:
self._border = vispy.visuals.MeshVisual()
vispy.visuals.CompoundVisual.__init__(self, [self._mesh, self._border], **kwargs)
self.mesh.set_gl_state(polygon_offset_fill=True,
polygon_offset=(1, 1), depth_test=True)
PositionedCube = vispy.scene.visuals.create_visual_node(PositionedCubeVisual)
I then plot the cubes as follows:
import numpy as np
import vispy.scene
def plot_grid_cubes(x, y, z, c=None, size=1, alpha=0.1, edge_color="black",
cmap="viridis", bgcolor="#FFFFFF"):
canvas = vispy.scene.SceneCanvas(keys='interactive', show=True)
view = canvas.central_widget.add_view()
view.bgcolor = bgcolor
view.camera = 'turntable'
c = get_color_array(c, alpha, min(len(x), len(y), len(z)), cmap)
for xx, yy, zz, cc in zip(x, y, z, c):
cube = PositionedCube(size, (xx, yy, zz), color=cc, edge_color=edge_color, parent=view.scene)
canvas.app.run()
def get_color_array(c, alpha, size, cmap):
if c is not None:
cmap = cm.get_cmap(cmap)
if hasattr(c, "__iter__"):
c = np.array(c, copy=True, dtype=float)
c -= c.min()
c *= 255/c.max()
return cmap(c.astype(int), alpha)
else:
color = np.ones((size, 4))
color[:, 3] = alpha
return color
This can then be applied as follows:
plot_grid_cubes([0, 1], [0, 1], [0, 1], c=[0.3, 0.5], alpha=[0.3, 0.8])
The example above works great, but it becomes poor if I plot thousands of cubes.