def __init__(self): app.Canvas.__init__(self, size=(512, 512), title='Rotating cube', keys='interactive') self.timer = app.Timer('auto', self.on_timer) # Build cube data V, I, O = create_cube() vertices = VertexBuffer(V) self.faces = IndexBuffer(I) self.outline = IndexBuffer(O) # Build program # -------------------------------------- self.program = Program(vertex, fragment) self.program.bind(vertices) # Build view, model, projection & normal # -------------------------------------- view = np.eye(4, dtype=np.float32) model = np.eye(4, dtype=np.float32) translate(view, 0, 0, -5) self.program['u_model'] = model self.program['u_view'] = view self.phi, self.theta = 0, 0 # OpenGL initalization # -------------------------------------- gloo.set_state(clear_color=(0.30, 0.30, 0.35, 1.00), depth_test=True, polygon_offset=(1, 1), line_width=0.75, blend_func=('src_alpha', 'one_minus_src_alpha')) self.timer.start()
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.timer = app.Timer('auto', connect=self.on_timer, start=True) with open('vertex.glsl') as f: self.vshader = f.read() with open('fragment.glsl') as f: self.fshader = f.read() self.program = Program(self.vshader, self.fshader) v, i, iOutline = create_cube() self.vertices = VertexBuffer(v) self.indices = IndexBuffer(i) self.outlineIndices = IndexBuffer(iOutline) self.program.bind(self.vertices) self.theta, self.phi = 0, 0 self.program['model'] = np.eye(4) self.program['view'] = translate([0, 0, -5]) self.program['texture'] = checkerboard() gloo.set_state(clear_color=(0.30, 0.30, 0.35, 1.00), polygon_offset=(1, 1), blend_func=('src_alpha', 'one_minus_src_alpha'), line_width=5, depth_test=True)
def on_initialize(self, event): # Build cube data V, I, O = create_cube() vertices = VertexBuffer(V) self.faces = IndexBuffer(I) self.outline = IndexBuffer(O) # Build program # -------------------------------------- self.program = Program(vertex, fragment) self.program.bind(vertices) # Build view, model, projection & normal # -------------------------------------- view = np.eye(4, dtype=np.float32) model = np.eye(4, dtype=np.float32) translate(view, 0, 0, -5) self.program['u_model'] = model self.program['u_view'] = view self.phi, self.theta = 0, 0 # OpenGL initalization # -------------------------------------- gloo.set_state(clear_color=(0.30, 0.30, 0.35, 1.00), depth_test=True, polygon_offset=(1, 1), line_width=0.75, blend_func=('src_alpha', 'one_minus_src_alpha')) self.timer.start()
def on_initialize(self, event): # Build cube data V, F, O = create_cube() vertices = VertexBuffer(V) self.faces = IndexBuffer(F) self.outline = IndexBuffer(O) # Build view, model, projection & normal # -------------------------------------- self.view = np.eye(4, dtype=np.float32) model = np.eye(4, dtype=np.float32) translate(self.view, 0, 0, -5) normal = np.array(np.matrix(np.dot(self.view, model)).I.T) # Build program # -------------------------------------- self.program = Program(vertex, fragment) self.program.bind(vertices) self.program["u_light_position"] = 2, 2, 2 self.program["u_light_intensity"] = 1, 1, 1 self.program["u_model"] = model self.program["u_view"] = self.view self.program["u_normal"] = normal self.phi, self.theta = 0, 0 # OpenGL initalization # -------------------------------------- gloo.set_state(clear_color=(0.30, 0.30, 0.35, 1.00), depth_test=True, polygon_offset=(1, 1), blend_func=('src_alpha', 'one_minus_src_alpha'), line_width=0.75) self.timer.start()
def __init__(self, vol, clim=None, method='mip', threshold=None, relative_step_size=0.8, cmap='grays', emulate_texture=False): tex_cls = TextureEmulated3D if emulate_texture else Texture3D # Storage of information of volume self._vol_shape = () self._clim = None self._need_vertex_update = True # Set the colormap self._cmap = get_colormap(cmap) # Create gloo objects self._vertices = VertexBuffer() self._texcoord = VertexBuffer( np.array([ [0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1], ], dtype=np.float32)) self._tex = tex_cls((10, 10, 10), interpolation='linear', wrapping='clamp_to_edge') # Create program Visual.__init__(self, vcode=VERT_SHADER, fcode="") self.shared_program['u_volumetex'] = self._tex self.shared_program['a_position'] = self._vertices self.shared_program['a_texcoord'] = self._texcoord self._draw_mode = 'triangle_strip' self._index_buffer = IndexBuffer() # Only show back faces of cuboid. This is required because if we are # inside the volume, then the front faces are outside of the clipping # box and will not be drawn. self.set_gl_state('translucent', cull_face=False) # Set data self.set_data(vol, clim) # Set params self.method = method self.relative_step_size = relative_step_size self.threshold = threshold if (threshold is not None) else vol.mean() self.freeze()
def uvsphere(radius, h, v): size = h*v index_count = size*2 - h*2 # hard to explain # What is rad for? rad = np.full(size, radius, dtype=np.float32) azi = np.zeros(size, dtype=np.float32) inc = np.zeros(size, dtype=np.float32) tex = np.zeros((size, 2), dtype=np.float32) ind = np.zeros(index_count, dtype=np.uint32) # TODO: Explain this boggy mess. for row in range(h): lat = pi * row / (h-1) t_y = row / (h-1) for col in range(v): i = row*h + col lon = pi * 2 * col / (v-1) t_x = col / (v-1) inc[i] = lat azi[i] = lon tex[i, :] = (t_x, t_y) # Generate indices for triangle_strip in another step. n1, n2 = 0, h for i in range(index_count): if i%2 == 0: ind[i] = n1 n1 += 1 else: ind[i] = n2 n2 += 1 #print(list(zip(azi, inc))) print(ind) return rad, azi, inc, tex, IndexBuffer(ind)
def make_eye(self, texture, eye): '''make_eye (eye_texture, eye) Arguments: - eye_texture: The texture (bound to a framebuffer), that represents the view of the eye - eye: 'left' or 'right', the eye being rendered Todo: - Use vertex buffer instead of manually binding ''' assert isinstance(texture, Texture2D), "texture not a texture 2D instance!" assert eye in ['left', 'right' ], eye + " is not a valid eye (Should be left or right)" program = Program(self._vert_shader, self._frag_shader) i_buffer = self._i_buffers[eye + '_indices'] _buffer = self._v_buffers[eye + '_buffer'] Logger.log('Loading {} eye distortion mesh pos'.format(eye)) program['pos'] = _buffer['pos'] Logger.log('Loading {} eye distortion mesh red_xy'.format(eye)) program['red_xy'] = _buffer['red_xy'] Logger.log('Loading {} eye distortion mesh green_xy'.format(eye)) program['green_xy'] = _buffer['green_xy'] Logger.log('Loading {} eye distortion mesh blue_xy'.format(eye)) program['blue_xy'] = _buffer['blue_xy'] program['vignette'] = _buffer['vignette'] program['texture'] = texture return program, IndexBuffer(i_buffer)
def __init__(self, volumes, clim=None, threshold=None, relative_step_size=0.8, cmap1='grays', cmap2='grays', emulate_texture=False, n_volume_max=10): # Choose texture class tex_cls = TextureEmulated3D if emulate_texture else Texture3D # We store the data and colormaps in a CallbackList which can warn us # when it is modified. self.volumes = CallbackList() self.volumes.on_size_change = self._update_all_volumes self.volumes.on_item_change = self._update_volume self._vol_shape = None self._need_vertex_update = True # Create OpenGL program vert_shader, frag_shader = get_shaders(n_volume_max) super(MultiVolumeVisual, self).__init__(vcode=vert_shader, fcode=frag_shader) # Create gloo objects self._vertices = VertexBuffer() self._texcoord = VertexBuffer( np.array([ [0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1], ], dtype=np.float32)) # Set up textures self.textures = [] for i in range(n_volume_max): self.textures.append(tex_cls((10, 10, 10), interpolation='linear', wrapping='clamp_to_edge')) self.shared_program['u_volumetex{0}'.format(i)] = self.textures[i] self.shared_program.frag['cmap{0:d}'.format(i)] = Function(get_colormap('grays').glsl_map) self.shared_program['a_position'] = self._vertices self.shared_program['a_texcoord'] = self._texcoord self._draw_mode = 'triangle_strip' self._index_buffer = IndexBuffer() self.shared_program.frag['sampler_type'] = self.textures[0].glsl_sampler_type self.shared_program.frag['sample'] = self.textures[0].glsl_sample # Only show back faces of cuboid. This is required because if we are # inside the volume, then the front faces are outside of the clipping # box and will not be drawn. self.set_gl_state('translucent', cull_face=False) self.relative_step_size = relative_step_size self.freeze() # Add supplied volumes self.volumes.extend(volumes)
def __init__(self): app.Canvas.__init__(self, size=(512, 512), title='Textured cube', keys='interactive') self.timer = app.Timer('auto', self.on_timer) # Build cube data V, I, _ = create_cube() vertices = VertexBuffer(V) self.indices = IndexBuffer(I) # Build program self.program = Program(vertex, fragment) self.program.bind(vertices) # Build view, model, projection & normal view = np.eye(4, dtype=np.float32) model = np.eye(4, dtype=np.float32) translate(view, 0, 0, -5) self.program['model'] = model self.program['view'] = view self.program['texture'] = checkerboard() self.phi, self.theta = 0, 0 # OpenGL initalization gloo.set_state(clear_color=(0.30, 0.30, 0.35, 1.00), depth_test=True) self.timer.start()
def __init__(self): app.Canvas.__init__(self, size=(512, 512), title='Colored cube', keys='interactive') # Build cube data V, I, _ = create_cube() vertices = VertexBuffer(V) self.indices = IndexBuffer(I) # Build program self.program = Program(vertex, fragment) self.program.bind(vertices) # Build view, model, projection & normal view = translate((0, 0, -5)) model = np.eye(4, dtype=np.float32) self.program['model'] = model self.program['view'] = view self.phi, self.theta = 0, 0 gloo.set_state(clear_color=(0.30, 0.30, 0.35, 1.00), depth_test=True) self.activate_zoom() self.timer = app.Timer('auto', self.on_timer, start=True) self.show()
def _update(self): """ Update vertex buffers & texture """ if self._vertices_buffer is not None: self._vertices_buffer.delete() self._vertices_buffer = VertexBuffer(self._vertices_list.data) if self.itype is not None: if self._indices_buffer is not None: self._indices_buffer.delete() self._indices_buffer = IndexBuffer(self._indices_list.data) if self.utype is not None: if self._uniforms_texture is not None: self._uniforms_texture.delete() # We take the whole array (_data), not the data one texture = self._uniforms_list._data.view(np.float32) size = len(texture) / self._uniforms_float_count shape = self._compute_texture_shape(size) # shape[2] = float count is only used in vertex shader code texture = texture.reshape(shape[0], shape[1], 4) self._uniforms_texture = Texture2D(texture) self._uniforms_texture.data = texture self._uniforms_texture.interpolation = "nearest" if len(self._programs): for program in self._programs: program.bind(self._vertices_buffer) if self._uniforms_list is not None: program["uniforms"] = self._uniforms_texture program["uniforms_shape"] = self._ushape
def render(mesh, view): program['xyz'] = mesh.vertices program['color'] = mesh.colors program['model'] = mesh.transform program['view'] = view.transform program['projection'] = view.proj program.draw('triangles', IndexBuffer(mesh.indices))
def initialize_renderer(self): self.fbuffer = FrameBuffer() vertices = np.array( [[-1.0, -1.0], [+1.0, -1.0], [-1.0, +1.0], [+1.0, +1.0]], np.float32) texcoords = np.array([[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]], dtype=np.float32) self.fbuf_vertices = VertexBuffer(data=vertices) self.fbuf_texcoords = VertexBuffer(data=texcoords) self.fbuffer_prog['texcoord'] = self.fbuf_texcoords self.fbuffer_prog['position'] = self.fbuf_vertices self.vertex_buffer = VertexBuffer() self.index_buffer = IndexBuffer()
def __init__(self, sensor=None, i=0, yaw=False, title='Rotating Cube'): app.Canvas.__init__(self, size=(640, 640), title=title, keys='interactive') self.timer = app.Timer('auto', self.on_timer) self.sensor = sensor self.i = i self.yaw = yaw # Build cube data V, I, O = create_cube() vertices = VertexBuffer(V) self.faces = IndexBuffer(I) self.outline = IndexBuffer(O) # Build program # -------------------------------------- self.program = Program(vertex, fragment) self.program.bind(vertices) # Build view, model, projection & normal # -------------------------------------- view = translate((0, 0, -5)) model = np.eye(4, dtype=np.float32) self.program['u_model'] = model self.program['u_view'] = view self.theta, self.psi, self.phi = 0, 0, 0 self.activate_zoom() # OpenGL initialization # -------------------------------------- gloo.set_state(clear_color=(0.30, 0.30, 0.35, 1.00), depth_test=True, polygon_offset=(1, 1), line_width=0.75, blend_func=('src_alpha', 'one_minus_src_alpha')) self.timer.start() self.show()
def __init__(self): app.Canvas.__init__(self, size=(512, 512), title='Lighted cube', keys='interactive') self.timer = app.Timer('auto', self.on_timer) # Build cube data V, F, outline = create_cube() vertices = VertexBuffer(V) self.faces = IndexBuffer(F) self.outline = IndexBuffer(outline) # Build view, model, projection & normal # -------------------------------------- self.view = translate((0, 0, -5)) model = np.eye(4, dtype=np.float32) normal = np.array(np.matrix(np.dot(self.view, model)).I.T) # Build program # -------------------------------------- self.program = Program(vertex, fragment) self.program.bind(vertices) self.program["u_light_position"] = 2, 2, 2 self.program["u_light_intensity"] = 1, 1, 1 self.program["u_model"] = model self.program["u_view"] = self.view self.program["u_normal"] = normal self.phi, self.theta = 0, 0 self.activate_zoom() # OpenGL initialization # -------------------------------------- gloo.set_state(clear_color=(0.30, 0.30, 0.35, 1.00), depth_test=True, polygon_offset=(1, 1), blend_func=('src_alpha', 'one_minus_src_alpha'), line_width=0.75) self.timer.start() self.show()
def initialize_renderer(self): self.fbuffer = FrameBuffer() vertices = np.array( [[-1.0, -1.0], [+1.0, -1.0], [-1.0, +1.0], [+1.0, +1.0]], np.float32) texcoords = np.array([[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]], dtype=np.float32) self.fbuf_vertices = VertexBuffer(data=vertices) self.fbuf_texcoords = VertexBuffer(data=texcoords) self.fbuffer_prog = Program(src_fbuffer.vert, src_fbuffer.frag) self.fbuffer_prog['texcoord'] = self.fbuf_texcoords self.fbuffer_prog['position'] = self.fbuf_vertices self.vertex_buffer = VertexBuffer() self.index_buffer = IndexBuffer() self.default_prog = Program(src_default.vert, src_default.frag) self.reset_view()
def __init__(self, data, relative_step_size=0.8, emulate_texture=False): # Choose texture class tex_cls = TextureEmulated3D if emulate_texture else Texture3D # Storage of information of volume self._need_vertex_update = True # Create OpenGL program Visual.__init__(self, vcode=VERT_SHADER, fcode=FRAG_SHADER) # Create gloo objects self._vertices = VertexBuffer() self._texcoord = VertexBuffer( np.array([ [0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1], ], dtype=np.float32)) # Set up RGBA texture self.texture = tex_cls((10, 10, 10, 4), interpolation='linear', wrapping='clamp_to_edge') self.texture.set_data(data.astype(np.float32)) self.shared_program['u_volumetex'] = self.texture self._vol_shape = data.shape[:-1] self.shared_program['u_shape'] = self._vol_shape[::-1] self.shared_program['a_position'] = self._vertices self.shared_program['a_texcoord'] = self._texcoord self._draw_mode = 'triangle_strip' self._index_buffer = IndexBuffer() self.shared_program.frag['sampler_type'] = self.texture.glsl_sampler_type self.shared_program.frag['sample'] = self.texture.glsl_sample # Only show back faces of cuboid. This is required because if we are # inside the volume, then the front faces are outside of the clipping # box and will not be drawn. self.set_gl_state('translucent', cull_face=False) self.relative_step_size = relative_step_size self.freeze()
def __init__(self): vertices, indices, _ = create_cube() vertices = VertexBuffer(vertices) self.indices = IndexBuffer(indices) self.program = Program(self.cube_vertex, self.cube_fragment) self.model = np.eye(4) self.view = np.eye(4) self.program.bind(vertices) self.program['texture'] = utils.checkerboard() self.program['texture'].interpolation = 'linear' self.program['model'] = self.model self.program['view'] = self.view
def on_initialize(self, event): # Build cube data V, I, _ = create_cube() vertices = VertexBuffer(V) self.indices = IndexBuffer(I) # Build program self.program = Program(vertex, fragment) self.program.bind(vertices) # Build view, model, projection & normal view = np.eye(4, dtype=np.float32) model = np.eye(4, dtype=np.float32) translate(view, 0, 0, -5) self.program['model'] = model self.program['view'] = view self.phi, self.theta = 0, 0 gloo.set_state(clear_color=(0.30, 0.30, 0.35, 1.00), depth_test=True) self.timer.start()
def __init__(self): app.Canvas.__init__(self, title='Framebuffer post-processing', keys='interactive', size=(512, 512)) # Build cube data # -------------------------------------- vertices, indices, _ = create_cube() vertices = VertexBuffer(vertices) self.indices = IndexBuffer(indices) # Build program # -------------------------------------- view = np.eye(4, dtype=np.float32) model = np.eye(4, dtype=np.float32) translate(view, 0, 0, -7) self.phi, self.theta = 60, 20 rotate(model, self.theta, 0, 0, 1) rotate(model, self.phi, 0, 1, 0) self.cube = Program(cube_vertex, cube_fragment) self.cube.bind(vertices) self.cube["texture"] = checkerboard() self.cube["texture"].interpolation = 'linear' self.cube['model'] = model self.cube['view'] = view color = Texture2D((512, 512, 3), interpolation='linear') self.framebuffer = FrameBuffer(color, RenderBuffer((512, 512))) self.quad = Program(quad_vertex, quad_fragment, count=4) self.quad['texcoord'] = [(0, 0), (0, 1), (1, 0), (1, 1)] self.quad['position'] = [(-1, -1), (-1, +1), (+1, -1), (+1, +1)] self.quad['texture'] = color # OpenGL and Timer initalization # -------------------------------------- set_state(clear_color=(.3, .3, .35, 1), depth_test=True) self.timer = app.Timer('auto', connect=self.on_timer, start=True) self._set_projection(self.size)
def buildProgram(self): ''' In dieser Methode wird das Programm samt Indices einer Kugel errechnet. Das Ganze wird mithilfe der Bibliothek vispy.gloo gemacht. Parameter: - Rueckgabewerte: - ''' vertices = np.zeros(self._mesh.getVertices().shape[0], [("position", np.float32, 3)]) vertices["position"] = self._mesh.getVertices() vertices = VertexBuffer(vertices) indices = self._mesh.getIndices() self._indices = IndexBuffer(indices) self._program = Program(vertex, fragment) self._program.bind(vertices) self._program['color'] = self._color self._program['model'] = None self._program['view'] = None self._program['drawHorizon'] = -3
def initialize_renderer(): """Initialize the OpenGL renderer. For an OpenGL based renderer this sets up the viewport and creates the shader programs. """ global fbuffer global fbuffer_prog global default_prog global texture_prog global vertex_buffer global index_buffer fbuffer = FrameBuffer() vertices = np.array( [[-1.0, -1.0], [+1.0, -1.0], [-1.0, +1.0], [+1.0, +1.0]], np.float32) texcoords = np.array([[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]], dtype=np.float32) fbuf_vertices = VertexBuffer(data=vertices) fbuf_texcoords = VertexBuffer(data=texcoords) fbuffer_prog = Program(src_fbuffer.vert, src_fbuffer.frag) fbuffer_prog['texcoord'] = fbuf_texcoords fbuffer_prog['position'] = fbuf_vertices vertex_buffer = VertexBuffer() index_buffer = IndexBuffer() default_prog = Program(src_default.vert, src_default.frag) texture_prog = Program(src_texture.vert, src_texture.frag) texture_prog['texcoord'] = fbuf_texcoords reset_view()
class NapariVolumeVisual(Visual): """ Displays a 3D Volume Parameters ---------- vol : ndarray The volume to display. Must be ndim==3. clim : tuple of two floats | None The contrast limits. The values in the volume are mapped to black and white corresponding to these values. Default maps between min and max. method : {'mip', 'translucent', 'additive', 'iso'} The render method to use. See corresponding docs for details. Default 'mip'. threshold : float The threshold to use for the isosurface render method. By default the mean of the given volume is used. relative_step_size : float The relative step size to step through the volume. Default 0.8. Increase to e.g. 1.5 to increase performance, at the cost of quality. cmap : str Colormap to use. emulate_texture : bool Use 2D textures to emulate a 3D texture. OpenGL ES 2.0 compatible, but has lower performance on desktop platforms. """ def __init__(self, vol, clim=None, method='mip', threshold=None, relative_step_size=0.8, cmap='grays', emulate_texture=False): tex_cls = TextureEmulated3D if emulate_texture else Texture3D # Storage of information of volume self._vol_shape = () self._clim = None self._need_vertex_update = True # Set the colormap self._cmap = get_colormap(cmap) # Create gloo objects self._vertices = VertexBuffer() self._texcoord = VertexBuffer( np.array([ [0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1], ], dtype=np.float32)) self._tex = tex_cls((10, 10, 10), interpolation='linear', wrapping='clamp_to_edge') # Create program Visual.__init__(self, vcode=VERT_SHADER, fcode="") self.shared_program['u_volumetex'] = self._tex self.shared_program['a_position'] = self._vertices self.shared_program['a_texcoord'] = self._texcoord self._draw_mode = 'triangle_strip' self._index_buffer = IndexBuffer() # Only show back faces of cuboid. This is required because if we are # inside the volume, then the front faces are outside of the clipping # box and will not be drawn. self.set_gl_state('translucent', cull_face=False) # Set data self.set_data(vol, clim) # Set params self.method = method self.relative_step_size = relative_step_size self.threshold = threshold if (threshold is not None) else vol.mean() self.freeze() def set_data(self, vol, clim=None): """ Set the volume data. Parameters ---------- vol : ndarray The 3D volume. clim : tuple | None Colormap limits to use. None will use the min and max values. """ # Check volume if not isinstance(vol, np.ndarray): raise ValueError('Volume visual needs a numpy array.') if not ((vol.ndim == 3) or (vol.ndim == 4 and vol.shape[-1] <= 4)): raise ValueError('Volume visual needs a 3D image.') # Handle clim if clim is not None: clim = np.array(clim, float) if not (clim.ndim == 1 and clim.size == 2): raise ValueError('clim must be a 2-element array-like') self._clim = tuple(clim) if self._clim is None: self._clim = vol.min(), vol.max() # Apply clim vol = np.array(vol, dtype='float32', copy=False) if self._clim[1] == self._clim[0]: if self._clim[0] != 0.: vol *= 1.0 / self._clim[0] else: vol -= self._clim[0] vol /= self._clim[1] - self._clim[0] # Apply to texture self._tex.set_data(vol) # will be efficient if vol is same shape self.shared_program['u_shape'] = (vol.shape[2], vol.shape[1], vol.shape[0]) shape = vol.shape[:3] if self._vol_shape != shape: self._vol_shape = shape self._need_vertex_update = True self._vol_shape = shape # Get some stats self._kb_for_texture = np.prod(self._vol_shape) / 1024 @property def clim(self): """ The contrast limits that were applied to the volume data. Settable via set_data(). """ return self._clim @property def cmap(self): return self._cmap @cmap.setter def cmap(self, cmap): self._cmap = get_colormap(cmap) self.shared_program.frag['cmap'] = Function(self._cmap.glsl_map) self.update() @property def method(self): """The render method to use Current options are: * translucent: voxel colors are blended along the view ray until the result is opaque. * mip: maxiumum intensity projection. Cast a ray and display the maximum value that was encountered. * additive: voxel colors are added along the view ray until the result is saturated. * iso: isosurface. Cast a ray until a certain threshold is encountered. At that location, lighning calculations are performed to give the visual appearance of a surface. """ return self._method @method.setter def method(self, method): # Check and save known_methods = list(frag_dict.keys()) if method not in known_methods: raise ValueError('Volume render method should be in %r, not %r' % (known_methods, method)) self._method = method # Get rid of specific variables - they may become invalid if 'u_threshold' in self.shared_program: self.shared_program['u_threshold'] = None self.shared_program.frag = frag_dict[method] self.shared_program.frag['sampler_type'] = self._tex.glsl_sampler_type self.shared_program.frag['sample'] = self._tex.glsl_sample self.shared_program.frag['cmap'] = Function(self._cmap.glsl_map) self.update() @property def threshold(self): """ The threshold value to apply for the isosurface render method. """ return self._threshold @threshold.setter def threshold(self, value): self._threshold = float(value) if 'u_threshold' in self.shared_program: self.shared_program['u_threshold'] = self._threshold self.update() @property def relative_step_size(self): """ The relative step size used during raycasting. Larger values yield higher performance at reduced quality. If set > 2.0 the ray skips entire voxels. Recommended values are between 0.5 and 1.5. The amount of quality degredation depends on the render method. """ return self._relative_step_size @relative_step_size.setter def relative_step_size(self, value): value = float(value) if value < 0.1: raise ValueError('relative_step_size cannot be smaller than 0.1') self._relative_step_size = value self.shared_program['u_relative_step_size'] = value def _create_vertex_data(self): """ Create and set positions and texture coords from the given shape We have six faces with 1 quad (2 triangles) each, resulting in 6*2*3 = 36 vertices in total. """ shape = self._vol_shape # Get corner coordinates. The -0.5 offset is to center # pixels/voxels. This works correctly for anisotropic data. x0, x1 = -0.5, shape[2] - 0.5 y0, y1 = -0.5, shape[1] - 0.5 z0, z1 = -0.5, shape[0] - 0.5 pos = np.array([ [x0, y0, z0], [x1, y0, z0], [x0, y1, z0], [x1, y1, z0], [x0, y0, z1], [x1, y0, z1], [x0, y1, z1], [x1, y1, z1], ], dtype=np.float32) """ 6-------7 /| /| 4-------5 | | | | | | 2-----|-3 |/ |/ 0-------1 """ # Order is chosen such that normals face outward; front faces will be # culled. indices = np.array([2, 6, 0, 4, 5, 6, 7, 2, 3, 0, 1, 5, 3, 7], dtype=np.uint32) # Apply self._vertices.set_data(pos) self._index_buffer.set_data(indices) def _compute_bounds(self, axis, view): return 0, self._vol_shape[axis] def _prepare_transforms(self, view): trs = view.transforms view.view_program.vert['transform'] = trs.get_transform() view_tr_f = trs.get_transform('visual', 'document') view_tr_i = view_tr_f.inverse view.view_program.vert['viewtransformf'] = view_tr_f view.view_program.vert['viewtransformi'] = view_tr_i def _prepare_draw(self, view): if self._need_vertex_update: self._create_vertex_data()
class OpenGLRenderer(ABC): def __init__(self, src_fbuffer, src_default): self.default_prog = None self.fbuffer_prog = None self.fbuffer = None self.fbuffer_tex_front = None self.fbuffer_tex_back = None self.vertex_buffer = None self.index_buffer = None # Renderer Globals: STYLE/MATERIAL PROPERTIES # self.style = Style() # Renderer Globals: Curves self.stroke_weight = 1 self.stroke_cap = ROUND self.stroke_join = MITER # Renderer Globals # VIEW MATRICES, ETC # self.viewport = None self.texture_viewport = None self.transform_matrix = np.identity(4) self.projection_matrix = np.identity(4) # Renderer Globals: RENDERING self.draw_queue = [] # Shaders self.fbuffer_prog = Program(src_fbuffer.vert, src_fbuffer.frag) self.default_prog = Program(src_default.vert, src_default.frag) def initialize_renderer(self): self.fbuffer = FrameBuffer() vertices = np.array( [[-1.0, -1.0], [+1.0, -1.0], [-1.0, +1.0], [+1.0, +1.0]], np.float32) texcoords = np.array([[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]], dtype=np.float32) self.fbuf_vertices = VertexBuffer(data=vertices) self.fbuf_texcoords = VertexBuffer(data=texcoords) self.fbuffer_prog['texcoord'] = self.fbuf_texcoords self.fbuffer_prog['position'] = self.fbuf_vertices self.vertex_buffer = VertexBuffer() self.index_buffer = IndexBuffer() def render_default(self, draw_type, draw_queue): # 1. Get the maximum number of vertices persent in the shapes # in the draw queue. # if len(draw_queue) == 0: return num_vertices = 0 for vertices, _, _ in draw_queue: num_vertices = num_vertices + len(vertices) # 2. Create empty buffers based on the number of vertices. # data = np.zeros(num_vertices, dtype=[('position', np.float32, 3), ('color', np.float32, 4)]) # 3. Loop through all the shapes in the geometry queue adding # it's information to the buffer. # sidx = 0 draw_indices = [] for vertices, idx, color in draw_queue: num_shape_verts = len(vertices) data['position'][sidx:(sidx + num_shape_verts), ] = np.array(vertices) color_array = np.array([color] * num_shape_verts) data['color'][sidx:sidx + num_shape_verts, :] = color_array draw_indices.append(sidx + idx) sidx += num_shape_verts self.vertex_buffer.set_data(data) self.index_buffer.set_data(np.hstack(draw_indices)) # 4. Bind the buffer to the shader. # self.default_prog.bind(self.vertex_buffer) # 5. Draw the shape using the proper shape type and get rid of # the buffers. # self.default_prog.draw(draw_type, indices=self.index_buffer) def cleanup(self): """Run the clean-up routine for the renderer. This method is called when all drawing has been completed and the program is about to exit. """ self.default_prog.delete() self.fbuffer_prog.delete() self.fbuffer.delete() def _transform_vertices(self, vertices, local_matrix, global_matrix): return np.dot(np.dot(vertices, local_matrix.T), global_matrix.T)[:, :3]
class Renderer2D: def __init__(self): self.default_prog = None self.fbuffer_prog = None self.texture_prog = None self.line_prog = None self.fbuffer = None self.fbuffer_tex_front = None self.fbuffer_tex_back = None self.vertex_buffer = None self.index_buffer = None ## Renderer Globals: USEFUL CONSTANTS self.COLOR_WHITE = (1, 1, 1, 1) self.COLOR_BLACK = (0, 0, 0, 1) self.COLOR_DEFAULT_BG = (0.8, 0.8, 0.8, 1.0) ## Renderer Globals: STYLE/MATERIAL PROPERTIES ## self.background_color = self.COLOR_DEFAULT_BG self.fill_color = self.COLOR_WHITE self.fill_enabled = True self.stroke_color = self.COLOR_BLACK self.stroke_enabled = True self.tint_color = self.COLOR_BLACK self.tint_enabled = False ## Renderer Globals: Curves self.stroke_weight = 1 self.stroke_cap = 2 self.stroke_join = 0 ## Renderer Globals ## VIEW MATRICES, ETC ## self.viewport = None self.texture_viewport = None self.transform_matrix = np.identity(4) self.modelview_matrix = np.identity(4) self.projection_matrix = np.identity(4) ## Renderer Globals: RENDERING self.draw_queue = [] def initialize_renderer(self): self.fbuffer = FrameBuffer() vertices = np.array( [[-1.0, -1.0], [+1.0, -1.0], [-1.0, +1.0], [+1.0, +1.0]], np.float32) texcoords = np.array([[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]], dtype=np.float32) self.fbuf_vertices = VertexBuffer(data=vertices) self.fbuf_texcoords = VertexBuffer(data=texcoords) self.fbuffer_prog = Program(src_fbuffer.vert, src_fbuffer.frag) self.fbuffer_prog['texcoord'] = self.fbuf_texcoords self.fbuffer_prog['position'] = self.fbuf_vertices self.vertex_buffer = VertexBuffer() self.index_buffer = IndexBuffer() self.default_prog = Program(src_default.vert, src_default.frag) self.texture_prog = Program(src_texture.vert, src_texture.frag) self.texture_prog['texcoord'] = self.fbuf_texcoords self.reset_view() def reset_view(self): self.viewport = ( 0, 0, int(builtins.width * builtins.pixel_x_density), int(builtins.height * builtins.pixel_y_density), ) self.texture_viewport = ( 0, 0, builtins.width, builtins.height, ) gloo.set_viewport(*self.viewport) cz = (builtins.height / 2) / math.tan(math.radians(30)) self.projection_matrix = matrix.perspective_matrix( math.radians(60), builtins.width / builtins.height, 0.1 * cz, 10 * cz) self.modelview_matrix = matrix.translation_matrix(-builtins.width / 2, \ builtins.height / 2, \ -cz) self.modelview_matrix = self.modelview_matrix.dot( matrix.scale_transform(1, -1, 1)) self.transform_matrix = np.identity(4) self.default_prog['modelview'] = self.modelview_matrix.T.flatten() self.default_prog['projection'] = self.projection_matrix.T.flatten() self.texture_prog['modelview'] = self.modelview_matrix.T.flatten() self.texture_prog['projection'] = self.projection_matrix.T.flatten() self.line_prog = Program(src_line.vert, src_line.frag) self.line_prog['modelview'] = self.modelview_matrix.T.flatten() self.line_prog['projection'] = self.projection_matrix.T.flatten() self.line_prog["height"] = builtins.height self.fbuffer_tex_front = Texture2D( (builtins.height, builtins.width, 3)) self.fbuffer_tex_back = Texture2D((builtins.height, builtins.width, 3)) for buf in [self.fbuffer_tex_front, self.fbuffer_tex_back]: self.fbuffer.color_buffer = buf with self.fbuffer: self.clear() def clear(self, color=True, depth=True): """Clear the renderer background.""" gloo.set_state(clear_color=self.background_color) gloo.clear(color=color, depth=depth) def _comm_toggles(self, state=True): gloo.set_state(blend=state) gloo.set_state(depth_test=state) if state: gloo.set_state(blend_func=('src_alpha', 'one_minus_src_alpha')) gloo.set_state(depth_func='lequal') @contextmanager def draw_loop(self): """The main draw loop context manager. """ self.transform_matrix = np.identity(4) self.default_prog['modelview'] = self.modelview_matrix.T.flatten() self.default_prog['projection'] = self.projection_matrix.T.flatten() self.fbuffer.color_buffer = self.fbuffer_tex_back with self.fbuffer: gloo.set_viewport(*self.texture_viewport) self._comm_toggles() self.fbuffer_prog['texture'] = self.fbuffer_tex_front self.fbuffer_prog.draw('triangle_strip') yield self.flush_geometry() self.transform_matrix = np.identity(4) gloo.set_viewport(*self.viewport) self._comm_toggles(False) self.clear() self.fbuffer_prog['texture'] = self.fbuffer_tex_back self.fbuffer_prog.draw('triangle_strip') self.fbuffer_tex_front, self.fbuffer_tex_back = self.fbuffer_tex_back, self.fbuffer_tex_front def _transform_vertices(self, vertices, local_matrix, global_matrix): return np.dot(np.dot(vertices, local_matrix.T), global_matrix.T)[:, :3] def _add_to_draw_queue_simple(self, stype, vertices, idx, fill, stroke, stroke_weight, stroke_cap, stroke_join): """Adds shape of stype to draw queue """ if stype == 'lines': self.draw_queue.append( (stype, (vertices, idx, stroke, stroke_weight, stroke_cap, stroke_join))) else: self.draw_queue.append((stype, (vertices, idx, fill))) def render(self, shape): fill = shape.fill.normalized if shape.fill else None stroke = shape.stroke.normalized if shape.stroke else None stroke_weight = shape.stroke_weight stroke_cap = shape.stroke_cap stroke_join = shape.stroke_join obj_list = get_render_primitives(shape) for obj in obj_list: stype, vertices, idx = obj # Transform vertices vertices = self._transform_vertices( np.hstack([vertices, np.ones((len(vertices), 1))]), shape._matrix, self.transform_matrix) # Add to draw queue self._add_to_draw_queue_simple(stype, vertices, idx, fill, stroke, stroke_weight, stroke_cap, stroke_join) def flush_geometry(self): """Flush all the shape geometry from the draw queue to the GPU. """ current_queue = [] for index, shape in enumerate(self.draw_queue): current_shape = self.draw_queue[index][0] current_queue.append(self.draw_queue[index][1]) if current_shape == "lines": self.render_line(current_queue) else: self.render_default(current_shape, current_queue) current_queue = [] self.draw_queue = [] def render_default(self, draw_type, draw_queue): # 1. Get the maximum number of vertices persent in the shapes # in the draw queue. # if len(draw_queue) == 0: return num_vertices = 0 for vertices, _, _ in draw_queue: num_vertices = num_vertices + len(vertices) # 2. Create empty buffers based on the number of vertices. # data = np.zeros(num_vertices, dtype=[('position', np.float32, 3), ('color', np.float32, 4)]) # 3. Loop through all the shapes in the geometry queue adding # it's information to the buffer. # sidx = 0 draw_indices = [] for vertices, idx, color in draw_queue: num_shape_verts = len(vertices) data['position'][sidx:(sidx + num_shape_verts), ] = vertices color_array = np.array([color] * num_shape_verts) data['color'][sidx:sidx + num_shape_verts, :] = color_array draw_indices.append(sidx + idx) sidx += num_shape_verts self.vertex_buffer.set_data(data) self.index_buffer.set_data(np.hstack(draw_indices)) # 4. Bind the buffer to the shader. # self.default_prog.bind(self.vertex_buffer) # 5. Draw the shape using the proper shape type and get rid of # the buffers. # self.default_prog.draw(draw_type, indices=self.index_buffer) def render_line(self, queue): ''' This rendering algorithm works by tesselating the line into multiple triangles. Reference: https://blog.mapbox.com/drawing-antialiased-lines-with-opengl-8766f34192dc ''' if len(queue) == 0: return pos = [] posPrev = [] posCurr = [] posNext = [] markers = [] side = [] linewidth = [] join_type = [] cap_type = [] color = [] for line in queue: if len(line[1]) == 0: continue for segment in line[1]: for i in range( len(segment) - 1): # the data is sent to renderer in line segments for j in [0, 0, 1, 0, 1, 1]: # all the vertices of triangles if i + j - 1 >= 0: posPrev.append(line[0][segment[i + j - 1]]) else: posPrev.append(line[0][segment[i + j]]) if i + j + 1 < len(segment): posNext.append(line[0][segment[i + j + 1]]) else: posNext.append(line[0][segment[i + j]]) posCurr.append(line[0][segment[i + j]]) markers.extend( [1.0, -1.0, -1.0, -1.0, 1.0, -1.0]) # Is the vertex up/below the line segment side.extend([1.0, 1.0, -1.0, 1.0, -1.0, -1.0]) # Left or right side of the segment pos.extend([line[0][segment[i]]] * 6) # Left vertex of each segment linewidth.extend([line[3]] * 6) join_type.extend([line[5]] * 6) cap_type.extend([line[4]] * 6) color.extend([line[2]] * 6) if len(pos) == 0: return posPrev = np.array(posPrev, np.float32) posCurr = np.array(posCurr, np.float32) posNext = np.array(posNext, np.float32) markers = np.array(markers, np.float32) side = np.array(side, np.float32) pos = np.array(pos, np.float32) linewidth = np.array(linewidth, np.float32) join_type = np.array(join_type, np.float32) cap_type = np.array(cap_type, np.float32) color = np.array(color, np.float32) self.line_prog['pos'] = gloo.VertexBuffer(pos) self.line_prog['posPrev'] = gloo.VertexBuffer(posPrev) self.line_prog['posCurr'] = gloo.VertexBuffer(posCurr) self.line_prog['posNext'] = gloo.VertexBuffer(posNext) self.line_prog['marker'] = gloo.VertexBuffer(markers) self.line_prog['side'] = gloo.VertexBuffer(side) self.line_prog['linewidth'] = gloo.VertexBuffer(linewidth) self.line_prog['join_type'] = gloo.VertexBuffer(join_type) self.line_prog['cap_type'] = gloo.VertexBuffer(cap_type) self.line_prog["color"] = gloo.VertexBuffer(color) self.line_prog.draw('triangles') def render_image(self, image, location, size): """Render the image. :param image: image to be rendered :type image: builtins.Image :param location: top-left corner of the image :type location: tuple | list | builtins.Vector :param size: target size of the image to draw. :type size: tuple | list | builtins.Vector """ self.flush_geometry() self.texture_prog[ 'fill_color'] = self.tint_color if self.tint_enabled else self.COLOR_WHITE self.texture_prog['transform'] = self.transform_matrix.T.flatten() x, y = location sx, sy = size imx, imy = image.size data = np.zeros(4, dtype=[('position', np.float32, 2), ('texcoord', np.float32, 2)]) data['texcoord'] = np.array( [[0.0, 1.0], [1.0, 1.0], [0.0, 0.0], [1.0, 0.0]], dtype=np.float32) data['position'] = np.array( [[x, y + sy], [x + sx, y + sy], [x, y], [x + sx, y]], dtype=np.float32) self.texture_prog['texture'] = image._texture self.texture_prog.bind(VertexBuffer(data)) self.texture_prog.draw('triangle_strip') def cleanup(self): """Run the clean-up routine for the renderer. This method is called when all drawing has been completed and the program is about to exit. """ self.default_prog.delete() self.fbuffer_prog.delete() self.line_prog.delete() self.fbuffer.delete()
def __init__(self, n_volume_max=16, emulate_texture=False, bgcolor='white', resolution=256): # Choose texture class tex_cls = TextureEmulated3D if emulate_texture else Texture3D self._n_volume_max = n_volume_max self._vol_shape = (resolution, resolution, resolution) self._need_vertex_update = True self._data_bounds = None self.resolution = resolution # We deliberately don't use super here because we don't want to call # VolumeVisual.__init__ Visual.__init__(self, vcode=VERT_SHADER, fcode="") self.volumes = defaultdict(dict) # We turn on clipping straight away - the following variable is needed # by _update_shader self._clip_data = True # Set up initial shader so that we can start setting shader variables # that don't depend on what volumes are actually active. self._update_shader() # Set initial clipping parameters self.shared_program['u_clip_min'] = [0, 0, 0] self.shared_program['u_clip_max'] = [1, 1, 1] # Set up texture vertices - note that these variables are required by # the parent VolumeVisual class. self._vertices = VertexBuffer() self._texcoord = VertexBuffer( np.array([[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1]], dtype=np.float32)) self._draw_mode = 'triangle_strip' self._index_buffer = IndexBuffer() self.shared_program['a_position'] = self._vertices self.shared_program['a_texcoord'] = self._texcoord # Only show back faces of cuboid. This is required because if we are # inside the volume, then the front faces are outside of the clipping # box and will not be drawn. self.set_gl_state('translucent', cull_face=False) # Set up the underlying volume shape and define textures self._vol_shape = (resolution, resolution, resolution) self.shared_program['u_shape'] = self._vol_shape self.textures = [] for i in range(n_volume_max): # Set up texture object self.textures.append(tex_cls(self._vol_shape, interpolation='linear', wrapping='clamp_to_edge')) # Pass texture object to shader program self.shared_program['u_volumetex_{0}'.format(i)] = self.textures[i] # Make sure all textures are disabled self.shared_program['u_enabled_{0}'.format(i)] = 0 self.shared_program['u_weight_{0}'.format(i)] = 1 # Don't use downsampling initially (1 means show 1:1 resolution) self.shared_program['u_downsample'] = 1. # Set up texture sampler self.shared_program.frag['sampler_type'] = self.textures[0].glsl_sampler_type self.shared_program.frag['sample'] = self.textures[0].glsl_sample # Set initial background color self.shared_program['u_bgcolor'] = Color(bgcolor).rgba # Prevent additional attributes from being added try: self.freeze() except AttributeError: # Older versions of VisPy pass
def _prepare_draw(self, view): # attributes / uniforms are not available until program is built if len(self.text) == 0: return False if self._vertices is None: text = self.text if isinstance(text, str): text = [text] n_char = sum(len(t) for t in text) # we delay creating vertices because it requires a context, # which may or may not exist when the object is initialized self._vertices = np.concatenate([ _text_to_vbo(t, self._font, self._anchors[0], self._anchors[1], self._font._lowres_size) for t in text ]) self._vertices = VertexBuffer(self._vertices) idx = (np.array([0, 1, 2, 0, 2, 3], np.uint32) + np.arange(0, 4 * n_char, 4, dtype=np.uint32)[:, np.newaxis]) self._index_buffer = IndexBuffer(idx.ravel()) self.shared_program.bind(self._vertices) # This is necessary to reset the GL drawing state after generating # SDF textures. A better way would be to enable the state to be # pushed/popped by the context. self._configure_gl_state() if self._pos_changed: # now we promote pos to the proper shape (attribute) text = self.text if not isinstance(text, str): repeats = [4 * len(t) for t in text] text = ''.join(text) else: repeats = [4 * len(text)] n_text = len(repeats) pos = self.pos # Rotation _rot = self._rotation if isinstance(_rot, (int, float)): _rot = np.full((pos.shape[0], ), self._rotation) _rot = np.asarray(_rot) if _rot.shape[0] < n_text: _rep = [1] * (len(_rot) - 1) + [n_text - len(_rot) + 1] _rot = np.repeat(_rot, _rep, axis=0) _rot = np.repeat(_rot[:n_text], repeats, axis=0) self.shared_program['a_rotation'] = _rot.astype(np.float32) # Position if pos.shape[0] < n_text: _rep = [1] * (len(pos) - 1) + [n_text - len(pos) + 1] pos = np.repeat(pos, _rep, axis=0) pos = np.repeat(pos[:n_text], repeats, axis=0) assert pos.shape[0] == self._vertices.size == len(_rot) self.shared_program['a_pos'] = pos self._pos_changed = False if self._color_changed: # now we promote color to the proper shape (varying) text = self.text if not isinstance(text, str): repeats = [4 * len(t) for t in text] text = ''.join(text) else: repeats = [4 * len(text)] n_text = len(repeats) color = self.color.rgba if color.shape[0] < n_text: color = np.repeat(color, [1] * (len(color) - 1) + [n_text - len(color) + 1], axis=0) color = np.repeat(color[:n_text], repeats, axis=0) assert color.shape[0] == self._vertices.size self._color_vbo = VertexBuffer(color) self.shared_program.vert['color'] = self._color_vbo self._color_changed = False transforms = self.transforms n_pix = (self._font_size / 72.) * transforms.dpi # logical pix tr = transforms.get_transform('document', 'render') px_scale = (tr.map((1, 0)) - tr.map((0, 1)))[:2] self._text_scale.scale = px_scale * n_pix self.shared_program.vert['text_scale'] = self._text_scale self.shared_program['u_npix'] = n_pix self.shared_program['u_kernel'] = self._font._kernel self.shared_program['u_color'] = self._color.rgba self.shared_program['u_font_atlas'] = self._font._atlas self.shared_program['u_font_atlas_shape'] = self._font._atlas.shape[:2]
class MultiVolumeVisual(Visual): """ Displays multiple 3D volumes simultaneously. Parameters ---------- volumes : list of tuples The volumes to show. Each tuple should contain three elements: the data array, the clim values, and the colormap to use. The clim values should be either a 2-element tuple, or None. relative_step_size : float The relative step size to step through the volume. Default 0.8. Increase to e.g. 1.5 to increase performance, at the cost of quality. emulate_texture : bool Use 2D textures to emulate a 3D texture. OpenGL ES 2.0 compatible, but has lower performance on desktop platforms. n_volume_max : int Absolute maximum number of volumes that can be shown. """ def __init__(self, volumes, clim=None, threshold=None, relative_step_size=0.8, cmap1='grays', cmap2='grays', emulate_texture=False, n_volume_max=10): # Choose texture class tex_cls = TextureEmulated3D if emulate_texture else Texture3D # We store the data and colormaps in a CallbackList which can warn us # when it is modified. self.volumes = CallbackList() self.volumes.on_size_change = self._update_all_volumes self.volumes.on_item_change = self._update_volume self._vol_shape = None self._need_vertex_update = True # Create OpenGL program vert_shader, frag_shader = get_shaders(n_volume_max) super(MultiVolumeVisual, self).__init__(vcode=vert_shader, fcode=frag_shader) # Create gloo objects self._vertices = VertexBuffer() self._texcoord = VertexBuffer( np.array([ [0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1], ], dtype=np.float32)) # Set up textures self.textures = [] for i in range(n_volume_max): self.textures.append(tex_cls((10, 10, 10), interpolation='linear', wrapping='clamp_to_edge')) self.shared_program['u_volumetex{0}'.format(i)] = self.textures[i] self.shared_program.frag['cmap{0:d}'.format(i)] = Function(get_colormap('grays').glsl_map) self.shared_program['a_position'] = self._vertices self.shared_program['a_texcoord'] = self._texcoord self._draw_mode = 'triangle_strip' self._index_buffer = IndexBuffer() self.shared_program.frag['sampler_type'] = self.textures[0].glsl_sampler_type self.shared_program.frag['sample'] = self.textures[0].glsl_sample # Only show back faces of cuboid. This is required because if we are # inside the volume, then the front faces are outside of the clipping # box and will not be drawn. self.set_gl_state('translucent', cull_face=False) self.relative_step_size = relative_step_size self.freeze() # Add supplied volumes self.volumes.extend(volumes) def _update_all_volumes(self, volumes): """ Update the number of simultaneous textures. Parameters ---------- n_textures : int The number of textures to use """ if len(self.volumes) > len(self.textures): raise ValueError("Number of volumes ({0}) exceeds number of textures ({1})".format(len(self.volumes), len(self.textures))) for index in range(len(self.volumes)): self._update_volume(volumes, index) def _update_volume(self, volumes, index): data, clim, cmap = volumes[index] cmap = get_colormap(cmap) if clim is None: clim = data.min(), data.max() data = data.astype(np.float32) if clim[1] == clim[0]: if clim[0] != 0.: data *= 1.0 / clim[0] else: data -= clim[0] data /= clim[1] - clim[0] self.shared_program['u_volumetex{0:d}'.format(index)].set_data(data) self.shared_program.frag['cmap{0:d}'.format(index)] = Function(cmap.glsl_map) print(self.shared_program.frag) if self._vol_shape is None: self.shared_program['u_shape'] = data.shape[::-1] self._vol_shape = data.shape elif data.shape != self._vol_shape: raise ValueError("Shape of arrays should be {0} instead of {1}".format(self._vol_shape, data.shape)) self.shared_program['u_n_tex'] = len(self.volumes) @property def relative_step_size(self): """ The relative step size used during raycasting. Larger values yield higher performance at reduced quality. If set > 2.0 the ray skips entire voxels. Recommended values are between 0.5 and 1.5. The amount of quality degredation depends on the render method. """ return self._relative_step_size @relative_step_size.setter def relative_step_size(self, value): value = float(value) if value < 0.1: raise ValueError('relative_step_size cannot be smaller than 0.1') self._relative_step_size = value self.shared_program['u_relative_step_size'] = value def _create_vertex_data(self): """ Create and set positions and texture coords from the given shape We have six faces with 1 quad (2 triangles) each, resulting in 6*2*3 = 36 vertices in total. """ shape = self._vol_shape # Get corner coordinates. The -0.5 offset is to center # pixels/voxels. This works correctly for anisotropic data. x0, x1 = -0.5, shape[2] - 0.5 y0, y1 = -0.5, shape[1] - 0.5 z0, z1 = -0.5, shape[0] - 0.5 pos = np.array([ [x0, y0, z0], [x1, y0, z0], [x0, y1, z0], [x1, y1, z0], [x0, y0, z1], [x1, y0, z1], [x0, y1, z1], [x1, y1, z1], ], dtype=np.float32) """ 6-------7 /| /| 4-------5 | | | | | | 2-----|-3 |/ |/ 0-------1 """ # Order is chosen such that normals face outward; front faces will be # culled. indices = np.array([2, 6, 0, 4, 5, 6, 7, 2, 3, 0, 1, 5, 3, 7], dtype=np.uint32) # Apply self._vertices.set_data(pos) self._index_buffer.set_data(indices) def _compute_bounds(self, axis, view): return 0, self._vol_shape[axis] def _prepare_transforms(self, view): trs = view.transforms view.view_program.vert['transform'] = trs.get_transform() view_tr_f = trs.get_transform('visual', 'document') view_tr_i = view_tr_f.inverse view.view_program.vert['viewtransformf'] = view_tr_f view.view_program.vert['viewtransformi'] = view_tr_i def _prepare_draw(self, view): if self._need_vertex_update: self._create_vertex_data() self._need_vertex_update = False
# Glut init # -------------------------------------- glut.glutInit(sys.argv) glut.glutInitDisplayMode(glut.GLUT_DOUBLE | glut.GLUT_RGBA | glut.GLUT_DEPTH) glut.glutCreateWindow('Lighted Cube') glut.glutReshapeWindow(512, 512) glut.glutReshapeFunc(reshape) glut.glutKeyboardFunc(keyboard) glut.glutDisplayFunc(display) glut.glutTimerFunc(1000 / 60, timer, 60) # Build cube data # -------------------------------------- V, F, O = cube() vertices = VertexBuffer(V) faces = IndexBuffer(F) outline = IndexBuffer(O) # Build view, model, projection & normal # -------------------------------------- view = np.eye(4, dtype=np.float32) model = np.eye(4, dtype=np.float32) projection = np.eye(4, dtype=np.float32) translate(view, 0, 0, -5) normal = np.array(np.matrix(np.dot(view, model)).I.T) # Build program # -------------------------------------- program = Program(vertex, fragment) program.bind(vertices) program["u_light_position"] = 2, 2, 2
def __init__(self): self.projection = np.eye(4) self.view = np.eye(4) self.model = np.eye(4) height, width = 3.0, 6.0 scale_factor = 0.2 x_offset = -8 y_offset = 2 pixel_to_length = 10 color = (1.0, 1.0, 1.0) scale(self.model, scale_factor) yrotate(self.model, -60) translate(self.model, x_offset, y_offset, -10) size = (int(height * 100), int(width * 100)) self.vertices = np.array([ [-width / 2, -height / 2, 0], [width / 2, -height / 2, 0], [width / 2, height / 2, 0], [-width / 2, height / 2, 0], ], dtype=np.float32) self.tex_coords = np.array([ [0, 0], [1, 0], [1, 1], [0, 1], ], dtype=np.float32) self.indices = IndexBuffer([ 0, 1, 2, 2, 3, 0, ]) self.program = Program(self.battery_vertex_shader, self.battery_fragment_shader) self.texture = Texture2D(shape=size + (3, )) self.text_buffer = FrameBuffer(self.texture, RenderBuffer(size)) images = [] images.append( Image.open( os.path.join(Paths.get_path_to_visar(), 'visar', 'images', 'battery', 'battery_low_color.png'))) images.append( Image.open( os.path.join(Paths.get_path_to_visar(), 'visar', 'images', 'battery', 'battery_used_color.png'))) images.append( Image.open( os.path.join(Paths.get_path_to_visar(), 'visar', 'images', 'battery', 'battery_full_color.png'))) self.level_texture = {} # texture for each level for x in range(0, len(images)): default_image = images[x] default_image = default_image.rotate(-90) default_image = default_image.resize(size) default_image = default_image.transpose(Image.FLIP_TOP_BOTTOM) default_image_array = np.asarray(default_image) self.level_texture[x + 1] = default_image_array #default_image_array = imageio.imread(os.path.join(Paths.get_path_to_visar(), 'visar', 'images', 'battery', 'battery_full_color.png')) # self.default_tex = Texture2D(data=default_image_array) # self.default_tex = Texture2D(shape=size + (3,)) # self.default_tex.set_data(self.level_texture[3]) self.program['vertex_position'] = self.vertices self.program['default_texcoord'] = self.tex_coords self.program['view'] = self.view self.program['model'] = self.model self.program['projection'] = self.projection self.program['hide'] = 0 # self.tex_program = Program(self.tex_vert_shader, self.battery_fragment_shader) # self.tex_program['vertex_position'] = self.vertices # self.tex_program['default_texcoord'] = self.tex_coords # self.tex_program['hide'] = 0 # self.tex_program['texture'] = self.default_tex self.flag = True # flag to update the texture self.level = 3 # level of the battery 1 - 3 full_middle_split = 75 # split between levels 2 and 3 middle_low_split = 25 # split between levels 1 and 2 fault_tolerance = 5 self.full_lower = full_middle_split - fault_tolerance # lower limit for going from 3 to 2 self.middle_upper = full_middle_split + fault_tolerance # upper limit for going from 2 to 3 self.middle_lower = middle_low_split - fault_tolerance # lower limit for going from 2 to 1 self.low_upper = middle_low_split + fault_tolerance # upper limit for going from 1 to 2
def __init__(self, text, canvas, position=1, color=(0.1, 0.0, 0.7)): ''' Give this the - text to be written - main app.canvas - position (1-9, or which button position this should occupy) ''' # State Controller State.register_button(position, text) self.position = position self.canvas = canvas self.projection = np.eye(4) self.view = np.eye(4) self.model = np.eye(4) height, width = 5.0, 15.0 # Meters orientation_vector = (1, 1, 0) unit_orientation_angle = np.array(orientation_vector) / np.linalg.norm( orientation_vector) scale_factor = 0.2 lowest_button = -5.2 midset = 0.2 scale(self.model, scale_factor) yrotate(self.model, -60) # rotate(self.model, 30, *unit_orientation_angle) offset = (position * ((height + midset) * scale_factor)) translate(self.model, -7.4, lowest_button + offset, -10) pixel_to_length = 10 self.size = map(lambda o: pixel_to_length * o, [width, height]) # Add texture coordinates # Rectangle of height height self.vertices = np.array([ [-width / 2, -height / 2, 0], [width / 2, -height / 2, 0], [width / 2, height / 2, 0], [-width / 2, height / 2, 0], ], dtype=np.float32) self.tex_coords = np.array([ [0, 0], [1, 0], [1, 1], [0, 1], ], dtype=np.float32) self.indices = IndexBuffer([ 0, 1, 2, 2, 3, 0, ]) self.program = Program(self.button_vertex_shader, self.button_fragment_shader) self.program['vertex_position'] = self.vertices self.program['default_texcoord'] = self.tex_coords self.program['view'] = self.view self.program['model'] = self.model self.program['projection'] = self.projection self.program['background_color'] = color self.program['highlighted'] = 0 # self.texture = Texture2D(shape=(1000, 1000) + (3,)) # self.text_buffer = FrameBuffer(self.texture, RenderBuffer((1000, 1000))) self.texture = Texture2D(shape=(500, 1500) + (3, )) self.text_buffer = FrameBuffer(self.texture, RenderBuffer((500, 1500))) self.program['texture'] = self.texture self.text = text self.make_text(self.text) self.first = True
class Renderer3D: def __init__(self): self.default_prog = None self.fbuffer = None self.fbuffer_tex_front = None self.fbuffer_tex_back = None self.vertex_buffer = None self.index_buffer = None ## Renderer Globals: USEFUL CONSTANTS self.COLOR_WHITE = (1, 1, 1, 1) self.COLOR_BLACK = (0, 0, 0, 1) self.COLOR_DEFAULT_BG = (0.8, 0.8, 0.8, 1.0) ## Renderer Globals: STYLE/MATERIAL PROPERTIES ## self.background_color = self.COLOR_DEFAULT_BG self.fill_color = self.COLOR_WHITE self.fill_enabled = True self.stroke_color = self.COLOR_BLACK self.stroke_enabled = True self.tint_color = self.COLOR_BLACK self.tint_enabled = False ## Renderer Globals: Curves self.stroke_weight = 1 self.stroke_cap = 2 self.stroke_join = 0 ## Renderer Globals ## VIEW MATRICES, ETC ## self.viewport = None self.texture_viewport = None self.transform_matrix = np.identity(4) self.projection_matrix = np.identity(4) self.lookat_matrix = np.identity(4) ## Renderer Globals: RENDERING self.draw_queue = [] def initialize_renderer(self): self.fbuffer = FrameBuffer() vertices = np.array( [[-1.0, -1.0], [+1.0, -1.0], [-1.0, +1.0], [+1.0, +1.0]], np.float32) texcoords = np.array([[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]], dtype=np.float32) self.fbuf_vertices = VertexBuffer(data=vertices) self.fbuf_texcoords = VertexBuffer(data=texcoords) self.fbuffer_prog = Program(src_fbuffer.vert, src_fbuffer.frag) self.fbuffer_prog['texcoord'] = self.fbuf_texcoords self.fbuffer_prog['position'] = self.fbuf_vertices self.vertex_buffer = VertexBuffer() self.index_buffer = IndexBuffer() self.default_prog = Program(src_default.vert, src_default.frag) self.reset_view() def reset_view(self): self.viewport = ( 0, 0, int(builtins.width * builtins.pixel_x_density), int(builtins.height * builtins.pixel_y_density), ) self.texture_viewport = ( 0, 0, builtins.width, builtins.height, ) gloo.set_viewport(*self.viewport) cz = (builtins.height / 2) / math.tan(math.radians(30)) self.projection_matrix = matrix.perspective_matrix( math.radians(60), builtins.width / builtins.height, 0.1 * cz, 10 * cz) self.transform_matrix = np.identity(4) self.default_prog['projection'] = self.projection_matrix.T.flatten() self.default_prog['perspective_matrix'] = self.lookat_matrix.T.flatten( ) self.fbuffer_tex_front = Texture2D( (builtins.height, builtins.width, 3)) self.fbuffer_tex_back = Texture2D((builtins.height, builtins.width, 3)) for buf in [self.fbuffer_tex_front, self.fbuffer_tex_back]: self.fbuffer.color_buffer = buf with self.fbuffer: self.clear() self.fbuffer.depth_buffer = gloo.RenderBuffer( (builtins.height, builtins.width)) def clear(self, color=True, depth=True): """Clear the renderer background.""" gloo.set_state(clear_color=self.background_color) gloo.clear(color=color, depth=depth) def _comm_toggles(self, state=True): gloo.set_state(blend=state) gloo.set_state(depth_test=state) if state: gloo.set_state(blend_func=('src_alpha', 'one_minus_src_alpha')) gloo.set_state(depth_func='lequal') @contextmanager def draw_loop(self): """The main draw loop context manager. """ self.transform_matrix = np.identity(4) self.default_prog['projection'] = self.projection_matrix.T.flatten() self.default_prog['perspective_matrix'] = self.lookat_matrix.T.flatten( ) self.fbuffer.color_buffer = self.fbuffer_tex_back with self.fbuffer: gloo.set_viewport(*self.texture_viewport) self._comm_toggles() self.fbuffer_prog['texture'] = self.fbuffer_tex_front self.fbuffer_prog.draw('triangle_strip') yield self.flush_geometry() self.transform_matrix = np.identity(4) gloo.set_viewport(*self.viewport) self._comm_toggles(False) self.clear() self.fbuffer_prog['texture'] = self.fbuffer_tex_back self.fbuffer_prog.draw('triangle_strip') self.fbuffer_tex_front, self.fbuffer_tex_back = self.fbuffer_tex_back, self.fbuffer_tex_front def _transform_vertices(self, vertices, local_matrix, global_matrix): return np.dot(np.dot(vertices, local_matrix.T), global_matrix.T)[:, :3] def render(self, shape): if isinstance(shape, Geometry): n = len(shape.vertices) tverts = self._transform_vertices( np.hstack([shape.vertices, np.ones((n, 1))]), shape.matrix, self.transform_matrix) edges = shape.edges faces = shape.faces self.add_to_draw_queue('poly', tverts, edges, faces, self.fill_color, self.stroke_color) elif isinstance(shape, PShape): vertices = shape._draw_vertices n, _ = vertices.shape tverts = self._transform_vertices( np.hstack([vertices, np.zeros((n, 1)), np.ones((n, 1))]), shape._matrix, self.transform_matrix) fill = shape.fill.normalized if shape.fill else None stroke = shape.stroke.normalized if shape.stroke else None edges = shape._draw_edges faces = shape._draw_faces if edges is None: print(vertices) print("whale") exit() if 'open' in shape.attribs: overtices = shape._draw_outline_vertices no, _ = overtices.shape toverts = self._transform_vertices( np.hstack([overtices, np.zeros((no, 1)), np.ones((no, 1))]), shape._matrix, self.transform_matrix) self.add_to_draw_queue('poly', tverts, edges, faces, fill, None) self.add_to_draw_queue('path', toverts, edges[:-1], None, None, stroke) else: self.add_to_draw_queue(shape.kind, tverts, edges, faces, fill, stroke) def add_to_draw_queue(self, stype, vertices, edges, faces, fill=None, stroke=None): """Add the given vertex data to the draw queue. :param stype: type of shape to be added. Should be one of {'poly', 'path', 'point'} :type stype: str :param vertices: (N, 3) array containing the vertices to be drawn. :type vertices: np.ndarray :param edges: (N, 2) array containing edges as tuples of indices into the vertex array. This can be None when not appropriate (eg. for points) :type edges: None | np.ndarray :param faces: (N, 3) array containing faces as tuples of indices into the vertex array. For 'point' and 'path' shapes, this can be None :type faces: np.ndarray :param fill: Fill color of the shape as a normalized RGBA tuple. When set to `None` the shape doesn't get a fill (default: None) :type fill: None | tuple :param stroke: Stroke color of the shape as a normalized RGBA tuple. When set to `None` the shape doesn't get stroke (default: None) :type stroke: None | tuple """ fill_shape = self.fill_enabled and not (fill is None) stroke_shape = self.stroke_enabled and not (stroke is None) if fill_shape and stype not in ['point', 'path']: idx = np.array(faces, dtype=np.uint32).ravel() self.draw_queue.append(["triangles", (vertices, idx, fill)]) if stroke_shape: if stype == 'point': idx = np.arange(0, len(vertices), dtype=np.uint32) self.draw_queue.append(["points", (vertices, idx, stroke)]) else: idx = np.array(edges, dtype=np.uint32).ravel() self.draw_queue.append(["lines", (vertices, idx, stroke)]) def flush_geometry(self): """Flush all the shape geometry from the draw queue to the GPU. """ current_queue = [] for index, shape in enumerate(self.draw_queue): current_shape, current_obj = self.draw_queue[index][ 0], self.draw_queue[index][1] # If current_shape is lines, bring it to the front by epsilon # to resolve z-fighting if current_shape == 'lines': # line_transform is used whenever we render lines to break ties in depth # We transform the points to camera space, move them by Z_EPSILON, and them move them back to world space line_transform = inv(self.lookat_matrix).dot( translation_matrix(0, 0, Z_EPSILON).dot(self.lookat_matrix)) vertices = current_obj[0] current_obj = (np.hstack( [vertices, np.ones( (vertices.shape[0], 1))]).dot(line_transform.T)[:, :3], current_obj[1], current_obj[2]) current_queue.append(current_obj) if index < len(self.draw_queue) - 1: if self.draw_queue[index][0] == self.draw_queue[index + 1][0]: continue self.render_default(current_shape, current_queue) current_queue = [] self.draw_queue = [] def render_default(self, draw_type, draw_queue): # 1. Get the maximum number of vertices persent in the shapes # in the draw queue. # if len(draw_queue) == 0: return num_vertices = 0 for vertices, _, _ in draw_queue: num_vertices = num_vertices + len(vertices) # 2. Create empty buffers based on the number of vertices. # data = np.zeros(num_vertices, dtype=[('position', np.float32, 3), ('color', np.float32, 4)]) # 3. Loop through all the shapes in the geometry queue adding # it's information to the buffer. # sidx = 0 draw_indices = [] for vertices, idx, color in draw_queue: num_shape_verts = len(vertices) data['position'][sidx:(sidx + num_shape_verts), ] = np.array(vertices) color_array = np.array([color] * num_shape_verts) data['color'][sidx:sidx + num_shape_verts, :] = color_array draw_indices.append(sidx + idx) sidx += num_shape_verts self.vertex_buffer.set_data(data) self.index_buffer.set_data(np.hstack(draw_indices)) # 4. Bind the buffer to the shader. # self.default_prog.bind(self.vertex_buffer) # 5. Draw the shape using the proper shape type and get rid of # the buffers. # self.default_prog.draw(draw_type, indices=self.index_buffer) def cleanup(self): """Run the clean-up routine for the renderer. This method is called when all drawing has been completed and the program is about to exit. """ self.default_prog.delete() self.fbuffer_prog.delete() self.fbuffer.delete()