def test_generateAffines(): v2w = affine.compose(np.random.random(3), np.random.random(3), np.random.random(3)) shape = (10, 10, 10) pixdim = (1, 1, 1) got, isneuro = fslimage.Nifti.generateAffines(v2w, shape, pixdim) w2v = npla.inv(v2w) assert isneuro == (npla.det(v2w) > 0) if not isneuro: v2f = np.eye(4) f2v = np.eye(4) f2w = v2w w2f = w2v else: v2f = affine.scaleOffsetXform([-1, 1, 1], [9, 0, 0]) f2v = npla.inv(v2f) f2w = affine.concat(v2w, f2v) w2f = affine.concat(v2f, w2v) assert np.all(np.isclose(v2w, got['voxel', 'world'])) assert np.all(np.isclose(w2v, got['world', 'voxel'])) assert np.all(np.isclose(v2f, got['voxel', 'fsl'])) assert np.all(np.isclose(f2v, got['fsl', 'voxel'])) assert np.all(np.isclose(f2w, got['fsl', 'world'])) assert np.all(np.isclose(w2f, got['world', 'fsl']))
def __xformChanged(self, ev=None): """Called when any of the scale, offset, or rotate widgets are modified. Updates the :attr:`.NiftiOpts.displayXform` for the overlay currently being edited. """ if self.__overlay is None: return overlay = self.__overlay opts = self.displayCtx.getOpts(overlay) if self.__extraXform is None: v2wXform = overlay.voxToWorldMat else: v2wXform = self.__extraXform xform = self.__getCurrentXform() xform = affine.concat(xform, v2wXform) self.__formatXform(xform, self.__newXform) # The NiftiOpts.displayXform is applied on # top of the image voxToWorldMat. But our # xform here has been constructed to replace # the voxToWorldMat entirely. So we include # a worldToVoxMat transform to trick the # NiftiOpts code. opts.displayXform = affine.concat(xform, overlay.worldToVoxMat)
def getTransform(self, from_, to): """Return a matrix which may be used to transform coordinates from ``from_`` to ``to``. The following values are accepted for the ``from_`` and ``to`` parameters: - ``'world'``: World coordinate system - ``'display'`` Display coordinate system - ``'mesh'`` The coordinate system of this mesh. """ nfrom_ = self.normaliseSpace(from_) nto = self.normaliseSpace(to) ref = self.refImage if ref is None: return np.eye(4) opts = self.displayCtx.getOpts(ref) xform = opts.getTransform(nfrom_, nto) if from_ == 'mesh' and self.coordSpace == 'torig': surfToVox = affine.invert(fslmgh.voxToSurfMat(ref)) xform = affine.concat(xform, ref.getAffine('voxel', 'world'), surfToVox) if to == 'mesh' and self.coordSpace == 'torig': voxToSurf = fslmgh.voxToSurfMat(ref) xform = affine.concat(voxToSurf, ref.getAffine('world', 'voxel'), xform) return xform
def test_applyDeformation_premat(): src2ref = affine.compose( np.random.randint(2, 5, 3), np.random.randint(1, 10, 3), [0, 0, 0]) ref2src = affine.invert(src2ref) srcdata = np.random.randint(1, 65536, (10, 10, 10)) refdata = np.random.randint(1, 65536, (10, 10, 10)) src = fslimage.Image(srcdata) ref = fslimage.Image(refdata, xform=src2ref) field = _affine_field(src, ref, ref2src, 'world', 'world') # First try a down-sampled version # of the original source image altsrc, xf = resample.resample(src, (5, 5, 5), origin='corner') altsrc = fslimage.Image(altsrc, xform=xf, header=src.header) expect, xf = resample.resampleToReference( altsrc, ref, matrix=src2ref, order=1, mode='nearest') premat = affine.concat(src .getAffine('world', 'voxel'), altsrc.getAffine('voxel', 'world')) result = nonlinear.applyDeformation( altsrc, field, order=1, mode='nearest', premat=premat) assert np.all(np.isclose(expect, result)) # Now try a down-sampled ROI # of the original source image altsrc = roi.roi(src, [(2, 9), (2, 9), (2, 9)]) altsrc, xf = resample.resample(altsrc, (4, 4, 4)) altsrc = fslimage.Image(altsrc, xform=xf, header=src.header) expect, xf = resample.resampleToReference( altsrc, ref, matrix=src2ref, order=1, mode='nearest') premat = affine.concat(src .getAffine('world', 'voxel'), altsrc.getAffine('voxel', 'world')) result = nonlinear.applyDeformation( altsrc, field, order=1, mode='nearest', premat=premat) assert np.all(np.isclose(expect, result)) # down-sampled and offset ROI # of the original source image altsrc = roi.roi(src, [(-5, 8), (-5, 8), (-5, 8)]) altsrc, xf = resample.resample(altsrc, (6, 6, 6)) altsrc = fslimage.Image(altsrc, xform=xf, header=src.header) expect, xf = resample.resampleToReference( altsrc, ref, matrix=src2ref, order=1, mode='nearest') premat = affine.concat(src .getAffine('world', 'voxel'), altsrc.getAffine('voxel', 'world')) result = nonlinear.applyDeformation( altsrc, field, order=1, mode='nearest', premat=premat) assert np.all(np.isclose(expect, result))
def draw2D(self, zpos, axes, xform=None, bbox=None): """Draws the line vectors at a plane at the specified Z location. Voxel coordinates are passed to the vertex shader, which calculates the corresponding line vertex locations. """ opts = self.opts shader = self.shader v2dMat = opts.getTransform('voxel', 'display') voxels = self.generateVoxelCoordinates2D(zpos, axes, bbox=bbox) voxels = np.repeat(voxels, 2, 0) indices = np.arange(voxels.shape[0], dtype=np.uint32) if xform is None: xform = v2dMat else: xform = affine.concat(xform, v2dMat) shader.set('voxToDisplayMat', xform) shader.setAtt('vertexID', indices) shader.setAtt('voxel', voxels) shader.loadAtts() gl.glLineWidth(opts.lineWidth) gl.glDrawArrays(gl.GL_LINES, 0, voxels.size // 3) shader.unloadAtts()
def test_convert_flirt(): with tempdir.tempdir(): src = random_image() ref = random_image() src.save('src') ref.save('ref') xform = affine.compose(np.random.randint(1, 10, 3), np.random.randint(-100, 100, 3), (np.random.random(3) - 0.5) * np.pi) np.savetxt('src2ref.mat', xform) fsl_convert_x5.main('flirt -s src -r ref ' 'src2ref.mat src2ref.x5'.split()) expxform = affine.concat(ref.getAffine('fsl', 'world'), xform, src.getAffine('world', 'fsl')) gotxform, gotsrc, gotref = x5.readLinearX5('src2ref.x5') assert np.all(np.isclose(gotxform, expxform)) assert src.sameSpace(gotsrc) assert ref.sameSpace(gotref) fsl_convert_x5.main('flirt src2ref.x5 src2ref_copy.mat'.split()) gotxform = flirt.readFlirt('src2ref_copy.mat') assert np.all(np.isclose(gotxform, xform))
def test_prepareMask(): reg = atlases.registry reg.rescanAtlases() probatlas = reg.loadAtlas('harvardoxford-cortical') probsumatlas = reg.loadAtlas('harvardoxford-cortical', loadSummary=True) lblatlas = reg.loadAtlas('talairach') for atlas in [probatlas, probsumatlas, lblatlas]: ashape = list(atlas.shape[:3]) m2shape = [s * 1.5 for s in ashape] goodmask1 = fslimage.Image( np.array(np.random.random(ashape), dtype=np.float32), xform=atlas.voxToWorldMat) goodmask2, xf = resample.resample(goodmask1, m2shape) goodmask2 = fslimage.Image(goodmask2, xform=xf) wrongdims = fslimage.Image( np.random.random(list(ashape) + [2])) wrongspace = fslimage.Image( np.random.random((20, 20, 20)), xform=affine.concat(atlas.voxToWorldMat, np.diag([2, 2, 2, 1]))) with pytest.raises(atlases.MaskError): atlas.prepareMask(wrongdims) with pytest.raises(atlases.MaskError): atlas.prepareMask(wrongspace) assert list(atlas.prepareMask(goodmask1).shape) == ashape assert list(atlas.prepareMask(goodmask2).shape) == ashape
def test_concat(): testfile = op.join(datadir, 'test_transform_test_concat.txt') lines = readlines(testfile) ntests = len(lines) // 4 tests = [] for i in range(ntests): ilines = lines[i * 4:i * 4 + 4] data = np.genfromtxt(ilines) ninputs = data.shape[1] // 4 - 1 inputs = [] for j in range(ninputs): inputs.append(data[:, j * 4:j * 4 + 4]) output = data[:, -4:] tests.append((inputs, output)) for inputs, expected in tests: result = affine.concat(*inputs) assert np.all(np.isclose(result, expected))
def _rotateModeLeftMouseDrag(self, ev, canvas, mousePos, canvasPos): """Called on left mouse drag events in ``rotate`` mode. Modifies the canvas rotation matrix according to the X and Y mouse position (relative to the mouse down location). """ if self.__rotateMousePos is None: return w, h = canvas.GetSize() x0, y0 = self.__rotateMousePos x1, y1 = mousePos # Normalise x/y mouse pos to [-fac*pi, +fac*pi] fac = 1 x0 = -1 + 2 * (x0 / float(w)) * fac * np.pi y0 = -1 + 2 * (y0 / float(h)) * fac * np.pi x1 = -1 + 2 * (x1 / float(w)) * fac * np.pi y1 = -1 + 2 * (y1 / float(h)) * fac * np.pi xrot = x1 - x0 yrot = y1 - y0 rot = affine.axisAnglesToRotMat(yrot, 0, xrot) self.__lastRot = rot self.__rotateMousePos = mousePos canvas.opts.rotation = affine.concat(rot, self.__lastRot, self.__baseXform)
def lookAt(eye, centre, up): """Replacement for ``gluLookAt`. Creates a transformation matrix which transforms the display coordinate system such that a camera at position (0, 0, 0), and looking towards (0, 0, -1), will see a scene as if from position ``eye``, oriented ``up``, and looking towards ``centre``. See: https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/gluLookAt.xml """ eye = np.array(eye) centre = np.array(centre) up = np.array(up) proj = np.eye(4) forward = centre - eye forward /= np.sqrt(np.dot(forward, forward)) right = np.cross(forward, up) right /= np.sqrt(np.dot(right, right)) up = np.cross(right, forward) up /= np.sqrt(np.dot(up, up)) proj[0, :3] = right proj[1, :3] = up proj[2, :3] = -forward eye = affine.scaleOffsetXform(1, -eye) proj = affine.concat(proj, eye) return proj
def __onApply(self, ev): """Called when the *Apply* button is pushed. Sets the ``voxToWorldMat`` attribute of the :class:`.Image` instance being transformed. """ overlay = self.__overlay if overlay is None: return if self.__extraXform is None: v2wXform = overlay.voxToWorldMat else: v2wXform = self.__extraXform newXform = self.__getCurrentXform() opts = self.displayCtx.getOpts(overlay) xform = affine.concat(newXform, v2wXform) with props.suppress(opts, 'displayXform'): opts.displayXform = np.eye(4) overlay.voxToWorldMat = xform # Reset the interface, and clear any # cached transform for this overlay self.__deregisterOverlay() self.__cachedXforms.pop(overlay, None) self.__selectedOverlayChanged()
def draw2D(self, zpos, axes, xform=None, bbox=None): """Draws the line vertices corresponding to a 2D plane located at the specified Z location. """ opts = self.opts vertices, voxCoords = self.lineVertices.getVertices2D(self, zpos, axes, bbox=bbox) if vertices.size == 0: return self.shader.setAtt('voxCoord', voxCoords) self.shader.loadAtts() v2d = opts.getTransform('voxel', 'display') if xform is None: xform = v2d else: xform = affine.concat(xform, v2d) gl.glPushMatrix() gl.glMultMatrixf(np.array(xform, dtype=np.float32).ravel('F')) gl.glVertexPointer(3, gl.GL_FLOAT, 0, vertices) gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_LINE) gl.glLineWidth(opts.lineWidth) gl.glDrawArrays(gl.GL_LINES, 0, vertices.size // 3) gl.glPopMatrix()
def doMovieUpdate(self, overlay, opts): """Overrides :meth:`.CanvasPanel.doMovieUpdate`. For x/y/z axis movies, the scene is rotated. Otherwise (for time) the ``CanvasPanel`` implementation is called. """ if self.movieAxis >= 3: return canvaspanel.CanvasPanel.doMovieUpdate(self, overlay, opts) else: canvas = self.__canvas currot = canvas.opts.rotation rate = float(self.movieRate) rateMin = self.getAttribute('movieRate', 'minval') rateMax = self.getAttribute('movieRate', 'maxval') rate = 0.1 + 0.9 * (rate - rateMin) / (rateMax - rateMin) rate = rate * np.pi / 10 rots = [0, 0, 0] rots[self.movieAxis] = rate xform = affine.axisAnglesToRotMat(*rots) xform = affine.concat(xform, currot) canvas.opts.rotation = xform return np.copy(xform)
def getModulateValueXform(self): """Returns an affine transform to normalise alpha modulation values. The GL volume shaders need to normalise the modulate value by the modulation range to generate an opacity value. We calculate a suitable scale and offset by buildin an affine transform which transforms voxel values from the image/modulate image texture range to 0/1, where 0 corresponds to the low modulate range bound, and 1 to the high modulate range bound. The resulting scale/offset can be used by the shader to convert a modulate value directly into an opacity value. """ opts = self.opts if opts.modulateImage is None: modXform = self.imageTexture.voxValXform else: modXform = self.modulateTexture.voxValXform modlo, modhi = opts.modulateRange modrange = modhi - modlo if modrange == 0: modXform = np.eye(4) else: modXform = affine.concat( affine.scaleOffsetXform(1 / modrange, -modlo / modrange), modXform) return modXform
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 draw2D(self, zpos, axes, xform=None, bbox=None): """Called by :meth:`.GLSH.draw2D`. Draws the scene. """ opts = self.opts shader = self.shader v2dMat = opts.getTransform('voxel', 'display') if xform is None: xform = v2dMat else: xform = affine.concat(v2dMat, xform) voxels = self.generateVoxelCoordinates2D(zpos, axes, bbox) voxels, radTexShape = self.updateRadTexture(voxels) if len(voxels) == 0: return voxIdxs = np.arange(voxels.shape[0], dtype=np.float32) shader.setAtt('voxel', voxels, divisor=1) shader.setAtt('voxelID', voxIdxs, divisor=1) shader.set('voxToDisplayMat', xform) shader.set('radTexShape', radTexShape) shader.set('radXform', self.radTexture.voxValXform) shader.loadAtts() arbdi.glDrawElementsInstancedARB(gl.GL_TRIANGLES, self.nVertices, gl.GL_UNSIGNED_INT, None, len(voxels))
def test_applyDeformation_altref(): src2ref = affine.compose( np.random.randint(2, 5, 3), np.random.randint(1, 10, 3), np.random.random(3)) ref2src = affine.invert(src2ref) srcdata = np.random.randint(1, 65536, (10, 10, 10)) refdata = np.random.randint(1, 65536, (10, 10, 10)) src = fslimage.Image(srcdata) ref = fslimage.Image(refdata, xform=src2ref) field = _affine_field(src, ref, ref2src, 'world', 'world') altrefxform = affine.concat( src2ref, affine.scaleOffsetXform([1, 1, 1], [5, 0, 0])) altref = fslimage.Image(refdata, xform=altrefxform) expect, xf = resample.resampleToReference( src, altref, matrix=src2ref, order=1, mode='constant', cval=0) result = nonlinear.applyDeformation( src, field, ref=altref, order=1, mode='constant', cval=0) # boundary voxels can get truncated # (4 is the altref-ref overlap boundary) expect[4, :, :] = 0 result[4, :, :] = 0 expect = expect[1:-1, 1:-1, 1:-1] result = result[1:-1, 1:-1, 1:-1] assert np.all(np.isclose(expect, result))
def test_MGHImage(): testfile = op.join(datadir, 'example.mgz') # Load from a file img = fslmgh.MGHImage(testfile) nbimg = nib.load(testfile) v2s = nbimg.header.get_vox2ras_tkr() w2s = affine.concat(v2s, affine.invert(nbimg.affine)) assert np.all(np.isclose(img[:], np.asanyarray(nbimg.dataobj))) assert np.all(np.isclose(img.voxToWorldMat, nbimg.affine)) assert np.all(np.isclose(img.voxToSurfMat, v2s)) assert np.all(np.isclose(img.surfToVoxMat, affine.invert(v2s))) assert np.all(np.isclose(img.worldToSurfMat, w2s)) assert np.all(np.isclose(img.surfToWorldMat, affine.invert(w2s))) assert img.name == op.basename(testfile) assert img.dataSource == testfile assert img.mghImageFile == testfile # Load from an in-memory nibabel object img = fslmgh.MGHImage(nbimg) assert np.all(np.isclose(img[:], np.asanyarray(nbimg.dataobj))) assert np.all(np.isclose(img.voxToWorldMat, nbimg.affine)) assert img.dataSource is None assert img.mghImageFile is None
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 transformCoords(self, coords, from_, to, *args, **kwargs): """Transforms the given ``coords`` from ``from_`` to ``to``. :arg coords: Coordinates to transform. :arg from_: Space that the coordinates are in :arg to: Space to transform the coordinates to All other parameters are passed through to the :meth:`.NiftiOpts.transformCoords` method of the reference image ``DisplayOpts``. The following values are accepted for the ``from_`` and ``to`` parameters: - ``'world'``: World coordinate system - ``'display'`` Display coordinate system - ``'mesh'`` The coordinate system of this mesh. - ``'voxel'``: The voxel coordinate system of the reference image - ``'id'``: Equivalent to ``'voxel'``. """ nfrom_ = self.normaliseSpace(from_) nto = self.normaliseSpace(to) ref = self.refImage pre = None post = None if ref is None: return coords if from_ == 'mesh' and self.coordSpace == 'torig': pre = affine.concat(ref.getAffine('voxel', 'world'), affine.invert(fslmgh.voxToSurfMat(ref))) if to == 'mesh' and self.coordSpace == 'torig': post = affine.concat(fslmgh.voxToSurfMat(ref), ref.getAffine('world', 'voxel')) opts = self.displayCtx.getOpts(ref) return opts.transformCoords(coords, nfrom_, nto, pre=pre, post=post, **kwargs)
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 drawOutline(self, zpos, axes, xform=None, bbox=None): """Called by :meth:`draw2D` when ``MeshOpts.outline is True or MeshOpts.vertexData is not None``. Calculates the intersection of the mesh with the viewing plane, and renders it as a set of ``GL_LINES``. If ``MeshOpts.vertexData is None``, the draw is performed using immediate mode OpenGL. Otherwise, the :func:`.gl14.glmesh_funcs.draw` or :func:`.gl21.glmesh_funcs.draw` function is used, which performs shader-based rendering. """ opts = self.opts # Makes code below a bit nicer if xform is None: xform = np.eye(4) vertices, faces, dists, vertXform = self.calculateIntersection( zpos, axes, bbox) if vertXform is not None: xform = affine.concat(xform, vertXform) vdata = self.getVertexData('vertex', faces, dists) mdata = self.getVertexData('modulate', faces, dists) useShader = vdata is not None vertices = vertices.reshape(-1, 3) nvertices = vertices.shape[0] if mdata is None: mdata = vdata gl.glMatrixMode(gl.GL_MODELVIEW) gl.glPushMatrix() gl.glMultMatrixf(np.array(xform, dtype=np.float32).ravel('F')) gl.glLineWidth(opts.outlineWidth) # Constant colour if not useShader: vertices = vertices.ravel('C') gl.glColor(*opts.getConstantColour()) gl.glEnableClientState(gl.GL_VERTEX_ARRAY) gl.glVertexPointer(3, gl.GL_FLOAT, 0, vertices) gl.glDrawArrays(gl.GL_LINES, 0, nvertices) gl.glDisableClientState(gl.GL_VERTEX_ARRAY) # Coloured from vertex data else: fslgl.glmesh_funcs.draw( self, gl.GL_LINES, vertices, vdata=vdata, mdata=mdata) gl.glPopMatrix()
def resampleToReference(image, reference, matrix=None, **kwargs): """Resample ``image`` into the space of the ``reference``. This is a wrapper around :func:`resample` - refer to its documenttion for details on the other arguments and the return values. When resampling to a reference image, resampling will only be applied along the spatial (first three) dimensions. :arg image: :class:`.Image` to resample :arg reference: :class:`.Nifti` defining the space to resample ``image`` into :arg matrix: Optional world-to-world affine alignment matrix """ oldShape = list(image.shape) newShape = list(reference.shape[:3]) if matrix is None: matrix = np.eye(4) # If the input image is >3D, pad the # new shape so that we only resample # along the first 3 dimensions. if len(newShape) < len(oldShape): newShape = newShape + oldShape[len(newShape):] # Align the two images together # via their vox-to-world affines, # and the world-to-world affine # if provided matrix = affine.concat(image.worldToVoxMat, affine.invert(matrix), reference.voxToWorldMat) # If the input image is >3D, we # have to adjust the resampling # matrix to take into account the # additional dimensions (see scipy. # ndimage.affine_transform) if len(newShape) > 3: rotmat = matrix[:3, :3] offsets = matrix[:3, 3] matrix = np.eye(len(newShape) + 1) matrix[:3, :3] = rotmat matrix[:3, -1] = offsets kwargs['mode'] = kwargs.get('mode', 'constant') kwargs['newShape'] = newShape kwargs['matrix'] = matrix data = resample(image, **kwargs)[0] # The image is now in the same space # as the reference, so it inherits # ref's voxel-to-world affine return data, reference.voxToWorldMat
def roi(image, bounds): """Extract an ROI from the given ``image`` according to the given ``bounds``. This function can also be used to pad, or expand the field-of-view, of an image, by passing in negative low bound values, or high bound values which are larger than the image shape. The padded region will contain zeroes. :arg image: :class:`.Image` object :arg bounds: Must be a sequence of tuples, containing the low/high bounds for each voxel dimension, where the low bound is *inclusive*, and the high bound is *exclusive*. For 4D images, the bounds for the fourth dimension are optional. :returns: A new :class:`.Image` object containing the region specified by the ``bounds``. """ bounds = _normaliseBounds(image.shape, bounds) newshape = [hi - lo for lo, hi in bounds] oldslc = [] newslc = [] # Figure out how to slice the input image # data array, and the corresponding slice # in the output data array. for (lo, hi), oldlen, newlen in zip(bounds, image.shape, newshape): oldlo = max(lo, 0) oldhi = min(hi, oldlen) newlo = max(0, -lo) newhi = newlo + (oldhi - oldlo) oldslc.append(slice(oldlo, oldhi)) newslc.append(slice(newlo, newhi)) oldslc = tuple(oldslc) newslc = tuple(newslc) # Copy the ROI into the new data array newdata = np.zeros(newshape, dtype=image.dtype) newdata[newslc] = image.data[oldslc] # Create a new affine for the ROI, # with an appropriate offset along # each spatial dimension oldaff = image.voxToWorldMat offset = [lo for lo, hi in bounds[:3]] offset = affine.scaleOffsetXform([1, 1, 1], offset) newaff = affine.concat(oldaff, offset) return fslimage.Image(newdata, xform=newaff, header=image.header, name=image.name + '_roi')
def swapdim(infile): basename = fslimage.removeExt(op.basename(infile)) outfile = '{}_swapdim.nii.gz'.format(basename) img = fslimage.Image(infile) data = img.data xform = img.voxToWorldMat data = data.transpose((2, 0, 1)) rot = affine.rotMatToAffine( affine.concat(affine.axisAnglesToRotMat(np.pi / 2, 0, 0), affine.axisAnglesToRotMat(0, 0, 3 * np.pi / 2))) xform = affine.concat(xform, affine.scaleOffsetXform((1, -1, -1), (0, 0, 0)), rot) fslimage.Image(data, xform=xform, header=img.header).save(outfile) return outfile
def transformCoords(self, coords, from_, to_, vround=False, vector=False, pre=None, post=None): """Transforms the given coordinates from ``from_`` to ``to_``. The ``from_`` and ``to_`` parameters must be those accepted by the :meth:`getTransform` method. :arg coords: Coordinates to transform :arg from_: Space to transform from :arg to_: Space to transform to :arg vround: If ``True``, and ``to_ in ('voxel', 'id)``, the transformed coordinates are rounded to the nearest integer. :arg vector: Defaults to ``False``. If ``True``, the coordinates are treated as vectors. :arg pre: Transformation to apply before the ``from_``-to-``to`` transformation. :arg post: Transformation to apply after the ``from_``-to-``to`` transformation. """ if not self.__child: raise RuntimeError('transformCoords cannot be called on ' 'a parent NiftiOpts instance') xform = self.getTransform(from_, to_) if pre is not None: xform = affine.concat(xform, pre) if post is not None: xform = affine.concat(post, xform) coords = np.array(coords) coords = affine.transform(coords, xform, vector=vector) # Round to integer voxel coordinates? if to_ in ('voxel', 'id') and vround: coords = self.roundVoxels(coords) return coords
def __init__(self, image, **kwargs): """Create a ``MGHImage``. :arg image: Name of MGH file, or a ``nibabel.freesurfer.mghformat.MGHImage`` instance. All other arguments are passed through to :meth:`Image.__init__` """ if isinstance(image, six.string_types): filename = op.abspath(image) name = op.basename(filename) image = nib.load(image) else: name = 'MGH image' filename = None data = np.asanyarray(image.dataobj) xform = image.affine pixdim = image.header.get_zooms() vox2surf = image.header.get_vox2ras_tkr() # the image may have an affine which # transforms the data into some space # with a scaling that is different to # the pixdims. So we create a header # object with both the affine and the # pixdims, so they are both preserved. # # Note that we have to set the zooms # after the s/qform, otherwise nibabel # will clobber them with zooms gleaned # fron the affine. header = nib.nifti1.Nifti1Header() header.set_data_shape(data.shape) header.set_sform(xform) header.set_qform(xform) header.set_zooms(pixdim) fslimage.Image.__init__(self, data, header=header, name=name, dataSource=filename, **kwargs) if filename is not None: self.setMeta('mghImageFile', filename) self.__voxToSurfMat = vox2surf self.__surfToVoxMat = affine.invert(vox2surf) self.__surfToWorldMat = affine.concat(xform, self.__surfToVoxMat) self.__worldToSurfMat = affine.invert(self.__surfToWorldMat)
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 getAuxTextureXform(self, which): """Calculates a transformation matrix which will transform from the image coordinate system into the :attr:`.VolumeOpts.clipImage` or :attr:`.VolumeOpts.modulateImage` coordinate system. If the property is ``None``, it will be an identity transform. This transform is used by shader programs to find the auxillary image coordinates that correspond with specific image coordinates. """ return affine.concat( self.auxmgr.textureXform(which), # to support 2D image textures self.imageTexture.invTexCoordXform(self.overlay.shape))
def translate(infile, x, y, z): basename = fslimage.removeExt(op.basename(infile)) outfile = '{}_translated_{}_{}_{}.nii.gz'.format(basename, x, y, z) img = fslimage.Image(infile) xform = img.voxToWorldMat shift = affine.scaleOffsetXform(1, (x, y, z)) xform = affine.concat(shift, xform) img.voxToWorldMat = xform img.save(outfile) return outfile