def applySmoothing(data, matrix, newShape): """Called by the :func:`resample` function. If interpolating and smoothing, we apply a gaussian filter along axes with a resampling ratio greater than 1.1. We do this so that interpolation has an effect when down-sampling to a resolution where the voxel centres are aligned (as otherwise any interpolation regime will be equivalent to nearest neighbour). This more-or-less mimics the behaviour of FLIRT. See the ``scipy.ndimage.gaussian_filter`` function for more details. :arg data: Data to be smoothed. :arg matrix: Affine matrix to be used during resampling. The voxel scaling factors are extracted from this. :arg newShape: Shape the data is to be resampled into. :returns: A smoothed copy of ``data``. """ ratio = affine.decompose(matrix[:3, :3])[0] if len(newShape) > 3: ratio = np.concatenate(( ratio, [float(o) / float(s) for o, s in zip(data.shape[3:], newShape[3:])])) sigma = np.array(ratio) sigma[ratio < 1.1] = 0 sigma[ratio >= 1.1] *= 0.425 return ndimage.gaussian_filter(data, sigma)
def __onAffine(self, ev): """Called when the affine changes. If linking is enabled, updates the pixdim values from the new affine. """ if not self.link: return scales = fslaffine.decompose(self.affine)[0] self.__pixdimx.SetValue(scales[0]) self.__pixdimy.SetValue(scales[1]) self.__pixdimz.SetValue(scales[2])
def __onPixdim(self, ev): """Called when any pixdim widget changes. If linking is enabled, reconstructs the affine with the new pixdim values. """ if not self.link: return # If we construct an affine with # scales == 0, we get explosions if any([p == 0 for p in self.pixdim]): return offsets, rotations = fslaffine.decompose(self.affine)[1:] self.affine = fslaffine.compose(self.pixdim, offsets, rotations)
def __newImage(self): """Displays a :class:`NewImageDialog`, then creates a new :class:`.Image`, and adds it to the :class:`.OverlayList`. If the currently selected overlay is a :class:`.Nifti`, the :class:`NewImageDialog` is initialised to the properties of the selected overlay. """ ovl = self.displayCtx.getSelectedOverlay() if ovl is not None and isinstance(ovl, fslimage.Nifti): shape = ovl.shape[:3] pixdim = ovl.pixdim[:3] dtype = ovl.dtype affine = ovl.voxToWorldMat xyzUnits = ovl.xyzUnits timeUnits = ovl.timeUnits # adjust pixdims in case there # are inversions (e.g. l/r flip) pixdim = pixdim * np.sign(fslaffine.decompose(affine)[0]) else: shape = None pixdim = None dtype = None affine = np.eye(4) xyzUnits = None timeUnits = None dlg = NewImageDialog(self.__frame, shape=shape, pixdim=pixdim, affine=affine, dtype=dtype) if dlg.ShowModal() != wx.ID_OK: return img = newImage(dlg.shape, dlg.pixdim, dlg.dtype, dlg.affine, xyzUnits=xyzUnits, timeUnits=timeUnits, name='new') self.overlayList.append(img) self.displayCtx.selectOverlay(img)
def __drawLegend(self): """Draws a legend in the bottom left corner of the screen, showing anatomical orientation. """ copts = self.opts b = self.__displayCtx.bounds w, h = self.GetSize() xlen, ylen = glroutines.adjust(b.xlen, b.ylen, w, h) # A line for each axis vertices = np.zeros((6, 3), dtype=np.float32) vertices[0, :] = [-1, 0, 0] vertices[1, :] = [1, 0, 0] vertices[2, :] = [0, -1, 0] vertices[3, :] = [0, 1, 0] vertices[4, :] = [0, 0, -1] vertices[5, :] = [0, 0, 1] # Each axis line is scaled to # 60 pixels, and the legend is # offset from the bottom-left # corner by twice this amount. scale = [xlen * 30.0 / w] * 3 offset = [ -0.5 * xlen + 2.0 * scale[0], -0.5 * ylen + 2.0 * scale[1], 0 ] # Apply the current camera # angle and rotation settings # to the legend vertices. Offset # anatomical labels off each # axis line by a small amount. rotation = affine.decompose(self.__viewMat)[2] xform = affine.compose(scale, offset, rotation) labelPoses = affine.transform(vertices * 1.2, xform) vertices = affine.transform(vertices, xform) # Draw the legend lines gl.glDisable(gl.GL_DEPTH_TEST) gl.glColor3f(*copts.cursorColour[:3]) gl.glLineWidth(2) gl.glBegin(gl.GL_LINES) gl.glVertex3f(*vertices[0]) gl.glVertex3f(*vertices[1]) gl.glVertex3f(*vertices[2]) gl.glVertex3f(*vertices[3]) gl.glVertex3f(*vertices[4]) gl.glVertex3f(*vertices[5]) gl.glEnd() canvas = np.array([w, h]) view = np.array([xlen, ylen]) # Draw each label for i, label in enumerate(self.__legendLabels): # Calculate pixel x/y # location for this label xx, xy = canvas * (labelPoses[i, :2] + 0.5 * view) / view label.xpix = xx label.ypix = xy label.draw(w, h)
def boundingBox(dataShape, voxToDisplayMat, displayToVoxMat, geometry='triangles', origin='centre', bbox=None): """Generates a bounding box to represent a 3D image of the given shape, in the coordinate system defined by the ``voxToDisplayMat`` affine. See the :func:`slice2D` function for details on the arguments. Returns a tuple containing: - A ``N*3`` ``numpy.float32`` array containing the vertex locations of a bounding box ``N=36`` if ``geometry=triangles``, or ``N=24`` if ``geometry=square``, - A ``N*3`` ``numpy.float32`` array containing the voxel coordinates that correspond to the vertex locations. """ xlo, ylo, zlo = (0, 0, 0) xhi, yhi, zhi = dataShape if origin == 'centre': xlo, ylo, zlo = xlo - 0.5, ylo - 0.5, zlo - 0.5 xhi, yhi, zhi = xhi - 0.5, yhi - 0.5, zhi - 0.5 # If the voxel -> display transformation # matrix contains negative scales, we need # to invert the voxel coordinate ranges to # ensure a correct triangle winding order # (so that back faces can be culled). scales = affine.decompose(voxToDisplayMat)[0] if scales[0] < 0: xlo, xhi = xhi, xlo if scales[1] < 0: ylo, yhi = yhi, ylo if scales[2] < 0: zlo, zhi = zhi, zlo if geometry == 'triangles': voxCoords = np.zeros((36, 3), dtype=np.float32) voxCoords[0, :] = (xlo, yhi, zlo) voxCoords[1, :] = (xhi, ylo, zlo) voxCoords[2, :] = (xlo, ylo, zlo) voxCoords[3, :] = (xlo, yhi, zlo) voxCoords[4, :] = (xhi, yhi, zlo) voxCoords[5, :] = (xhi, ylo, zlo) voxCoords[6, :] = (xlo, ylo, zhi) voxCoords[7, :] = (xhi, ylo, zhi) voxCoords[8, :] = (xlo, yhi, zhi) voxCoords[9, :] = (xlo, yhi, zhi) voxCoords[10, :] = (xhi, ylo, zhi) voxCoords[11, :] = (xhi, yhi, zhi) voxCoords[12, :] = (xlo, ylo, zlo) voxCoords[13, :] = (xhi, ylo, zlo) voxCoords[14, :] = (xlo, ylo, zhi) voxCoords[15, :] = (xlo, ylo, zhi) voxCoords[16, :] = (xhi, ylo, zlo) voxCoords[17, :] = (xhi, ylo, zhi) voxCoords[18, :] = (xlo, yhi, zlo) voxCoords[19, :] = (xhi, yhi, zlo) voxCoords[20, :] = (xlo, yhi, zhi) voxCoords[21, :] = (xlo, yhi, zhi) voxCoords[22, :] = (xhi, yhi, zlo) voxCoords[23, :] = (xhi, yhi, zhi) voxCoords[18, :] = (xlo, yhi, zhi) voxCoords[19, :] = (xhi, yhi, zlo) voxCoords[20, :] = (xlo, yhi, zlo) voxCoords[21, :] = (xlo, yhi, zhi) voxCoords[22, :] = (xhi, yhi, zhi) voxCoords[23, :] = (xhi, yhi, zlo) voxCoords[24, :] = (xlo, ylo, zhi) voxCoords[25, :] = (xlo, yhi, zlo) voxCoords[26, :] = (xlo, ylo, zlo) voxCoords[27, :] = (xlo, ylo, zhi) voxCoords[28, :] = (xlo, yhi, zhi) voxCoords[29, :] = (xlo, yhi, zlo) voxCoords[30, :] = (xhi, ylo, zlo) voxCoords[31, :] = (xhi, yhi, zlo) voxCoords[32, :] = (xhi, ylo, zhi) voxCoords[33, :] = (xhi, ylo, zhi) voxCoords[34, :] = (xhi, yhi, zlo) voxCoords[35, :] = (xhi, yhi, zhi) elif geometry == 'square': voxCoords = np.zeros((24, 3), dtype=np.float32) voxCoords[0, :] = (xlo, ylo, zlo) voxCoords[1, :] = (xhi, ylo, zlo) voxCoords[2, :] = (xhi, yhi, zlo) voxCoords[3, :] = (xlo, yhi, zlo) voxCoords[4, :] = (xlo, ylo, zhi) voxCoords[5, :] = (xhi, ylo, zhi) voxCoords[6, :] = (xhi, yhi, zhi) voxCoords[7, :] = (xlo, yhi, zhi) voxCoords[8, :] = (xlo, ylo, zlo) voxCoords[9, :] = (xhi, ylo, zlo) voxCoords[10, :] = (xhi, ylo, zhi) voxCoords[11, :] = (xlo, ylo, zhi) voxCoords[12, :] = (xlo, yhi, zlo) voxCoords[13, :] = (xhi, yhi, zlo) voxCoords[14, :] = (xhi, yhi, zhi) voxCoords[15, :] = (xlo, yhi, zhi) voxCoords[16, :] = (xlo, ylo, zlo) voxCoords[17, :] = (xlo, yhi, zlo) voxCoords[18, :] = (xlo, yhi, zhi) voxCoords[19, :] = (xlo, ylo, zhi) voxCoords[20, :] = (xhi, ylo, zlo) voxCoords[21, :] = (xhi, yhi, zlo) voxCoords[22, :] = (xhi, yhi, zhi) voxCoords[23, :] = (xhi, ylo, zhi) else: raise ValueError('Unrecognised geometry type: {}'.format(geometry)) vertices = affine.transform(voxCoords, voxToDisplayMat) return vertices, voxCoords
def test_compose_and_decompose(): testfile = op.join(datadir, 'test_transform_test_compose.txt') lines = readlines(testfile) ntests = len(lines) // 4 for i in range(ntests): xform = lines[i * 4:i * 4 + 4] xform = np.genfromtxt(xform) scales, offsets, rotations, shears = affine.decompose(xform, shears=True) result = affine.compose(scales, offsets, rotations, shears=shears) assert np.all(np.isclose(xform, result, atol=1e-5)) # The decompose function does not support a # different rotation origin, but we test # explicitly passing the origin for # completeness scales, offsets, rotations, shears = affine.decompose(xform, shears=True) result = affine.compose(scales, offsets, rotations, origin=[0, 0, 0], shears=shears) assert np.all(np.isclose(xform, result, atol=1e-5)) # compose should also accept a rotation matrix rots = [np.pi / 5, np.pi / 4, np.pi / 3] rmat = affine.axisAnglesToRotMat(*rots) xform = affine.compose([1, 1, 1], [0, 0, 0], rmat) # And the angles flag should cause decompose # to return the rotation matrix, instead of # the axis angls sc, of, rot = affine.decompose(xform) scat, ofat, rotat = affine.decompose(xform, angles=True) scaf, ofaf, rotaf = affine.decompose(xform, angles=False) sc, of, rot = np.array(sc), np.array(of), np.array(rot) scat, ofat, rotat = np.array(scat), np.array(ofat), np.array(rotat) scaf, ofaf, rotaf = np.array(scaf), np.array(ofaf), np.array(rotaf) assert np.all(np.isclose(sc, [1, 1, 1])) assert np.all(np.isclose(of, [0, 0, 0])) assert np.all(np.isclose(scat, [1, 1, 1])) assert np.all(np.isclose(ofat, [0, 0, 0])) assert np.all(np.isclose(scaf, [1, 1, 1])) assert np.all(np.isclose(ofaf, [0, 0, 0])) assert np.all(np.isclose(rot, rots)) assert np.all(np.isclose(rotat, rots)) assert np.all(np.isclose(rotaf, rmat)) # decompose should accept a 3x3 # affine, and return translations of 0 affine.decompose(xform[:3, :3]) sc, of, rot = affine.decompose(xform[:3, :3]) sc, of, rot = np.array(sc), np.array(of), np.array(rot) assert np.all(np.isclose(sc, [1, 1, 1])) assert np.all(np.isclose(of, [0, 0, 0])) assert np.all(np.isclose(rot, rots))
def __drawLegend(self): """Draws a legend in the bottom left corner of the screen, showing anatomical orientation. """ copts = self.opts b = self.__displayCtx.bounds w, h = self.GetSize() xlen, ylen = glroutines.adjust(b.xlen, b.ylen, w, h) # A line for each axis vertices = np.zeros((6, 3), dtype=np.float32) vertices[0, :] = [-1, 0, 0] vertices[1, :] = [ 1, 0, 0] vertices[2, :] = [ 0, -1, 0] vertices[3, :] = [ 0, 1, 0] vertices[4, :] = [ 0, 0, -1] vertices[5, :] = [ 0, 0, 1] # Each axis line is scaled to # 60 pixels, and the legend is # offset from the bottom-left # corner by twice this amount. scale = [xlen * 30.0 / w] * 3 offset = [-0.5 * xlen + 2.0 * scale[0], -0.5 * ylen + 2.0 * scale[1], 0] # Apply the current camera # angle and rotation settings # to the legend vertices. Offset # anatomical labels off each # axis line by a small amount. rotation = affine.decompose(self.__viewMat)[2] xform = affine.compose(scale, offset, rotation) labelPoses = affine.transform(vertices * 1.2, xform) vertices = affine.transform(vertices, xform) # Draw the legend lines gl.glDisable(gl.GL_DEPTH_TEST) gl.glColor3f(*copts.cursorColour[:3]) gl.glLineWidth(2) gl.glBegin(gl.GL_LINES) gl.glVertex3f(*vertices[0]) gl.glVertex3f(*vertices[1]) gl.glVertex3f(*vertices[2]) gl.glVertex3f(*vertices[3]) gl.glVertex3f(*vertices[4]) gl.glVertex3f(*vertices[5]) gl.glEnd() # Figure out the anatomical # labels for each axis. overlay = self.__displayCtx.getSelectedOverlay() dopts = self.__displayCtx.getOpts(overlay) labels = dopts.getLabels()[0] # getLabels returns (xlo, ylo, zlo, xhi, yhi, zhi) - # - rearrange them to (xlo, xhi, ylo, yhi, zlo, zhi) labels = [labels[0], labels[3], labels[1], labels[4], labels[2], labels[5]] canvas = np.array([w, h]) view = np.array([xlen, ylen]) # Draw each label for i in range(6): # Calculate pixel x/y # location for this label xx, xy = canvas * (labelPoses[i, :2] + 0.5 * view) / view # Calculate the size of the label # in pixels, so we can centre the # label tw, th = glroutines.text2D(labels[i], (xx, xy), 10, (w, h), calcSize=True) # Draw the text xx -= 0.5 * tw xy -= 0.5 * th gl.glColor3f(*copts.legendColour[:3]) glroutines.text2D(labels[i], (xx, xy), 10, (w, h))