예제 #1
0
 def SetTexture(self, data):
     """ SetTexture(data)
     
     Set the texture image to map to the mesh.
     Use None as an argument to remove the texture.
     
     """
     if data is not None:
         # Check dimensions
         if data.ndim==2:
             pass # ok: gray image
         elif data.ndim==3 and data.shape[2] in [3, 4]:
             pass # ok: color image
         else:
             raise ValueError('Only 2D images can be mapped to a mesh.')
         # Make texture object and bind
         self._texture = TextureObjectToVisualize(2, data, interpolate=True)
         self._texture.SetData(data)
     else:
         self._texture = None
예제 #2
0
 def __init__(self, parent, data, axis=0, index=0):
     BaseTexture.__init__(self, parent, data)
     self._ndim = 3
     
     # Init parameters
     self._axis = axis
     self._index = index
     
     # create texture
     self._texture1 = TextureObjectToVisualize(2, data)
     
     # init shader
     self._InitShader()
     
     # set data  (data to textureToV. only for min/max)
     self.SetData(data)
     
     # init interpolation
     self._texture1._interpolate = True 
     
     # For edge
     self._edgeColor = None
     self._edgeColor2 = getColor('g')
     self._edgeWidth = 3.0
     
     # For interaction
     self._interact_over = False
     self._interact_down = False
     self._screenVec = None
     self._refPos = (0,0)
     self._refIndex = 0
     #
     self.hitTest = True
     #
     self.eventEnter.Bind(self._OnMouseEnter)
     self.eventLeave.Bind(self._OnMouseLeave)
     self.eventMouseDown.Bind(self._OnMouseDown)
     self.eventMouseUp.Bind(self._OnMouseUp)
     self.eventMotion.Bind(self._OnMouseMotion)
예제 #3
0
class Mesh(Wobject, BaseMesh, Colormapable):
    """ Mesh(parent, vertices, faces=None, normals=None, values=None, verticesPerFace=3)
    
    A mesh is a generic object to visualize a 3D object made up of
    polygons. These polygons can be triangles or quads. The mesh
    is affected by lighting and its material properties can be
    changed using properties. The reference color and shading can
    be set individually for the faces and edges (using the faceColor,
    edgeColor, faceShading and edgeShading properties).
    
    A mesh can also be created from another mesh using Mesh(parent, otherMesh),
    where otherMesh should be an instance of BaseMesh.
    
    The old signature may also be used, but will be removed in future versions:
    Mesh(vertices, normals=None, faces=None,
                                colors=None, texcords=None, verticesPerFace=3)
    
    Parameters
    ----------
    vertices : Nx3 numpy array
        The vertex positions in 3D space.
    faces : (optional) numpy array or list of indices
        Defines the faces. If this array is Nx3 or Nx4, verticesPerFace is
        inferred from this array. Faces should be of uint8, uint16 or
        uint32 (if it is not, the data is converted to uint32).
        The front of the face is defined using the right-hand-rule.
    normals :(optional) Nx3 numpy array
        The vertex normals. If not given, they are calcululated
        from the vertices.
    values : (optional) Nx1, Nx2, Nx3 or Nx4 numpy array
        The value data for each vertex. If Nx1, they represent the indices
        in the colormap. If Nx2, they represent the texture coordinates for
        the texturegiven with SetTexture(). If Nx3 or Nx4 they represent
        the ambient and diffuse color for each vertex.
    verticesPerFace : 3 or 4
        Determines whether the faces are triangles or quads. If faces is
        specified and is 2D, the number of vertices per face is determined
        from that array.
    
    Note on texture mapping
    -----------------------
    The texture color is multiplied after the ambient and diffuse
    lighting calculations, but before calculating the specular component.
    
    """
    
    
    def __init__(self, parent, *args, **kwargs):
        Wobject.__init__(self, parent)
        
        # Init flat normals
        self._flatNormals = None
        
        # Create colormap and init texture
        Colormapable.__init__(self)
        self._texture = None
        
        # create glsl shaders for this wobject. For faces, edges and shape
        self._faceShader = shaders.Shader()
        self._edgeShader = shaders.Shader()
        self._shapeShader = shaders.Shader()
        self._InitShaders()
        self.useNativeShading = False
        
        # Material properties
        self._ambient = 0.7
        self._diffuse = 0.7
        self._specular = 0.3
        self._shininess = 50
        self._emission = 0.0
        
        # Reference colors
        self._faceColor = (1, 1, 1, 1)
        self._edgeColor = (0,0,0,1)
        
        # Shading
        self.faceShading = 'smooth'
        self.edgeShading = None
        
        # What faces to cull
        self._cullFaces = None # gl.GL_BACK (for surf(), None makes most sense)
        
        # Save data
        BaseMesh.__init__(self, *args, **kwargs)
        
        # Store value2, which are like 'values' but clim-corrected
        self._values2 = self._values
    
    
    def _InitShaders(self):
        
        # Give all shaders the base components, plain shading
        for shader in [self.faceShader, self.edgeShader, self.shapeShader]:
            
            # Vertex shader
            shader.vertex.Clear()
            shader.vertex.AddPart(shaders.SH_MV_BASE)
            shader.vertex.AddPart(shaders.SH_MV_SHADING_PLAIN)
            shader.vertex.AddPart(shaders.SH_NLIGHTS_1)
            
            # Fragment shader
            shader.fragment.Clear()
            shader.fragment.AddPart(shaders.SH_MF_BASE)
            shader.fragment.AddPart(shaders.SH_MF_SHADING_PLAIN)
            shader.fragment.AddPart(shaders.SH_MF_ALBEIDO_UNIT)
            shader.fragment.AddPart(shaders.SH_NLIGHTS_1)
        
    
    ## Material properties: how the object is lit
    
    @PropWithDraw
    def ambient():
        """ Get/Set the ambient reflection color of the material. Ambient
        light is the light that is everywhere, coming from all directions,
        independent of the light position.
        
        The value can be a 3- or 4-element tuple, a character in
        "rgbycmkw", or a scalar between 0 and 1 that indicates the
        fraction of the reference color.
        """
        def fget(self):
            return self._ambient
        def fset(self, value):
            self._ambient = _testColor(value)
        return locals()
    
    
    @PropWithDraw
    def diffuse():
        """ Get/Set the diffuse reflection color of the material. Diffuse
        light comes from one direction, so it's brighter if it comes
        squarely down on a surface than if it barely glances off the
        surface. It depends on the light position how a material is lit.
        
        The value can be a 3- or 4-element tuple, a character in
        "rgbycmkw", or a scalar between 0 and 1 that indicates the
        fraction of the reference color.
        """
        def fget(self):
            return self._diffuse
        def fset(self, value):
            self._diffuse = _testColor(value)
        return locals()
    
    
    @PropWithDraw
    def ambientAndDiffuse():
        """ Set the diffuse and ambient component simultaneously. Usually,
        you want to give them the same value. Getting returns the diffuse
        component.
        """
        def fget(self):
            return self._diffuse
        def fset(self, value):
            self._diffuse = self._ambient = _testColor(value)
        return locals()
    
    
    @PropWithDraw
    def specular():
        """ Get/Set the specular reflection color of the material. Specular
        light represents the light that comes from the light source and bounces
        off a surface in a particular direction. It is what makes
        materials appear shiny.
        
        The value can be a 3- or 4-element tuple, a character in
        "rgbycmkw", or a scalar between 0 and 1 that indicates the
        fraction of white (1,1,1).
        """
        def fget(self):
            return self._specular
        def fset(self, value):
            self._specular = _testColor(value)
        return locals()
    
    
    @PropWithDraw
    def shininess():
        """ Get/Set the shininess value of the material as a number between
        0 and 128. The higher the value, the brighter and more focussed the
        specular spot, thus the shinier the material appears to be.
        """
        def fget(self):
            return self._shininess
        def fset(self, value):
            if value < 0: value = 0
            if value > 128: value = 128
            self._shininess = value
        return locals()
    
    
    @PropWithDraw
    def emission():
        """ Get/Set the emission color of the material. It is the
        "self-lighting" property of the material, and usually only makes
        sense for objects that represent lamps or candles etc.
        
        The value can be a 3- or 4-element tuple, a character in
        "rgbycmkw", or a scalar between 0 and 1 that indicates the
        fraction of the reference color.
        """
        def fget(self):
            return self._emission
        def fset(self, value):
            self._emission = _testColor(value)
        return locals()
    
    
    ## Face and edge shading properties, and culling
    
    @PropWithDraw
    def faceColor():
        """ Get/Set the face reference color of the object. If the
        ambient, diffuse or emissive properties specify a scalar, that
        scalar represents the fraction of *this* color for the faces.
        """
        def fget(self):
            return self._faceColor
        def fset(self, value):
            self._faceColor = _testColor(value, True)
        return locals()
    
    
    @PropWithDraw
    def edgeColor():
        """ Get/Set the edge reference color of the object. If the
        ambient, diffuse or emissive properties specify a scalar, that
        scalar represents the fraction of *this* color for the edges.
        """
        def fget(self):
            return self._edgeColor
        def fset(self, value):
            self._edgeColor = _testColor(value, True)
        return locals()
    
    
    @PropWithDraw
    def useNativeShading():
        """ Get/set whether to use the native OpenGl shading. The default
        is False, which means that GLSL-based shading is used, allowing for
        more advanced shader styles.
        
        Note that regardless of the value of this property, native shading
        is used if the hardward does not support GLSL (OpenGl version<2).
        """
        def fget(self):
            return self._useNativeShading
        def fset(self, value):
            self._useNativeShading = bool(value)
        return locals()
    
    
    @PropWithDraw
    def faceShading():
        """ Get/Set the type of shading to apply for the faces.
          * None - Do not show the faces.
          * 'plain' - Display the faces in the faceColor (without lighting).
          * 'flat' - Lit shading uniform for each face
          * 'gouraud' - Lighting is calculated at vertices and interpolated
            over the face.
          * 'smooth' - Lighting is calculated for each fragment (aka
            phong-shading or phong-interpolation).
          * 'toon' - A cartoonish look (aka cel-shading).
        
        Notes
        -----
        In native mode 'smooth' and 'toon' fall back to 'gouraud'.
        In both native and nonnative mode the blinn-phong reflectance model
        is used.
        """
        def fget(self):
            return self._faceShading
        def fset(self, value):
            # Process value
            if value is None:
                self._faceShading = None
            elif isinstance(value, basestring) and (value.lower() in
                    ['plain', 'flat', 'gouraud', 'smooth', 'toon']):
                self._faceShading = value.lower()
            else:
                tmp = "Shading must be None, 'plain', 'flat', 'gouraud, 'smooth' or 'toon.'"
                raise ValueError(tmp)
            
            # Apply for shader code
            self._SetShading(self._faceShading, self.faceShader)
        return locals()
    
    
    @PropWithDraw
    def edgeShading():
        """ Get/Set the type of shading to apply for the edges.
          * None - Do not show the faces.
          * 'plain' - Display the faces in the faceColor (without lighting).
          * 'flat' - Lit shading uniform for each face
          * 'gouraud' - Lighting is calculated at vertices and interpolated
            over the face.
          * 'smooth' - Lighting is calculated for each fragment (aka
            phong-shading or phong-interpolation).
        
        Notes
        -----
        In native mode 'smooth' falls back to 'gouraud'.
        In both native and nonnative mode the blinn-phong reflectance model
        is used.
        """
        def fget(self):
            return self._edgeShading
        def fset(self, value):
            # Process value
            if value is None:
                self._edgeShading = None
            elif isinstance(value, basestring) and (value.lower() in
                    ['plain', 'flat', 'gouraud', 'smooth']):
                self._edgeShading = value.lower()
            else:
                tmp = "Edge shading must be None, 'plain', 'flat', 'gouraud, 'smooth'."
                raise ValueError(tmp)
            
            # Apply for shader code
            self._SetShading(self._edgeShading, self.edgeShader)
        return locals()
    
    
    def _SetShading(self, shading, shader):
            
            # Select shader part
            M = [(shaders.SH_MV_SHADING_PLAIN,   shaders.SH_MF_SHADING_PLAIN),
                 (shaders.SH_MV_SHADING_GOURAUD, shaders.SH_MF_SHADING_GOURAUD),
                 (shaders.SH_MV_SHADING_SMOOTH,  shaders.SH_MF_SHADING_SMOOTH),
                 (shaders.SH_MV_SHADING_TOON, shaders.SH_MF_SHADING_TOON),
                ]
            #
            if shading == 'plain':
                vShading, fShading = M[0]
            elif shading in ['flat', 'gouraud']:
                vShading, fShading = M[1]
            elif shading == 'smooth':
                vShading, fShading = M[2]
            elif shading == 'toon':
                vShading, fShading = M[3]
            else:
                return
            
            # Change shaders accordingly. Does not cause recompile if new
            # part replaces itself.
            if shader.vertex.HasPart('shading'):
                shader.vertex.ReplacePart(vShading)
            if shader.fragment.HasPart('shading'):
                shader.fragment.ReplacePart(fShading)
    
    
    @PropWithDraw
    def cullFaces():
        """ Get/Set the culling of faces.
        Values can be 'front', 'back', or None (default). If 'back',
        backfacing faces are not drawn. If 'front', frontfacing faces
        are not drawn. The front of the face is defined using the
        right-hand-rule.
        """
        def fget(self):
            D = {gl.GL_FRONT:'front', gl.GL_BACK:'back', None:None}
            return D[self._cullFaces]
        def fset(self, value):
            if isinstance(value, basestring):
                try:
                    D = {'front':gl.GL_FRONT, 'back':gl.GL_BACK}
                    self._cullFaces = D[value.lower()]
                except KeyError:
                    raise ValueError('Invalid value for cullFaces')
            elif value is None:
                self._cullFaces = None
            else:
                raise ValueError('Invalid value for cullFaces')
        return locals()
    
    @property
    def faceShader(self):
        """ Get the shader object for the faces. This can
        be used to add code of your own and customize the vertex and
        fragment part of the shader.
        """
        return self._faceShader
    
    @property
    def edgeShader(self):
        """ Get the shader object for the edges. This can
        be used to add code of your own and customize the vertex and
        fragment part of the shader.
        """
        return self._edgeShader
    
    @property
    def shapeShader(self):
        """ Get the shader object for the shape. This can
        be used to add code of your own and customize the vertex and
        fragment part of the shader.
        """
        return self._shapeShader
    
    
    ## Setters
    
    
    @DrawAfter
    def SetTexture(self, data):
        """ SetTexture(data)
        
        Set the texture image to map to the mesh.
        Use None as an argument to remove the texture.
        
        """
        if data is not None:
            # Check dimensions
            if data.ndim==2:
                pass # ok: gray image
            elif data.ndim==3 and data.shape[2] in [3, 4]:
                pass # ok: color image
            else:
                raise ValueError('Only 2D images can be mapped to a mesh.')
            # Make texture object and bind
            self._texture = TextureObjectToVisualize(2, data, interpolate=True)
            self._texture.SetData(data)
        else:
            self._texture = None
    
    
    def _GetClim(self):
        return self._clim
    def _SetClim(self, value):
        self._clim = value
        if self._values is not None:
            if value.min==0 and value.max==1:
                self._values2 = self._values
            else:
                if value.range == 0:
                    scale = 1.0
                else:
                    scale = 1.0/(value.range)
                self._values2 = (self._values - value.min) * scale
    
    
    ## Method implementations to function as a proper wobject
    
    def _GetLimits(self):
        """ _GetLimits()
        
        Get the limits in world coordinates between which the object exists.
        
        """
        
        # Get vertices with all coordinates unmasked and finite
        v = self._vertices
        if isinstance(v, np.ma.MaskedArray):
            v = v.filled(np.nan)
        valid = np.isfinite(v[:,0]) * np.isfinite(v[:,1]) * np.isfinite(v[:,2])
        validverts = v[valid,:]
        
        try:
            # Obtain untransformed coords
            x1, y1, z1 = validverts.min(axis=0)
            x2, y2, z2 = validverts.max(axis=0)
            
            # There we are
            return Wobject._GetLimits(self, x1, x2, y1, y2, z1, z2)
        except Exception:
            return None
    
    
    def OnDestroyGl(self):
        # Clean up OpenGl resources.
        self._faceShader.DestroyGl()
        self._edgeShader.DestroyGl()
        self._shapeShader.DestroyGl()
        self._colormap.DestroyGl()
        if self._texture is not None:
            self._texture.DestroyGl()
    
    
    def OnDestroy(self):
        # Clean up any resources.
        self._faceShader.Destroy()
        self._edgeShader.Destroy()
        self._shapeShader.Destroy()
        self._colormap.Destroy()
        if self._texture is not None:
            self._texture.Destroy()
    
    
    def OnDraw(self):
        
        # Draw faces
        if self._faceShading:
            
            if self._faceShading == 'toon':
                # Draw outlines. We do check depth buffer, but do not write
                # to it, so all fragments can be overwritten.
                gl.glDepthMask(False)
                gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_LINE)
                gl.glLineWidth(3.5)
                clr = 0.0, 0.0, 0.0
                self._Draw('plain', clr, self.shapeShader)
                gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_FILL)
                gl.glDepthMask(True)
            if True:
                # Draw faces normally
                self._Draw(self._faceShading, self._faceColor, self.faceShader)
        
        # Draw edges
        if self._edgeShading:
            gl.glDepthFunc(gl.GL_LEQUAL)
            gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_LINE)
            #
            self._Draw(self._edgeShading, self._edgeColor, self.edgeShader)
            #
            gl.glDepthFunc(gl.GL_LESS)
            gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_FILL)
    
    
    def OnDrawShape(self, color):
        self._Draw('plain', color, self.shapeShader)
    
    
    def _Draw(self, shading, refColor, shader):
        """ The actual drawing. Used for drawing faces, lines, and shape.
        """
        
        # Need vertices
        if self._vertices is None:
            return
        
        # Prepare normals
        if shading != 'plain':
            # Need normals
            if self._normals is None:
                processing.calculateNormals(self)
            # Do we need flat normals?
            if shading == 'flat':
                if self._flatNormals is None:
                    processing.calculateFlatNormals(self)
                normals = self._flatNormals
            else:
                normals = self._normals
            #
            gl.glEnableClientState(gl.GL_NORMAL_ARRAY)
            gl.glNormalPointerf(normals)
        
        # Prepare vertices (in the code above the vertex array can be updated)
        gl.glEnableClientState(gl.GL_VERTEX_ARRAY)
        gl.glVertexPointerf(self._vertices)
        
        # Prepare colormap indices, texture cords or colors (if available)
        # useTexCords = False
        SH_ALBEIDO = shaders.SH_MF_ALBEIDO_UNIT
        if self._values is not None:
            values = values2 = self._values
            if self._values2 is not None:
                values2 = self._values2
            if values.shape[1] == 1:
                # Colormap: use values2
                values = values2
                # useTexCords = True
                gl.glEnableClientState(gl.GL_TEXTURE_COORD_ARRAY)
                gl.glTexCoordPointer(1, gl.GL_FLOAT, 0, values)
                shader.SetUniform('colormap', self._colormap)
                SH_ALBEIDO = shaders.SH_MF_ALBEIDO_LUT1
            elif values.shape[1] == 2 and self._texture is not None:
                # texcords, use original values
                # useTexCords = True
                gl.glEnableClientState(gl.GL_TEXTURE_COORD_ARRAY)
                gl.glTexCoordPointerf(values)
                shader.SetUniform('texture', self._texture)
                SH_ALBEIDO = shaders.SH_MF_ALBEIDO_LUT2
            elif values.shape[1] in [3,4]:
                # Color, use values2
                values = values2
                gl.glEnable(gl.GL_COLOR_MATERIAL)
                gl.glColorMaterial(gl.GL_FRONT_AND_BACK,
                                    gl.GL_AMBIENT_AND_DIFFUSE)
                gl.glEnableClientState(gl.GL_COLOR_ARRAY)
                gl.glColorPointerf(values)
                if values.shape[1] == 3:
                    SH_ALBEIDO = shaders.SH_MF_ALBEIDO_RGB
                else:
                    SH_ALBEIDO = shaders.SH_MF_ALBEIDO_RGBA
        
        # Prepare material (ambient and diffuse may be overriden by colors)
        if shading == 'plain':
            gl.glColor(*refColor)
        else:
            # Set glColor: unless ALBEIDO is RGB or RGBA,
            # this is used to dermine the alpha value
            gl.glColor(*refColor)
            # Set material properties
            what = gl.GL_FRONT_AND_BACK
            gc = _getColor
            gl.glMaterial(what, gl.GL_AMBIENT, gc(self._ambient, refColor))
            gl.glMaterial(what, gl.GL_DIFFUSE, gc(self._diffuse, refColor))
            gl.glMaterial(what, gl.GL_SPECULAR, gc(self._specular, (1,1,1,1)))
            gl.glMaterial(what, gl.GL_SHININESS, self._shininess)
            gl.glMaterial(what, gl.GL_EMISSION, gc(self._emission, refColor))
        
        
        # Prepare lights
        if shading != 'plain':
            gl.glEnable(gl.GL_LIGHTING)
            gl.glEnable(gl.GL_NORMALIZE)  # GL_NORMALIZE or GL_RESCALE_NORMAL
            if shading == 'flat':
                gl.glShadeModel(gl.GL_FLAT)
            else:
                gl.glShadeModel(gl.GL_SMOOTH)
        
        
        # Set culling (take data aspect into account!)
        # From visvis v1.6 we use the right hand rule (CCW)
        axes = self.GetAxes()
        tmp = 1
        if axes:
            for i in axes.daspect:
                if i<0:
                    tmp *= -1
        gl.glFrontFace({1:gl.GL_CCW, -1:gl.GL_CW}[tmp])
        if self._cullFaces:
            gl.glEnable(gl.GL_CULL_FACE)
            gl.glCullFace(self._cullFaces)
        
        
        # Check number of lights
        self._EnsureRightNumberOfLights(axes, shader)
        
        # Ensure that the right albeido shader part is selected
        if shader.fragment.HasPart('albeido'):
            shader.fragment.AddOrReplace(SH_ALBEIDO)
        
        
        if shader.isUsable and shader.hasCode and not self.useNativeShading:
            # GLSL shading
            shader.Enable()
        else:
            # Fixed pipeline
            if SH_ALBEIDO is shaders.SH_MF_ALBEIDO_LUT1:
                shader.EnableTextureOnly('colormap')
            elif SH_ALBEIDO is shaders.SH_MF_ALBEIDO_LUT2:
                shader.EnableTextureOnly('texture')
        
        # Draw
        type = {3:gl.GL_TRIANGLES, 4:gl.GL_QUADS}[self._verticesPerFace]
        if self._faces is None:
            gl.glDrawArrays(type, 0, self._vertices.shape[0])
        else:
            # Get data type
            if self._faces.dtype == np.uint8:
                face_dtype = gl.GL_UNSIGNED_BYTE
            elif self._faces.dtype == np.uint16:
                face_dtype = gl.GL_UNSIGNED_SHORT
            else:
                face_dtype = gl.GL_UNSIGNED_INT
            # Go
            N = self._faces.size
            gl.glDrawElements(type, N, face_dtype, self._faces)
        
        # Clean up
        gl.glFlush()
        gl.glDisableClientState(gl.GL_VERTEX_ARRAY)
        gl.glDisableClientState(gl.GL_NORMAL_ARRAY)
        gl.glDisableClientState(gl.GL_COLOR_ARRAY)
        gl.glDisableClientState(gl.GL_TEXTURE_COORD_ARRAY)
        #
        shader.Disable()
        #
        gl.glDisable(gl.GL_COLOR_MATERIAL)
        gl.glShadeModel(gl.GL_FLAT)
        #
        gl.glDisable(gl.GL_LIGHTING)
        gl.glDisable(gl.GL_NORMALIZE)
        gl.glDisable(gl.GL_CULL_FACE)
    
    
    def _EnsureRightNumberOfLights(self, axes, shader):
        
        # Check number of lights in axes
        nlights = 1
        for i in range(1, 7): # len(axes.lights)):
            if axes.lights[i].isOn:
                nlights = i+1
        
        # Define shader part to use
        M = [ shaders.SH_NLIGHTS_1, shaders.SH_NLIGHTS_2, shaders.SH_NLIGHTS_3,
              shaders.SH_NLIGHTS_4, shaders.SH_NLIGHTS_5, shaders.SH_NLIGHTS_6,
              shaders.SH_NLIGHTS_7, shaders.SH_NLIGHTS_8, ]
        SH_LIGHTS = M[nlights-1]
        
        # Ensure that the right light shaderpart is selected
        if not shader.vertex.HasPart(SH_LIGHTS):
            shader.vertex.AddOrReplace(SH_LIGHTS)
        if not shader.fragment.HasPart(SH_LIGHTS):
            shader.fragment.AddOrReplace(SH_LIGHTS)
예제 #4
0
class SliceTexture(BaseTexture):
    """ SliceTexture
    
    A slice texture is a 2D texture of a 3D data volume. It enables
    visualizing 3D data without the need for glsl renderering (and can
    therefore be used on older systems.
    
    """
    
    def __init__(self, parent, data, axis=0, index=0):
        BaseTexture.__init__(self, parent, data)
        self._ndim = 3
        
        # Init parameters
        self._axis = axis
        self._index = index
        
        # create texture
        self._texture1 = TextureObjectToVisualize(2, data)
        
        # init shader
        self._InitShader()
        
        # set data  (data to textureToV. only for min/max)
        self.SetData(data)
        
        # init interpolation
        self._texture1._interpolate = True
        
        # For edge
        self._edgeColor = None
        self._edgeColor2 = getColor('g')
        self._edgeWidth = 3.0
        
        # For interaction
        self._interact_over = False
        self._interact_down = False
        self._screenVec = None
        self._refPos = (0,0)
        self._refIndex = 0
        #
        self.eventEnter.Bind(self._OnMouseEnter)
        self.eventLeave.Bind(self._OnMouseLeave)
        self.eventMouseDown.Bind(self._OnMouseDown)
        self.eventMouseUp.Bind(self._OnMouseUp)
        self.eventMotion.Bind(self._OnMouseMotion)
    
    
    def _InitShader(self):
        
        # Add components of shaders
        self.shader.vertex.Clear()
        self.shader.fragment.Clear()
        self.shader.fragment.AddPart(shaders.SH_2F_BASE)
        self.shader.fragment.AddPart(shaders.SH_2F_AASTEPS_0)
        self.shader.fragment.AddPart(shaders.SH_COLOR_SCALAR)
        
        def uniform_shape():
            shape = self._texture1._shape[:2] # as in opengl
            return [float(s) for s in reversed(list(shape))]
        def uniform_extent():
            data = self._texture1._dataRef # as original array
            shape = reversed(data.shape[:2])
            if hasattr(data, 'sampling'):
                sampling = reversed(data.sampling[:2])
            else:
                sampling = [1.0 for s in range(2)]
            
            del data
            return [s1*s2 for s1, s2 in zip(shape, sampling)]
        
        # Set some uniforms
        self.shader.SetStaticUniform('colormap', self._colormap)
        self.shader.SetStaticUniform('shape', uniform_shape)
        self.shader.SetStaticUniform('scaleBias', self._texture1._ScaleBias_get)
        self.shader.SetStaticUniform('extent', uniform_extent)
        self.shader.SetStaticUniform('aakernel', [1.0, 0, 0, 0])
    
    
    def _SetData(self, data):
        """ _SetData(data)
        
        Give reference to the raw data. For internal use. Inheriting
        classes can override this to store data in their own way and
        update the OpenGL textures accordingly.
        
        """
        
        # Store data
        self._dataRef3D = data
        
        # Slice it
        i = self._index
        if self._axis == 0:
            slice = self._dataRef3D[i]
        elif self._axis == 1:
            slice = self._dataRef3D[:,i]
        elif self._axis == 2:
            slice = self._dataRef3D[:,:,i]
        
        # Update texture
        self._texture1.SetData(slice)
    
    
    def _GetData(self):
        """ _GetData()
        
        Get a reference to the raw data. For internal use.
        
        """
        return self._dataRef3D
    
    
    def _GetLimits(self):
        """ Get the limits in world coordinates between which the object exists.
        """
        
        # Obtain untransformed coords
        shape = self._dataRef3D.shape
        x1, x2 = -0.5, shape[2]-0.5
        y1, y2 = -0.5, shape[1]-0.5
        z1, z2 = -0.5, shape[0]-0.5
        
        # There we are
        return Wobject._GetLimits(self, x1, x2, y1, y2, z1, z2)
    
    
    def OnDestroy(self):
        # Clear normaly, and also remove reference to data
        BaseTexture.OnDestroy(self)
        self._dataRef3D = None
    
    
    def OnDrawShape(self, clr):
        # Implementation of the OnDrawShape method.
        gl.glColor(clr[0], clr[1], clr[2], 1.0)
        self._DrawQuads()
    
    
    def OnDraw(self, fast=False):
        # Draw the texture.
        
        # set color to white, otherwise with no shading, there is odd scaling
        gl.glColor3f(1.0,1.0,1.0)
        
        # Enable texture, so that it has a corresponding OpenGl texture.
        # Binding is done by the shader
        self._texture1.Enable(-1)
        self.shader.SetUniform('texture', self._texture1)
        
        # _texture._shape is a good indicator of a valid texture
        if not self._texture1._shape:
            return
        
        if self.shader.isUsable and self.shader.hasCode:
            # fragment shader on -> anti-aliasing
            self.shader.Enable()
        else:
            # Fixed funcrion pipeline
            self.shader.EnableTextureOnly('texture')
        
        # do the drawing!
        self._DrawQuads()
        gl.glFlush()
        
        # clean up
        self.shader.Disable()
        
        
        # Draw outline?
        clr = self._edgeColor
        if self._interact_down or self._interact_over:
            clr = self._edgeColor2
        if clr:
            self._DrawQuads(clr)
        
        # Get screen vector?
        if self._screenVec is None:
            pos1 = [int(s/2) for s in self._dataRef3D.shape]
            pos2 = [s for s in pos1]
            pos2[self._axis] += 1
            #
            screen1 = glu.gluProject(pos1[2], pos1[1], pos1[0])
            screen2 = glu.gluProject(pos2[2], pos2[1], pos2[0])
            #
            self._screenVec = screen2[0]-screen1[0], screen1[1]-screen2[1]
    
    
    def _DrawQuads(self, clr=None):
        """ Draw the quads of the texture.
        This is done in a seperate method to reuse code in
        OnDraw() and OnDrawShape().
        """
        if not self._texture1._shape:
            return
        
        # The -0.5 offset is to center pixels/voxels. This works correctly
        # for anisotropic data.
        x1, x2 = -0.5, self._dataRef3D.shape[2]-0.5
        y2, y1 = -0.5, self._dataRef3D.shape[1]-0.5
        z2, z1 = -0.5, self._dataRef3D.shape[0]-0.5
        
        # Calculate quads
        i = self._index
        if self._axis == 0:
            quads = [   (x1, y2, i),
                        (x2, y2, i),
                        (x2, y1, i),
                        (x1, y1, i),    ]
        elif self._axis == 1:
            quads = [   (x1, i, z2),
                        (x2, i, z2),
                        (x2, i, z1),
                        (x1, i, z1),    ]
        elif self._axis == 2:
            quads = [   (i, y2, z2),
                        (i, y1, z2),
                        (i, y1, z1),
                        (i, y2, z1),    ]
        
        if clr:
            # Draw lines
            gl.glColor(clr[0], clr[1], clr[2], 1.0)
            gl.glLineWidth(self._edgeWidth)
            gl.glBegin(gl.GL_LINE_STRIP)
            for i in [0,1,2,3,0]:
                gl.glVertex3d(*quads[i])
            gl.glEnd()
        else:
            # Draw texture
            gl.glBegin(gl.GL_QUADS)
            gl.glTexCoord2f(0,0); gl.glVertex3d(*quads[0])
            gl.glTexCoord2f(1,0); gl.glVertex3d(*quads[1])
            gl.glTexCoord2f(1,1); gl.glVertex3d(*quads[2])
            gl.glTexCoord2f(0,1); gl.glVertex3d(*quads[3])
            gl.glEnd()
    
    
    ## Interaction
    
    def _OnMouseEnter(self, event):
        self._interact_over = True
        self.Draw()
    
    def _OnMouseLeave(self, event):
        self._interact_over = False
        self.Draw()
    
    def _OnMouseDown(self, event):
        
        if event.button == 1:
            
            # Signal that its down
            self._interact_down = True
            
            # Make the screen vector be calculated on the next draw
            self._screenVec = None
            
            # Store position and index for reference
            self._refPos = event.x, event.y
            self._refIndex = self._index
            
            # Redraw
            self.Draw()
            
            # Handle the event
            return True
        
        else:
            event.Ignore()
    
    def _OnMouseUp(self, event):
        self._interact_down = False
        self.Draw()
    
    def _OnMouseMotion(self, event):
        
        # Handle or pass?
        if not (self._interact_down and self._screenVec):
            return
        
        # Get vector relative to reference position
        refPos = Point(self._refPos)
        pos = Point(event.x, event.y)
        vec = pos - refPos
        
        # Length of reference vector, and its normalized version
        screenVec = Point(self._screenVec)
        L = screenVec.norm()
        V = screenVec.normalize()
        
        # Number of indexes to change
        n = vec.dot(V) / L
        
        # Apply!
        self.index = int(self._refIndex + n)
    
    
    ## Properties
    
    
    @PropWithDraw
    def index():
        """ The index of the slice in the volume to display.
        """
        def fget(self):
            return self._index
        def fset(self, value):
            # Check value
            if value < 0:
                value = 0
            maxIndex = self._dataRef3D.shape[self._axis] - 1
            if value > maxIndex:
                value = maxIndex
            # Set and update
            self._index = value
            self._SetData(self._dataRef3D)
        return locals()
    
    
    @PropWithDraw
    def axis():
        """ The axis of the slice in the volume to display.
        """
        def fget(self):
            return self._axis
        def fset(self, value):
            # Check value
            if value < 0 or value >= 3:
                raise ValueError('Invalid axis.')
            # Set and update index (can now be out of bounds.
            self._axis = value
            self.index = self.index
        return locals()
    
    
    @PropWithDraw
    def edgeColor():
        """ The color of the edge of the slice (can be None).
        """
        def fget(self):
            return self._edgeColor
        def fset(self, value):
            self._edgeColor = getColor(value)
        return locals()
    
    
    @PropWithDraw
    def edgeColor2():
        """ The color of the edge of the slice when interacting.
        """
        def fget(self):
            return self._edgeColor2
        def fset(self, value):
            self._edgeColor2 = getColor(value)
        return locals()