Exemplo n.º 1
0
    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 = transform.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 = transform.concat(xform, overlay.worldToVoxMat)
Exemplo n.º 2
0
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 = transform.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))
Exemplo n.º 3
0
def updateShaderState(self):
    """Sets all variables required by the vertex and fragment programs. """

    if not self.ready():
        return

    opts   = self.opts
    shader = self.shader

    # enable the vertex and fragment programs
    shader.load()

    # The voxValXform transformation turns
    # an image texture value into a raw
    # voxel value. The colourMapXform
    # transformation turns a raw voxel value
    # into a value between 0 and 1, suitable
    # for looking up an appropriate colour
    # in the 1D colour map texture.
    voxValXform = transform.concat(self.colourTexture.getCoordinateTransform(),
                                   self.imageTexture.voxValXform)
    voxValXform = [voxValXform[0, 0], voxValXform[0, 3], 0, 0]

    # And the clipping range, normalised
    # to the image texture value range
    invClip     = 1 if opts.invertClipping    else -1
    useNegCmap  = 1 if opts.useNegativeCmap   else  0
    imageIsClip = 1 if opts.clipImage is None else -1

    imgXform = self.imageTexture.invVoxValXform
    if opts.clipImage is None: clipXform = imgXform
    else:                      clipXform = self.clipTexture.invVoxValXform

    clipLo  = opts.clippingRange[0] * clipXform[0, 0] + clipXform[0, 3]
    clipHi  = opts.clippingRange[1] * clipXform[0, 0] + clipXform[0, 3]
    texZero = 0.0                   * imgXform[ 0, 0] + imgXform[ 0, 3]

    clipping = [clipLo, clipHi, invClip, imageIsClip]
    negCmap  = [useNegCmap, texZero, 0, 0]

    changed  = False
    changed |= shader.setFragParam('voxValXform', voxValXform)
    changed |= shader.setFragParam('clipping',    clipping)
    changed |= shader.setFragParam('negCmap',     negCmap)

    if self.threedee:
        clipPlanes  = np.zeros((5, 4), dtype=np.float32)
        d2tmat      = opts.getTransform('display', 'texture')

        for i in range(opts.numClipPlanes):
            origin, normal   = self.get3DClipPlane(i)
            origin           = transform.transform(origin, d2tmat)
            normal           = transform.transformNormal(normal, d2tmat)
            clipPlanes[i, :] = glroutines.planeEquation2(origin, normal)

        changed |= shader.setFragParam('clipPlanes', clipPlanes)

    self.shader.unload()

    return changed
Exemplo n.º 4
0
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 = transform.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()
Exemplo n.º 5
0
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 = transform.scaleOffsetXform(1, -eye)
    proj = transform.concat(proj, eye)

    return proj
Exemplo n.º 6
0
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 = transform.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()
Exemplo n.º 7
0
    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 = transform.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()
Exemplo n.º 8
0
def test_prepareMask():

    reg = atlases.registry
    reg.rescanAtlases()

    probatlas    = reg.loadAtlas('harvardoxford-cortical',
                                 indexed=True, loadData=False, calcRange=False)
    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 = goodmask1.resample(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=transform.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
Exemplo n.º 9
0
    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 = transform.axisAnglesToRotMat(yrot, 0, xrot)

        self.__lastRot        = rot
        self.__rotateMousePos = mousePos

        canvas.opts.rotation = transform.concat(rot,
                                                self.__lastRot,
                                                self.__baseXform)
Exemplo n.º 10
0
    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 = transform.axisAnglesToRotMat(*rots)
            xform = transform.concat(xform, currot)

            canvas.opts.rotation = xform
            return np.copy(xform)
Exemplo n.º 11
0
    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 = transform.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 = transform.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
Exemplo n.º 12
0
    def getTransform(self, from_, to):
        """Overrides :meth:`.MeshOpts.getTransform`. If the
        :attr:`.MeshOpts.coordSpace` property is ``'torig'``, and one of
        ``from_`` or ``to`` is ``'mesh'``, the transform is adjusted to
        account for the difference between Freesurfer's RAS and RAStkr spaces.
        """

        ref   = self.refImage
        xform = meshopts.MeshOpts.getTransform(self, from_, to)

        if isinstance(ref, fslmgh.MGHImage) and self.coordSpace == 'torig':

            surf2world = ref.surfToWorldMat
            world2surf = ref.worldToSurfMat

            if   from_ == 'mesh': xform = transform.concat(xform, surf2world)
            elif to    == 'mesh': xform = transform.concat(world2surf, xform)

        return xform
Exemplo n.º 13
0
def updateShaderState(self):
    """Updates the vertex/fragment shader state based on the current
    state of the :class:`.MIPOpts` instance.
    """

    if not self.ready():
        return

    opts = self.opts
    shader = self.shader

    vmin, vmax = self.overlay.dataRange

    # Convert clipping values from voxel value
    # range totexture value range (0.0 - 1.0).
    imgXform = self.imageTexture.invVoxValXform
    clipLow = opts.clippingRange[0] * imgXform[0, 0] + imgXform[0, 3]
    clipHigh = opts.clippingRange[1] * imgXform[0, 0] + imgXform[0, 3]
    textureMin = vmin * imgXform[0, 0] + imgXform[0, 3]
    textureMax = vmax * imgXform[0, 0] + imgXform[0, 3]
    imageShape = self.image.shape[:3]

    # Create a single transformation matrix
    # which transforms from image texture values
    # to voxel values, and scales said voxel
    # values to colour map texture coordinates.
    img2CmapXform = transform.concat(self.cmapTexture.getCoordinateTransform(),
                                     self.imageTexture.voxValXform)

    # sqrt(3) so the window is 100%
    # along the diagonal of a cube
    window = np.sqrt(3) * opts.window / 100.0

    shader.load()

    changed = False

    changed |= shader.set('imageTexture', 0)
    changed |= shader.set('cmapTexture', 1)
    changed |= shader.set('textureMin', textureMin)
    changed |= shader.set('textureMax', textureMax)
    changed |= shader.set('img2CmapXform', img2CmapXform)
    changed |= shader.set('imageShape', imageShape)
    changed |= shader.set('useSpline', opts.interpolation == 'spline')
    changed |= shader.set('clipLow', clipLow)
    changed |= shader.set('clipHigh', clipHigh)
    changed |= shader.set('invertClip', opts.invertClipping)
    changed |= shader.set('window', window)
    changed |= shader.set('useMinimum', opts.minimum)
    changed |= shader.set('useAbsolute', opts.absolute)

    shader.unload()

    return changed
Exemplo n.º 14
0
    def _draw(self):
        """Draws the scene to the canvas. """

        if not self._setGLContext():
            return

        opts = self.opts
        glroutines.clear(opts.bgColour)

        if not self.__setViewport():
            return

        overlays, globjs = self.getGLObjects()

        if len(overlays) == 0:
            return

        # If occlusion is on, we offset the
        # depth of each overlay so that, where
        # a depth collision occurs, overlays
        # which are higher in the list will get
        # drawn above (closer to the screen)
        # than lower ones.
        depthOffset = transform.scaleOffsetXform(1, [0, 0, 0.1])
        depthOffset = np.array(depthOffset, dtype=np.float32, copy=False)
        xform = np.array(self.__viewMat, dtype=np.float32, copy=False)

        for ovl, globj in zip(overlays, globjs):

            display = self.__displayCtx.getDisplay(ovl)

            if not globj.ready():
                continue
            if not display.enabled:
                continue

            if opts.occlusion:
                xform = transform.concat(depthOffset, xform)
            elif isinstance(ovl, fslimage.Image):
                gl.glClear(gl.GL_DEPTH_BUFFER_BIT)

            log.debug('Drawing {} [{}]'.format(ovl, globj))

            globj.preDraw(xform=xform)
            globj.draw3D(xform=xform)
            globj.postDraw(xform=xform)

        if opts.showCursor:
            with glroutines.enabled((gl.GL_DEPTH_TEST)):
                self.__drawCursor()

        if opts.showLegend:
            self.__drawLegend()
Exemplo n.º 15
0
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             = transform.scaleOffsetXform(1, (x, y, z))
    xform             = transform.concat(shift, xform)
    img.voxToWorldMat = xform

    img.save(outfile)

    return outfile
Exemplo n.º 16
0
    def getAuxTextureXform(self, which):
        """Generates and returns a transformation matrix which can be used
        to transform texture coordinates from the vector image to the specified
        auxillary image (``'clip'``, ``'modulate'`` or ``'colour'``).
        """
        opts = self.opts
        auxImage = getattr(self, '{}Image'.format(which), None)
        auxOpts = getattr(self, '{}Opts'.format(which), None)

        if auxImage is None:
            return np.eye(4)
        else:
            return transform.concat(auxOpts.getTransform('display', 'texture'),
                                    opts.getTransform('texture', 'display'))
Exemplo n.º 17
0
    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 = transform.concat(xform, vertXform)

        vdata = self.getVertexData(faces, dists)
        useShader = vdata is not None
        vertices = vertices.reshape(-1, 3)
        nvertices = vertices.shape[0]

        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)

        gl.glPopMatrix()
Exemplo n.º 18
0
def rotate(infile, rx, ry, rz):
    basename = fslimage.removeExt(op.basename(infile))
    outfile  = '{}_rotated_{}_{}_{}.nii.gz'.format(basename, rx, ry, rz)
    img      = fslimage.Image(infile)

    rx = rx * np.pi / 180
    ry = ry * np.pi / 180
    rz = rz * np.pi / 180

    rot               = transform.axisAnglesToRotMat(rx, ry, rz)
    rot               = transform.rotMatToAffine(rot)
    img.voxToWorldMat = transform.concat(rot, img.voxToWorldMat)

    img.save(outfile)

    return outfile
Exemplo n.º 19
0
    def calculateClipCoordTransform(self):
        """Calculates a transformation matrix which will transform from the
        image coordinate system into the :attr:`.VolumeOpts.clipImage`
        coordinate system. If ``clipImage is None``, it will be an identity
        transform.

        This transform is used by shader programs to find the clip image
        coordinates that correspond with specific image coordinates.
        """
        if self.opts.clipImage is None:
            clipCoordXform = np.eye(4)
        else:
            clipCoordXform = transform.concat(
                self.clipOpts.getTransform('display', 'texture'),
                self.opts.getTransform('texture', 'display'))

        return clipCoordXform
Exemplo n.º 20
0
    def calculateRayCastSettings(self, viewmat):
        """Calculates a camera direction and ray casting step vector, based
        on the given view matrix.
        """

        d2tmat = self.getTransform('display', 'texture')
        xform = transform.concat(d2tmat, viewmat)
        cdir = np.array([0, 0, 1])
        cdir = transform.transform(cdir, xform, vector=True)
        cdir = transform.normalise(cdir)

        # sqrt(3) so the maximum number
        # of samplews is taken along the
        # diagonal of a cube
        rayStep = np.sqrt(3) * cdir / self.numSteps

        return cdir, rayStep
Exemplo n.º 21
0
    def __prepareSliceTransforms(self, globj, xforms):
        """Applies the :attr:`.SliceCanvas.invertX` and
        :attr:`.SliceCanvas.invertY` properties to the given transformation
        matrices, if necessary. Returns the transformations.
        """

        opts = self.opts

        if not opts.invertX or opts.invertY:
            return xforms

        invXforms = []
        lo, hi = globj.getDisplayBounds()
        xmin = lo[opts.xax]
        xmax = hi[opts.xax]
        ymin = lo[opts.yax]
        ymax = hi[opts.yax]
        xlen = xmax - xmin
        ylen = ymax - ymin

        # We have to translate each slice transformation
        # to the origin, perform the flip there, then
        # transform it back to its original location.
        for xform in xforms:

            invert = np.eye(4)
            toOrigin = np.eye(4)
            fromOrigin = np.eye(4)

            xoff = xlen / 2.0 + xform[opts.xax, 3] + xmin
            yoff = ylen / 2.0 + xform[opts.yax, 3] + ymin

            if opts.invertX:
                invert[opts.xax, opts.xax] = -1
                toOrigin[opts.xax, 3] = -xoff
                fromOrigin[opts.xax, 3] = xoff
            if opts.invertY:
                invert[opts.yax, opts.yax] = -1
                toOrigin[opts.yax, 3] = -yoff
                fromOrigin[opts.yax, 3] = yoff

            xform = transform.concat(fromOrigin, invert, toOrigin, xform)
            invXforms.append(xform)

        return invXforms
Exemplo n.º 22
0
    def __onSaveFlirt(self, ev):
        """Called when the user clicks the *Save FLIRT* button. Saves the
        current transformation to a FLIRT matrix file.
        """

        overlay = self.__overlay

        if overlay is None:
            return

        overlayList = self.overlayList
        displayCtx = self.displayCtx
        matFile, refFile = applyflirtxfm.promptForFlirtFiles(self,
                                                             overlay,
                                                             overlayList,
                                                             displayCtx,
                                                             save=True)

        if matFile is None or refFile is None:
            return

        if self.__extraXform is None: v2wXform = overlay.voxToWorldMat
        else: v2wXform = self.__extraXform

        newXform = self.__getCurrentXform()
        v2wXform = transform.concat(newXform, v2wXform)

        xform = saveflirtxfm.calculateTransform(overlay,
                                                overlayList,
                                                displayCtx,
                                                refFile,
                                                srcXform=v2wXform)

        try:
            np.savetxt(matFile, xform, fmt='%0.10f')

        except Exception as e:

            log.warn('Error saving FLIRT matrix: {}'.format(e))

            wx.MessageDialog(self,
                             strings.messages[self, 'saveFlirt.error'].format(
                                 str(e)),
                             style=wx.ICON_ERROR).ShowModal()
Exemplo n.º 23
0
def test_bad_mask(seed):

    fslatlases.rescanAtlases()
    capture = CaptureStdout()

    with tempdir() as td:

        for atlasID, use_label in it.product(atlases, use_labels):

            atlas = fslatlases.loadAtlas(atlasID,
                                         loadSummary=use_label,
                                         indexed=True,
                                         loadData=False,
                                         calcRange=False)
            ashape = list(atlas.shape[:3])

            wrongdims = fslimage.Image(
                np.array(np.random.random(list(ashape) + [2]),
                         dtype=np.float32))
            wrongspace = fslimage.Image(np.random.random((20, 20, 20)),
                                        xform=transform.concat(
                                            atlas.voxToWorldMat,
                                            np.diag([2, 2, 2, 1])))

            print(wrongdims.shape)
            print(wrongspace.shape)

            wrongdims.save('wrongdims.nii.gz')
            wrongspace.save('wrongspace.nii.gz')

            cmd = ['query', atlasID, '-m', 'wrongdims']
            expected = 'Mask has wrong number of dimensions'
            capture.reset()
            with capture:
                assert fslatlasq.main(cmd) != 0
            assert capture.stdout.strip() == expected

            cmd = ['query', atlasID, '-m', 'wrongspace']
            expected = 'Mask is not in the same space as atlas'
            capture.reset()
            with capture:
                assert fslatlasq.main(cmd) != 0
            assert capture.stdout.strip() == expected
Exemplo n.º 24
0
    def __setViewport(self):
        """Called by :meth:`_draw`. Configures the viewport and calculates
        the model-view trasformation matrix.

        :returns: ``True`` if the viewport was successfully configured,
                  ``False`` otherwise.
        """

        width, height = self.GetScaledSize()
        b = self.__displayCtx.bounds
        blo = [b.xlo, b.ylo, b.zlo]
        bhi = [b.xhi, b.yhi, b.zhi]
        zoom = self.opts.zoom / 100.0

        if width == 0 or height == 0:
            return False

        # We allow one dimension to be
        # flat, so we can display 2D
        # meshes (e.g. flattened surfaces)
        if np.sum(np.isclose(blo, bhi)) > 1:
            return False

        # Generate the view and projection matrices
        self.__genViewMatrix(width, height)
        projmat, viewport = glroutines.ortho(blo, bhi, width, height, zoom)
        self.__projMat = projmat
        self.__viewport = viewport
        self.__invViewProjMat = transform.concat(self.__projMat,
                                                 self.__viewMat)
        self.__invViewProjMat = transform.invert(self.__invViewProjMat)

        gl.glViewport(0, 0, width, height)
        gl.glMatrixMode(gl.GL_PROJECTION)
        gl.glLoadMatrixf(self.__projMat.ravel('F'))
        gl.glMatrixMode(gl.GL_MODELVIEW)
        gl.glLoadIdentity()

        return True
Exemplo n.º 25
0
def draw2D(self, zpos, axes, xform=None, bbox=None):
    """Generates voxel coordinates for each tensor to be drawn, does some
    final shader state configuration, and draws the tensors.
    """

    opts   = self.opts
    shader = self.shader
    v2dMat = opts.getTransform('voxel',   'display')

    if xform is None: xform = v2dMat
    else:             xform = transform.concat(v2dMat, xform)

    voxels  = self.generateVoxelCoordinates2D(zpos, axes, bbox)
    nVoxels = len(voxels)

    # Set divisor to 1, so we use one set of
    # voxel coordinates for every sphere drawn
    shader.setAtt('voxel',           voxels, divisor=1)
    shader.set(   'voxToDisplayMat', xform)
    shader.loadAtts()

    arbdi.glDrawElementsInstancedARB(
        gl.GL_QUADS, self.nVertices, gl.GL_UNSIGNED_INT, None, nVoxels)
Exemplo n.º 26
0
def preDraw(self, xform=None, bbox=None):
    """Called by :meth:`.GLSH.preDraw`. Loads the shader program, and updates
    some shader attributes.
    """
    shader = self.shader

    shader.load()

    # Calculate a transformation matrix for
    # normal vectors - T(I(MV matrix))
    # We transpose mvMat because OpenGL is column-major
    mvMat = gl.glGetFloatv(gl.GL_MODELVIEW_MATRIX)[:3, :3].T
    v2dMat = self.opts.getTransform('voxel', 'display')[:3, :3]

    normalMatrix = transform.concat(mvMat, v2dMat)
    normalMatrix = npla.inv(normalMatrix).T

    shader.set('normalMatrix', normalMatrix)

    gl.glEnable(gl.GL_CULL_FACE)
    gl.glClear(gl.GL_DEPTH_BUFFER_BIT)
    gl.glEnable(gl.GL_DEPTH_TEST)
    gl.glCullFace(gl.GL_BACK)
Exemplo n.º 27
0
    def get3DClipPlane(self, planeIdx):
        """A convenience method which calculates a point-vector description
        of the specified clipping plane. ``planeIdx`` is an index into the
        :attr:`clipPosition`, :attr:`clipAzimuth`, and
        :attr:`clipInclination`, properties.

        Returns the clip plane at the given ``planeIdx`` as an origin and
        normal vector, in the display coordinate system..
        """

        pos = self.clipPosition[planeIdx]
        azimuth = self.clipAzimuth[planeIdx]
        incline = self.clipInclination[planeIdx]

        b = self.bounds
        pos = pos / 100.0
        azimuth = azimuth * np.pi / 180.0
        incline = incline * np.pi / 180.0

        xmid = b.xlo + 0.5 * b.xlen
        ymid = b.ylo + 0.5 * b.ylen
        zmid = b.zlo + 0.5 * b.zlen

        centre = [xmid, ymid, zmid]
        normal = [0, 0, -1]

        rot1 = transform.axisAnglesToRotMat(incline, 0, 0)
        rot2 = transform.axisAnglesToRotMat(0, 0, azimuth)
        rotation = transform.concat(rot2, rot1)

        normal = transform.transformNormal(normal, rotation)
        normal = transform.normalise(normal)

        offset = (pos - 0.5) * max((b.xlen, b.ylen, b.zlen))
        origin = centre + normal * offset

        return origin, normal
Exemplo n.º 28
0
    def __genViewMatrix(self, w, h):
        """Generate and return a transformation matrix to be used as the
        model-view matrix. This includes applying the current :attr:`zoom`,
        :attr:`rotation` and :attr:`offset` settings, and configuring
        the camera. This method is called by :meth:`__setViewport`.

        :arg w: Canvas width in pixels
        :arg h: Canvas height in pixels
        """

        opts = self.opts
        b = self.__displayCtx.bounds
        centre = [
            b.xlo + 0.5 * b.xlen, b.ylo + 0.5 * b.ylen, b.zlo + 0.5 * b.zlen
        ]

        # The MV matrix comprises (in this order):
        #
        #    - A rotation (the rotation property)
        #
        #    - Camera configuration. With no rotation, the
        #      camera will be looking towards the positive
        #      Y axis (i.e. +y is forwards), and oriented
        #      towards the positive Z axis (i.e. +z is up)
        #
        #    - A translation (the offset property)
        #    - A scaling (the zoom property)

        # Scaling and rotation matrices. Rotation
        # is always around the centre of the
        # displaycontext bounds (the bounding
        # box which contains all loaded overlays).
        scale = opts.zoom / 100.0
        scale = transform.scaleOffsetXform([scale] * 3, 0)
        rotate = transform.rotMatToAffine(opts.rotation, centre)

        # The offset property is defined in x/y
        # pixels, normalised to [-1, 1]. We need
        # to convert them into viewport space,
        # where the horizontal axis maps to
        # (-xhalf, xhalf), and the vertical axis
        # maps to (-yhalf, yhalf). See
        # gl.routines.ortho.
        offset = np.array(opts.offset[:] + [0])
        xlen, ylen = glroutines.adjust(b.xlen, b.ylen, w, h)
        offset[0] = xlen * offset[0] / 2
        offset[1] = ylen * offset[1] / 2
        offset = transform.scaleOffsetXform(1, offset)

        # And finally the camera.
        eye = list(centre)
        eye[1] += 1
        up = [0, 0, 1]
        camera = glroutines.lookAt(eye, centre, up)

        # Order is very important!
        xform = transform.concat(offset, scale, camera, rotate)
        np.array(xform, dtype=np.float32)

        self.__viewOffset = offset
        self.__viewScale = scale
        self.__viewRotate = rotate
        self.__viewCamera = camera
        self.__viewMat = xform
Exemplo n.º 29
0
    def calculateRayCastSettings(self, view=None, proj=None):
        """Calculates various parameters required for 3D ray-cast rendering
        (see the :class:`.GLVolume` class).


        :arg view: Transformation matrix which transforms from model
                   coordinates to view coordinates (i.e. the GL view matrix).


        :arg proj: Transformation matrix which transforms from view coordinates
                   to normalised device coordinates (i.e. the GL projection
                   matrix).

        Returns a tuple containing:

          - A vector defining the amount by which to move along a ray in a
            single iteration of the ray-casting algorithm. This can be added
            directly to the volume texture coordinates.

          - A transformation matrix which transforms from image texture
            coordinates into the display coordinate system.

        .. note:: This method will raise an error if called on a
                  ``GLImageObject`` which is managing an overlay that is not
                  associated with a :class:`.Volume3DOpts` instance.
        """

        if view is None: view = np.eye(4)
        if proj is None: proj = np.eye(4)

        # In GL, the camera position
        # is initially pointing in
        # the -z direction.
        eye = [0, 0, -1]
        target = [0, 0, 1]

        # We take this initial camera
        # configuration, and transform
        # it by the inverse modelview
        # matrix
        t2dmat = self.getTransform('texture', 'display')
        xform = transform.concat(view, t2dmat)
        ixform = transform.invert(xform)

        eye = transform.transform(eye, ixform, vector=True)
        target = transform.transform(target, ixform, vector=True)

        # Direction that the 'camera' is
        # pointing, normalied to unit length
        cdir = transform.normalise(eye - target)

        # Calculate the length of one step
        # along the camera direction in a
        # single iteration of the ray-cast
        # loop. Multiply by sqrt(3) so that
        # the maximum number of steps will
        # be reached across the longest axis
        # of the image texture cube.
        rayStep = np.sqrt(3) * cdir / self.getNumSteps()

        # A transformation matrix which can
        # transform image texture coordinates
        # into the corresponding screen
        # (normalised device) coordinates.
        # This allows the fragment shader to
        # convert an image texture coordinate
        # into a relative depth value.
        #
        # The projection matrix puts depth into
        # [-1, 1], but we want it in [0, 1]
        zscale = transform.scaleOffsetXform([1, 1, 0.5], [0, 0, 0.5])
        xform = transform.concat(zscale, proj, xform)

        return rayStep, xform
Exemplo n.º 30
0
def copyImage(overlayList,
              displayCtx,
              overlay,
              createMask=False,
              copy4D=True,
              copyDisplay=True,
              name=None,
              roi=None,
              data=None):
    """Creates a copy of the given :class:`.Image` overlay, and inserts it
    into the :class:`.OverlayList`.

    :arg overlayList: The :class:`.OverlayList`.

    :arg displayCtx:  The :class:`.DisplayContext`.

    :arg overlay:     The :class:`.Image` to be copied.

    :arg createMask:  If ``True``, the copy will be an empty ``Image`` the
                      same shape as the ``overlay``.

    :arg copy4D:      If ``True``, and the ``overlay`` is 4D, the copy will
                      also be 4D. Otherwise, the current 3D voluem is copied.

    :arg copyDisplay: If ``True``, the copy will inherit the display settings
                      of the ``overlay``. Otherwise, the copy will be
                      initialised  with default display settings.

    :arg name:        If provided, will be used as the :attr:`.Display.name`
                      of the copy. Otherwise the copy will be given a name.

    :arg roi:         If provided, the copy will be cropped to the low/high
                      voxel bounds specified in the image. Must be a sequence
                      of tuples, containing the low/high bounds for each voxel
                      dimension. For 4D images, the bounds for the fourth
                      dimension are optional.

    :arg data:        If provided, is used as the image data for the new copy.
                      Must match the shape dictated by the other arguments
                      (i.e. ``copy4D`` and ``roi``). If ``data`` is provided,
                      the ``createMask`` argument is ignored.
    """

    ovlIdx = overlayList.index(overlay)
    opts = displayCtx.getOpts(overlay)
    isROI = roi is not None
    is4D = len(overlay.shape) > 3 and overlay.shape[3] > 1

    if name is None:
        name = '{}_copy'.format(overlay.name)

    if roi is None:
        roi = [(0, s) for s in overlay.shape]

    # If the image is 4D, and an ROI of
    # length 3 has been given, add some
    # bounds for the fourth dimension
    if is4D and copy4D and len(roi) == 3:
        roi = list(roi) + [(0, overlay.shape[3])]

    # If we are only supposed to copy
    # the current 3D volume of a 4D
    # image, adjust the ROI accordingly.
    if is4D and not copy4D:
        roi = list(roi[:3]) + [(opts.volume, opts.volume + 1)]

    shape = [hi - lo for lo, hi in roi]
    slc = tuple([slice(lo, hi) for lo, hi in roi])

    if data is not None: pass
    elif createMask: data = np.zeros(shape)
    else: data = np.copy(overlay[slc])

    # If this is an ROI, we need to add
    # an offset to the image affine
    if isROI:
        xform = overlay.voxToWorldMat
        offset = [lo for lo, hi in roi[:3]]
        offset = transform.scaleOffsetXform([1, 1, 1], offset)
        xform = transform.concat(xform, offset)

    else:
        xform = None

    # Create the copy, put it in the list
    header = overlay.header.copy()
    copy = fslimage.Image(data, name=name, header=header, xform=xform)

    overlayList.insert(ovlIdx + 1, copy)

    # Copy the Display/DisplayOpts settings
    if copyDisplay:

        srcDisplay = displayCtx.getDisplay(overlay)
        destDisplay = displayCtx.getDisplay(copy)

        for prop in srcDisplay.getAllProperties()[0]:

            # Don't override the name
            # that we set above
            if prop == 'name':
                continue

            val = getattr(srcDisplay, prop)
            setattr(destDisplay, prop, val)

        # And after the Display has been configured
        # copy the DisplayOpts settings.
        srcOpts = displayCtx.getOpts(overlay)
        destOpts = displayCtx.getOpts(copy)

        for prop in srcOpts.getAllProperties()[0]:

            # But don't clobber the transform, and related,
            # properties, as it is (typically) automatically
            # controlled via the DisplayContext.displaySpace
            if prop in ('transform', 'bounds'):
                continue

            val = getattr(srcOpts, prop)
            setattr(destOpts, prop, val)