Example #1
0
    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]]
Example #2
0
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])
Example #3
0
    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)
Example #4
0
    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])
Example #5
0
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
Example #6
0
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