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 __prepareCoords(self, vertices, xform=None): """Called by :meth:`draw`. Prepares vertices, texture coordinates and indices for drawing the texture. If ``vertices is None``, it is assumed that the caller has already assigned vertices and texture coordinates, either via a shader, or via vertex/texture coordinate pointers. In this case, :returns: A tuple containing the vertices, texture coordinates, and indices, or ``(None, None, indices)`` if ``vertices is None`` """ indices = np.arange(6, dtype=np.uint32) if vertices is None: return None, None, indices if vertices.shape != (6, 3): raise ValueError('Six vertices must be provided') if xform is not None: vertices = transform.transform(vertices, xform) vertices = np.array(vertices, dtype=np.float32).ravel('C') texCoords = self.generateTextureCoords().ravel('C') return vertices, texCoords, indices
def __drawLight(self): opts = self.opts lightPos = np.array(opts.lightPos) lightPos *= (opts.zoom / 100.0) gl.glColor4f(1, 1, 1, 1) gl.glPointSize(10) gl.glBegin(gl.GL_POINTS) gl.glVertex3f(*lightPos) gl.glEnd() b = self.__displayCtx.bounds centre = np.array([ b.xlo + 0.5 * (b.xhi - b.xlo), b.ylo + 0.5 * (b.yhi - b.ylo), b.zlo + 0.5 * (b.zhi - b.zlo) ]) centre = transform.transform(centre, self.__viewMat) gl.glColor4f(1, 0, 1, 1) gl.glBegin(gl.GL_LINES) gl.glVertex3f(*lightPos) gl.glVertex3f(*centre) gl.glEnd()
def draw2D(self, zpos, axes, xform=None, bbox=None): """Draws the specified 2D slice from the specified image on the canvas. :arg self: The :class:`.GLVolume` object which is managing the image to be drawn. :arg zpos: World Z position of slice to be drawn. :arg axes: x, y, z axis indices. :arg xform: A 4*4 transformation matrix to be applied to the vertex data. :arg bbox: An optional bounding box. """ vertices, voxCoords, texCoords = self.generateVertices2D(zpos, axes, bbox=bbox) if xform is not None: vertices = transform.transform(vertices, xform) self.shader.setAtt('vertex', vertices) self.shader.setAtt('voxCoord', voxCoords) self.shader.setAtt('texCoord', texCoords) self.shader.loadAtts() gl.glDrawArrays(gl.GL_TRIANGLES, 0, 6)
def draw2D(self, zpos, axes): """Draws this ``VoxelSelection``.""" xax, yax = axes[:2] opts = self.__opts texture = self.__texture shape = self.__selection.getSelection().shape displayToVox = opts.getTransform('display', 'voxel') voxToDisplay = opts.getTransform('voxel', 'display') voxToTex = opts.getTransform('voxel', 'texture') verts, voxs = glroutines.slice2D(shape, xax, yax, zpos, voxToDisplay, displayToVox) texs = transform.transform(voxs, voxToTex) verts = np.array(verts, dtype=np.float32).ravel('C') texs = np.array(texs, dtype=np.float32).ravel('C') texture.bindTexture(gl.GL_TEXTURE0) gl.glClientActiveTexture(gl.GL_TEXTURE0) gl.glTexEnvf(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE, gl.GL_MODULATE) with glroutines.enabled( (gl.GL_TEXTURE_3D, gl.GL_TEXTURE_COORD_ARRAY, gl.GL_VERTEX_ARRAY)): gl.glVertexPointer(3, gl.GL_FLOAT, 0, verts) gl.glTexCoordPointer(3, gl.GL_FLOAT, 0, texs) gl.glDrawArrays(gl.GL_TRIANGLES, 0, 6) texture.unbindTexture()
def drawAll(self, axes, zposes, xforms): """Draws mutltiple slices of the given image at the given Z position, applying the corresponding transformation to each of the slices. """ nslices = len(zposes) vertices = np.zeros((nslices * 6, 3), dtype=np.float32) texCoords = np.zeros((nslices * 6, 3), dtype=np.float32) indices = np.arange(nslices * 6, dtype=np.uint32) for i, (zpos, xform) in enumerate(zip(zposes, xforms)): v, vc, tc = self.generateVertices2D(zpos, axes) vertices[ i * 6: i * 6 + 6, :] = transform.transform(v, xform) texCoords[i * 6: i * 6 + 6, :] = tc vertices = vertices.ravel('C') self.shader.setAtt('texCoord', texCoords) with glroutines.enabled((gl.GL_VERTEX_ARRAY)): gl.glVertexPointer(3, gl.GL_FLOAT, 0, vertices) gl.glDrawElements(gl.GL_TRIANGLES, nslices * 6, gl.GL_UNSIGNED_INT, indices)
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 draw2D(self, zpos, axes): """Draws this ``VoxelGrid`` annotation. """ xax, yax, zax = axes dispLoc = [0] * 3 dispLoc[zax] = zpos voxLoc = transform.transform([dispLoc], self.displayToVoxMat)[0] vox = int(round(voxLoc[zax])) restrictions = [slice(None)] * 3 restrictions[zax] = slice(vox - self.offsets[zax], vox - self.offsets[zax] + 1) xs, ys, zs = np.where(self.selectMask[restrictions]) voxels = np.vstack((xs, ys, zs)).T for ax in range(3): off = restrictions[ax].start if off is None: off = 0 voxels[:, ax] += off + self.offsets[ax] verts, idxs = glroutines.voxelGrid(voxels, xax, yax, 1, 1) verts = verts.ravel('C') gl.glVertexPointer(3, gl.GL_FLOAT, 0, verts) gl.glDrawElements(gl.GL_LINES, len(idxs), gl.GL_UNSIGNED_INT, idxs)
def draw2D(self, zpos, axes): """Draws this ``VoxelSelection``.""" xax, yax = axes[:2] shape = self.selection.getSelection().shape verts, voxs = glroutines.slice2D(shape, xax, yax, zpos, self.voxToDisplayMat, self.displayToVoxMat) texs = transform.transform(voxs, self.voxToTexMat) verts = np.array(verts, dtype=np.float32).ravel('C') texs = np.array(texs, dtype=np.float32).ravel('C') self.texture.bindTexture(gl.GL_TEXTURE0) gl.glTexEnvf(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE, gl.GL_MODULATE) gl.glEnable(gl.GL_TEXTURE_3D) gl.glEnableClientState(gl.GL_TEXTURE_COORD_ARRAY) gl.glVertexPointer(3, gl.GL_FLOAT, 0, verts) gl.glTexCoordPointer(3, gl.GL_FLOAT, 0, texs) gl.glDrawArrays(gl.GL_TRIANGLES, 0, 6) gl.glDisableClientState(gl.GL_TEXTURE_COORD_ARRAY) gl.glDisable(gl.GL_TEXTURE_3D) self.texture.unbindTexture()
def coordLabel(self, loc, voxel=False): """Looks up and returns the label at the given location. :arg loc: A sequence of three values, interpreted as atlas coordinates. In this case, :meth:`coordLabel` is called. :arg voxel: Defaults to ``False``. If ``True``, the ``location`` is interpreted as voxel coordinates. :returns: The label at the given coordinates, or ``None`` if the coordinates are out of bounds. .. note:: Use the :meth:`find` method to retrieve the ``AtlasLabel`` associated with each returned value. """ if not voxel: loc = transform.transform([loc], self.worldToVoxMat)[0] loc = [int(v) for v in loc.round()] if loc[0] < 0 or \ loc[1] < 0 or \ loc[2] < 0 or \ loc[0] >= self.shape[0] or \ loc[1] >= self.shape[1] or \ loc[2] >= self.shape[2]: return None return self[loc[0], loc[1], loc[2]]
def updateShaderState(self): """Sets all variables required by the vertex and fragment programs. """ if not self.ready(): return opts = self.opts shader = self.shader # enable the vertex and fragment programs shader.load() # The voxValXform transformation turns # an image texture value into a raw # voxel value. The colourMapXform # transformation turns a raw voxel value # into a value between 0 and 1, suitable # for looking up an appropriate colour # in the 1D colour map texture. voxValXform = transform.concat(self.colourTexture.getCoordinateTransform(), self.imageTexture.voxValXform) voxValXform = [voxValXform[0, 0], voxValXform[0, 3], 0, 0] # And the clipping range, normalised # to the image texture value range invClip = 1 if opts.invertClipping else -1 useNegCmap = 1 if opts.useNegativeCmap else 0 imageIsClip = 1 if opts.clipImage is None else -1 imgXform = self.imageTexture.invVoxValXform if opts.clipImage is None: clipXform = imgXform else: clipXform = self.clipTexture.invVoxValXform clipLo = opts.clippingRange[0] * clipXform[0, 0] + clipXform[0, 3] clipHi = opts.clippingRange[1] * clipXform[0, 0] + clipXform[0, 3] texZero = 0.0 * imgXform[ 0, 0] + imgXform[ 0, 3] clipping = [clipLo, clipHi, invClip, imageIsClip] negCmap = [useNegCmap, texZero, 0, 0] changed = False changed |= shader.setFragParam('voxValXform', voxValXform) changed |= shader.setFragParam('clipping', clipping) changed |= shader.setFragParam('negCmap', negCmap) if self.threedee: clipPlanes = np.zeros((5, 4), dtype=np.float32) d2tmat = opts.getTransform('display', 'texture') for i in range(opts.numClipPlanes): origin, normal = self.get3DClipPlane(i) origin = transform.transform(origin, d2tmat) normal = transform.transformNormal(normal, d2tmat) clipPlanes[i, :] = glroutines.planeEquation2(origin, normal) changed |= shader.setFragParam('clipPlanes', clipPlanes) self.shader.unload() return changed
def coordProportions(self, loc, voxel=False): """Looks up the region probabilities for the given location. :arg loc: A sequence of three values, interpreted as atlas world or voxel coordinates. :arg voxel: Defaults to ``False``. If ``True``, the ``loc`` argument is interpreted as voxel coordinates. :returns: a list of values, one per region, which represent the probability of each region for the specified location. Returns an empty list if the given location is out of bounds. """ if not voxel: loc = transform.transform([loc], self.worldToVoxMat)[0] loc = [int(v) for v in loc.round()] if loc[0] < 0 or \ loc[1] < 0 or \ loc[2] < 0 or \ loc[0] >= self.shape[0] or \ loc[1] >= self.shape[1] or \ loc[2] >= self.shape[2]: return [] props = self[loc[0], loc[1], loc[2], :] # We only return labels for this atlas - # the underlying image may have more # volumes than this atlas has labels. return [props[l.index] for l in self.desc.labels]
def __drawCursor(self): """Draws three lines at the current :attr:`.DisplayContext.location`. """ opts = self.opts b = self.__displayCtx.bounds pos = opts.pos points = np.array([ [pos.x, pos.y, b.zlo], [pos.x, pos.y, b.zhi], [pos.x, b.ylo, pos.z], [pos.x, b.yhi, pos.z], [b.xlo, pos.y, pos.z], [b.xhi, pos.y, pos.z], ], dtype=np.float32) points = transform.transform(points, self.__viewMat) gl.glLineWidth(1) r, g, b = opts.cursorColour[:3] gl.glColor4f(r, g, b, 1) gl.glBegin(gl.GL_LINES) for p in points: gl.glVertex3f(*p) gl.glEnd()
def draw3D(self, xform=None, bbox=None): """Draws the image in 3D on the canvas. :arg self: The :class:`.GLVolume` object which is managing the image to be drawn. :arg xform: A 4*4 transformation matrix to be applied to the vertex data. :arg bbox: An optional bounding box. """ opts = self.opts tex = self.renderTexture1 proj = self.canvas.projectionMatrix vertices, voxCoords, texCoords = self.generateVertices3D(bbox) rayStep, texform = opts.calculateRayCastSettings(xform, proj) if xform is not None: vertices = transform.transform(vertices, xform) self.shader.set('tex2ScreenXform', texform) self.shader.set('rayStep', rayStep) self.shader.setAtt('vertex', vertices) self.shader.setAtt('texCoord', texCoords) self.shader.loadAtts() tex.bindAsRenderTarget() gl.glDrawArrays(gl.GL_TRIANGLES, 0, 36) tex.unbindAsRenderTarget() self.shader.unloadAtts() self.shader.unload()
def generateVertices3D(self, bbox=None): """Generates vertex coordinates defining the 3D bounding box of the :class:`.Image`, with the optional ``bbox`` applied to the coordinates. See the :func:`.routines.boundingBox` function. A tuple of three values is returned, containing: - A ``36*3 numpy.float32`` array containing the vertex coordinates - A ``36*3 numpy.float32`` array containing the voxel coordinates corresponding to each vertex - A ``36*3 numpy.float32`` array containing the texture coordinates corresponding to each vertex """ opts = self.opts v2dMat = opts.getTransform('voxel', 'display') d2vMat = opts.getTransform('display', 'voxel') v2tMat = opts.getTransform('voxel', 'texture') vertices, voxCoords = glroutines.boundingBox( self.image.shape[:3], v2dMat, d2vMat, bbox=bbox) texCoords = transform.transform(voxCoords, v2tMat) return vertices, voxCoords, texCoords
def _eval_coord_voxel_query(atlas, query, qtype, qin): voxel = qtype == 'voxel' if voxel: vx, vy, vz = query else: vx, vy, vz = transform.transform(query, atlas.worldToVoxMat) vx, vy, vz = [int(round(v)) for v in [vx, vy, vz]] def evalLabel(): if qin in ('in', 'zero'): expval = atlas[vx, vy, vz] else: expval = None assert atlas.label(query, voxel=voxel) == expval assert atlas.coordLabel(query, voxel=voxel) == expval def evalProb(): if qin in ('in', 'zero'): expval = atlas[vx, vy, vz, :] expval = [expval[l.index] for l in atlas.desc.labels] elif qin == 'out': expval = [] assert atlas.proportions(query, voxel=voxel) == expval assert atlas.coordProportions(query, voxel=voxel) == expval if isinstance(atlas, fslatlases.LabelAtlas): evalLabel() elif isinstance(atlas, fslatlases.ProbabilisticAtlas): evalProb()
def __getMeshInfo(self, overlay, display): """Creates and returns an :class:`OverlayInfo` object containing information about the given :class:`.Mesh` overlay. :arg overlay: A :class:`.Mesh` instance. :arg display: The :class:`.Display` instance assocated with the ``Mesh``. """ opts = display.opts refImg = opts.refImage modelInfo = [ ('numVertices', overlay.vertices.shape[0]), ('numTriangles', overlay.indices.shape[0]), ] if refImg is None: modelInfo.append( ('displaySpace', strings.labels[self, overlay, 'coordSpace', 'display'])) mesh2worldXform = np.eye(4) else: refOpts = self.displayCtx.getOpts(refImg) dsImg = self.displayCtx.displaySpace displaySpace = strings.labels[self, refImg, 'displaySpace', refOpts.transform] coordSpace = strings.labels[self, overlay, 'coordSpace', opts.coordSpace].format(refImg.name) mesh2worldXform = transform.concat( refOpts.getTransform('display', 'world'), opts.getTransform('mesh', 'display')) if refOpts.transform == 'reference': dsDisplay = self.displayCtx.getDisplay(dsImg) displaySpace = displaySpace.format(dsDisplay.name) modelInfo.append(('refImage', refImg.dataSource)) modelInfo.append(('coordSpace', coordSpace)) modelInfo.append(('displaySpace', displaySpace)) bounds = transform.transform(overlay.bounds, mesh2worldXform) lens = bounds[1] - bounds[0] lens = 'X={:0.0f} mm Y={:0.0f} mm Z={:0.0f} mm'.format(*lens) modelInfo.append(('size', lens)) info = OverlayInfo('{} - {}'.format(display.name, strings.labels[self, overlay])) info.addInfo(strings.labels[self, 'dataSource'], overlay.dataSource) for name, value in modelInfo: info.addInfo(strings.labels[self, overlay, name], value) return info
def generateVoxelCoordinates2D(self, zpos, axes, bbox=None, space='voxel'): """Generates a 2D grid of voxel coordinates along the XY display coordinate system plane, at the given ``zpos``. :arg zpos: Position along the display coordinate system Z axis. :arg axes: Axis indices. :arg bbox: Limiting bounding box. :arg space: Either ``'voxel'`` (the default) or ``'display'``. If the latter, the returned coordinates are in terms of the display coordinate system. Otherwise, the returned coordinates are integer voxel coordinates. :returns: A ``numpy.float32`` array of shape ``(N, 3)``, containing the coordinates for ``N`` voxels. See the :func:`.pointGrid` function. """ if space not in ('voxel', 'display'): raise ValueError('Unknown value for space ("{}")'.format(space)) image = self.image opts = self.opts v2dMat = opts.getTransform('voxel', 'display') d2vMat = opts.getTransform('display', 'voxel') xax, yax, zax = axes # TODO If space == voxel, you should call # pointGrid to generate voxels, and # avoid the subsequent transform back # from display to voxel space. if opts.transform == 'id': resolution = [1, 1, 1] elif opts.transform in ('pixdim', 'pixdim-flip'): resolution = image.pixdim[:3] else: resolution = [min(image.pixdim[:3])] * 3 voxels = glroutines.pointGrid(image.shape, resolution, v2dMat, xax, yax, bbox=bbox)[0] voxels[:, zax] = zpos if space == 'voxel': voxels = transform.transform(voxels, d2vMat) voxels = opts.roundVoxels(voxels, daxes=[zax], roundOther=False) return voxels
def broadcast(vertices, indices, zposes, xforms, zax): """Given a set of vertices and indices (assumed to be 2D representations of some geometry in a 3D space, with the depth axis specified by ``zax``), replicates them across all of the specified Z positions, applying the corresponding transformation to each set of vertices. :arg vertices: Vertex array (a ``N*3`` numpy array). :arg indices: Index array. :arg zposes: Positions along the depth axis at which the vertices are to be replicated. :arg xforms: Sequence of transformation matrices, one for each Z position. :arg zax: Index of the 'depth' axis Returns three values: - A numpy array containing all of the generated vertices - A numpy array containing the original vertices for each of the generated vertices, which may be used as texture coordinates - A new numpy array containing all of the generated indices. """ vertices = np.array(vertices) indices = np.array(indices) nverts = vertices.shape[0] nidxs = indices.shape[0] allTexCoords = np.zeros((nverts * len(zposes), 3), dtype=np.float32) allVertCoords = np.zeros((nverts * len(zposes), 3), dtype=np.float32) allIndices = np.zeros(nidxs * len(zposes), dtype=np.uint32) for i, (zpos, xform) in enumerate(zip(zposes, xforms)): vertices[:, zax] = zpos vStart = i * nverts vEnd = vStart + nverts iStart = i * nidxs iEnd = iStart + nidxs allIndices[iStart:iEnd] = indices + i * nverts allTexCoords[vStart:vEnd, :] = vertices allVertCoords[vStart:vEnd, :] = transform.transform(vertices, xform) return allVertCoords, allTexCoords, allIndices
def _gen_coord_voxel_query(atlas, use_label, q_type, q_in, res): a_img = _get_atlas(atlas, use_label, res) voxel = q_type == 'voxel' if voxel: dtype = int else: dtype = float if q_in == 'out': if voxel: dlo = (0, 0, 0) dhi = a_img.shape else: dlo, dhi = transform.axisBounds(a_img.shape, a_img.voxToWorldMat) dlen = [hi - lo for lo, hi in zip(dlo, dhi)] coords = [] for d in range(3): # over if np.random.random() > 0.5: coords.append(dlo[d] + dlen[d] + dlen[d] * np.random.random()) # or under else: coords.append(dlo[d] - dlen[d] * np.random.random()) coords = np.array(coords, dtype=dtype) else: # Make a mask which tells us which # voxels in the atlas are all zeros zmask = _get_zero_mask(a_img, atlas, use_label, res) # get indices to voxels which are # either all zero, or which are # not all all zero, depending on # the value of q_in if q_in == 'in': zidxs = np.where(zmask == 0) else: zidxs = np.where(zmask) # Randomly choose a voxel cidx = np.random.randint(0, len(zidxs[0])) coords = [zidxs[0][cidx], zidxs[1][cidx], zidxs[2][cidx]] coords = np.array(coords, dtype=dtype) if not voxel: coords = transform.transform(coords, a_img.voxToWorldMat) return tuple([dtype(c) for c in coords])
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 pointGrid3D(shape, xform=None, origin='centre', bbox=None): """Generates a 3D grid of points into an image of the given ``shape``, with the given ``xform`` defining the index to display coordinate transform. note: Not implemented properly yet. """ coords = np.indices(shape).transpose(1, 2, 3, 0).reshape(-1, 3) if xform is not None: coords = transform.transform(coords, xform) return coords
def generateVertices2D(self, zpos, axes, bbox=None): """Generates vertex coordinates for a 2D slice of the :class:`.Image`, through the given ``zpos``, with the optional ``bbox`` applied to the coordinates. This is a convenience method for generating vertices which can be used to render a slice through a 3D texture. It is used by the :mod:`.gl14.glvolume_funcs` and :mod:`.gl21.glvolume_funcs` (and other) modules. A tuple of three values is returned, containing: - A ``6*3 numpy.float32`` array containing the vertex coordinates - A ``6*3 numpy.float32`` array containing the voxel coordinates corresponding to each vertex - A ``6*3 numpy.float32`` array containing the texture coordinates corresponding to each vertex """ opts = self.opts v2dMat = opts.getTransform('voxel', 'display') d2vMat = opts.getTransform('display', 'voxel') v2tMat = opts.getTransform('voxel', 'texture') xax, yax, zax = axes vertices, voxCoords = glroutines.slice2D( self.image.shape[:3], xax, yax, zpos, v2dMat, d2vMat, bbox=bbox) # If not interpolating, centre the # voxel coordinates on the Z/depth # axis. We do this to avoid rounding # bias when the display Z position is # on a voxel boundary. if not hasattr(opts, 'interpolation') or opts.interpolation == 'none': voxCoords = opts.roundVoxels(voxCoords, daxes=[zax]) texCoords = transform.transform(voxCoords, v2tMat) return vertices, voxCoords, texCoords
def drawClipPlanes(self, xform=None, bbox=None): """A convenience method for use with overlays being displayed in terms of a :class:`.Volume3DOpts` instance. Draws the active clipping planes, as specified by the :class:`.Volume3DOpts` clipping properties. :arg xform: A transformation matrix to apply to the clip plane vertices before drawing them. :arg bbox: A bounding box by which the clip planes can be limited (not currently honoured). """ if not self.opts.showClipPlanes: return for i in range(self.opts.numClipPlanes): verts, idxs = self.clipPlaneVertices(i, bbox) if len(idxs) == 0: continue if xform is not None: verts = transform.transform(verts, xform) verts = np.array(verts.ravel('C'), dtype=np.float32, copy=False) # A consistent colour for # each clipping plane rgb = self.__clipPlaneColours.get(i, None) if rgb is None: rgb = fslcmaps.randomBrightColour()[:3] self.__clipPlaneColours[i] = rgb r, g, b = rgb with glroutines.enabled(gl.GL_VERTEX_ARRAY): gl.glColor4f(r, g, b, 0.3) gl.glVertexPointer(3, gl.GL_FLOAT, 0, verts) gl.glDrawElements(gl.GL_TRIANGLES, len(idxs), gl.GL_UNSIGNED_INT, idxs)
def calculateRayCastSettings(self, viewmat): """Calculates a camera direction and ray casting step vector, based on the given view matrix. """ d2tmat = self.getTransform('display', 'texture') xform = transform.concat(d2tmat, viewmat) cdir = np.array([0, 0, 1]) cdir = transform.transform(cdir, xform, vector=True) cdir = transform.normalise(cdir) # sqrt(3) so the maximum number # of samplews is taken along the # diagonal of a cube rayStep = np.sqrt(3) * cdir / self.numSteps return cdir, rayStep
def draw(self, vertices, xform=None): """Draw the contents of this ``Texture2D`` to a region specified by the given vertices. The texture is bound to texture unit 0. :arg vertices: A ``numpy`` array of shape ``6 * 3`` specifying the region, made up of two triangles, to which this ``Texture2D`` should be drawn. :arg xform: A transformation to be applied to the vertices. """ if vertices.shape != (6, 3): raise ValueError('Six vertices must be provided') if xform is not None: vertices = transform.transform(vertices, xform) vertices = np.array(vertices, dtype=np.float32) texCoords = np.zeros((6, 2), dtype=np.float32) indices = np.arange(6, dtype=np.uint32) texCoords[0, :] = [0, 0] texCoords[1, :] = [0, 1] texCoords[2, :] = [1, 0] texCoords[3, :] = [1, 0] texCoords[4, :] = [0, 1] texCoords[5, :] = [1, 1] vertices = vertices.ravel('C') texCoords = texCoords.ravel('C') self.bindTexture(gl.GL_TEXTURE0) gl.glClientActiveTexture(gl.GL_TEXTURE0) gl.glTexEnvf(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE, gl.GL_REPLACE) with glroutines.enabled( (gl.GL_TEXTURE_2D, gl.GL_TEXTURE_COORD_ARRAY, gl.GL_VERTEX_ARRAY)): gl.glVertexPointer(3, gl.GL_FLOAT, 0, vertices) gl.glTexCoordPointer(2, gl.GL_FLOAT, 0, texCoords) gl.glDrawElements(gl.GL_TRIANGLES, 6, gl.GL_UNSIGNED_INT, indices) self.unbindTexture()
def draw2D(self, zpos, axes, xform=None, bbox=None): """Draws a 2D slice of the image at the given Z location. """ vertices, voxCoords, texCoords = self.generateVertices2D( zpos, axes, bbox=bbox) if xform is not None: vertices = transform.transform(vertices, xform) vertices = np.array(vertices, dtype=np.float32).ravel('C') # Voxel coordinates are calculated # in the vertex program self.shader.setAtt('texCoord', texCoords) with glroutines.enabled((gl.GL_VERTEX_ARRAY)): gl.glVertexPointer(3, gl.GL_FLOAT, 0, vertices) gl.glDrawArrays(gl.GL_TRIANGLES, 0, 6)
def __updateBounds(self): """Called whenever any of the :attr:`refImage`, :attr:`coordSpace`, or :attr:`transform` properties change. Updates the :attr:`.DisplayOpts.bounds` property accordingly. """ lo, hi = self.overlay.bounds xform = self.getTransform('mesh', 'display') lohi = transform.transform([lo, hi], xform) lohi.sort(axis=0) lo, hi = lohi[0, :], lohi[1, :] oldBounds = self.bounds self.bounds = [lo[0], hi[0], lo[1], hi[1], lo[2], hi[2]] if np.all(np.isclose(oldBounds, self.bounds)): self.propNotify('bounds')
def generateVertices(cls, zpos, xmin, xmax, ymin, ymax, xax, yax, xform=None): """Generates a set of vertices suitable for passing to the :meth:`.Texture2D.draw` method, for drawing a ``Texture2D`` to a 2D canvas. :arg zpos: Position along the Z axis, in the display coordinate system. :arg xmin: Minimum X axis coordinate. :arg xmax: Maximum X axis coordinate. :arg ymin: Minimum Y axis coordinate. :arg ymax: Maximum Y axis coordinate. :arg xax: Display space axis which maps to the horizontal screen axis. :arg yax: Display space axis which maps to the vertical screen axis. :arg xform: Transformation matrix to appply to vertices. """ zax = 3 - xax - yax vertices = np.zeros((6, 3), dtype=np.float32) vertices[:, zax] = zpos vertices[0, [xax, yax]] = [xmin, ymin] vertices[1, [xax, yax]] = [xmin, ymax] vertices[2, [xax, yax]] = [xmax, ymin] vertices[3, [xax, yax]] = [xmax, ymin] vertices[4, [xax, yax]] = [xmin, ymax] vertices[5, [xax, yax]] = [xmax, ymax] if xform is not None: vertices = transform.transform(vertices, xform) return vertices
def __drawBoundingBox(self): b = self.__displayCtx.bounds xlo, xhi = b.x ylo, yhi = b.y zlo, zhi = b.z xlo += 0.1 xhi -= 0.1 vertices = np.array([ [xlo, ylo, zlo], [xlo, ylo, zhi], [xlo, yhi, zlo], [xlo, yhi, zhi], [xhi, ylo, zlo], [xhi, ylo, zhi], [xhi, yhi, zlo], [xhi, yhi, zhi], [xlo, ylo, zlo], [xlo, yhi, zlo], [xhi, ylo, zlo], [xhi, yhi, zlo], [xlo, ylo, zhi], [xlo, yhi, zhi], [xhi, ylo, zhi], [xhi, yhi, zhi], [xlo, ylo, zlo], [xhi, ylo, zlo], [xlo, ylo, zhi], [xhi, ylo, zhi], [xlo, yhi, zlo], [xhi, yhi, zlo], [xlo, yhi, zhi], [xhi, yhi, zhi], ]) vertices = transform.transform(vertices, self.__viewMat) gl.glLineWidth(2) gl.glColor3f(0.5, 0, 0) gl.glBegin(gl.GL_LINES) for v in vertices: gl.glVertex3f(*v) gl.glEnd()