def __transformChanged(self, *a): """Called when the :attr:`transform` property changes. Calculates the min/max values of a 3D bounding box, in the display coordinate system, which is big enough to contain the image. Sets the :attr:`.DisplayOpts.bounds` property accordingly. """ lo, hi = transform.axisBounds(self.overlay.shape[:3], self.getTransform('voxel', 'display')) self.bounds[:] = [lo[0], hi[0], lo[1], hi[1], lo[2], hi[2]]
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 __getCurrentXform(self): """Returns the current transformation matrix defined by the scale, offset, and rotation widgets. """ scales, offsets, rotations = self.__getCurrentXformComponents() rotations = [r * np.pi / 180 for r in rotations] # We need to figure out the centre # of the image in world coordinates # to define the origin of rotation. shape = self.__overlay.shape lo, hi = transform.axisBounds(shape, self.__overlay.voxToWorldMat) origin = [l + (h - l) / 2.0 for h, l in zip(hi, lo)] return transform.compose(scales, offsets, rotations, origin)
def __updateWidgets(self): """Called by the :meth:`__selectedOverlayChanged` and :meth:`__displayOptsChanged` methods. Enables/disables the voxel/world location and volume controls depending on the currently selected overlay (or reference image). """ overlay = self.__registeredOverlay opts = self.__registeredOpts if overlay is not None: refImage = opts.referenceImage else: refImage = None haveRef = refImage is not None self.__voxelX .Enable(haveRef) self.__voxelY .Enable(haveRef) self.__voxelZ .Enable(haveRef) self.__voxelLabel .Enable(haveRef) ###################### # World location label ###################### label = strings.labels[self, 'worldLocation'] if haveRef: label += strings.anatomy[refImage, 'space', refImage.getXFormCode()] else: label += strings.labels[ self, 'worldLocation', 'unknown'] self.__worldLabel.SetLabel(label) #################################### # Voxel/world location widget limits #################################### # Figure out the limits for the # voxel/world location widgets if haveRef: opts = self.displayCtx.getOpts(refImage) v2w = opts.getTransform('voxel', 'world') shape = refImage.shape[:3] vlo = [0, 0, 0] vhi = np.array(shape) - 1 wlo, whi = transform.axisBounds(shape, v2w) wstep = refImage.pixdim[:3] else: vlo = [0, 0, 0] vhi = [0, 0, 0] wbounds = self.displayCtx.bounds[:] wlo = wbounds[0::2] whi = wbounds[1::2] wstep = [1, 1, 1] log.debug('Setting voxelLocation limits: {} - {}'.format(vlo, vhi)) log.debug('Setting worldLocation limits: {} - {}'.format(wlo, whi)) # Update the voxel and world location limits, # but don't trigger a listener callback, as # this would change the display location. widgets = [self.__worldX, self.__worldY, self.__worldZ] with props.suppress(self, 'worldLocation'), \ props.suppress(self, 'voxelLocation'): for i in range(3): self.voxelLocation.setLimits(i, vlo[i], vhi[i]) self.worldLocation.setLimits(i, wlo[i], whi[i]) widgets[i].SetIncrement(wstep[i])
def slice2D(dataShape, xax, yax, zpos, voxToDisplayMat, displayToVoxMat, geometry='triangles', origin='centre', bbox=None): """Generates and returns vertices which denote a slice through an array of the given ``dataShape``, parallel to the plane defined by the given ``xax`` and ``yax`` and at the given z position, in the space defined by the given ``voxToDisplayMat``. If ``geometry`` is ``triangles`` (the default), six vertices are returned, arranged as follows:: 4---5 1 \ | |\ \ | | \ \| | \ 3 0---2 Otherwise, if geometry is ``square``, four vertices are returned, arranged as follows:: 3---2 | | | | | | 0---1 If ``origin`` is set to ``centre`` (the default), it is assumed that a voxel at location ``(x, y, z)`` is located in the space:: (x - 0.5 : x + 0.5, y - 0.5 : y + 0.5, z - 0.5 : z + 0.5) Otherwise, if ``origin`` is set to ``corner``, a voxel at location ``(x, y, z)`` is assumed to be located in the space:: (x : x + 1, y : y + 1, z : z + 1) :arg dataShape: Number of elements along each dimension in the image data. :arg xax: Index of display axis which corresponds to the horizontal screen axis. :arg yax: Index of display axis which corresponds to the vertical screen axis. :arg zpos: Position of the slice along the screen z axis. :arg voxToDisplayMat: Affine transformation matrix which transforms from voxel/array indices into the display coordinate system. :arg displayToVoxMat: Inverse of the ``voxToDisplayMat``. :arg geometry: ``square`` or ``triangle``. :arg origin: ``centre`` or ``corner``. See the :func:`.transform.axisBounds` function. :arg bbox: An optional sequence of three ``(low, high)`` values, defining the bounding box in the display coordinate system which should be considered - the generated grid will be constrained to lie within this bounding box. Returns a tuple containing: - A ``N*3`` ``numpy.float32`` array containing the vertex locations of a slice through the data, where ``N=6`` if ``geometry=triangles``, or ``N=4`` if ``geometry=square``, - A ``N*3`` ``numpy.float32`` array containing the voxel coordinates that correspond to the vertex locations. """ zax = 3 - xax - yax xmin, xmax = transform.axisBounds(dataShape, voxToDisplayMat, xax, origin, boundary=None) ymin, ymax = transform.axisBounds(dataShape, voxToDisplayMat, yax, origin, boundary=None) if bbox is not None: bbxmin = bbox[xax][0] bbxmax = bbox[xax][1] bbymin = bbox[yax][0] bbymax = bbox[yax][1] # The returned vertices and voxCoords # have to be aligned, so we need to # clamp the bounding box limits to the # nearest voxel boundary to preserve # this alignment. # Voxel lengths along x/y axes xvlen = (xmax - xmin) / dataShape[xax] yvlen = (ymax - ymin) / dataShape[yax] # Clamp the bbox limits to the # nearest voxel boundaries bbxmin = xmin + np.floor((bbxmin - xmin) / xvlen) * xvlen bbxmax = xmin + np.ceil((bbxmax - xmin) / xvlen) * xvlen bbymin = ymin + np.floor((bbymin - ymin) / yvlen) * yvlen bbymax = ymin + np.ceil((bbymax - ymin) / yvlen) * yvlen xmin = max((xmin, bbxmin)) xmax = min((xmax, bbxmax)) ymin = max((ymin, bbymin)) ymax = min((ymax, bbymax)) if geometry == 'triangles': vertices = np.zeros((6, 3), dtype=np.float32) vertices[0, [xax, yax]] = [xmin, ymin] vertices[1, [xax, yax]] = [xmax, ymin] vertices[2, [xax, yax]] = [xmin, ymax] vertices[3, [xax, yax]] = [xmax, ymin] vertices[4, [xax, yax]] = [xmax, ymax] vertices[5, [xax, yax]] = [xmin, ymax] elif geometry == 'square': vertices = np.zeros((4, 3), dtype=np.float32) vertices[0, [xax, yax]] = [xmin, ymin] vertices[1, [xax, yax]] = [xmax, ymin] vertices[2, [xax, yax]] = [xmax, ymax] vertices[3, [xax, yax]] = [xmin, ymax] else: raise ValueError('Unrecognised geometry type: {}'.format(geometry)) vertices[:, zax] = zpos voxCoords = transform.transform(vertices, displayToVoxMat) return vertices, voxCoords
def pointGrid(shape, resolution, xform, xax, yax, origin='centre', bbox=None): """Calculates a uniform grid of points, in the display coordinate system (as specified by the given :class:`.Display` object properties) along the x-y plane (as specified by the xax/yax indices), at which the given image should be sampled for display purposes. This function returns a tuple containing: - a numpy array of shape ``(N, 3)``, containing the coordinates of the centre of every sampling point in the display coordinate system. - the horizontal distance (along xax) between adjacent points - the vertical distance (along yax) between adjacent points - The number of samples along the horizontal axis (xax) - The number of samples along the vertical axis (yax) :arg shape: The shape of the data to be sampled. :arg resolution: The desired resolution in display coordinates, along each display axis. :arg xform: A transformation matrix which converts from data coordinates to the display coordinate system. :arg xax: The horizontal display coordinate system axis (0, 1, or 2). :arg yax: The vertical display coordinate system axis (0, 1, or 2). :arg origin: ``centre`` or ``corner``. See the :func:`.transform.axisBounds` function. :arg bbox: An optional sequence of three ``(low, high)`` values, defining the bounding box in the display coordinate system which should be considered - the generated grid will be constrained to lie within this bounding box. """ xres = resolution[xax] yres = resolution[yax] # These values give the min/max x/y # values of a bounding box which # encapsulates the entire image, # in the display coordinate system xmin, xmax = transform.axisBounds(shape, xform, xax, origin, boundary=None) ymin, ymax = transform.axisBounds(shape, xform, yax, origin, boundary=None) # Number of samples along each display # axis, given the requested resolution xNumSamples = int(np.floor((xmax - xmin) / xres)) yNumSamples = int(np.floor((ymax - ymin) / yres)) # adjust the x/y resolution so # the samples fit exactly into # the data bounding box xres = (xmax - xmin) / xNumSamples yres = (ymax - ymin) / yNumSamples # Calculate the locations of every # sample point in display space worldX = np.linspace(xmin + 0.5 * xres, xmax - 0.5 * xres, xNumSamples) worldY = np.linspace(ymin + 0.5 * yres, ymax - 0.5 * yres, yNumSamples) # Apply bounding box constraint # if it has been provided if bbox is not None: xoff = 0.5 * xres yoff = 0.5 * yres xmin = max((xmin, bbox[xax][0] - xoff)) xmax = min((xmax, bbox[xax][1] + xoff)) ymin = max((ymin, bbox[yax][0] - yoff)) ymax = min((ymax, bbox[yax][1] + yoff)) worldX = worldX[(worldX >= xmin) & (worldX <= xmax)] worldY = worldY[(worldY >= ymin) & (worldY <= ymax)] # Generate the coordinates worldX, worldY = np.meshgrid(worldX, worldY) # reshape them to N*3 coords = np.zeros((worldX.size, 3), dtype=np.float32) coords[:, xax] = worldX.flatten() coords[:, yax] = worldY.flatten() return coords, xres, yres, xNumSamples, yNumSamples
def __getImageInfo(self, overlay, display, title=None): """Creates and returns an :class:`OverlayInfo` object containing information about the given :class:`.Image` overlay. :arg overlay: A :class:`.Image` instance. :arg display: The :class:`.Display` instance assocated with the ``Image``. """ img = overlay.nibImage hdr = overlay.header isNifti = overlay.niftiVersion >= 1 opts = display.opts if isNifti: title = strings.labels[self, overlay] else: title = strings.labels[self, 'Analyze'] info = OverlayInfo('{} - {}'.format(display.name, title)) generalSect = strings.labels[self, 'general'] dimSect = strings.labels[self, overlay, 'dimensions'] xformSect = strings.labels[self, overlay, 'transform'] orientSect = strings.labels[self, overlay, 'orient'] info.addSection(generalSect) info.addSection(dimSect) info.addSection(xformSect) info.addSection(orientSect) displaySpace = strings.labels[self, overlay, 'displaySpace', opts.transform] if opts.transform == 'reference': dsImg = self.displayCtx.displaySpace if isinstance(dsImg, fslimage.Nifti): dsDisplay = self.displayCtx.getDisplay(dsImg) displaySpace = displaySpace.format(dsDisplay.name) else: log.warn('{} transform ({}) seems to be out ' 'of date (display space: {})'.format( overlay, opts.transform, self.displayCtx.displaySpace)) dataType = strings.nifti.get(('datatype', int(hdr['datatype'])), 'Unknown') info.addInfo(strings.labels[self, 'niftiVersion'], strings.nifti['version.{}'.format(overlay.niftiVersion)], section=generalSect) info.addInfo(strings.labels[self, 'dataSource'], overlay.dataSource, section=generalSect) info.addInfo(strings.nifti['datatype'], dataType, section=generalSect) info.addInfo(strings.nifti['descrip'], overlay.strval('descrip'), section=generalSect) if isNifti: intent = strings.nifti.get( ('intent_code', int(hdr['intent_code'])), 'Unknown') info.addInfo(strings.nifti['intent_code'], intent, section=generalSect) info.addInfo(strings.nifti['intent_name'], overlay.strval('intent_name'), section=generalSect) info.addInfo(strings.nifti['aux_file'], overlay.strval('aux_file'), section=generalSect) info.addInfo(strings.labels[self, 'overlayType'], strings.choices[display, 'overlayType'][display.overlayType], section=generalSect) info.addInfo(strings.labels[self, 'displaySpace'], displaySpace, section=generalSect) info.addInfo(strings.nifti['dimensions'], '{}D'.format(len(overlay.shape)), section=dimSect) for i in range(len(overlay.shape)): info.addInfo(strings.nifti['dim{}'.format(i + 1)], str(overlay.shape[i]), section=dimSect) # NIFTI images can specify different units. if isNifti: voxUnits = overlay.xyzUnits timeUnits = overlay.timeUnits # Assume mm / seconds for ANALYZE images # We are using nifti xyzt_unit code values # here else: voxUnits, timeUnits = 2, 8 # Convert the unit codes into labels voxUnits = strings.nifti.get(('xyz_unit', voxUnits), 'INVALID CODE') timeUnits = strings.nifti.get(('t_unit', timeUnits), 'INVALID CODE') for i in range(len(overlay.shape)): pixdim = hdr['pixdim'][i + 1] if i < 3: pixdim = '{:0.4g} {}'.format(pixdim, voxUnits) elif i == 3: pixdim = '{:0.4g} {}'.format(pixdim, timeUnits) info.addInfo(strings.nifti['pixdim{}'.format(i + 1)], pixdim, section=dimSect) bounds = transform.axisBounds(overlay.shape[:3], opts.getTransform('voxel', 'world')) lens = bounds[1] - bounds[0] lens = 'X={:0.0f} {} Y={:0.0f} {} Z={:0.0f} {}'.format( lens[0], voxUnits, lens[1], voxUnits, lens[2], voxUnits) info.addInfo(strings.labels[self, overlay, 'size'], lens, section=dimSect) # For NIFTI images, we show both # the sform and qform matrices, # in addition to the effective # transformation if isNifti: qformCode = int(hdr['qform_code']) sformCode = int(hdr['sform_code']) info.addInfo(strings.nifti['transform'], self.__formatArray(overlay.voxToWorldMat), section=xformSect) info.addInfo(strings.nifti['sform_code'], strings.anatomy['Nifti', 'space', sformCode], section=xformSect) info.addInfo(strings.nifti['qform_code'], strings.anatomy['Nifti', 'space', qformCode], section=xformSect) if sformCode != constants.NIFTI_XFORM_UNKNOWN: sform = img.get_sform() info.addInfo(strings.nifti['sform'], self.__formatArray(sform), section=xformSect) if qformCode != constants.NIFTI_XFORM_UNKNOWN: try: qform = img.get_qform() except Exception as e: log.warning('Could not read qform from {}: {}'.format( overlay.name, str(e))) qform = np.eye(4) * np.nan info.addInfo(strings.nifti['qform'], self.__formatArray(qform), section=xformSect) # For ANALYZE images, we show # the scale/offset matrix else: info.addInfo(strings.nifti['affine'], self.__formatArray(hdr.get_best_affine()), section=xformSect) if overlay.getXFormCode() == constants.NIFTI_XFORM_UNKNOWN: storageOrder = 'unknown' elif overlay.isNeurological(): storageOrder = 'neuro' else: storageOrder = 'radio' storageOrder = strings.nifti['storageOrder.{}'.format(storageOrder)] info.addInfo(strings.nifti['storageOrder'], storageOrder, section=orientSect) for i in range(3): xform = opts.getTransform('voxel', 'world') orient = overlay.getOrientation(i, xform) orient = '{} - {}'.format( strings.anatomy['Nifti', 'lowlong', orient], strings.anatomy['Nifti', 'highlong', orient]) info.addInfo(strings.nifti['voxOrient.{}'.format(i)], orient, section=orientSect) for i in range(3): xform = np.eye(4) orient = overlay.getOrientation(i, xform) orient = '{} - {}'.format( strings.anatomy['Nifti', 'lowlong', orient], strings.anatomy['Nifti', 'highlong', orient]) info.addInfo(strings.nifti['worldOrient.{}'.format(i)], orient, section=orientSect) return info