class Canvas(app.Canvas): def __init__(self): app.Canvas.__init__(self, title='Rain [Move mouse]', size=(512, 512), keys='interactive') # Build data # -------------------------------------- n = 500 self.data = np.zeros(n, [('a_position', np.float32, 2), ('a_fg_color', np.float32, 4), ('a_size', np.float32, 1)]) self.index = 0 self.program = Program(vertex, fragment) self.vdata = VertexBuffer(self.data) self.program.bind(self.vdata) self.program['u_antialias'] = 1.00 self.program['u_linewidth'] = 1.00 self.program['u_model'] = np.eye(4, dtype=np.float32) self.program['u_view'] = np.eye(4, dtype=np.float32) self.activate_zoom() gloo.set_clear_color('white') gloo.set_state(blend=True, blend_func=('src_alpha', 'one_minus_src_alpha')) self.timer = app.Timer('auto', self.on_timer, start=True) self.show() def on_draw(self, event): gloo.clear() self.program.draw('points') def on_resize(self, event): self.activate_zoom() def activate_zoom(self): gloo.set_viewport(0, 0, *self.physical_size) projection = ortho(0, self.size[0], 0, self.size[1], -1, +1) self.program['u_projection'] = projection def on_timer(self, event): self.data['a_fg_color'][..., 3] -= 0.01 self.data['a_size'] += 1.0 self.vdata.set_data(self.data) self.update() def on_mouse_move(self, event): x, y = event.pos h = self.size[1] self.data['a_position'][self.index] = x, h - y self.data['a_size'][self.index] = 5 self.data['a_fg_color'][self.index] = 0, 0, 0, 1 self.index = (self.index + 1) % 500
def __init__(self): app.Canvas.__init__(self, title='Rain [Move mouse]', size=(512, 512), keys='interactive') # Build data # -------------------------------------- n = 500 self.data = np.zeros(n, [('a_position', np.float32, 2), ('a_fg_color', np.float32, 4), ('a_size', np.float32, 1)]) self.index = 0 self.program = Program(vertex, fragment) self.vdata = VertexBuffer(self.data) self.program.bind(self.vdata) self.program['u_antialias'] = 1.00 self.program['u_linewidth'] = 1.00 self.program['u_model'] = np.eye(4, dtype=np.float32) self.program['u_view'] = np.eye(4, dtype=np.float32) self.activate_zoom() gloo.set_clear_color('white') gloo.set_state(blend=True, blend_func=('src_alpha', 'one_minus_src_alpha')) self.timer = app.Timer('auto', self.on_timer, start=True) self.show()
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, radius, **kwargs): self._vbo = VertexBuffer() # self._v_size_var = Variable('varying float v_size') self._data = None Visual.__init__(self, vcode=vert, fcode=frag) # m_window_h / tanf(m_fov * 0.5f * (float)M_PI / 180.0) self.view_program["pointScale"] = 600 / np.tan(60 * 0.5 * np.pi / 180) self.view_program["pointRadius"] = radius self.set_gl_state(depth_test=True, blend=False) self._draw_mode = 'points' if len(kwargs) > 0: self.set_data(**kwargs) self.freeze()
def __init__(self, **kwargs): self._vbo = VertexBuffer() self._v_size_var = Variable('varying float v_size') self._symbol = None self._marker_fun = None self._data = None self.antialias = 1 self.scaling = False Visual.__init__(self, vcode=vert, fcode=frag) self.shared_program.vert['v_size'] = self._v_size_var self.shared_program.frag['v_size'] = self._v_size_var self.set_gl_state(depth_test=True, blend=True, blend_func=('src_alpha', 'one_minus_src_alpha')) self._draw_mode = 'points' if len(kwargs) > 0: self.set_data(**kwargs) self.freeze()
def on_initialize(self, event): # Build data # -------------------------------------- n = 500 self.data = np.zeros(n, [('a_position', np.float32, 2), ('a_fg_color', np.float32, 4), ('a_size', np.float32, 1)]) self.index = 0 self.program = Program(vertex, fragment) self.vdata = VertexBuffer(self.data) self.program.bind(self.vdata) self.program['u_antialias'] = 1.00 self.program['u_linewidth'] = 1.00 self.program['u_model'] = np.eye(4, dtype=np.float32) self.program['u_view'] = np.eye(4, dtype=np.float32) gloo.set_clear_color('white') gloo.set_state(blend=True, blend_func=('src_alpha', 'one_minus_src_alpha')) self.timer = app.Timer('auto', self.on_timer, start=True)
def __init__(self, data_handler, parent): app.Canvas.__init__(self, size=(512, 512), title='map', keys='interactive') self.__parent__ = parent self.__data_handler = data_handler ##read building data ##fetch single building #building1 = self.__sh.get_single_at_id(1595) #nya #building2 = self.__sh.get_single_at_id(1999) #stryk #building3 = self.__sh.get_single_at_id(1886) #strykbrada #building4 = self.__sh.get_single_at_id(1722) #Gula vid bron #building5 = self.__sh.get_single_at_id(1723) # ##and its vertices #verts1 = building1.gl_ready_vertices() #verts2 = building2.gl_ready_vertices() #verts3 = building3.gl_ready_vertices() #verts4 = building4.gl_ready_vertices() #verts5 = building5.gl_ready_vertices() self.all_buildings = self.__data_handler.get_all_buildings() all_pos = self.all_buildings[0].gl_ready_vertices() for building in self.all_buildings[1:]: all_pos = np.vstack((all_pos, building.gl_ready_vertices())) abets = self.__data_handler.get_single_at_id(1886) abets = abets.gl_ready_vertices() #Problem: 977, 1061, 1342(tva stora hal), 1389(hal), 1393(hal), 1434(hal), 1327(rese) #Error 1072, 1327(rese). 1472(?!?!?), 1504(!?!?!) #for j in range(100000): # for i in range(1503, 1504): #print("----------------\n----------------",i) #all_pos = all_buildings[i].gl_ready_vertices() self.translate_by = np.mean(abets[:], 0) self.scale = 1.0 all_pos = (all_pos - self.translate_by) * self.scale allverts = np.zeros(len(all_pos), [('position', np.float32, 2)]) allverts['position'] = all_pos self.selectedverts = np.zeros(len(abets), [('position', np.float32, 2)]) self.selectedverts['position'] = (abets - self.translate_by) * self.scale self.vertices = VertexBuffer(allverts) self.selectedvertices = VertexBuffer(self.selectedverts) #self.indices = IndexBuffer(I) # Build program self.program = Program(vertex, fragment) self.program.bind(self.vertices) self.cam2 = Cam3D.Camera([0, 0, 250], [0, 0, 0], [0, 1, 0], 45.0, self.size[0] / float(self.size[1]), 0.001, 10000.0, is_2d=True) # Build view, model, projection & normal self.program['model'] = np.eye( 4, dtype=np.float32) #models are at palce from beginnings? self.program['model'] = np.transpose(vut.rotx(-10)) self.program['view'] = self.cam2.view projection = perspective(45.0, self.size[0] / float(self.size[1]), 1.0, 1000.0) self.program['projection'] = projection self.phi, self.theta = 0, 0 gloo.set_state(clear_color=(0.70, 0.70, 0.7, 1.00), depth_test=True) self.set_selected([1999]) self.activate_zoom() self.timer = app.Timer('auto', self.on_timer, start=True) self.show()
def __init__(self, data, origin_x, origin_y, cell_width, cell_height, shape=None, tile_shape=(DEFAULT_TILE_HEIGHT, DEFAULT_TILE_WIDTH), texture_shape=(DEFAULT_TEXTURE_HEIGHT, DEFAULT_TEXTURE_WIDTH), wrap_lon=False, projection=DEFAULT_PROJECTION, cmap='viridis', method='tiled', clim='auto', gamma=1., interpolation='nearest', **kwargs): if method != 'tiled': raise ValueError("Only 'tiled' method is currently supported") method = 'subdivide' grid = (1, 1) # visual nodes already have names, so be careful if not hasattr(self, "name"): self.name = kwargs.get("name", None) self._viewable_mesh_mask = None self._ref1 = None self._ref2 = None self.origin_x = origin_x self.origin_y = origin_y self.cell_width = cell_width self.cell_height = cell_height # Note: cell_height is usually negative self.texture_shape = texture_shape self.tile_shape = tile_shape self.num_tex_tiles = self.texture_shape[0] * self.texture_shape[1] self._stride = (0, 0) # Current stride is None when we are showing the overview self._latest_tile_box = None self.wrap_lon = wrap_lon self._tiles = {} assert (shape or data is not None), "`data` or `shape` must be provided" self.shape = shape or data.shape self.ndim = len(self.shape) or data.ndim # Where does this image lie in this lonely world self.calc = TileCalculator( self.name, self.shape, Point(x=self.origin_x, y=self.origin_y), Resolution(dy=abs(self.cell_height), dx=abs(self.cell_width)), self.tile_shape, self.texture_shape, wrap_lon=self.wrap_lon, projection=projection, ) # What tiles have we used and can we use self.texture_state = TextureTileState(self.num_tex_tiles) # load 'float packed rgba8' interpolation kernel # to load float interpolation kernel use # `load_spatial_filters(packed=False)` kernel, self._interpolation_names = load_spatial_filters() self._kerneltex = Texture2D(kernel, interpolation='nearest') # The unpacking can be debugged by changing "spatial-filters.frag" # to have the "unpack" function just return the .r component. That # combined with using the below as the _kerneltex allows debugging # of the pipeline # self._kerneltex = Texture2D(kernel, interpolation='linear', # internalformat='r32f') # create interpolation shader functions for available # interpolations fun = [Function(_interpolation_template % n) for n in self._interpolation_names] self._interpolation_names = [n.lower() for n in self._interpolation_names] self._interpolation_fun = dict(zip(self._interpolation_names, fun)) self._interpolation_names.sort() self._interpolation_names = tuple(self._interpolation_names) # overwrite "nearest" and "bilinear" spatial-filters # with "hardware" interpolation _data_lookup_fn self._interpolation_fun['nearest'] = Function(_texture_lookup) self._interpolation_fun['bilinear'] = Function(_texture_lookup) if interpolation not in self._interpolation_names: raise ValueError("interpolation must be one of %s" % ', '.join(self._interpolation_names)) self._interpolation = interpolation # check texture interpolation if self._interpolation == 'bilinear': texture_interpolation = 'linear' else: texture_interpolation = 'nearest' self._method = method self._grid = grid self._need_texture_upload = True self._need_vertex_update = True self._need_colortransform_update = True self._need_interpolation_update = True self._texture = TextureAtlas2D(self.texture_shape, tile_shape=self.tile_shape, interpolation=texture_interpolation, format="LUMINANCE", internalformat="R32F", ) self._subdiv_position = VertexBuffer() self._subdiv_texcoord = VertexBuffer() # impostor quad covers entire viewport vertices = np.array([[-1, -1], [1, -1], [1, 1], [-1, -1], [1, 1], [-1, 1]], dtype=np.float32) self._impostor_coords = VertexBuffer(vertices) self._null_tr = NullTransform() self._init_view(self) super(ImageVisual, self).__init__(vcode=VERT_SHADER, fcode=FRAG_SHADER) self.set_gl_state('translucent', cull_face=False) self._draw_mode = 'triangles' # define _data_lookup_fn as None, will be setup in # self._build_interpolation() self._data_lookup_fn = None self.gamma = gamma self.clim = clim if clim != 'auto' else (np.nanmin(data), np.nanmax(data)) self._texture_LUT = None self.cmap = cmap self.overview_info = None self.init_overview(data) # self.transform = PROJ4Transform(projection, inverse=True) self.freeze()
def __init__( self, data=None, method='auto', grid=(1, 1), cmap='viridis', clim='auto', gamma=1.0, interpolation='nearest', **kwargs, ): self._data = None self._gamma = gamma # load 'float packed rgba8' interpolation kernel # to load float interpolation kernel use # `load_spatial_filters(packed=False)` kernel, self._interpolation_names = load_spatial_filters() self._kerneltex = Texture2D(kernel, interpolation='nearest') # The unpacking can be debugged by changing "spatial-filters.frag" # to have the "unpack" function just return the .r component. That # combined with using the below as the _kerneltex allows debugging # of the pipeline # self._kerneltex = Texture2D(kernel, interpolation='linear', # internalformat='r32f') # create interpolation shader functions for available # interpolations fun = [ Function(_interpolation_template % n) for n in self._interpolation_names ] self._interpolation_names = [ n.lower() for n in self._interpolation_names ] self._interpolation_fun = dict(zip(self._interpolation_names, fun)) self._interpolation_names.sort() self._interpolation_names = tuple(self._interpolation_names) # overwrite "nearest" and "bilinear" spatial-filters # with "hardware" interpolation _data_lookup_fn self._interpolation_fun['nearest'] = Function(_texture_lookup) self._interpolation_fun['bilinear'] = Function(_texture_lookup) if interpolation not in self._interpolation_names: raise ValueError( "interpolation must be one of %s" % ', '.join(self._interpolation_names) ) self._interpolation = interpolation # check texture interpolation if self._interpolation == 'bilinear': texture_interpolation = 'linear' else: texture_interpolation = 'nearest' self._method = method self._grid = grid self._texture_limits = None self._need_texture_upload = True self._need_vertex_update = True self._need_colortransform_update = True self._need_interpolation_update = True self._texture = Texture2D( np.zeros((1, 1, 4)), interpolation=texture_interpolation ) self._subdiv_position = VertexBuffer() self._subdiv_texcoord = VertexBuffer() # impostor quad covers entire viewport vertices = np.array( [[-1, -1], [1, -1], [1, 1], [-1, -1], [1, 1], [-1, 1]], dtype=np.float32, ) self._impostor_coords = VertexBuffer(vertices) self._null_tr = NullTransform() self._init_view(self) super(ImageVisual, self).__init__(vcode=VERT_SHADER, fcode=FRAG_SHADER) self.set_gl_state('translucent', cull_face=False) self._draw_mode = 'triangles' # define _data_lookup_fn as None, will be setup in # self._build_interpolation() self._data_lookup_fn = None self.clim = clim self.cmap = cmap if data is not None: self.set_data(data) self.freeze()
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()
def vertex_time(self, v_time): self._vertex_time = np.array(v_time).reshape(-1, 1).astype(np.float32) self.vshader['a_vertex_time'] = VertexBuffer(self.vertex_time)
class TextureFilter(Filter): """Filter to apply a texture to a mesh. Note the texture is applied by multiplying the texture color by the Visual's produced color. By specifying `color="white"` when creating a `MeshVisual` the result will be the unaltered texture value. Any other color, including the default, will result in a blending of that color and the color of the texture. """ def __init__(self, texture, texcoords, enabled=True): """Apply a texture on a mesh. Parameters ---------- texture : (M, N) or (M, N, C) array The 2D texture image. texcoords : (N, 2) array The texture coordinates. enabled : bool Whether the display of the texture is enabled. """ vfunc = Function(""" void pass_coords() { $v_texcoords = $texcoords; } """) ffunc = Function(""" void apply_texture() { if ($enabled == 1) { gl_FragColor *= texture2D($u_texture, $texcoords); } } """) self._texcoord_varying = Varying('v_texcoord', 'vec2') vfunc['v_texcoords'] = self._texcoord_varying ffunc['texcoords'] = self._texcoord_varying self._texcoords_buffer = VertexBuffer( np.zeros((0, 2), dtype=np.float32)) vfunc['texcoords'] = self._texcoords_buffer super().__init__(vcode=vfunc, vhook='pre', fcode=ffunc) self.enabled = enabled self.texture = texture self.texcoords = texcoords @property def enabled(self): """True to display the texture, False to disable.""" return self._enabled @enabled.setter def enabled(self, enabled): self._enabled = enabled self.fshader['enabled'] = 1 if enabled else 0 @property def texture(self): """The texture image.""" return self._texture @texture.setter def texture(self, texture): self._texture = texture self.fshader['u_texture'] = Texture2D(texture) @property def texcoords(self): """The texture coordinates as an (N, 2) array of floats.""" return self._texcoords @texcoords.setter def texcoords(self, texcoords): self._texcoords = texcoords self._update_texcoords_buffer(texcoords) def _update_texcoords_buffer(self, texcoords): if not self._attached or self._visual is None: return # FIXME: Indices for texture coordinates might be different than face # indices, although in some cases they are the same. Currently, # vispy.io.read_mesh assumes face indices and texture coordinates are # the same. # TODO: # 1. Add reading and returning of texture coordinate indices in # read_mesh. # 2. Add texture coordinate indices in MeshData from # vispy.geometry.meshdata # 3. Use mesh_data.get_texcoords_indices() here below. tc = texcoords[self._visual.mesh_data.get_faces()] self._texcoords_buffer.set_data(tc, convert=True) def _attach(self, visual): super()._attach(visual) self._update_texcoords_buffer(self._texcoords)
class WireframeFilter(Filter): """Add wireframe to a mesh. The wireframe filter should be attached before the shading filter for the wireframe to be shaded. Parameters ---------- color : str or tuple or Color Line color of the wireframe width : float Line width of the wireframe enabled : bool Whether the wireframe is drawn or not Examples -------- See `examples/basics/scene/mesh_shading.py <https://github.com/vispy/vispy/blob/main/examples/basics/scene/mesh_shading.py>`_ example script. """ def __init__(self, enabled=True, color='black', width=1.0, wireframe_only=False, faces_only=False): self._attached = False self._color = Color(color) self._width = width self._enabled = enabled self._wireframe_only = wireframe_only self._faces_only = faces_only vfunc = Function(wireframe_vertex_template) ffunc = Function(wireframe_fragment_template) self._bc = VertexBuffer(np.zeros((0, 3), dtype=np.float32)) vfunc['bc'] = self._bc super().__init__(vcode=vfunc, fcode=ffunc) self.enabled = enabled @property def enabled(self): """True to enable the display of the wireframe, False to disable.""" return self._enabled @enabled.setter def enabled(self, enabled): self._enabled = enabled self.fshader['enabled'] = 1 if enabled else 0 self._update_data() @property def color(self): """The wireframe color.""" return self._color @color.setter def color(self, color): self._color = Color(color) self._update_data() @property def width(self): """The wireframe width.""" return self._width @width.setter def width(self, width): if width < 0: raise ValueError("width must be greater than zero") self._width = width self._update_data() @property def wireframe_only(self): """Draw only the wireframe and discard the interior of the faces.""" return self._wireframe_only @wireframe_only.setter def wireframe_only(self, wireframe_only): self._wireframe_only = wireframe_only self._update_data() @property def faces_only(self): """Make the wireframe transparent. Draw only the interior of the faces. """ return self._faces_only @faces_only.setter def faces_only(self, faces_only): self._faces_only = faces_only self._update_data() def _update_data(self): if not self.attached: return self.fshader['color'] = self._color.rgba self.fshader['width'] = self._width self.fshader['wireframe_only'] = 1 if self._wireframe_only else 0 self.fshader['faces_only'] = 1 if self._faces_only else 0 if self._visual.mesh_data.is_empty(): n_faces = 0 else: n_faces = len(self._visual.mesh_data.get_faces()) bc = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]], dtype='float') bc = np.tile(bc[None, ...], (n_faces, 1, 1)) self._bc.set_data(bc, convert=True) def on_mesh_data_updated(self, event): self._update_data() def _attach(self, visual): super()._attach(visual) visual.events.data_updated.connect(self.on_mesh_data_updated) def _detach(self, visual): visual.events.data_updated.disconnect(self.on_mesh_data_updated) super()._detach(visual)
class ShadingFilter(Filter): """Apply shading to a :class:`~vispy.visuals.mesh.MeshVisual` using the Phong reflection model. For convenience, a :class:`~vispy.visuals.mesh.MeshVisual` creates and embeds a shading filter when constructed with an explicit `shading` parameter, e.g. `mesh = MeshVisual(..., shading='smooth')`. The filter is then accessible as `mesh.shading_filter`. When attached manually to a :class:`~vispy.visuals.mesh.MeshVisual`, the shading filter should come after any other filter that modifies the base color to be shaded. See the examples below. Parameters ---------- shading : str Shading mode: None, 'flat' or 'smooth'. If None, the shading is disabled. ambient_coefficient : str or tuple or Color Color and intensity of the ambient reflection coefficient (Ka). diffuse_coefficient : str or tuple or Color Color and intensity of the diffuse reflection coefficient (Kd). specular_coefficient : str or tuple or Color Color and intensity of the specular reflection coefficient (Ks). shininess : float The shininess controls the size of specular highlight. The higher, the more localized. Must be greater than or equal to zero. light_dir : array_like Direction of the light. Assuming a directional light. ambient_light : str or tuple or Color Color and intensity of the ambient light. diffuse_light : str or tuple or Color Color and intensity of the diffuse light. specular_light : str or tuple or Color Color and intensity of the specular light. enabled : bool, default=True Whether the filter is enabled at creation time. This can be changed at run time with :obj:`~enabled`. Notes ----- Under the Phong reflection model, the illumination `I` is computed as:: I = I_ambient + mesh_color * I_diffuse + I_specular for each color channel independently. `mesh_color` is the color of the :class:`~vispy.visuals.mesh.MeshVisual`, possibly modified by the filters applied before this one. The ambient, diffuse and specular terms are defined as:: I_ambient = Ka * Ia I_diffuse = Kd * Id * dot(L, N) I_specular = Ks * Is * dot(R, V) ** s with `L` the light direction, assuming a directional light, `N` the normal to the surface at the reflection point, `R` the direction of the reflection, `V` the direction to the viewer, `s` the shininess factor. The `Ka`, `Kd` and `Ks` coefficients are defined as an RGBA color. The RGB components define the color that the surface reflects, and the alpha component (A) defines the intensity/attenuation of the reflection. When applied in the per-channel illumation formulas above, the color component is multiplied by the intensity to obtain the final coefficient, e.g. `Kd = R * A` for the red channel. Similarly, the light intensities, `Ia`, `Id` and `Is`, are defined by RGBA colors, corresponding to the color of the light and its intensity. Examples -------- Define the mesh data for a :class:`vispy.visuals.mesh.MeshVisual`: >>> # A triangle. >>> vertices = np.array([(0, 0, 0), (1, 1, 1), (0, 1, 0)], dtype=float) >>> faces = np.array([(0, 1, 2)], dtype=int) Let the :class:`vispy.visuals.mesh.MeshVisual` create and embed a shading filter: >>> mesh = MeshVisual(vertices, faces, shading='smooth') >>> # Configure the filter afterwards. >>> mesh.shading_filter.shininess = 64 >>> mesh.shading_filter.specular_coefficient = 0.3 Create the shading filter manually and attach it to a :class:`vispy.visuals.mesh.MeshVisual`: >>> # With the default shading parameters. >>> shading_filter = ShadingFilter() >>> mesh = MeshVisual(vertices, faces) >>> mesh.attach(shading_filter) The filter can be configured at creation time and at run time: >>> # Configure at creation time. >>> shading_filter = ShadingFilter( ... # A shiny surface (small specular highlight). ... shininess=250, ... # A blue higlight, at half intensity. ... specular_coefficient=(0, 0, 1, 0.5), ... # Equivalent to `(0.7, 0.7, 0.7, 1.0)`. ... diffuse_coefficient=0.7, ... # Same as `(0.2, 0.3, 0.3, 1.0)`. ... ambient_coefficient=(0.2, 0.3, 0.3), ... ) >>> # Change the configuration at run time. >>> shading_filter.shininess = 64 >>> shading_filter.specular_coefficient = 0.3 Disable the filter temporarily: >>> # Turn off the shading. >>> shading_filter.enabled = False ... # Some time passes... >>> # Turn on the shading again. >>> shading_filter.enabled = True When using the :class:`WireframeFilter`, the wireframe is shaded only if the wireframe filter is attached before the shading filter: >>> shading_filter = ShadingFilter() >>> wireframe_filter = WireframeFilter() >>> # Option 1: Shade the wireframe. >>> mesh1 = MeshVisual(vertices, faces) >>> mesh1.attached(wireframe_filter) >>> mesh1.attached(shading_filter) >>> # Option 2: Do not shade the wireframe. >>> mesh2 = MeshVisual(vertices, faces) >>> mesh2.attached(shading_filter) >>> mesh2.attached(wireframe_filter) See also `examples/basics/scene/mesh_shading.py <https://github.com/vispy/vispy/blob/main/examples/basics/scene/mesh_shading.py>`_ example script. """ def __init__(self, shading='flat', ambient_coefficient=(1, 1, 1, 1), diffuse_coefficient=(1, 1, 1, 1), specular_coefficient=(1, 1, 1, 1), shininess=100, light_dir=(10, 5, -5), ambient_light=(1, 1, 1, .25), diffuse_light=(1, 1, 1, 0.7), specular_light=(1, 1, 1, .25), enabled=True): self._shading = shading self._ambient_coefficient = _as_rgba(ambient_coefficient) self._diffuse_coefficient = _as_rgba(diffuse_coefficient) self._specular_coefficient = _as_rgba(specular_coefficient) self._shininess = shininess self._light_dir = light_dir self._ambient_light = _as_rgba(ambient_light) self._diffuse_light = _as_rgba(diffuse_light) self._specular_light = _as_rgba(specular_light) self._enabled = enabled vfunc = Function(shading_vertex_template) ffunc = Function(shading_fragment_template) self._normals = VertexBuffer(np.zeros((0, 3), dtype=np.float32)) vfunc['normal'] = self._normals super().__init__(vcode=vfunc, fcode=ffunc) @property def enabled(self): """True to enable the filter, False to disable.""" return self._enabled @enabled.setter def enabled(self, enabled): self._enabled = enabled self._update_data() @property def shading(self): """The shading method.""" return self._shading @shading.setter def shading(self, shading): assert shading in (None, 'flat', 'smooth') self._shading = shading self._update_data() @property def light_dir(self): """The light direction.""" return self._light_dir @light_dir.setter def light_dir(self, direction): direction = np.array(direction, float).ravel() if direction.size != 3 or not np.isfinite(direction).all(): raise ValueError('Invalid direction %s' % direction) self._light_dir = tuple(direction) self._update_data() @property def ambient_light(self): """The color and intensity of the ambient light.""" return self._ambient_light @ambient_light.setter def ambient_light(self, light_color): self._ambient_light = _as_rgba(light_color) self._update_data() @property def diffuse_light(self): """The color and intensity of the diffuse light.""" return self._diffuse_light @diffuse_light.setter def diffuse_light(self, light_color): self._diffuse_light = _as_rgba(light_color) self._update_data() @property def specular_light(self): """The color and intensity of the specular light.""" return self._specular_light @specular_light.setter def specular_light(self, light_color): self._specular_light = _as_rgba(light_color) self._update_data() @property def ambient_coefficient(self): """The ambient reflection coefficient.""" return self._ambient_coefficient @ambient_coefficient.setter def ambient_coefficient(self, color): self._ambient_coefficient = _as_rgba(color) self._update_data() @property def diffuse_coefficient(self): """The diffuse reflection coefficient.""" return self._diffuse_coefficient @diffuse_coefficient.setter def diffuse_coefficient(self, diffuse_coefficient): self._diffuse_coefficient = _as_rgba(diffuse_coefficient) self._update_data() @property def specular_coefficient(self): """The specular reflection coefficient.""" return self._specular_coefficient @specular_coefficient.setter def specular_coefficient(self, specular_coefficient): self._specular_coefficient = _as_rgba(specular_coefficient) self._update_data() @property def shininess(self): """The shininess controlling the spread of the specular highlight.""" return self._shininess @shininess.setter def shininess(self, shininess): self._shininess = float(shininess) self._update_data() def _update_data(self): if not self._attached: return self.vshader['light_dir'] = self._light_dir self.fshader['ambient_light'] = self._ambient_light.rgba self.fshader['diffuse_light'] = self._diffuse_light.rgba self.fshader['specular_light'] = self._specular_light.rgba self.fshader['ambient_coefficient'] = self._ambient_coefficient.rgba self.fshader['diffuse_coefficient'] = self._diffuse_coefficient.rgba self.fshader['specular_coefficient'] = self._specular_coefficient.rgba self.fshader['shininess'] = self._shininess self.fshader['flat_shading'] = 1 if self._shading == 'flat' else 0 self.fshader['shading_enabled'] = ( 1 if self._enabled and self._shading is not None else 0 ) normals = self._visual.mesh_data.get_vertex_normals(indexed='faces') self._normals.set_data(normals, convert=True) def on_mesh_data_updated(self, event): self._update_data() def _attach(self, visual): super()._attach(visual) render2scene = visual.transforms.get_transform('render', 'scene') visual2scene = visual.transforms.get_transform('visual', 'scene') scene2doc = visual.transforms.get_transform('scene', 'document') doc2scene = visual.transforms.get_transform('document', 'scene') self.vshader['render2scene'] = render2scene self.vshader['visual2scene'] = visual2scene self.vshader['scene2doc'] = scene2doc self.vshader['doc2scene'] = doc2scene if self._visual.mesh_data is not None: self._update_data() visual.events.data_updated.connect(self.on_mesh_data_updated) def _detach(self, visual): visual.events.data_updated.disconnect(self.on_mesh_data_updated) super()._detach(visual)
class ShadingFilter(Filter): """Filter to apply shading to a mesh. To disable shading, either detach (ex. ``mesh.detach(filter_obj)``) or set the shading type to ``None`` (ex. ``filter_obj.shading = None``). Examples -------- See `examples/basics/scene/mesh_shading.py <https://github.com/vispy/vispy/blob/main/examples/basics/scene/mesh_shading.py>`_ example script. """ def __init__(self, shading='flat', light_dir=(10, 5, -5), light_color=(1, 1, 1, 1), ambient_color=(.3, .3, .3, 1), diffuse_color=(1, 1, 1, 1), specular_color=(1, 1, 1, 1), shininess=100): self._shading = shading self._light_dir = light_dir self._light_color = Color(light_color) self._ambient_color = Color(ambient_color) self._diffuse_color = Color(diffuse_color) self._specular_color = Color(specular_color) self._shininess = shininess vfunc = Function(shading_vertex_template) ffunc = Function(shading_fragment_template) self._normals = VertexBuffer(np.zeros((0, 3), dtype=np.float32)) vfunc['normal'] = self._normals super().__init__(vcode=vfunc, fcode=ffunc) @property def shading(self): """The shading method.""" return self._shading @shading.setter def shading(self, shading): assert shading in (None, 'flat', 'smooth') self._shading = shading self._update_data() @property def light_dir(self): """The light direction.""" return self._light_dir @light_dir.setter def light_dir(self, direction): direction = np.array(direction, float).ravel() if direction.size != 3 or not np.isfinite(direction).all(): raise ValueError('Invalid direction %s' % direction) self._light_dir = tuple(direction) self._update_data() @property def light_color(self): """The light color.""" return self._light_color @light_color.setter def light_color(self, light_color): self._light_color = Color(light_color) self._update_data() @property def diffuse_color(self): """The diffuse light color.""" return self._diffuse_color @diffuse_color.setter def diffuse_color(self, diffuse_color): self._diffuse_color = Color(diffuse_color) self._update_data() @property def specular_color(self): """The specular light color.""" return self._specular_color @specular_color.setter def specular_color(self, specular_color): self._specular_color = Color(specular_color) self._update_data() @property def ambient_color(self): """The ambient color.""" return self._ambient_color @ambient_color.setter def ambient_color(self, color): self._ambient_color = Color(color) self._update_data() @property def shininess(self): """The shininess.""" return self._shininess @shininess.setter def shininess(self, shininess): self._shininess = float(shininess) self._update_data() def _update_data(self): if not self._attached: return self.vshader['light_dir'] = self._light_dir self.fshader['shininess'] = self._shininess self.fshader['light_color'] = self._light_color.rgb self.fshader['ambient_color'] = self._ambient_color.rgb self.fshader['diffuse_color'] = self._diffuse_color.rgb self.fshader['specular_color'] = self._specular_color.rgb self.fshader['flat_shading'] = 1 if self._shading == 'flat' else 0 self.fshader['shading_enabled'] = 1 if self._shading is not None else 0 normals = self._visual.mesh_data.get_vertex_normals(indexed='faces') self._normals.set_data(normals, convert=True) def on_mesh_data_updated(self, event): self._update_data() def _attach(self, visual): super()._attach(visual) render2scene = visual.transforms.get_transform('render', 'scene') visual2scene = visual.transforms.get_transform('visual', 'scene') scene2doc = visual.transforms.get_transform('scene', 'document') doc2scene = visual.transforms.get_transform('document', 'scene') self.vshader['render2scene'] = render2scene self.vshader['visual2scene'] = visual2scene self.vshader['scene2doc'] = scene2doc self.vshader['doc2scene'] = doc2scene if self._visual.mesh_data is not None: self._update_data() visual.events.data_updated.connect(self.on_mesh_data_updated) def _detach(self, visual): visual.events.data_updated.disconnect(self.on_mesh_data_updated) super()._detach(visual)
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 flush_geometry(): """Flush all the shape geometry from the draw queue to the GPU. """ global poly_draw_queue global line_draw_queue global point_draw_queue ## RETAINED MODE RENDERING. # names = ['poly', 'line', 'point'] types = ['triangles', 'lines', 'points'] queues = [poly_draw_queue, line_draw_queue, point_draw_queue] for draw_type, draw_queue, name in zip(types, queues, names): # 1. Get the maximum number of vertices persent in the shapes # in the draw queue. # if len(draw_queue) == 0: continue num_vertices = 0 for shape, _ in draw_queue: num_vertices = num_vertices + len(shape.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 shape, color in draw_queue: num_shape_verts = len(shape.vertices) data['position'][sidx:(sidx + num_shape_verts),] = \ shape.transformed_vertices[:, :3] color_array = np.array([color] * num_shape_verts) data['color'][sidx:sidx + num_shape_verts, :] = color_array if name == 'point': idx = np.arange(0, num_shape_verts, dtype=np.uint32) elif name == 'line': idx = np.array(shape.edges, dtype=np.uint32).ravel() else: idx = np.array(shape.faces, dtype=np.uint32).ravel() draw_indices.append(sidx + idx) sidx += num_shape_verts V = VertexBuffer(data) I = IndexBuffer(np.hstack(draw_indices)) # 4. Bind the buffer to the shader. # default_prog.bind(V) # 5. Draw the shape using the proper shape type and get rid of # the buffers. # default_prog.draw(draw_type, indices=I) V.delete() I.delete() # 6. Empty the draw queue. poly_draw_queue = [] line_draw_queue = [] point_draw_queue = []
def _prepare_draw(self, view): self.shared_program['a_position'] = VertexBuffer(self._vert) self.shared_program['a_color'] = VertexBuffer(self._color)
class SpheresVisual(Visual): """ Visual displaying raytraced spheres. """ def __init__(self, radius, **kwargs): self._vbo = VertexBuffer() # self._v_size_var = Variable('varying float v_size') self._data = None Visual.__init__(self, vcode=vert, fcode=frag) # m_window_h / tanf(m_fov * 0.5f * (float)M_PI / 180.0) self.view_program["pointScale"] = 600 / np.tan(60 * 0.5 * np.pi / 180) self.view_program["pointRadius"] = radius self.set_gl_state(depth_test=True, blend=False) self._draw_mode = 'points' if len(kwargs) > 0: self.set_data(**kwargs) self.freeze() def set_data(self, pos=None, size=10., scaling=False): """ Set the data used to display this visual. Parameters ---------- pos : array The array of locations to display each symbol. size : float or array The symbol size in px. scaling : bool If set to True, marker scales when rezooming. """ assert (isinstance(pos, np.ndarray) and pos.ndim == 2 and pos.shape[1] in (2, 3)) if self._data is None: n = len(pos) data = np.zeros(n, dtype=[('a_position', np.float32, 3), ('a_color', np.float32, 4), ]) data['a_position'][:, :pos.shape[1]] = pos data['a_color'] = 1 data['a_color'][:, :3] = np.random.rand(n, 3) self._data = data else: self._data['a_position'][:, :pos.shape[1]] = pos # self.shared_program['u_antialias'] = self.antialias # XXX make prop self._vbo.set_data(self._data) self.shared_program.bind(self._vbo) self.update() def _prepare_transforms(self, view): xform = view.transforms.get_transform() view.view_program.vert['transform'] = xform xform = view.transforms.get_transform(map_to='render') view.view_program.vert['transform_scene'] = xform def _prepare_draw(self, view): pass def _compute_bounds(self, axis, view): pos = self._data['a_position'] if pos is None: return None if pos.shape[1] > axis: return (pos[:, axis].min(), pos[:, axis].max()) else: return (0, 0)
def __init__(self, nb_boxes, vertices=None, faces=None, vertex_colors=None, face_colors=None, color=(0.5, 0.5, 1, 1), vertex_values=None, meshdata=None, shading=None, mode='triangles', variable_vis=False, **kwargs): # Visual.__init__ -> prepare_transforms() -> uses shading if shading is not None: raise ValueError('"shading" must be "None"') self.shading = shading self._variable_vis = variable_vis if variable_vis: Visual.__init__(self, vcode=vertex_template_vis, fcode=fragment_template_vis, **kwargs) else: Visual.__init__(self, vcode=vertex_template, fcode=fragment_template, **kwargs) self.set_gl_state('translucent', depth_test=True, cull_face=False) # Define buffers self._vertices = VertexBuffer(np.zeros((0, 3), dtype=np.float32)) self._normals = None self._faces = IndexBuffer() self._normals = VertexBuffer(np.zeros((0, 3), dtype=np.float32)) self._ambient_light_color = Color((0.3, 0.3, 0.3, 1.0)) self._light_dir = (10, 5, -5) self._shininess = 1. / 200. self._cmap = get_colormap('cubehelix') self._clim = 'auto' self._mode = mode # Uniform color self._color = Color(color) # primitive mode self._draw_mode = mode # Init self._bounds = None # Note we do not call subclass set_data -- often the signatures # do no match. VarVisMeshVisual.set_data(self, vertices=vertices, faces=faces, vertex_colors=vertex_colors, face_colors=face_colors, color=color, vertex_values=vertex_values, meshdata=meshdata) self.freeze()
def test_attributes(self): program = Program("attribute float A; attribute vec4 B;", "foo") assert ('attribute', 'float', 'A') in program.variables assert ('attribute', 'vec4', 'B') in program.variables assert len(program.variables) == 2 from vispy.gloo import VertexBuffer vbo = VertexBuffer() # Set existing uniforms program['A'] = vbo assert program['A'] == vbo assert 'A' in program._user_variables assert program._user_variables['A'] is vbo # Set data - update existing vbp program['A'] = np.zeros((10, ), np.float32) assert program._user_variables['A'] is vbo # Set data - create new vbo program['B'] = np.zeros((10, 4), np.float32) assert isinstance(program._user_variables['B'], VertexBuffer) # Set non-existent uniforms vbo = VertexBuffer() # new one since old one is now wrong size program['C'] = vbo assert program['C'] == vbo assert 'C' not in program._user_variables assert 'C' in program._pending_variables # C should be taken up when code comes along that mentions it program.set_shaders("attribute float A; attribute vec2 C;", "foo") assert program['C'] == vbo assert 'C' in program._user_variables assert 'C' not in program._pending_variables # Set wrong values self.assertRaises(ValueError, program.__setitem__, 'A', 'asddas') # Set wrong values beforehand program['D'] = "" self.assertRaises(ValueError, program.set_shaders, 'attribute vec3 D;', '') # Set to one value per vertex program.set_shaders("attribute float A; attribute vec2 C;", "foo") program['A'] = 1.0 assert program['A'] == 1.0 program['C'] = 1.0, 2.0 assert all(program['C'] == np.array((1.0, 2.0), np.float32)) # self.assertRaises(ValueError, program.__setitem__, 'A', (1.0, 2.0)) self.assertRaises(ValueError, program.__setitem__, 'C', 1.0) self.assertRaises(ValueError, program.bind, 'notavertexbuffer') program = Program("attribute vec2 C;", "foo") # first code path: no exsting variable self.assertRaises(ValueError, program.__setitem__, 'C', np.ones((2, 10), np.float32)) # second code path: variable exists (VertexBuffer.set_data) program['C'] = np.ones((10, 2), np.float32) self.assertRaises(ValueError, program.__setitem__, 'C', np.ones((2, 10), np.float32))
class MarkersVisual(Visual): """Visual displaying marker symbols.""" def __init__(self, **kwargs): self._vbo = VertexBuffer() self._v_size_var = Variable('varying float v_size') self._symbol = None self._marker_fun = None self._data = None self.antialias = 1 self.scaling = False Visual.__init__(self, vcode=vert, fcode=frag) self.shared_program.vert['v_size'] = self._v_size_var self.shared_program.frag['v_size'] = self._v_size_var self.set_gl_state(depth_test=True, blend=True, blend_func=('src_alpha', 'one_minus_src_alpha')) self._draw_mode = 'points' if len(kwargs) > 0: self.set_data(**kwargs) self.freeze() def set_data(self, pos=None, symbol='o', size=10., edge_width=1., edge_width_rel=None, edge_color='black', face_color='white', scaling=False): """Set the data used to display this visual. Parameters ---------- pos : array The array of locations to display each symbol. symbol : str The style of symbol to draw (see Notes). size : float or array The symbol size in px. edge_width : float | None The width of the symbol outline in pixels. edge_width_rel : float | None The width as a fraction of marker size. Exactly one of `edge_width` and `edge_width_rel` must be supplied. edge_color : Color | ColorArray The color used to draw each symbol outline. face_color : Color | ColorArray The color used to draw each symbol interior. scaling : bool If set to True, marker scales when rezooming. Notes ----- Allowed style strings are: disc, arrow, ring, clobber, square, diamond, vbar, hbar, cross, tailed_arrow, x, triangle_up, triangle_down, and star. """ assert (isinstance(pos, np.ndarray) and pos.ndim == 2 and pos.shape[1] in (2, 3)) if (edge_width is not None) + (edge_width_rel is not None) != 1: raise ValueError('exactly one of edge_width and edge_width_rel ' 'must be non-None') if edge_width is not None: if edge_width < 0: raise ValueError('edge_width cannot be negative') else: if edge_width_rel < 0: raise ValueError('edge_width_rel cannot be negative') self.symbol = symbol self.scaling = scaling edge_color = ColorArray(edge_color).rgba if len(edge_color) == 1: edge_color = edge_color[0] face_color = ColorArray(face_color).rgba if len(face_color) == 1: face_color = face_color[0] n = len(pos) data = np.zeros(n, dtype=[('a_position', np.float32, 3), ('a_fg_color', np.float32, 4), ('a_bg_color', np.float32, 4), ('a_size', np.float32, 1), ('a_edgewidth', np.float32, 1)]) data['a_fg_color'] = edge_color data['a_bg_color'] = face_color if edge_width is not None: data['a_edgewidth'] = edge_width else: data['a_edgewidth'] = size * edge_width_rel data['a_position'][:, :pos.shape[1]] = pos data['a_size'] = size self.shared_program['u_antialias'] = self.antialias # XXX make prop self._data = data self._vbo.set_data(data) self.shared_program.bind(self._vbo) self.update() @property def symbol(self): return self._symbol @symbol.setter def symbol(self, symbol): if symbol == self._symbol: return self._symbol = symbol if symbol is None: self._marker_fun = None else: _check_valid('symbol', symbol, marker_types) self._marker_fun = Function(_marker_dict[symbol]) self._marker_fun['v_size'] = self._v_size_var self.shared_program.frag['marker'] = self._marker_fun self.update() def _prepare_transforms(self, view): xform = view.transforms.get_transform() view.view_program.vert['transform'] = xform def _prepare_draw(self, view): if self._symbol is None: return False view.view_program['u_px_scale'] = view.transforms.pixel_scale if self.scaling: tr = view.transforms.get_transform('visual', 'document').simplified scale = np.linalg.norm((tr.map([1, 0]) - tr.map([0, 0]))[:2]) view.view_program['u_scale'] = scale else: view.view_program['u_scale'] = 1 from vispy.gloo import gl view.view_program['u_viewport'] = gl.glGetParameter(gl.GL_VIEWPORT) view.view_program['u_SimulatePointCoord'] = True def _compute_bounds(self, axis, view): pos = self._data['a_position'] if pos is None: return None if pos.shape[1] > axis: return (pos[:, axis].min(), pos[:, axis].max()) else: return (0, 0)
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 render(self, shape): 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 stroke_weight = shape.stroke_weight stroke_cap = shape.stroke_cap stroke_join = shape.stroke_join edges = shape._draw_edges faces = shape._draw_faces if edges is None: print(vertices) print("whale") exit() if 'open' in shape.attribs: self.add_to_draw_queue('poly', tverts, edges[:-1], faces, fill, stroke, stroke_weight, stroke_cap, stroke_join) else: self.add_to_draw_queue(shape.kind, tverts, edges, faces, fill, stroke, stroke_weight, stroke_cap, stroke_join) def add_to_draw_queue(self, stype, vertices, edges, faces, fill=None, stroke=None, stroke_weight=None, stroke_cap=None, stroke_join=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: self.draw_queue.append([ "lines", (vertices, edges, stroke, stroke_weight, stroke_cap, stroke_join) ]) def flush_geometry(self): """Flush all the shape geometry from the draw queue to the GPU. """ current_shape = None 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 index < len(self.draw_queue) - 1: if self.draw_queue[index][0] == self.draw_queue[index + 1][0]: continue if current_shape == "points" or current_shape == "triangles": self.render_default(current_shape, current_queue) elif current_shape == "lines": self.render_line(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 on_initialize(self, event): # Build cube data # -------------------------------------- texcoord = [(0, 0), (0, 1), (1, 0), (1, 1)] vertices = [(-1, -1, 0), (-1, +1, 0), (+1, -1, 0), (+1, +1, 0)] vertices = VertexBuffer(vertices) #self.listener.waitForTransform("/robot", "/wrist_joint", rospy.Time(), rospy.Duration(4)) """while not rospy.is_shutdown(): try: now = rospy.Time.now() self.listener.waitForTransform("/wrist_joint", "/robot", rospy.Time(), rospy.Duration(4)) (trans,rot) = listener.lookupTransform("/wrist_joint", "/robot", rospy.Time(), rospy.Duration(4)) """ #pos, rot = self.listener.lookupTransform("/wrist_joint", "/robot", rospy.Time(0)) #print list(pos) camera_pitch = 0.0 # Degrees self.rotate = [camera_pitch, 0, 0] #self.translate = list(pos) self.translate = [0, 0, -5] # Build program # -------------------------------------- view = np.eye(4, dtype=np.float32) model = np.eye(4, dtype=np.float32) scale(model, 10, 10, 10) self.phi = 0 self.cube = Program(cube_vertex, cube_fragment) self.cube['position'] = vertices # 4640 x 2256 imtex = cv2.imread(os.path.join(img_path, 'rubixFront.jpg')) self.cube['texcoord'] = texcoord self.cube["texture"] = np.uint8(np.clip(imtex + np.random.randint(-60, 20, size=imtex.shape), 0, 255)) + 5 self.cube["texture"].interpolation = 'linear' self.cube['model'] = model self.cube['view'] = view color = Texture2D((640, 640, 3), interpolation='linear') self.framebuffer = FrameBuffer(color, RenderBuffer((640, 640))) self.quad = Program(quad_vertex, quad_fragment) 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 self.objects = [self.cube] # 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.pub_timer = app.Timer(0.1, connect=self.send_ros_img, start=True) self._set_projection(self.size)
class WindbarbVisual(Visual): """Visual displaying windbarbs.""" def __init__(self, **kwargs): self._vbo = VertexBuffer() self._v_size_var = Variable('varying float v_size') self._marker_fun = None self._data = None Visual.__init__(self, vcode=vert, fcode=frag) self.shared_program.vert['v_size'] = self._v_size_var self.shared_program.frag['v_size'] = self._v_size_var self.set_gl_state(depth_test=True, blend=True, blend_func=('src_alpha', 'one_minus_src_alpha')) self._draw_mode = 'points' if len(kwargs) > 0: self.set_data(**kwargs) self.freeze() def set_data(self, pos=None, wind=None, trig=True, size=50., antialias=1., edge_width=1., edge_color='black', face_color='white'): """Set the data used to display this visual. Parameters ---------- pos : array The array of locations to display each windbarb. wind : array The array of wind vector components to display each windbarb. in m/s. For knots divide by two. trig : bool True - wind contains (mag, ang) False - wind contains (u, v) defaults to True size : float or array The windbarb size in px. antialias : float The antialiased area (in pixels). edge_width : float | None The width of the windbarb outline in pixels. edge_color : Color | ColorArray The color used to draw each symbol outline. face_color : Color | ColorArray The color used to draw each symbol interior. """ assert (isinstance(pos, np.ndarray) and pos.ndim == 2 and pos.shape[1] in (2, 3)) assert (isinstance(wind, np.ndarray) and pos.ndim == 2 and pos.shape[1] == 2) if edge_width < 0: raise ValueError('edge_width cannot be negative') # since the windbarb starts in the fragment center, # we need to multiply by 2 for correct length size *= 2 edge_color = ColorArray(edge_color).rgba if len(edge_color) == 1: edge_color = edge_color[0] face_color = ColorArray(face_color).rgba if len(face_color) == 1: face_color = face_color[0] n = len(pos) data = np.zeros(n, dtype=[('a_position', np.float32, 3), ('a_wind', np.float32, 2), ('a_trig', np.float32, 0), ('a_fg_color', np.float32, 4), ('a_bg_color', np.float32, 4), ('a_size', np.float32), ('a_edgewidth', np.float32)]) data['a_fg_color'] = edge_color data['a_bg_color'] = face_color data['a_edgewidth'] = edge_width data['a_position'][:, :pos.shape[1]] = pos data['a_wind'][:, :wind.shape[1]] = wind if trig: data['a_trig'] = 1. else: data['a_trig'] = 0. data['a_size'] = size self.shared_program['u_antialias'] = antialias self._data = data self._vbo.set_data(data) self.shared_program.bind(self._vbo) self.update() def _prepare_transforms(self, view): xform = view.transforms.get_transform() view.view_program.vert['transform'] = xform def _prepare_draw(self, view): view.view_program['u_px_scale'] = view.transforms.pixel_scale view.view_program['u_scale'] = 1 def _compute_bounds(self, axis, view): pos = self._data['a_position'] if pos is None: return None if pos.shape[1] > axis: return (pos[:, axis].min(), pos[:, axis].max()) else: return (0, 0)
def vertex_mask(self, v_mask): if v_mask is None: v_mask = np.ones(self.vertex_time.shape, dtype=np.float32) self._vertex_mask = v_mask.reshape(-1, 1).astype(np.float32) self.vshader['a_vertex_mask'] = VertexBuffer(self.vertex_mask)
glut.glutInitDisplayMode(glut.GLUT_DOUBLE | glut.GLUT_RGBA | glut.GLUT_DEPTH) glut.glutCreateWindow('Rotating Cube') glut.glutReshapeWindow(512, 512) glut.glutReshapeFunc(reshape) glut.glutKeyboardFunc(keyboard) glut.glutDisplayFunc(display) glut.glutTimerFunc(1000 / 60, timer, 60) # Build cube data # -------------------------------------- V = np.zeros(8, [("position", np.float32, 3), ("color", np.float32, 4)]) V["position"] = [[1, 1, 1], [-1, 1, 1], [-1, -1, 1], [1, -1, 1], [1, -1, -1], [1, 1, -1], [-1, 1, -1], [-1, -1, -1]] V["color"] = [[0, 1, 1, 1], [0, 0, 1, 1], [0, 0, 0, 1], [0, 1, 0, 1], [1, 1, 0, 1], [1, 1, 1, 1], [1, 0, 1, 1], [1, 0, 0, 1]] vertices = VertexBuffer(data=V) I = [ 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 5, 6, 0, 6, 1, 1, 6, 7, 1, 7, 2, 7, 4, 3, 7, 3, 2, 4, 7, 6, 4, 6, 5 ] indices = IndexBuffer(I) # Build program # -------------------------------------- program = Program(vertex, fragment) program.bind(vertices) # Build view, model, projection & normal # -------------------------------------- view = np.eye(4, dtype=np.float32)
class ImageVisual(Visual): """Visual subclass displaying an image. Parameters ---------- data : ndarray ImageVisual data. Can be shape (M, N), (M, N, 3), or (M, N, 4). method : str Selects method of rendering image in case of non-linear transforms. Each method produces similar results, but may trade efficiency and accuracy. If the transform is linear, this parameter is ignored and a single quad is drawn around the area of the image. * 'auto': Automatically select 'impostor' if the image is drawn with a nonlinear transform; otherwise select 'subdivide'. * 'subdivide': ImageVisual is represented as a grid of triangles with texture coordinates linearly mapped. * 'impostor': ImageVisual is represented as a quad covering the entire view, with texture coordinates determined by the transform. This produces the best transformation results, but may be slow. grid: tuple (rows, cols) If method='subdivide', this tuple determines the number of rows and columns in the image grid. cmap : str | ColorMap Colormap to use for luminance images. clim : str | tuple Limits to use for the colormap. Can be 'auto' to auto-set bounds to the min and max of the data. gamma : float Gamma to use during colormap lookup. Final color will be cmap(val**gamma). by default: 1. interpolation : str Selects method of image interpolation. Makes use of the two Texture2D interpolation methods and the available interpolation methods defined in vispy/gloo/glsl/misc/spatial_filters.frag * 'nearest': Default, uses 'nearest' with Texture2D interpolation. * 'bilinear': uses 'linear' with Texture2D interpolation. * 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric', 'bicubic', 'catrom', 'mitchell', 'spline16', 'spline36', 'gaussian', 'bessel', 'sinc', 'lanczos', 'blackman' **kwargs : dict Keyword arguments to pass to `Visual`. Notes ----- The colormap functionality through ``cmap`` and ``clim`` are only used if the data are 2D. """ def __init__( self, data=None, method='auto', grid=(1, 1), cmap='viridis', clim='auto', gamma=1.0, interpolation='nearest', **kwargs, ): self._data = None self._gamma = gamma # load 'float packed rgba8' interpolation kernel # to load float interpolation kernel use # `load_spatial_filters(packed=False)` kernel, self._interpolation_names = load_spatial_filters() self._kerneltex = Texture2D(kernel, interpolation='nearest') # The unpacking can be debugged by changing "spatial-filters.frag" # to have the "unpack" function just return the .r component. That # combined with using the below as the _kerneltex allows debugging # of the pipeline # self._kerneltex = Texture2D(kernel, interpolation='linear', # internalformat='r32f') # create interpolation shader functions for available # interpolations fun = [ Function(_interpolation_template % n) for n in self._interpolation_names ] self._interpolation_names = [ n.lower() for n in self._interpolation_names ] self._interpolation_fun = dict(zip(self._interpolation_names, fun)) self._interpolation_names.sort() self._interpolation_names = tuple(self._interpolation_names) # overwrite "nearest" and "bilinear" spatial-filters # with "hardware" interpolation _data_lookup_fn self._interpolation_fun['nearest'] = Function(_texture_lookup) self._interpolation_fun['bilinear'] = Function(_texture_lookup) if interpolation not in self._interpolation_names: raise ValueError( "interpolation must be one of %s" % ', '.join(self._interpolation_names) ) self._interpolation = interpolation # check texture interpolation if self._interpolation == 'bilinear': texture_interpolation = 'linear' else: texture_interpolation = 'nearest' self._method = method self._grid = grid self._texture_limits = None self._need_texture_upload = True self._need_vertex_update = True self._need_colortransform_update = True self._need_interpolation_update = True self._texture = Texture2D( np.zeros((1, 1, 4)), interpolation=texture_interpolation ) self._subdiv_position = VertexBuffer() self._subdiv_texcoord = VertexBuffer() # impostor quad covers entire viewport vertices = np.array( [[-1, -1], [1, -1], [1, 1], [-1, -1], [1, 1], [-1, 1]], dtype=np.float32, ) self._impostor_coords = VertexBuffer(vertices) self._null_tr = NullTransform() self._init_view(self) super(ImageVisual, self).__init__(vcode=VERT_SHADER, fcode=FRAG_SHADER) self.set_gl_state('translucent', cull_face=False) self._draw_mode = 'triangles' # define _data_lookup_fn as None, will be setup in # self._build_interpolation() self._data_lookup_fn = None self.clim = clim self.cmap = cmap if data is not None: self.set_data(data) self.freeze() def set_data(self, image): """Set the data Parameters ---------- image : array-like The image data. """ data = np.asarray(image) if self._data is None or self._data.shape != data.shape: self._need_vertex_update = True self._data = data self._need_texture_upload = True def view(self): v = Visual.view(self) self._init_view(v) return v def _init_view(self, view): # Store some extra variables per-view view._need_method_update = True view._method_used = None @property def clim(self): return self._clim if isinstance(self._clim, str) else tuple(self._clim) @clim.setter def clim(self, clim): if isinstance(clim, str): if clim != 'auto': raise ValueError('clim must be "auto" if a string') self._need_texture_upload = True else: clim = np.array(clim, float) if clim.shape != (2,): raise ValueError('clim must have two elements') if self._texture_limits is not None and ( (clim[0] < self._texture_limits[0]) or (clim[1] > self._texture_limits[1]) ): self._need_texture_upload = True self._clim = clim if self._texture_limits is not None: self.shared_program.frag['color_transform'][1][ 'clim' ] = self.clim_normalized self.update() @property def clim_normalized(self): """Normalize current clims between 0-1 based on last-used texture data range. In _build_texture(), the data is normalized (on the CPU) to 0-1 using ``clim``. During rendering, the frag shader will apply the final contrast adjustment based on the current ``clim``. """ range_min, range_max = self._texture_limits clim_min, clim_max = self.clim clim_min = (clim_min - range_min) / (range_max - range_min) clim_max = (clim_max - range_min) / (range_max - range_min) return clim_min, clim_max @property def cmap(self): return self._cmap @cmap.setter def cmap(self, cmap): self._cmap = get_colormap(cmap) self._need_colortransform_update = True self.update() @property def gamma(self): """The gamma used when rendering the image.""" return self._gamma @gamma.setter def gamma(self, value): """Set gamma used when rendering the image.""" if value <= 0: raise ValueError("gamma must be > 0") self._gamma = float(value) self.shared_program.frag['color_transform'][2]['gamma'] = self._gamma self.update() @property def method(self): return self._method @method.setter def method(self, m): if self._method != m: self._method = m self._need_vertex_update = True self.update() @property def size(self): return self._data.shape[:2][::-1] @property def interpolation(self): return self._interpolation @interpolation.setter def interpolation(self, i): if i not in self._interpolation_names: raise ValueError( "interpolation must be one of %s" % ', '.join(self._interpolation_names) ) if self._interpolation != i: self._interpolation = i self._need_interpolation_update = True self.update() @property def interpolation_functions(self): return self._interpolation_names # The interpolation code could be transferred to a dedicated filter # function in visuals/filters as discussed in #1051 def _build_interpolation(self): """Rebuild the _data_lookup_fn using different interpolations within the shader """ interpolation = self._interpolation self._data_lookup_fn = self._interpolation_fun[interpolation] self.shared_program.frag['get_data'] = self._data_lookup_fn # only 'bilinear' uses 'linear' texture interpolation if interpolation == 'bilinear': texture_interpolation = 'linear' else: # 'nearest' (and also 'bilinear') doesn't use spatial_filters.frag # so u_kernel and shape setting is skipped texture_interpolation = 'nearest' if interpolation != 'nearest': self.shared_program['u_kernel'] = self._kerneltex self._data_lookup_fn['shape'] = self._data.shape[:2][::-1] if self._texture.interpolation != texture_interpolation: self._texture.interpolation = texture_interpolation self._data_lookup_fn['texture'] = self._texture self._need_interpolation_update = False def _build_vertex_data(self): """Rebuild the vertex buffers used for rendering the image when using the subdivide method. """ grid = self._grid w = 1.0 / grid[1] h = 1.0 / grid[0] quad = np.array( [[0, 0, 0], [w, 0, 0], [w, h, 0], [0, 0, 0], [w, h, 0], [0, h, 0]], dtype=np.float32, ) quads = np.empty((grid[1], grid[0], 6, 3), dtype=np.float32) quads[:] = quad mgrid = np.mgrid[0.0 : grid[1], 0.0 : grid[0]].transpose(1, 2, 0) mgrid = mgrid[:, :, np.newaxis, :] mgrid[..., 0] *= w mgrid[..., 1] *= h quads[..., :2] += mgrid tex_coords = quads.reshape(grid[1] * grid[0] * 6, 3) tex_coords = np.ascontiguousarray(tex_coords[:, :2]) vertices = tex_coords * self.size self._subdiv_position.set_data(vertices.astype('float32')) self._subdiv_texcoord.set_data(tex_coords.astype('float32')) self._need_vertex_update = False def _update_method(self, view): """Decide which method to use for *view* and configure it accordingly. """ method = self._method if method == 'auto': if view.transforms.get_transform().Linear: method = 'subdivide' else: method = 'impostor' view._method_used = method if method == 'subdivide': view.view_program['method'] = 0 view.view_program['a_position'] = self._subdiv_position view.view_program['a_texcoord'] = self._subdiv_texcoord elif method == 'impostor': view.view_program['method'] = 1 view.view_program['a_position'] = self._impostor_coords view.view_program['a_texcoord'] = self._impostor_coords else: raise ValueError("Unknown image draw method '%s'" % method) self.shared_program['image_size'] = self.size view._need_method_update = False self._prepare_transforms(view) def _build_texture(self): data = self._data if data.dtype == np.float64: data = data.astype(np.float32) if data.ndim == 2 or data.shape[2] == 1: # deal with clim on CPU b/c of texture depth limits :( # can eventually do this by simulating 32-bit float... maybe clim = self._clim if isinstance(clim, str) and clim == 'auto': clim = np.min(data), np.max(data) clim = np.asarray(clim, dtype=np.float32) data = data - clim[0] # not inplace so we don't modify orig data if clim[1] - clim[0] > 0: data /= clim[1] - clim[0] else: data[:] = 1 if data[0, 0] != 0 else 0 self._clim = np.array(clim) else: # assume that RGB data is already scaled (0, 1) if isinstance(self._clim, str) and self._clim == 'auto': self._clim = (0, 1) self._texture_limits = np.array(self._clim) self._need_colortransform_update = True self._texture.set_data(data) self._need_texture_upload = False def _compute_bounds(self, axis, view): if axis > 1: return (0, 0) else: return (0, self.size[axis]) def _prepare_transforms(self, view): trs = view.transforms prg = view.view_program method = view._method_used if method == 'subdivide': prg.vert['transform'] = trs.get_transform() prg.frag['transform'] = self._null_tr else: prg.vert['transform'] = self._null_tr prg.frag['transform'] = trs.get_transform().inverse def _prepare_draw(self, view): if self._data is None: return False if self._need_interpolation_update: self._build_interpolation() if self._need_texture_upload: self._build_texture() if self._need_colortransform_update: prg = view.view_program grayscale = self._data.ndim == 2 or self._data.shape[2] == 1 self.shared_program.frag[ 'color_transform' ] = _build_color_transform( grayscale, self.clim_normalized, self.gamma, self.cmap ) self._need_colortransform_update = False prg['texture2D_LUT'] = ( self.cmap.texture_lut() if (hasattr(self.cmap, 'texture_lut')) else None ) if self._need_vertex_update: self._build_vertex_data() if view._need_method_update: self._update_method(view)
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: 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) # 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 TiledGeolocatedImageVisual(ImageVisual): def __init__(self, data, origin_x, origin_y, cell_width, cell_height, shape=None, tile_shape=(DEFAULT_TILE_HEIGHT, DEFAULT_TILE_WIDTH), texture_shape=(DEFAULT_TEXTURE_HEIGHT, DEFAULT_TEXTURE_WIDTH), wrap_lon=False, projection=DEFAULT_PROJECTION, cmap='viridis', method='tiled', clim='auto', gamma=1., interpolation='nearest', **kwargs): if method != 'tiled': raise ValueError("Only 'tiled' method is currently supported") method = 'subdivide' grid = (1, 1) # visual nodes already have names, so be careful if not hasattr(self, "name"): self.name = kwargs.get("name", None) self._viewable_mesh_mask = None self._ref1 = None self._ref2 = None self.origin_x = origin_x self.origin_y = origin_y self.cell_width = cell_width self.cell_height = cell_height # Note: cell_height is usually negative self.texture_shape = texture_shape self.tile_shape = tile_shape self.num_tex_tiles = self.texture_shape[0] * self.texture_shape[1] self._stride = (0, 0) # Current stride is None when we are showing the overview self._latest_tile_box = None self.wrap_lon = wrap_lon self._tiles = {} assert (shape or data is not None), "`data` or `shape` must be provided" self.shape = shape or data.shape self.ndim = len(self.shape) or data.ndim # Where does this image lie in this lonely world self.calc = TileCalculator( self.name, self.shape, Point(x=self.origin_x, y=self.origin_y), Resolution(dy=abs(self.cell_height), dx=abs(self.cell_width)), self.tile_shape, self.texture_shape, wrap_lon=self.wrap_lon, projection=projection, ) # What tiles have we used and can we use self.texture_state = TextureTileState(self.num_tex_tiles) # load 'float packed rgba8' interpolation kernel # to load float interpolation kernel use # `load_spatial_filters(packed=False)` kernel, self._interpolation_names = load_spatial_filters() self._kerneltex = Texture2D(kernel, interpolation='nearest') # The unpacking can be debugged by changing "spatial-filters.frag" # to have the "unpack" function just return the .r component. That # combined with using the below as the _kerneltex allows debugging # of the pipeline # self._kerneltex = Texture2D(kernel, interpolation='linear', # internalformat='r32f') # create interpolation shader functions for available # interpolations fun = [Function(_interpolation_template % n) for n in self._interpolation_names] self._interpolation_names = [n.lower() for n in self._interpolation_names] self._interpolation_fun = dict(zip(self._interpolation_names, fun)) self._interpolation_names.sort() self._interpolation_names = tuple(self._interpolation_names) # overwrite "nearest" and "bilinear" spatial-filters # with "hardware" interpolation _data_lookup_fn self._interpolation_fun['nearest'] = Function(_texture_lookup) self._interpolation_fun['bilinear'] = Function(_texture_lookup) if interpolation not in self._interpolation_names: raise ValueError("interpolation must be one of %s" % ', '.join(self._interpolation_names)) self._interpolation = interpolation # check texture interpolation if self._interpolation == 'bilinear': texture_interpolation = 'linear' else: texture_interpolation = 'nearest' self._method = method self._grid = grid self._need_texture_upload = True self._need_vertex_update = True self._need_colortransform_update = True self._need_interpolation_update = True self._texture = TextureAtlas2D(self.texture_shape, tile_shape=self.tile_shape, interpolation=texture_interpolation, format="LUMINANCE", internalformat="R32F", ) self._subdiv_position = VertexBuffer() self._subdiv_texcoord = VertexBuffer() # impostor quad covers entire viewport vertices = np.array([[-1, -1], [1, -1], [1, 1], [-1, -1], [1, 1], [-1, 1]], dtype=np.float32) self._impostor_coords = VertexBuffer(vertices) self._null_tr = NullTransform() self._init_view(self) super(ImageVisual, self).__init__(vcode=VERT_SHADER, fcode=FRAG_SHADER) self.set_gl_state('translucent', cull_face=False) self._draw_mode = 'triangles' # define _data_lookup_fn as None, will be setup in # self._build_interpolation() self._data_lookup_fn = None self.gamma = gamma self.clim = clim if clim != 'auto' else (np.nanmin(data), np.nanmax(data)) self._texture_LUT = None self.cmap = cmap self.overview_info = None self.init_overview(data) # self.transform = PROJ4Transform(projection, inverse=True) self.freeze() @property def gamma(self): return self._gamma @gamma.setter def gamma(self, gamma): self._gamma = gamma if gamma is not None else 1. self._need_texture_upload = True self.update() # @property # def clim(self): # return (self._clim if isinstance(self._clim, string_types) else # tuple(self._clim)) # # @clim.setter # def clim(self, clim): # if isinstance(clim, string_types): # if clim != 'auto': # raise ValueError('clim must be "auto" if a string') # else: # clim = np.array(clim, float) # if clim.shape != (2,): # raise ValueError('clim must have two elements') # self._clim = clim # # FIXME: Is this supposed to be assigned to something?: # self._data_lookup_fn # self._need_clim_update = True # self.update() @property def size(self): # Added to shader program, but not used by subdivide/tiled method return self.shape[-2:][::-1] def init_overview(self, data): """Create and add a low resolution version of the data that is always shown behind the higher resolution image tiles. """ # FUTURE: Actually use this data attribute. For now let the base # think there is data (not None) self._data = ArrayProxy(self.ndim, self.shape) self.overview_info = nfo = {} y_slice, x_slice = self.calc.overview_stride nfo["data"] = data[y_slice, x_slice] # Update kwargs to reflect the new spatial resolution of the overview image nfo["cell_width"] = self.cell_width * x_slice.step nfo["cell_height"] = self.cell_height * y_slice.step # Tell the texture state that we are adding a tile that should never expire and should always exist nfo["texture_tile_index"] = ttile_idx = self.texture_state.add_tile((0, 0, 0), expires=False) self._texture.set_tile_data(ttile_idx, self._normalize_data(nfo["data"])) # Handle wrapping around the anti-meridian so there is a -180/180 continuous image num_tiles = 1 if not self.wrap_lon else 2 tl = TESS_LEVEL * TESS_LEVEL nfo["texture_coordinates"] = np.empty((6 * num_tiles * tl, 2), dtype=np.float32) nfo["vertex_coordinates"] = np.empty((6 * num_tiles * tl, 2), dtype=np.float32) factor_rez, offset_rez = self.calc.calc_tile_fraction(0, 0, Point(np.int64(y_slice.step), np.int64(x_slice.step))) nfo["texture_coordinates"][:6 * tl, :2] = self.calc.calc_texture_coordinates(ttile_idx, factor_rez, offset_rez, tessellation_level=TESS_LEVEL) nfo["vertex_coordinates"][:6 * tl, :2] = self.calc.calc_vertex_coordinates(0, 0, y_slice.step, x_slice.step, factor_rez, offset_rez, tessellation_level=TESS_LEVEL) self._set_vertex_tiles(nfo["vertex_coordinates"], nfo["texture_coordinates"]) def _normalize_data(self, data): if data is not None and data.dtype == np.float64: data = data.astype(np.float32) return data def _build_texture_tiles(self, data, stride, tile_box: Box): """Prepare and organize strided data in to individual tiles with associated information. """ data = self._normalize_data(data) LOG.debug("Uploading texture data for %d tiles (%r)", (tile_box.bottom - tile_box.top) * (tile_box.right - tile_box.left), tile_box) # Tiles start at upper-left so go from top to bottom tiles_info = [] for tiy in range(tile_box.top, tile_box.bottom): for tix in range(tile_box.left, tile_box.right): already_in = (stride, tiy, tix) in self.texture_state # Update the age if already in there # Assume that texture_state does not change from the main thread if this is run in another tex_tile_idx = self.texture_state.add_tile((stride, tiy, tix)) if already_in: # FIXME: we should make a list/set of the tiles we need to add before this continue # Assume we were given a total image worth of this stride y_slice, x_slice = self.calc.calc_tile_slice(tiy, tix, stride) # force a copy of the data from the content array (provided by the workspace) # to a vispy-compatible contiguous float array # this can be a potentially time-expensive operation since content array is # often huge and always memory-mapped, so paging may occur # we don't want this paging deferred until we're back in the GUI thread pushing data to OpenGL! tile_data = np.array(data[y_slice, x_slice], dtype=np.float32) tiles_info.append((stride, tiy, tix, tex_tile_idx, tile_data)) return tiles_info def _set_texture_tiles(self, tiles_info): for tile_info in tiles_info: stride, tiy, tix, tex_tile_idx, data = tile_info self._texture.set_tile_data(tex_tile_idx, data) def _build_vertex_tiles(self, preferred_stride, tile_box: Box): """Rebuild the vertex buffers used for rendering the image when using the subdivide method. SIFT Note: Copied from 0.5.0dev original ImageVisual class """ total_num_tiles = (tile_box.bottom - tile_box.top) * (tile_box.right - tile_box.left) total_overview_tiles = 0 if self.overview_info is not None: # we should be providing an overview image total_overview_tiles = int( self.overview_info["vertex_coordinates"].shape[0] / 6 / (TESS_LEVEL * TESS_LEVEL)) if total_num_tiles <= 0: # we aren't looking at this image # FIXME: What's the correct way to stop drawing here raise RuntimeError("View calculations determined a negative number of tiles are visible") elif total_num_tiles > self.num_tex_tiles - total_overview_tiles: LOG.warning("Current view sees more tiles than can be held in the GPU") # We continue on because there should be an overview image for any tiles that can't be drawn total_num_tiles += total_overview_tiles tex_coords = np.empty((6 * total_num_tiles * (TESS_LEVEL * TESS_LEVEL), 2), dtype=np.float32) vertices = np.empty((6 * total_num_tiles * (TESS_LEVEL * TESS_LEVEL), 2), dtype=np.float32) # What tile are we currently describing out of all the tiles being viewed used_tile_idx = -1 # Set up the overview tile if self.overview_info is not None: # XXX: This completely depends on drawing order, putting it at the end seems to work tex_coords[-6 * total_overview_tiles * TESS_LEVEL * TESS_LEVEL:, :] = \ self.overview_info["texture_coordinates"] vertices[-6 * total_overview_tiles * TESS_LEVEL * TESS_LEVEL:, :] = \ self.overview_info["vertex_coordinates"] LOG.debug("Building vertex data for %d tiles (%r)", total_num_tiles, tile_box) tl = TESS_LEVEL * TESS_LEVEL # Tiles start at upper-left so go from top to bottom for tiy in range(tile_box.top, tile_box.bottom): for tix in range(tile_box.left, tile_box.right): # Update the index here because we have multiple exit/continuation points used_tile_idx += 1 # Check if the tile we want to draw is actually in the GPU # if not (atlas too small?) fill with zeros and keep going if (preferred_stride, tiy, tix) not in self.texture_state: # THIS SHOULD NEVER HAPPEN IF TEXTURE BUILDING IS DONE CORRECTLY AND THE ATLAS IS BIG ENOUGH tile_start = TESS_LEVEL * TESS_LEVEL * used_tile_idx * 6 tile_end = TESS_LEVEL * TESS_LEVEL * (used_tile_idx + 1) * 6 tex_coords[tile_start: tile_end, :] = 0 vertices[tile_start: tile_end, :] = 0 continue # we should have already loaded the texture data in to the GPU so get the index of that texture tex_tile_idx = self.texture_state[(preferred_stride, tiy, tix)] factor_rez, offset_rez = self.calc.calc_tile_fraction(tiy, tix, preferred_stride) tex_coords[tl * used_tile_idx * 6: tl * (used_tile_idx + 1) * 6, :] = \ self.calc.calc_texture_coordinates(tex_tile_idx, factor_rez, offset_rez, tessellation_level=TESS_LEVEL) vertices[tl * used_tile_idx * 6: tl * (used_tile_idx + 1) * 6, :] = self.calc.calc_vertex_coordinates( tiy, tix, preferred_stride[0], preferred_stride[1], factor_rez, offset_rez, tessellation_level=TESS_LEVEL) return vertices, tex_coords def _set_vertex_tiles(self, vertices, tex_coords): self._subdiv_position.set_data(vertices.astype('float32')) self._subdiv_texcoord.set_data(tex_coords.astype('float32')) def determine_reference_points(self): # Image points transformed to canvas coordinates img_cmesh = self.transforms.get_transform().map(self.calc.image_mesh) # Mask any points that are really far off screen (can't be transformed) valid_mask = (np.abs(img_cmesh[:, 0]) < CANVAS_EPSILON) & (np.abs(img_cmesh[:, 1]) < CANVAS_EPSILON) # The image mesh projected to canvas coordinates (valid only) img_cmesh = img_cmesh[valid_mask] # The image mesh of only valid "viewable" projected coordinates img_vbox = self.calc.image_mesh[valid_mask] if not img_cmesh[:, 0].size or not img_cmesh[:, 0].size: self._viewable_mesh_mask = None self._ref1, self._ref2 = None, None return x_cmin, x_cmax = img_cmesh[:, 0].min(), img_cmesh[:, 0].max() y_cmin, y_cmax = img_cmesh[:, 1].min(), img_cmesh[:, 1].max() center_x = (x_cmax - x_cmin) / 2. + x_cmin center_y = (y_cmax - y_cmin) / 2. + y_cmin dist = img_cmesh.copy() dist[:, 0] = center_x - img_cmesh[:, 0] dist[:, 1] = center_y - img_cmesh[:, 1] self._viewable_mesh_mask = valid_mask self._ref1, self._ref2 = get_reference_points(dist, img_vbox) def get_view_box(self): """Calculate shown portion of image and image units per pixel This method utilizes a precomputed "mesh" of relatively evenly spaced points over the entire image space. This mesh is transformed to the canvas space (-1 to 1 user-viewed space) to figure out which portions of the image are currently being viewed and which portions can actually be projected on the viewed projection. While the result of the chosen method may not always be completely accurate, it should work for all possible viewing cases. """ if self._viewable_mesh_mask is None: raise ValueError("Image '%s' is not viewable in this projection" % (self.name,)) # Image points transformed to canvas coordinates img_cmesh = self.transforms.get_transform().map(self.calc.image_mesh) # The image mesh projected to canvas coordinates (valid only) img_cmesh = img_cmesh[self._viewable_mesh_mask] # The image mesh of only valid "viewable" projected coordinates img_vbox = self.calc.image_mesh[self._viewable_mesh_mask] ref_idx_1, ref_idx_2 = get_reference_points(img_cmesh, img_vbox) dx, dy = calc_pixel_size(img_cmesh[(self._ref1, self._ref2), :], img_vbox[(self._ref1, self._ref2), :], self.canvas.size) view_extents = self.calc.calc_view_extents(img_cmesh[ref_idx_1], img_vbox[ref_idx_1], self.canvas.size, dx, dy) return ViewBox(*view_extents, dx=dx, dy=dy) def _get_stride(self, view_box): return self.calc.calc_stride(view_box) def assess(self): """Determine if a retile is needed. Tell workspace we will be needed """ try: view_box = self.get_view_box() preferred_stride = self._get_stride(view_box) tile_box = self.calc.visible_tiles(view_box, stride=preferred_stride, extra_tiles_box=Box(1, 1, 1, 1)) except ValueError: LOG.error("Could not determine viewable image area for '{}'".format(self.name)) return False, self._stride, self._latest_tile_box num_tiles = (tile_box.bottom - tile_box.top) * (tile_box.right - tile_box.left) LOG.debug("Assessment: Prefer '%s' have '%s', was looking at %r, now looking at %r", preferred_stride, self._stride, self._latest_tile_box, tile_box) # If we zoomed out or we panned need_retile = (num_tiles > 0) and (preferred_stride != self._stride or self._latest_tile_box != tile_box) return need_retile, preferred_stride, tile_box def retile(self, data, preferred_stride, tile_box): """Get data from workspace and retile/retexture as needed. """ tiles_info = self._build_texture_tiles(data, preferred_stride, tile_box) vertices, tex_coords = self._build_vertex_tiles(preferred_stride, tile_box) return tiles_info, vertices, tex_coords def set_retiled(self, preferred_stride, tile_box, tiles_info, vertices, tex_coords): self._set_texture_tiles(tiles_info) self._set_vertex_tiles(vertices, tex_coords) # don't update here, the caller will do that # Store the most recent level of detail that we've done self._stride = preferred_stride self._latest_tile_box = tile_box def set_data(self, image): """Set the data Parameters ---------- image : array-like The image data. """ raise NotImplementedError("This image subclass does not support the 'set_data' method") def _build_texture(self): # _build_texture should not be used in this class, use the 2-step # process of '_build_texture_tiles' and '_set_texture_tiles' self._set_clim_vars() self._need_texture_upload = False def _build_vertex_data(self): # _build_vertex_data should not be used in this class, use the 2-step # process of '_build_vertex_tiles' and '_set_vertex_tiles' return def _set_clim_vars(self): self._data_lookup_fn["vmin"] = self._clim[0] self._data_lookup_fn["vmax"] = self._clim[1] self._data_lookup_fn["gamma"] = self._gamma
class VolumeVisual(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. gamma : float Gamma to use during colormap lookup. Final color will be cmap(val**gamma). by default: 1. clim_range_threshold : float When changing the clims, if the new clim range is smaller than this fraction of the last-used texture data range, then it will trigger a rescaling of the texture data. For instance: if the texture data was last scaled from 0-1, and the clims are set to 0.4-0.5, then a texture rescale will be triggered if ``clim_range_threshold < 0.1``. To prevent rescaling, set this value to 0. To *always* rescale, set the value to >= 1. By default, 0.2 emulate_texture : bool Use 2D textures to emulate a 3D texture. OpenGL ES 2.0 compatible, but has lower performance on desktop platforms. interpolation : {'linear', 'nearest'} Selects method of image interpolation. """ _interpolation_names = ['linear', 'nearest'] def __init__( self, vol, clim=None, method='mip', threshold=None, relative_step_size=0.8, cmap='grays', gamma=1.0, clim_range_threshold=0.2, emulate_texture=False, interpolation='linear', ): tex_cls = TextureEmulated3D if emulate_texture else Texture3D # Storage of information of volume self._vol_shape = () self._clim = None self._texture_limits = None self._gamma = gamma self._need_vertex_update = True self._clim_range_threshold = clim_range_threshold # 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._interpolation = interpolation self._tex = tex_cls( (10, 10, 10), interpolation=self._interpolation, 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.shared_program['gamma'] = self._gamma 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, copy=True): """ 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. copy : bool | True Whether to copy the input volume prior to applying clim normalization. """ # 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() # store clims used to normalize _tex data for use in clim_normalized self._texture_limits = self._clim # store volume in case it needs to be renormalized by clim.setter self._last_data = vol self.shared_program['clim'] = self.clim_normalized # Apply clim (copy data by default... see issue #1727) vol = np.array(vol, dtype='float32', copy=copy) if self._clim[1] == self._clim[0]: if self._clim[0] != 0.0: vol *= 1.0 / self._clim[0] elif self._clim[0] > self._clim[1]: vol *= -1 vol += self._clim[1] vol /= self._clim[1] - 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 def rescale_data(self): """Force rescaling of data to the current contrast limits and texture upload. Because Textures are currently 8-bits, and contrast adjustment is done during rendering by scaling the values retrieved from the texture on the GPU (provided that the new contrast limits settings are within the range of the clims used when the last texture was uploaded), posterization may become visible if the contrast limits become *too* small of a fraction of the clims used to normalize the texture. This function is a convenience to "force" rescaling of the Texture data to the current contrast limits range. """ self.set_data(self._last_data, clim=self._clim) self.update() @property def clim(self): """The contrast limits that were applied to the volume data. Volume display is mapped from black to white with these values. Settable via set_data() as well as @clim.setter. """ return self._clim @property def texture_is_inverted(self): if self._texture_limits is not None: return self._texture_limits[1] < self._texture_limits[0] @clim.setter def clim(self, value): """Set contrast limits used when rendering the image. ``value`` should be a 2-tuple of floats (min_clim, max_clim), where each value is within the range set by self.clim. If the new value is outside of the (min, max) range of the clims previously used to normalize the texture data, then data will be renormalized using set_data. """ clim = np.array(value, 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.texture_is_inverted: if (clim[0] > self._texture_limits[0]) or ( clim[1] < self._texture_limits[1]): self.rescale_data() return else: if (clim[0] < self._texture_limits[0]) or ( clim[1] > self._texture_limits[1]): self.rescale_data() return # if the clim range is too small of a percentage of the last-used texture range, # posterization may be visible, so downscale the texture range. range_ratio = np.subtract(*clim) / abs( np.subtract(*self._texture_limits)) if np.abs(range_ratio) < self._clim_range_threshold: self.rescale_data() else: # new clims are within reasonable range of the texture data, just call shader self.shared_program['clim'] = self.clim_normalized self.update() @property def clim_normalized(self): """Normalize current clims between 0-1 based on last-used texture data range. In set_data(), the data is normalized (on the CPU) to 0-1 using ``clim``. During rendering, the frag shader will apply the final contrast adjustment based on the current ``clim``. """ range_min, range_max = self._texture_limits clim0, clim1 = self.clim if self.texture_is_inverted: tex_range = range_min - range_max clim0 = (clim0 - range_max) / tex_range clim1 = (clim1 - range_max) / tex_range else: tex_range = range_max - range_min clim0 = (clim0 - range_min) / tex_range clim1 = (clim1 - range_min) / tex_range return clim0, clim1 @property def gamma(self): """The gamma used when rendering the image.""" return self._gamma @gamma.setter def gamma(self, value): """Set gamma used when rendering the image.""" if value <= 0: raise ValueError("gamma must be > 0") self._gamma = float(value) self.shared_program['gamma'] = self._gamma self.update() @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 interpolation(self): """The interpolation method to use Current options are: * linear: this method is appropriate for most volumes as it creates nice looking visuals. * nearest: this method is appropriate for volumes with discrete data where additional interpolation does not make sense. """ return self._interpolation @interpolation.setter def interpolation(self, interp): if interp not in self._interpolation_names: raise ValueError("interpolation must be one of %s" % ', '.join(self._interpolation_names)) if self._interpolation != interp: self._interpolation = interp self._tex.interpolation = self._interpolation 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.shared_program['texture2D_LUT'] = (self.cmap.texture_lut() if ( hasattr(self.cmap, 'texture_lut')) else None) 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()
def __init__(self, data_arrays, origin_x, origin_y, cell_width, cell_height, shape=None, tile_shape=(DEFAULT_TILE_HEIGHT, DEFAULT_TILE_WIDTH), texture_shape=(DEFAULT_TEXTURE_HEIGHT, DEFAULT_TEXTURE_WIDTH), wrap_lon=False, cmap='viridis', method='tiled', clim='auto', gamma=None, interpolation='nearest', **kwargs): # projection properties to be filled in later self.cell_width = None self.cell_height = None self.origin_x = None self.origin_y = None self.shape = None if method != 'tiled': raise ValueError("Only 'tiled' method is currently supported") method = 'subdivide' grid = (1, 1) # visual nodes already have names, so be careful if not hasattr(self, "name"): self.name = kwargs.get("name", None) self._viewable_mesh_mask = None self._ref1 = None self._ref2 = None self.texture_shape = texture_shape self.tile_shape = tile_shape self.num_tex_tiles = self.texture_shape[0] * self.texture_shape[1] self._stride = 0 # Current stride is None when we are showing the overview self._latest_tile_box = None self.wrap_lon = wrap_lon self._tiles = {} # What tiles have we used and can we use (each texture uses the same 'state') self.texture_state = TextureTileState(self.num_tex_tiles) self.set_channels(data_arrays, shape=shape, cell_width=cell_width, cell_height=cell_height, origin_x=origin_x, origin_y=origin_y) self.ndim = len(self.shape) or [x for x in data_arrays if x is not None][0].ndim self.num_channels = len(data_arrays) # load 'float packed rgba8' interpolation kernel # to load float interpolation kernel use # `load_spatial_filters(packed=False)` kernel, self._interpolation_names = load_spatial_filters() self._kerneltex = Texture2D(kernel, interpolation='nearest') # The unpacking can be debugged by changing "spatial-filters.frag" # to have the "unpack" function just return the .r component. That # combined with using the below as the _kerneltex allows debugging # of the pipeline # self._kerneltex = Texture2D(kernel, interpolation='linear', # internalformat='r32f') # create interpolation shader functions for available # interpolations fun = [Function(_interpolation_template % n) for n in self._interpolation_names] self._interpolation_names = [n.lower() for n in self._interpolation_names] self._interpolation_fun = dict(zip(self._interpolation_names, fun)) self._interpolation_names.sort() self._interpolation_names = tuple(self._interpolation_names) # overwrite "nearest" and "bilinear" spatial-filters # with "hardware" interpolation _data_lookup_fn self._interpolation_fun['nearest'] = Function(_texture_lookup) self._interpolation_fun['bilinear'] = Function(_texture_lookup) if interpolation not in self._interpolation_names: raise ValueError("interpolation must be one of %s" % ', '.join(self._interpolation_names)) self._interpolation = interpolation # check texture interpolation if self._interpolation == 'bilinear': texture_interpolation = 'linear' else: texture_interpolation = 'nearest' self._method = method self._grid = grid self._need_texture_upload = True self._need_vertex_update = True self._need_colortransform_update = False self._need_interpolation_update = True self._textures = [TextureAtlas2D(self.texture_shape, tile_shape=self.tile_shape, interpolation=texture_interpolation, format="LUMINANCE", internalformat="R32F", ) for i in range(self.num_channels) ] self._subdiv_position = VertexBuffer() self._subdiv_texcoord = VertexBuffer() # impostor quad covers entire viewport vertices = np.array([[-1, -1], [1, -1], [1, 1], [-1, -1], [1, 1], [-1, 1]], dtype=np.float32) self._impostor_coords = VertexBuffer(vertices) self._null_tr = NullTransform() self._init_view(self) if self.VERT_SHADER is None or self.FRAG_SHADER is None: raise RuntimeError("No shader specified for this subclass") super(ImageVisual, self).__init__(vcode=self.VERT_SHADER, fcode=self.FRAG_SHADER) self.set_gl_state('translucent', cull_face=False) self._draw_mode = 'triangles' # define _data_lookup_fn as None, will be setup in # self._build_interpolation() self._data_lookup_fns = [Function(_rgb_texture_lookup) for i in range(self.num_channels)] if isinstance(clim, str): if clim != 'auto': raise ValueError("C-limits can only be 'auto' or 2 floats for each provided channel") clim = [clim] * self.num_channels if not isinstance(cmap, (tuple, list)): cmap = [cmap] * self.num_channels assert (len(clim) == self.num_channels) assert (len(cmap) == self.num_channels) _clim = [] _cmap = [] for idx in range(self.num_channels): cl = clim[idx] if cl == 'auto': _clim.append((np.nanmin(data_arrays[idx]), np.nanmax(data_arrays[idx]))) elif cl is None: # Color limits don't matter (either empty channel array or other) _clim.append((0., 1.)) elif isinstance(cl, tuple) and len(cl) == 2: _clim.append(cl) else: raise ValueError("C-limits must be a 2-element tuple or the string 'auto' for each channel provided") cm = cmap[idx] _cmap.append(cm) self.clim = _clim self._texture_LUT = None self.gamma = gamma if gamma is not None else (1.,) * self.num_channels # only set colormap if it isn't None # (useful when a subclass's shader doesn't expect a colormap) if _cmap[0] is not None: self.cmap = _cmap[0] self.overview_info = None self.init_overview(data_arrays) self.freeze()
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
class MeshVisual(Visual): """Mesh visual Parameters ---------- vertices : array-like | None The vertices. faces : array-like | None The faces. vertex_colors : array-like | None Colors to use for each vertex. face_colors : array-like | None Colors to use for each face. color : instance of Color The color to use. vertex_values : array-like | None The values to use for each vertex (for colormapping). meshdata : instance of MeshData | None The meshdata. shading : str | None Shading to use. This uses the :class:`~vispy.visuals.filters.mesh.ShadingFilter` filter introduced in VisPy 0.7. This class provides additional features that are available when the filter is attached manually. See 'examples/basics/scene/mesh_shading.py' for an example. mode : str The drawing mode. **kwargs : dict Keyword arguments to pass to `Visual`. Notes ----- Additional functionality is available through filters. Mesh-specific filters can be found in the :mod:`vispy.visuals.filters.mesh` module. This class emits a `data_updated` event when the mesh data is updated. This is used for example by filters for synchronization. """ def __init__(self, vertices=None, faces=None, vertex_colors=None, face_colors=None, color=(0.5, 0.5, 1, 1), vertex_values=None, meshdata=None, shading=None, mode='triangles', **kwargs): Visual.__init__(self, vcode=vertex_template, fcode=fragment_template, **kwargs) self.set_gl_state('translucent', depth_test=True, cull_face=False) self.events.add(data_updated=Event) # Define buffers self._vertices = VertexBuffer(np.zeros((0, 3), dtype=np.float32)) self._cmap = get_colormap('cubehelix') self._clim = 'auto' # Uniform color self._color = Color(color) # add filters for various modifiers self.shading_filter = None self.shading = shading # Init self._bounds = None # Note we do not call subclass set_data -- often the signatures # do no match. MeshVisual.set_data( self, vertices=vertices, faces=faces, vertex_colors=vertex_colors, face_colors=face_colors, vertex_values=vertex_values, meshdata=meshdata, color=color) # primitive mode self._draw_mode = mode self.freeze() @property def shading(self): """The shading method.""" return self._shading @shading.setter def shading(self, shading): assert shading in (None, 'flat', 'smooth') self._shading = shading if shading is None and self.shading_filter is None: # Delay creation of filter until necessary. return if self.shading_filter is None: from .filters import ShadingFilter self.shading_filter = ShadingFilter(shading=shading) else: self.shading_filter.shading = shading self.attach(self.shading_filter) def set_data(self, vertices=None, faces=None, vertex_colors=None, face_colors=None, color=None, vertex_values=None, meshdata=None): """Set the mesh data Parameters ---------- vertices : array-like | None The vertices. faces : array-like | None The faces. vertex_colors : array-like | None Colors to use for each vertex. face_colors : array-like | None Colors to use for each face. color : instance of Color The color to use. vertex_values : array-like | None Values for each vertex. meshdata : instance of MeshData | None The meshdata. """ if meshdata is not None: self._meshdata = meshdata else: self._meshdata = MeshData(vertices=vertices, faces=faces, vertex_colors=vertex_colors, face_colors=face_colors, vertex_values=vertex_values) self._bounds = self._meshdata.get_bounds() if color is not None: self._color = Color(color) self.mesh_data_changed() @property def clim(self): return (self._clim if isinstance(self._clim, str) else tuple(self._clim)) @clim.setter def clim(self, clim): if isinstance(clim, str): if clim != 'auto': raise ValueError('clim must be "auto" if a string') else: clim = np.array(clim, float) if clim.shape != (2,): raise ValueError('clim must have two elements') self._clim = clim self.mesh_data_changed() @property def _clim_values(self): if isinstance(self._clim, str): # == 'auto' if self._meshdata.has_vertex_value(): clim = self._meshdata.get_vertex_values() clim = (np.min(clim), np.max(clim)) else: clim = (0, 1) else: clim = self._clim return clim @property def cmap(self): return self._cmap @cmap.setter def cmap(self, cmap): self._cmap = get_colormap(cmap) self.mesh_data_changed() @property def mode(self): """The triangle mode used to draw this mesh. Options are: * 'triangles': Draw one triangle for every three vertices (eg, [1,2,3], [4,5,6], [7,8,9) * 'triangle_strip': Draw one strip for every vertex excluding the first two (eg, [1,2,3], [2,3,4], [3,4,5]) * 'triangle_fan': Draw each triangle from the first vertex and the last two vertices (eg, [1,2,3], [1,3,4], [1,4,5]) """ return self._draw_mode @mode.setter def mode(self, m): modes = ['triangles', 'triangle_strip', 'triangle_fan'] if m not in modes: raise ValueError("Mesh mode must be one of %s" % ', '.join(modes)) self._draw_mode = m @property def mesh_data(self): """The mesh data""" return self._meshdata @property def color(self): """The uniform color for this mesh""" return self._color @color.setter def color(self, c): """Set the uniform color of the mesh This value is only used if per-vertex or per-face colors are not specified. Parameters ---------- c : instance of Color The color to use. """ if c is not None: self._color = Color(c) self.mesh_data_changed() def mesh_data_changed(self): self._data_changed = True self.update() def _update_data(self): md = self.mesh_data v = md.get_vertices(indexed='faces') if v is None: return False if v.shape[-1] == 2: v = np.concatenate((v, np.zeros((v.shape[:-1] + (1,)))), -1) self._vertices.set_data(v, convert=True) if md.has_vertex_color(): colors = md.get_vertex_colors(indexed='faces') colors = colors.astype(np.float32) elif md.has_face_color(): colors = md.get_face_colors(indexed='faces') colors = colors.astype(np.float32) elif md.has_vertex_value(): colors = md.get_vertex_values(indexed='faces') colors = colors.ravel()[:, np.newaxis] colors = colors.astype(np.float32) else: colors = self._color.rgba self.shared_program.vert['position'] = self._vertices self.shared_program['texture2D_LUT'] = self._cmap.texture_lut() \ if (hasattr(self._cmap, 'texture_lut')) else None # Position input handling if v.shape[-1] == 2: self.shared_program.vert['to_vec4'] = vec2to4 elif v.shape[-1] == 3: self.shared_program.vert['to_vec4'] = vec3to4 else: raise TypeError("Vertex data must have shape (...,2) or (...,3).") # Set the base color. # # The base color is mixed further by the material filters for texture # or shading effects. self.shared_program.vert['color_transform'] = \ _build_color_transform(colors, self._cmap, self._clim_values) if colors.ndim == 1: self.shared_program.vert['base_color'] = colors else: self.shared_program.vert['base_color'] = VertexBuffer(colors) self._data_changed = False self.events.data_updated() def _prepare_draw(self, view): if self._data_changed: if self._update_data() is False: return False self._data_changed = False def draw(self, *args, **kwds): Visual.draw(self, *args, **kwds) @staticmethod def _prepare_transforms(view): tr = view.transforms.get_transform() view.view_program.vert['transform'] = tr def _compute_bounds(self, axis, view): if self._bounds is None: return None if axis >= len(self._bounds): return (0, 0) else: return self._bounds[axis]
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()
def __init__( self, vol, clim=None, method='mip', threshold=None, relative_step_size=0.8, cmap='grays', gamma=1.0, clim_range_threshold=0.2, emulate_texture=False, interpolation='linear', ): tex_cls = TextureEmulated3D if emulate_texture else Texture3D # Storage of information of volume self._vol_shape = () self._clim = None self._texture_limits = None self._gamma = gamma self._need_vertex_update = True self._clim_range_threshold = clim_range_threshold # 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._interpolation = interpolation self._tex = tex_cls( (10, 10, 10), interpolation=self._interpolation, 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.shared_program['gamma'] = self._gamma 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()
class VarVisMeshVisual(MeshVisual): """Variable Visibility Mesh visual Parameters ---------- vertices : array-like | None The vertices. faces : array-like | None The faces. vertex_colors : array-like | None Colors to use for each vertex. face_colors : array-like | None Colors to use for each face. color : instance of Color The color to use. vertex_values : array-like | None The values to use for each vertex (for colormapping). meshdata : instance of MeshData | None The meshdata. shading : str | None Shading to use. mode : str The drawing mode. variable_vis : bool If instance of UpdatableMeshVisual has variable visibility **kwargs : dict Keyword arguments to pass to `Visual`. """ def __init__(self, nb_boxes, vertices=None, faces=None, vertex_colors=None, face_colors=None, color=(0.5, 0.5, 1, 1), vertex_values=None, meshdata=None, shading=None, mode='triangles', variable_vis=False, **kwargs): # Visual.__init__ -> prepare_transforms() -> uses shading if shading is not None: raise ValueError('"shading" must be "None"') self.shading = shading self._variable_vis = variable_vis if variable_vis: Visual.__init__(self, vcode=vertex_template_vis, fcode=fragment_template_vis, **kwargs) else: Visual.__init__(self, vcode=vertex_template, fcode=fragment_template, **kwargs) self.set_gl_state('translucent', depth_test=True, cull_face=False) # Define buffers self._vertices = VertexBuffer(np.zeros((0, 3), dtype=np.float32)) self._normals = None self._faces = IndexBuffer() self._normals = VertexBuffer(np.zeros((0, 3), dtype=np.float32)) self._ambient_light_color = Color((0.3, 0.3, 0.3, 1.0)) self._light_dir = (10, 5, -5) self._shininess = 1. / 200. self._cmap = get_colormap('cubehelix') self._clim = 'auto' self._mode = mode # Uniform color self._color = Color(color) # primitive mode self._draw_mode = mode # Init self._bounds = None # Note we do not call subclass set_data -- often the signatures # do no match. VarVisMeshVisual.set_data(self, vertices=vertices, faces=faces, vertex_colors=vertex_colors, face_colors=face_colors, color=color, vertex_values=vertex_values, meshdata=meshdata) self.freeze() def set_data(self, vertices=None, faces=None, vertex_colors=None, face_colors=None, color=None, vertex_values=None, meshdata=None): """Set the mesh data Parameters ---------- vertices : array-like | None The vertices. faces : array-like | None The faces. vertex_colors : array-like | None Colors to use for each vertex. face_colors : array-like | None Colors to use for each face. color : instance of Color The color to use. vertex_values : array-like | None Values for each vertex. meshdata : instance of MeshData | None The meshdata. """ if meshdata is not None: self._meshdata = meshdata else: self._meshdata = MeshData(vertices=vertices, faces=faces, vertex_colors=vertex_colors, face_colors=face_colors, vertex_values=vertex_values) self._bounds = self._meshdata.get_bounds() if color is not None: self._color = Color(color) self.mesh_data_changed() if self.variable_vis: # Initialize all faces as visible if self.mode is 'lines': self._visible_verts = np.ones((faces.shape[0], 2, 1), dtype=np.int8) else: self._visible_verts = np.ones((faces.shape[0], 3, 1), dtype=np.int8) # Create visibility VertexBuffer self.vis_buffer = VertexBuffer() self.vis_buffer.set_data(self._visible_verts) self.shared_program.vert['vis_vert'] = self.vis_buffer def set_visible_faces(self, idx_vis): """Set idx_vis indexes of visible_verts to "visible" (1). """ self.visible_verts[idx_vis, :, :] = 1 def set_invisible_faces(self, idx_vis): """Set idx_vis indexes of visible_verts to "invisible" (0). """ self.visible_verts[idx_vis, :, :] = 0 def update_vis_buffer(self): """Update the visibility VertexBuffer. """ self.vis_buffer.set_data(self.visible_verts) self.shared_program.vert['vis_vert'] = self.vis_buffer @property def visible_verts(self): """Bool (float) array indicating which vertices are visible (1) and which are invisible (0). """ return self._visible_verts @visible_verts.setter def visible_verts(self, visible_verts): self._visible_verts = visible_verts @property def variable_vis(self): """Bool if instance of UpdatableMeshVisual posseses variable visibility. """ return self._variable_vis @variable_vis.setter def variable_vis(self, variable_vis): raise ValueError( 'Not allowed to change "variable_vis" after initialization.')
# 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)
def __init__(self, n_volume_max=10, threshold=None, relative_step_size=0.8, emulate_texture=False): # Choose texture class tex_cls = TextureEmulated3D if emulate_texture else Texture3D self._n_volume_max = n_volume_max self._initial_shape = True self._vol_shape = (10, 10, 10) self._need_vertex_update = True # Create OpenGL program vert_shader, frag_shader = get_shaders(n_volume_max) # We deliberately don't use super here because we don't want to call # VolumeVisual.__init__ 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)) 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 and default colormap to shader program 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) # Make sure all textures are disbaled self.shared_program['u_enabled_{0}'.format(i)] = 0 self.shared_program['u_weight_{0}'.format(i)] = 1 self.shared_program['a_position'] = self._vertices self.shared_program['a_texcoord'] = self._texcoord self.shared_program['u_shape'] = self._vol_shape[::-1] 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.volumes = defaultdict(dict) self._data_shape = None self._block_size = np.array([1, 1, 1]) try: self.freeze() except AttributeError: # Older versions of VisPy pass