Esempio n. 1
0
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
Esempio n. 2
0
    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)
Esempio n. 4
0
    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()
Esempio n. 5
0
 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()
Esempio n. 6
0
File: rain.py Progetto: Zulko/vispy
 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)
Esempio n. 7
0
    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()
Esempio n. 8
0
    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()
Esempio n. 9
0
    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()
Esempio n. 10
0
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()
Esempio n. 11
0
 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)
Esempio n. 12
0
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)
Esempio n. 13
0
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)
Esempio n. 14
0
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)
Esempio n. 15
0
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)
Esempio n. 16
0
    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
Esempio n. 17
0
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 = []
Esempio n. 18
0
 def _prepare_draw(self, view):
     self.shared_program['a_position'] = VertexBuffer(self._vert)
     self.shared_program['a_color'] = VertexBuffer(self._color)
Esempio n. 19
0
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)
Esempio n. 20
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()
Esempio n. 21
0
    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))
Esempio n. 22
0
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)
Esempio n. 23
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()
Esempio n. 24
0
    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)
Esempio n. 25
0
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)
Esempio n. 26
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)
Esempio n. 27
0
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)
Esempio n. 28
0
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)
Esempio n. 29
0
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]
Esempio n. 30
0
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
Esempio n. 31
0
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()
Esempio n. 32
0
    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
Esempio n. 34
0
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]
Esempio n. 35
0
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()
Esempio n. 36
0
    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()
Esempio n. 37
0
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.')
Esempio n. 38
0
# 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)
Esempio n. 39
0
    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