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 __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)
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)
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()