def canvasToWorld(self, xpos, ypos): """Transform the given x/y canvas coordinates into the display coordinate system. """ b = self.__displayCtx.bounds width, height = self.GetSize() # The first step is to invert the mouse # coordinates w.r.t. the viewport. # # The canvas x axis corresponds to # (-xhalf, xhalf), and the canvas y # corresponds to (-yhalf, yhalf) - # see routines.show3D. xlen, ylen = glroutines.adjust(b.xlen, b.ylen, width, height) xhalf = 0.5 * xlen yhalf = 0.5 * ylen # Pixels to viewport coordinates xpos = xlen * (xpos / float(width)) - xhalf ypos = ylen * (ypos / float(height)) - yhalf # The second step is to transform from # viewport coords into model-view coords. # This is easy - transform by the inverse # MV matrix. # # z=-1 because the camera is offset by 1 # on the depth axis (see __setViewport). pos = np.array([xpos, ypos, -1]) xform = transform.invert(self.__viewMat) pos = transform.transform(pos, xform) return pos
def updateVertices(self, *a): """Called by :meth:`__init__`, and when certain display properties change. (Re-)generates the mesh vertices, indices and normals (if being displayed in 3D). They are stored as attributes called ``vertices``, ``indices``, and ``normals`` respectively. """ overlay = self.overlay vertices = overlay.vertices indices = overlay.indices normals = self.overlay.vnormals xform = self.opts.getCoordSpaceTransform() if not np.all(np.isclose(xform, np.eye(4))): vertices = transform.transform(vertices, xform) if self.threedee: nmat = transform.invert(xform).T normals = transform.transform(normals, nmat, vector=True) self.vertices = np.array(vertices, dtype=np.float32) self.indices = np.array(indices.flatten(), dtype=np.uint32) if self.threedee: self.normals = np.array(normals, dtype=np.float32)
def draw3D(self, *args, **kwargs): """Calls the version dependent ``draw3D`` function. """ opts = self.opts w, h = self.canvas.GetSize() res = self.opts.resolution / 100.0 w = int(np.ceil(w * res)) h = int(np.ceil(h * res)) # Initialise and resize # the offscreen textures for rt in [self.renderTexture1, self.renderTexture2]: if rt.getSize() != (w, h): rt.setSize(w, h) rt.bindAsRenderTarget() gl.glClearColor(0, 0, 0, 0) gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT) rt.unbindAsRenderTarget() if opts.resolution != 100: gl.glViewport(0, 0, w, h) # Do the render. Even though we're # drawing off-screen, we need to # enable depth-testing, otherwise # depth values will not get written # to the depth buffer! with glroutines.enabled((gl.GL_DEPTH_TEST, gl.GL_CULL_FACE)): gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_FILL) gl.glFrontFace(gl.GL_CCW) gl.glCullFace(gl.GL_BACK) fslgl.glvolume_funcs.draw3D(self, *args, **kwargs) # renderTexture1 should now # contain the final result - # draw it to the screen. verts = np.array([[-1, -1, 0], [-1, 1, 0], [1, -1, 0], [1, -1, 0], [-1, 1, 0], [1, 1, 0]], dtype=np.float32) invproj = transform.invert(self.canvas.getProjectionMatrix()) verts = transform.transform(verts, invproj) if opts.resolution != 100: w, h = self.canvas.GetSize() gl.glViewport(0, 0, w, h) with glroutines.enabled((gl.GL_DEPTH_TEST)): self.renderTexture1.draw(verts, useDepth=True)
def draw(self, glType, vertices, indices=None, normals=None, vdata=None): """Called for 3D meshes, and :attr:`.MeshOpts.vertexData` is not ``None``. Loads and runs the shader program. :arg glType: The OpenGL primitive type. :arg vertices: ``(n, 3)`` array containing the line vertices to draw. :arg indices: Indices into the ``vertices`` array. If not provided, ``glDrawArrays`` is used. :arg normals: Vertex normals. :arg vdata: ``(n, )`` array containing data for each vertex. """ shader = self.activeShader if normals is not None: shader.setAtt('normal', normals) if vdata is not None: shader.setAtt('vertexData', vdata.reshape(-1, 1)) if normals is not None: # NOTE You are assuming here that the canvas # view matrix is the GL model view matrix. normalMatrix = self.canvas.viewMatrix normalMatrix = transform.invert(normalMatrix).T shader.setVertParam('normalMatrix', normalMatrix) shader.loadAtts() nvertices = vertices.shape[0] vertices = vertices.ravel('C') with glroutines.enabled((gl.GL_VERTEX_ARRAY)): gl.glVertexPointer(3, gl.GL_FLOAT, 0, vertices) if indices is None: gl.glDrawArrays(glType, 0, nvertices) else: gl.glDrawElements(glType, indices.shape[0], gl.GL_UNSIGNED_INT, indices.ravel('C'))
def __setViewport(self): """Called by :meth:`_draw`. Configures the viewport and calculates the model-view trasformation matrix. :returns: ``True`` if the viewport was successfully configured, ``False`` otherwise. """ width, height = self.GetScaledSize() b = self.__displayCtx.bounds blo = [b.xlo, b.ylo, b.zlo] bhi = [b.xhi, b.yhi, b.zhi] zoom = self.opts.zoom / 100.0 if width == 0 or height == 0: return False # We allow one dimension to be # flat, so we can display 2D # meshes (e.g. flattened surfaces) if np.sum(np.isclose(blo, bhi)) > 1: return False # Generate the view and projection matrices self.__genViewMatrix(width, height) projmat, viewport = glroutines.ortho(blo, bhi, width, height, zoom) self.__projMat = projmat self.__viewport = viewport self.__invViewProjMat = transform.concat(self.__projMat, self.__viewMat) self.__invViewProjMat = transform.invert(self.__invViewProjMat) gl.glViewport(0, 0, width, height) gl.glMatrixMode(gl.GL_PROJECTION) gl.glLoadMatrixf(self.__projMat.ravel('F')) gl.glMatrixMode(gl.GL_MODELVIEW) gl.glLoadIdentity() return True
def calculateRayCastSettings(self, view=None, proj=None): """Calculates various parameters required for 3D ray-cast rendering (see the :class:`.GLVolume` class). :arg view: Transformation matrix which transforms from model coordinates to view coordinates (i.e. the GL view matrix). :arg proj: Transformation matrix which transforms from view coordinates to normalised device coordinates (i.e. the GL projection matrix). Returns a tuple containing: - A vector defining the amount by which to move along a ray in a single iteration of the ray-casting algorithm. This can be added directly to the volume texture coordinates. - A transformation matrix which transforms from image texture coordinates into the display coordinate system. .. note:: This method will raise an error if called on a ``GLImageObject`` which is managing an overlay that is not associated with a :class:`.Volume3DOpts` instance. """ if view is None: view = np.eye(4) if proj is None: proj = np.eye(4) # In GL, the camera position # is initially pointing in # the -z direction. eye = [0, 0, -1] target = [0, 0, 1] # We take this initial camera # configuration, and transform # it by the inverse modelview # matrix t2dmat = self.getTransform('texture', 'display') xform = transform.concat(view, t2dmat) ixform = transform.invert(xform) eye = transform.transform(eye, ixform, vector=True) target = transform.transform(target, ixform, vector=True) # Direction that the 'camera' is # pointing, normalied to unit length cdir = transform.normalise(eye - target) # Calculate the length of one step # along the camera direction in a # single iteration of the ray-cast # loop. Multiply by sqrt(3) so that # the maximum number of steps will # be reached across the longest axis # of the image texture cube. rayStep = np.sqrt(3) * cdir / self.getNumSteps() # A transformation matrix which can # transform image texture coordinates # into the corresponding screen # (normalised device) coordinates. # This allows the fragment shader to # convert an image texture coordinate # into a relative depth value. # # The projection matrix puts depth into # [-1, 1], but we want it in [0, 1] zscale = transform.scaleOffsetXform([1, 1, 0.5], [0, 0, 0.5]) xform = transform.concat(zscale, proj, xform) return rayStep, xform
def draw3D(self, *args, **kwargs): """Calls the version dependent ``draw3D`` function. """ opts = self.opts w, h = self.canvas.GetScaledSize() res = self.opts.resolution / 100.0 sw = int(np.ceil(w * res)) sh = int(np.ceil(h * res)) # Initialise and resize # the offscreen textures for rt in [self.renderTexture1, self.renderTexture2]: if rt.getSize() != (sw, sh): rt.setSize(sw, sh) with rt.bound(): gl.glClearColor(0, 0, 0, 0) gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT) if opts.resolution != 100: gl.glViewport(0, 0, sw, sh) # Do the render. Even though we're # drawing off-screen, we need to # enable depth-testing, otherwise # depth values will not get written # to the depth buffer! # # The glvolume_funcs.draw3D function # will put the final render into # renderTexture1 with glroutines.enabled((gl.GL_DEPTH_TEST, gl.GL_CULL_FACE)): gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_FILL) gl.glFrontFace(gl.GL_CCW) gl.glCullFace(gl.GL_BACK) fslgl.glvolume_funcs.draw3D(self, *args, **kwargs) # Apply smoothing if needed. If smoothing # is enabled, the final final render will # be in renderTexture2 if opts.smoothing > 0: self.smoothFilter.set(offsets=[1.0 / sw, 1.0 / sh]) self.smoothFilter.osApply(self.renderTexture1, self.renderTexture2) # We now have the final result # - draw it to the screen. verts = np.array([[-1, -1, 0], [-1, 1, 0], [1, -1, 0], [1, -1, 0], [-1, 1, 0], [1, 1, 0]], dtype=np.float32) invproj = transform.invert(self.canvas.projectionMatrix) verts = transform.transform(verts, invproj) if opts.resolution != 100: gl.glViewport(0, 0, w, h) with glroutines.enabled(gl.GL_DEPTH_TEST): # If smoothing was not applied, rt1 # contains the final render. Otherwise, # rt2 contains the final render, but rt1 # contains the depth information. So we # need to # temporarily replace rt2.depth # with rt1.depth. if opts.smoothing > 0: src = self.renderTexture2 olddep = self.renderTexture2.depthTexture dep = self.renderTexture1.depthTexture else: src = self.renderTexture1 olddep = self.renderTexture1.depthTexture dep = olddep src.depthTexture = dep src.draw(verts, useDepth=True) src.depthTexture = olddep
def __setupTransforms(self): """Calculates transformation matrices between all of the possible spaces in which the overlay may be displayed. These matrices are accessible via the :meth:`getTransform` method. """ image = self.overlay shape = np.array(image.shape[:3]) voxToIdMat = np.eye(4) voxToPixdimMat = np.diag(list(image.pixdim[:3]) + [1.0]) voxToPixFlipMat = image.voxToScaledVoxMat voxToWorldMat = image.voxToWorldMat voxToWorldMat = transform.concat(self.displayXform, voxToWorldMat) ds = self.displayCtx.displaySpace # The reference transforms depend on the value of # displaySpace if ds == 'world': voxToRefMat = voxToWorldMat elif ds is self.overlay: voxToRefMat = voxToPixFlipMat else: voxToRefMat = transform.concat(ds.voxToScaledVoxMat, ds.worldToVoxMat, voxToWorldMat) # When going from voxels to textures, # we add 0.5 to centre the voxel (see # the note on coordinate systems at # the top of this file). voxToTexMat = transform.scaleOffsetXform(tuple(1.0 / shape), tuple(0.5 / shape)) idToVoxMat = transform.invert(voxToIdMat) idToPixdimMat = transform.concat(voxToPixdimMat, idToVoxMat) idToPixFlipMat = transform.concat(voxToPixFlipMat, idToVoxMat) idToWorldMat = transform.concat(voxToWorldMat, idToVoxMat) idToRefMat = transform.concat(voxToRefMat, idToVoxMat) idToTexMat = transform.concat(voxToTexMat, idToVoxMat) pixdimToVoxMat = transform.invert(voxToPixdimMat) pixdimToIdMat = transform.concat(voxToIdMat, pixdimToVoxMat) pixdimToPixFlipMat = transform.concat(voxToPixFlipMat, pixdimToVoxMat) pixdimToWorldMat = transform.concat(voxToWorldMat, pixdimToVoxMat) pixdimToRefMat = transform.concat(voxToRefMat, pixdimToVoxMat) pixdimToTexMat = transform.concat(voxToTexMat, pixdimToVoxMat) pixFlipToVoxMat = transform.invert(voxToPixFlipMat) pixFlipToIdMat = transform.concat(voxToIdMat, pixFlipToVoxMat) pixFlipToPixdimMat = transform.concat(voxToPixdimMat, pixFlipToVoxMat) pixFlipToWorldMat = transform.concat(voxToWorldMat, pixFlipToVoxMat) pixFlipToRefMat = transform.concat(voxToRefMat, pixFlipToVoxMat) pixFlipToTexMat = transform.concat(voxToTexMat, pixFlipToVoxMat) worldToVoxMat = transform.invert(voxToWorldMat) worldToIdMat = transform.concat(voxToIdMat, worldToVoxMat) worldToPixdimMat = transform.concat(voxToPixdimMat, worldToVoxMat) worldToPixFlipMat = transform.concat(voxToPixFlipMat, worldToVoxMat) worldToRefMat = transform.concat(voxToRefMat, worldToVoxMat) worldToTexMat = transform.concat(voxToTexMat, worldToVoxMat) refToVoxMat = transform.invert(voxToRefMat) refToIdMat = transform.concat(voxToIdMat, refToVoxMat) refToPixdimMat = transform.concat(voxToPixdimMat, refToVoxMat) refToPixFlipMat = transform.concat(voxToPixFlipMat, refToVoxMat) refToWorldMat = transform.concat(voxToWorldMat, refToVoxMat) refToTexMat = transform.concat(voxToTexMat, refToVoxMat) texToVoxMat = transform.invert(voxToTexMat) texToIdMat = transform.concat(voxToIdMat, texToVoxMat) texToPixdimMat = transform.concat(voxToPixdimMat, texToVoxMat) texToPixFlipMat = transform.concat(voxToPixFlipMat, texToVoxMat) texToWorldMat = transform.concat(voxToWorldMat, texToVoxMat) texToRefMat = transform.concat(voxToRefMat, texToVoxMat) self.__xforms['id', 'id'] = np.eye(4) self.__xforms['id', 'pixdim'] = idToPixdimMat self.__xforms['id', 'pixdim-flip'] = idToPixFlipMat self.__xforms['id', 'affine'] = idToWorldMat self.__xforms['id', 'reference'] = idToRefMat self.__xforms['id', 'texture'] = idToTexMat self.__xforms['pixdim', 'pixdim'] = np.eye(4) self.__xforms['pixdim', 'id'] = pixdimToIdMat self.__xforms['pixdim', 'pixdim-flip'] = pixdimToPixFlipMat self.__xforms['pixdim', 'affine'] = pixdimToWorldMat self.__xforms['pixdim', 'reference'] = pixdimToRefMat self.__xforms['pixdim', 'texture'] = pixdimToTexMat self.__xforms['pixdim-flip', 'pixdim-flip'] = np.eye(4) self.__xforms['pixdim-flip', 'id'] = pixFlipToIdMat self.__xforms['pixdim-flip', 'pixdim'] = pixFlipToPixdimMat self.__xforms['pixdim-flip', 'affine'] = pixFlipToWorldMat self.__xforms['pixdim-flip', 'reference'] = pixFlipToRefMat self.__xforms['pixdim-flip', 'texture'] = pixFlipToTexMat self.__xforms['affine', 'affine'] = np.eye(4) self.__xforms['affine', 'id'] = worldToIdMat self.__xforms['affine', 'pixdim'] = worldToPixdimMat self.__xforms['affine', 'pixdim-flip'] = worldToPixFlipMat self.__xforms['affine', 'reference'] = worldToRefMat self.__xforms['affine', 'texture'] = worldToTexMat self.__xforms['reference', 'reference'] = np.eye(4) self.__xforms['reference', 'id'] = refToIdMat self.__xforms['reference', 'pixdim'] = refToPixdimMat self.__xforms['reference', 'pixdim-flip'] = refToPixFlipMat self.__xforms['reference', 'affine'] = refToWorldMat self.__xforms['reference', 'texture'] = refToTexMat self.__xforms['texture', 'texture'] = np.eye(4) self.__xforms['texture', 'id'] = texToIdMat self.__xforms['texture', 'pixdim'] = texToPixdimMat self.__xforms['texture', 'pixdim-flip'] = texToPixFlipMat self.__xforms['texture', 'affine'] = texToWorldMat self.__xforms['texture', 'reference'] = texToRefMat