def _affine_field(src, ref, xform, srcSpace, refSpace, shape=None, fv2w=None): if shape is None: shape = ref.shape[:3] if fv2w is None: fv2w = ref.getAffine('voxel', 'world') rx, ry, rz = np.meshgrid(np.arange(shape[0]), np.arange(shape[1]), np.arange(shape[2]), indexing='ij') rvoxels = np.vstack((rx.flatten(), ry.flatten(), rz.flatten())).T f2r = affine.concat(ref.getAffine('world', refSpace), fv2w) rcoords = affine.transform(rvoxels, f2r) scoords = affine.transform(rcoords, xform) field = np.zeros(list(shape[:3]) + [3]) field[:] = (scoords - rcoords).reshape(*it.chain(shape, [3])) field = nonlinear.DeformationField(field, src, ref, srcSpace=srcSpace, refSpace=refSpace, xform=fv2w, header=ref.header, defType='relative') return field
def __drawLight(self): """Draws a representation of the light source. """ lightPos = self.lightPos bounds = self.__displayCtx.bounds centre = np.array([ bounds.xlo + 0.5 * (bounds.xhi - bounds.xlo), bounds.ylo + 0.5 * (bounds.yhi - bounds.ylo), bounds.zlo + 0.5 * (bounds.zhi - bounds.zlo) ]) lightPos = affine.transform(lightPos, self.__viewMat) centre = affine.transform(centre, self.__viewMat) # draw the light as a point gl.glColor4f(1, 1, 0, 1) gl.glPointSize(10) gl.glBegin(gl.GL_POINTS) gl.glVertex3f(*lightPos) gl.glEnd() # draw a line from the light to the # centre of the display bounding box gl.glBegin(gl.GL_LINES) gl.glVertex3f(*lightPos) gl.glVertex3f(*centre) gl.glEnd()
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.getTransform('mesh', 'display') if not np.all(np.isclose(xform, np.eye(4))): vertices = affine.transform(vertices, xform) if self.threedee: nmat = affine.invert(xform).T normals = affine.transform(normals, nmat, vector=True) self.vertices = np.asarray(vertices, dtype=np.float32) self.indices = np.asarray(indices.flatten(), dtype=np.uint32) self.vertices = dutils.makeWriteable(self.vertices) self.indices = dutils.makeWriteable(self.indices) if self.threedee: self.normals = np.array(normals, dtype=np.float32)
def _random_affine_field(): src = _random_image() ref = _random_image() # our test field just encodes an affine xform = affine.compose(np.random.randint(2, 5, 3), np.random.randint(1, 10, 3), np.random.random(3)) rx, ry, rz = np.meshgrid(np.arange(ref.shape[0]), np.arange(ref.shape[1]), np.arange(ref.shape[2]), indexing='ij') rvoxels = np.vstack((rx.flatten(), ry.flatten(), rz.flatten())).T rcoords = affine.transform(rvoxels, ref.voxToScaledVoxMat) scoords = affine.transform(rcoords, xform) field = np.zeros(list(ref.shape[:3]) + [3]) field[:] = (scoords - rcoords).reshape(*it.chain(ref.shape, [3])) field = nonlinear.DeformationField(field, src, ref, header=ref.header, defType='relative') return field, xform
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 canvas = self.canvas copts = canvas.opts tex = self.renderTexture1 proj = self.canvas.projectionMatrix vertices, voxCoords, texCoords = self.generateVertices3D(bbox) rayStep, texform = opts.calculateRayCastSettings(xform, proj) rayStep = affine.transformNormal( rayStep, self.imageTexture.texCoordXform(self.overlay.shape)) texform = affine.concat( texform, self.imageTexture.invTexCoordXform(self.overlay.shape)) # If lighting is enabled, we specify the light # position in image texture coordinates, to make # life easier for the shader if copts.light: lxform = opts.getTransform('display', 'texture') lightPos = affine.transform(canvas.lightPos, lxform) else: lightPos = [0, 0, 0] if xform is not None: vertices = affine.transform(vertices, xform) self.shader.set('lighting', copts.light) self.shader.set('tex2ScreenXform', texform) self.shader.set('rayStep', rayStep) self.shader.set('lightPos', lightPos) 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 __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 = affine.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 lightPos(self): """Takes the values of :attr:`.Scene3DOpts.lightPos` and :attr:`.Scene3DOpts.lightDistance`, and converts it to a position in the display coordinate system. The ``Scene3DOpts.lightPos`` property contains rotations about the centre of the display bounding box, and the :attr:`.Scene3DOpts.lightDistance` property specifies the distance of the light from the bounding box centre. """ 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) ]) yaw, pitch, roll = self.opts.lightPos distance = self.opts.lightDistance yaw = yaw * np.pi / 180 pitch = pitch * np.pi / 180 roll = roll * np.pi / 180 rotmat = affine.axisAnglesToRotMat(pitch, roll, yaw) xform = affine.compose([1, 1, 1], [0, 0, 0], rotmat, origin=centre) lightPos = centre + [0, 0, distance * b.zlen] lightPos = affine.transform(lightPos, xform) return lightPos
def __updateBounds(self): """Called whenever any of the :attr:`refImage`, :attr:`coordSpace`, or :attr:`transform` properties change. Updates the :attr:`.DisplayOpts.bounds` property accordingly. """ # create a bounding box for the # overlay vertices in their # native coordinate system lo, hi = self.overlay.bounds xlo, ylo, zlo = lo xhi, yhi, zhi = hi # Transform the bounding box # into display coordinates xform = self.getTransform('mesh', 'display') bbox = list(it.product(*zip(lo, hi))) bbox = affine.transform(bbox, xform) # re-calculate the min/max bounds x = np.sort(bbox[:, 0]) y = np.sort(bbox[:, 1]) z = np.sort(bbox[:, 2]) xlo, xhi = x.min(), x.max() ylo, yhi = y.min(), y.max() zlo, zhi = z.min(), z.max() oldBounds = self.bounds self.bounds = [xlo, xhi, ylo, yhi, zlo, zhi] if np.all(np.isclose(oldBounds, self.bounds)): self.propNotify('bounds')
def _eval_coord_voxel_query(atlas, query, qtype, qin): voxel = qtype == 'voxel' if voxel: vx, vy, vz = query else: vx, vy, vz = affine.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.values(query, voxel=voxel) == expval assert atlas.coordValues(query, voxel=voxel) == expval if isinstance(atlas, fslatlases.LabelAtlas): evalLabel() elif isinstance(atlas, fslatlases.ProbabilisticAtlas): evalProb()
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 = affine.transform(vertices, xform) return vertices
def draw2D(self, zpos, axes): """Draws this ``VoxelGrid`` annotation. """ xax, yax, zax = axes dispLoc = [0] * 3 dispLoc[zax] = zpos voxLoc = affine.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 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 = affine.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 __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 = affine.transform(vertices, xform) vertices = np.array(vertices, dtype=np.float32).ravel('C') texCoords = self.generateTextureCoords() .ravel('C') return vertices, texCoords, indices
def coordValues(self, loc, voxel=False): """Looks up the region values 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. Returns an empty list if the given location is out of bounds. """ if not voxel: loc = affine.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 [] vals = 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 [vals[l.index] for l in self.desc.labels]
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 = affine.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') voxToTex = affine.concat(texture.texCoordXform(shape), voxToTex) verts, voxs = glroutines.slice2D(shape, xax, yax, zpos, voxToDisplay, displayToVox) texs = affine.transform(voxs, voxToTex)[:, :texture.ndim] 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((texture.target, gl.GL_TEXTURE_COORD_ARRAY, gl.GL_VERTEX_ARRAY)): gl.glVertexPointer( 3, gl.GL_FLOAT, 0, verts) gl.glTexCoordPointer(texture.ndim, 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, :] = affine.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 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 = affine.transform(voxCoords, v2tMat) return vertices, voxCoords, texCoords
def test_fromFnirt(): basefield, basexform = _random_affine_field() src = basefield.src ref = basefield.ref spaces = list(it.permutations(('voxel', 'fsl', 'world'), 2)) for from_, to in spaces: got = fnirt.fromFnirt(basefield, from_, to) assert got.srcSpace == to assert got.refSpace == from_ coords = [ np.random.randint(0, basefield.shape[0], 5), np.random.randint(0, basefield.shape[1], 5), np.random.randint(0, basefield.shape[2], 5) ] coords = np.array(coords).T coords = affine.transform(coords, ref.getAffine('voxel', from_)) aff = affine.concat(src.getAffine('fsl', to), basexform, ref.getAffine(from_, 'fsl')) got = got.transform(coords) exp = affine.transform(coords, aff) enan = np.isnan(exp) gnan = np.isnan(got) assert np.all(np.isclose(enan, gnan)) assert np.all(np.isclose(exp[~enan], got[~gnan])) # Converting from a FNIRT coefficient field src = fslimage.Image(op.join(datadir, 'src')) ref = fslimage.Image(op.join(datadir, 'ref')) coef = fnirt.readFnirt(op.join(datadir, 'coefficientfield'), src, ref) disp = fnirt.readFnirt(op.join(datadir, 'displacementfield'), src, ref) for from_, to in spaces: cnv = fnirt.fromFnirt(coef, from_, to) exp = nonlinear.convertDeformationSpace(disp, from_, to) tol = dict(atol=1e-5, rtol=1e-5) assert np.all(np.isclose(cnv.data, exp.data, **tol))
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 = affine.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 = affine.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 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 opts = self.opts threedee = self.threedee vertices = overlay.vertices indices = overlay.indices normals = self.overlay.vnormals vdata = opts.getVertexData('vertex') xform = opts.getTransform('mesh', 'display') if not np.all(np.isclose(xform, np.eye(4))): vertices = affine.transform(vertices, xform) if self.threedee: nmat = affine.invert(xform).T normals = affine.transform(normals, nmat, vector=True) self.origIndices = indices indices = np.asarray(indices.flatten(), dtype=np.uint32) # If flatShading is active, we cannot share # vertices between triangles, so we generate # a set of unique vertices for each triangle, # and then re-generate the triangle indices. # The original indices are saved above, as # they will be used by the getVertexData # method to duplicate the vertex data. if threedee and (vdata is not None) and opts.flatShading: self.vertices = vertices[indices].astype(np.float32) self.indices = np.arange(0, len(self.vertices), dtype=np.uint32) normals = normals[indices, :] else: self.vertices = np.asarray(vertices, dtype=np.float32) self.indices = indices self.vertices = dutils.makeWriteable(self.vertices) self.indices = dutils.makeWriteable(self.indices) if self.threedee: self.normals = np.array(normals, dtype=np.float32)
def _field_coords(field): vx, vy, vz = field.shape[ :3] coords = np.meshgrid(np.arange(vx), np.arange(vy), np.arange(vz), indexing='ij') coords = np.array(coords).transpose((1, 2, 3, 0)) return affine.transform( coords.reshape(-1, 3), field.getAffine('voxel', 'fsl')).reshape(field.shape)
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 = affine.transform(voxels, d2vMat) voxels = opts.roundVoxels(voxels, daxes=[zax], roundOther=False) return voxels
def draw(self, glType, vertices, indices=None, normals=None, vdata=None, mdata=None): """Called for 3D meshes, and when :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. :arg mdata: ``(n, )`` array containing alpha modulation data for each vertex. """ canvas = self.canvas shader = self.activeShader # for 3D, shader attributes are # configured in updateShaderState if self.threedee: vertices = None normals = None vdata = None mdata = None if vertices is not None: shader.setAtt('vertex', vertices) if normals is not None: shader.setAtt('normal', normals) if vdata is not None: shader.setAtt('vertexData', vdata) if mdata is not None: shader.setAtt('modulateData', mdata) if self.threedee: lightPos = affine.transform(canvas.lightPos, canvas.viewMatrix) shader.set('lighting', canvas.opts.light) shader.set('lightPos', lightPos) shader.loadAtts() if indices is None: gl.glDrawArrays(glType, 0, vertices.shape[0]) else: nverts = indices.shape[0] if self.threedee: indices = None gl.glDrawElements(glType, nverts, gl.GL_UNSIGNED_INT, indices)
def test_transform_vector(seed): # Some transform with a # translation component xform = affine.compose([1, 2, 3], [5, 10, 15], [np.pi / 2, np.pi / 2, 0]) vecs = np.random.random((20, 3)) for v in vecs: vecExpected = np.dot(xform, list(v) + [0])[:3] ptExpected = np.dot(xform, list(v) + [1])[:3] vecResult = affine.transform(v, xform, vector=True) vec33Result = affine.transform(v, xform[:3, :3], vector=True) ptResult = affine.transform(v, xform, vector=False) assert np.all(np.isclose(vecExpected, vecResult)) assert np.all(np.isclose(vecExpected, vec33Result)) assert np.all(np.isclose(ptExpected, ptResult))
def __imageDataChanged(self, image, topic, sliceobj): """Called when the :class:`.Image` notifies about a data changes. Triggers an image texture refresh via a call to :meth:`set`. :arg image: The ``Image`` instance :arg topic: The string ``'data'`` :arg sliceobj: Slice object specifying the portion of the image that was changed. """ # TODO If the change has caused the image # data range to change, and texture # data normalisation is on, you have # to refresh the full texture. # # The Image instance does follow up # data change notifications with a # data range notification; perhaps # you can use this somehow. # If the data change was performed using # normal array indexing, we can just replace # that part of the image texture. if isinstance(sliceobj, tuple): # Get the new data, and calculate an # offset into the full image from the # slice object. data = np.array(image[sliceobj]) offset = imagewrapper.sliceObjToSliceTuple(sliceobj, image.shape) offset = [o[0] for o in offset] # Make sure the data/offset are # compatible with 2D textures data = self.shapeData(data, oldShape=image.shape) offset[:3] = affine.transform(offset[:3], self.texCoordXform(image.shape)) log.debug('{} data changed - refreshing part of ' 'texture (offset: {}, size: {})'.format( image.name, offset, data.shape)) self.patchData(data, offset) # Otherwise (boolean array indexing) we have # to replace the whole image texture. else: log.debug('{} data changed - refreshing full ' 'texture'.format(image.name)) self.set()
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, :] = affine.transform(vertices, xform) return allVertCoords, allTexCoords, allIndices
def test_convertDeformationSpace(): basefield, xform = _random_affine_field() src = basefield.src ref = basefield.ref # generate reference fsl->fsl coordinate mappings # For each combination of srcspace->tospace # Generate random coordinates, check that # displacements are correct spaces = ['fsl', 'voxel', 'world'] spaces = list(it.combinations_with_replacement(spaces, 2)) spaces = spaces + [(r, s) for s, r in spaces] spaces = list(set(spaces)) for from_, to in spaces: refcoords = [ np.random.randint(0, basefield.shape[0], 5), np.random.randint(0, basefield.shape[1], 5), np.random.randint(0, basefield.shape[2], 5) ] refcoords = np.array(refcoords, dtype=np.int).T refcoords = affine.transform(refcoords, ref.voxToScaledVoxMat) srccoords = basefield.transform(refcoords) field = nonlinear.convertDeformationSpace(basefield, from_, to) premat = ref.getAffine('fsl', from_) postmat = src.getAffine('fsl', to) input = affine.transform(refcoords, premat) expect = affine.transform(srccoords, postmat) got = field.transform(input) enan = np.isnan(expect) gnan = np.isnan(got) assert np.all(np.isclose(enan, gnan)) assert np.all(np.isclose(expect[~enan], got[~gnan]))
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 = affine.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 = affine.transform(coords, a_img.voxToWorldMat) return tuple([dtype(c) for c in coords])
def generateVertices3D(self, bbox=None): """Overrides :meth:`.GLImageObject.generateVertices3D`. Appliies the :meth:`.ImageTextureBase.texCoordXform` to the texture coordinates - this is performed to support 2D images/textures. """ vertices, voxCoords, texCoords = \ glimageobject.GLImageObject.generateVertices3D(self, bbox) texCoords = affine.transform( texCoords, self.imageTexture.texCoordXform(self.overlay.shape)) return vertices, voxCoords, texCoords