Ejemplo n.º 1
0
def test_linear_altref(seed):
    with tempdir.tempdir():

        src2ref = affine.scaleOffsetXform([1, 1, 1], [5, 5, 5])
        altv2w = affine.scaleOffsetXform([1, 1, 1], [10, 10, 10])

        srcdata = np.random.randint(1, 65536, (10, 10, 10))
        src = fslimage.Image(srcdata, xform=np.eye(4))
        ref = fslimage.Image(src.data, xform=src2ref)
        altref = fslimage.Image(src.data, xform=altv2w)

        src.save('src')
        ref.save('ref')
        altref.save('altref')

        x5.writeLinearX5('xform.x5', src2ref, src, ref)

        fsl_apply_x5.main('src xform.x5 out -r altref'.split())

        result = fslimage.Image('out')
        expect = np.zeros(srcdata.shape)
        expect[:5, :5, :5] = srcdata[5:, 5:, 5:]

        assert result.sameSpace(altref)
        assert np.all(result.data == expect)
Ejemplo n.º 2
0
def test_applyDeformation_worldAligned():
    refv2w   = affine.scaleOffsetXform([1, 1, 1], [10,   10,   10])
    fieldv2w = affine.scaleOffsetXform([2, 2, 2], [10.5, 10.5, 10.5])
    src2ref  = refv2w
    ref2src  = affine.invert(src2ref)

    srcdata = np.random.randint(1, 65536, (10, 10, 10))

    src   = fslimage.Image(srcdata)
    ref   = fslimage.Image(srcdata, xform=src2ref)
    field = _affine_field(src, ref, ref2src, 'world', 'world',
                          shape=(5, 5, 5), fv2w=fieldv2w)

    field = nonlinear.DeformationField(
        nonlinear.convertDeformationType(field, 'absolute'),
        header=field.header,
        src=src,
        ref=ref,
        srcSpace='world',
        refSpace='world',
        defType='absolute')

    expect, xf = resample.resampleToReference(
        src, ref, matrix=src2ref, order=1, mode='constant', cval=0)
    result = nonlinear.applyDeformation(
        src, field, order=1, mode='constant', cval=0)

    expect = expect[1:-1, 1:-1, 1:-1]
    result = result[1:-1, 1:-1, 1:-1]

    assert np.all(np.isclose(expect, result))
Ejemplo n.º 3
0
def test_rmsdev():

    t1 = np.eye(4)
    t2 = affine.scaleOffsetXform([1, 1, 1], [2, 0, 0])

    assert np.isclose(affine.rmsdev(t1, t2), 2)
    assert np.isclose(affine.rmsdev(t1, t2, R=2), 2)
    assert np.isclose(affine.rmsdev(t1, t2, R=2, xc=(1, 1, 1)), 2)

    t1 = np.eye(3)
    lastdist = 0

    for i in range(1, 11):
        rot = np.pi * i / 10.0
        t2 = affine.axisAnglesToRotMat(rot, 0, 0)
        result = affine.rmsdev(t1, t2)

        assert result > lastdist

        lastdist = result

    for i in range(11, 20):
        rot = np.pi * i / 10.0
        t2 = affine.axisAnglesToRotMat(rot, 0, 0)
        result = affine.rmsdev(t1, t2)

        assert result < lastdist

        lastdist = result
Ejemplo n.º 4
0
    def getModulateValueXform(self):
        """Returns an affine transform to normalise alpha modulation values.

        The GL volume shaders need to normalise the modulate value by the
        modulation range to generate an opacity value. We calculate a suitable
        scale and offset by buildin an affine transform which transforms voxel
        values from the image/modulate image texture range to 0/1, where 0
        corresponds to the low modulate range bound, and 1 to the high
        modulate range bound. The resulting scale/offset can be used by the
        shader to convert a modulate value directly into an opacity value.
        """

        opts = self.opts
        if opts.modulateImage is None:
            modXform = self.imageTexture.voxValXform
        else:
            modXform = self.modulateTexture.voxValXform

        modlo, modhi = opts.modulateRange
        modrange     = modhi - modlo
        if modrange == 0:
            modXform = np.eye(4)
        else:
            modXform = affine.concat(
                affine.scaleOffsetXform(1 / modrange, -modlo / modrange),
                modXform)

        return modXform
Ejemplo n.º 5
0
def test_applyDeformation_altref():
    src2ref = affine.compose(
        np.random.randint(2, 5, 3),
        np.random.randint(1, 10, 3),
        np.random.random(3))
    ref2src = affine.invert(src2ref)

    srcdata = np.random.randint(1, 65536, (10, 10, 10))
    refdata = np.random.randint(1, 65536, (10, 10, 10))

    src   = fslimage.Image(srcdata)
    ref   = fslimage.Image(refdata, xform=src2ref)
    field = _affine_field(src, ref, ref2src, 'world', 'world')

    altrefxform = affine.concat(
        src2ref,
        affine.scaleOffsetXform([1, 1, 1], [5, 0, 0]))

    altref = fslimage.Image(refdata, xform=altrefxform)

    expect, xf = resample.resampleToReference(
        src, altref, matrix=src2ref, order=1, mode='constant', cval=0)
    result = nonlinear.applyDeformation(
        src, field, ref=altref, order=1, mode='constant', cval=0)

    # boundary voxels can get truncated
    # (4 is the altref-ref overlap boundary)
    expect[4, :, :] = 0
    result[4, :, :] = 0
    expect = expect[1:-1, 1:-1, 1:-1]
    result = result[1:-1, 1:-1, 1:-1]

    assert np.all(np.isclose(expect, result))
Ejemplo n.º 6
0
def test_generateAffines():

    v2w = affine.compose(np.random.random(3), np.random.random(3),
                         np.random.random(3))
    shape = (10, 10, 10)
    pixdim = (1, 1, 1)

    got, isneuro = fslimage.Nifti.generateAffines(v2w, shape, pixdim)

    w2v = npla.inv(v2w)

    assert isneuro == (npla.det(v2w) > 0)

    if not isneuro:
        v2f = np.eye(4)
        f2v = np.eye(4)
        f2w = v2w
        w2f = w2v
    else:
        v2f = affine.scaleOffsetXform([-1, 1, 1], [9, 0, 0])
        f2v = npla.inv(v2f)
        f2w = affine.concat(v2w, f2v)
        w2f = affine.concat(v2f, w2v)

    assert np.all(np.isclose(v2w, got['voxel', 'world']))
    assert np.all(np.isclose(w2v, got['world', 'voxel']))
    assert np.all(np.isclose(v2f, got['voxel', 'fsl']))
    assert np.all(np.isclose(f2v, got['fsl', 'voxel']))
    assert np.all(np.isclose(f2w, got['fsl', 'world']))
    assert np.all(np.isclose(w2f, got['world', 'fsl']))
Ejemplo n.º 7
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 = affine.scaleOffsetXform(1, -eye)
    proj = affine.concat(proj, eye)

    return proj
Ejemplo n.º 8
0
    def updateShaderState(self):
        """Updates the vertex/fragment shader program(s) state, via a call to
        the GL-version specific ``updateShaderState`` function.
        """

        dopts = self.opts
        canvas = self.canvas
        flatColour = dopts.getConstantColour()
        useNegCmap = (not dopts.useLut) and dopts.useNegativeCmap

        if dopts.useLut:
            delta = 1.0 / (dopts.lut.max() + 1)
            cmapXform = affine.scaleOffsetXform(delta, 0.5 * delta)
        else:
            cmapXform = self.cmapTexture.getCoordinateTransform()

        # calculate a scale+offset which transforms
        # modulate alpha value from the data range
        # into an alpha value, according to the
        # modulateRange
        modlo, modhi = dopts.modulateRange
        modRange = modhi - modlo
        if modRange == 0:
            modScale = 1
            modOffset = 0
        else:
            modScale = 1 / modRange
            modOffset = -modlo / modRange

        fslgl.glmesh_funcs.updateShaderState(self,
                                             useNegCmap=useNegCmap,
                                             cmapXform=cmapXform,
                                             modScale=modScale,
                                             modOffset=modOffset,
                                             flatColour=flatColour)
Ejemplo n.º 9
0
def roi(image, bounds):
    """Extract an ROI from the given ``image`` according to the given
    ``bounds``.

    This function can also be used to pad, or expand the field-of-view, of an
    image, by passing in negative low bound values, or high bound values which
    are larger than the image shape. The padded region will contain zeroes.

    :arg image:  :class:`.Image` object

    :arg bounds: Must be a sequence of tuples, containing the low/high bounds
                 for each voxel dimension, where the low bound is *inclusive*,
                 and the high bound is *exclusive*. For 4D images, the bounds
                 for the fourth dimension are optional.

    :returns:    A new :class:`.Image` object containing the region specified
                 by the ``bounds``.
    """

    bounds = _normaliseBounds(image.shape, bounds)

    newshape = [hi - lo for lo, hi in bounds]
    oldslc = []
    newslc = []

    # Figure out how to slice the input image
    # data array, and the corresponding slice
    # in the output data array.
    for (lo, hi), oldlen, newlen in zip(bounds, image.shape, newshape):

        oldlo = max(lo, 0)
        oldhi = min(hi, oldlen)
        newlo = max(0, -lo)
        newhi = newlo + (oldhi - oldlo)

        oldslc.append(slice(oldlo, oldhi))
        newslc.append(slice(newlo, newhi))

    oldslc = tuple(oldslc)
    newslc = tuple(newslc)

    # Copy the ROI into the new data array
    newdata = np.zeros(newshape, dtype=image.dtype)
    newdata[newslc] = image.data[oldslc]

    # Create a new affine for the ROI,
    # with an appropriate offset along
    # each spatial dimension
    oldaff = image.voxToWorldMat
    offset = [lo for lo, hi in bounds[:3]]
    offset = affine.scaleOffsetXform([1, 1, 1], offset)
    newaff = affine.concat(oldaff, offset)

    return fslimage.Image(newdata,
                          xform=newaff,
                          header=image.header,
                          name=image.name + '_roi')
Ejemplo n.º 10
0
def test_resample_image_dim():
    with tempdir():
        img = Image(make_random_image('image.nii.gz', dims=(10, 10, 10)))

        resample_image.main('image resampled -d 0.5,0.5,0.5'.split())

        res = Image('resampled')
        expv2w = affine.concat(img.voxToWorldMat,
                               affine.scaleOffsetXform([0.5, 0.5, 0.5], 0))

        assert np.all(np.isclose(res.shape, (20, 20, 20)))
        assert np.all(np.isclose(res.pixdim, (0.5, 0.5, 0.5)))
        assert np.all(np.isclose(res.voxToWorldMat, expv2w))
Ejemplo n.º 11
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 = affine.scaleOffsetXform(1, (x, y, z))
    xform = affine.concat(shift, xform)
    img.voxToWorldMat = xform

    img.save(outfile)

    return outfile
Ejemplo n.º 12
0
def test_determineAffine():

    # sformcode, qformcode, intent, expaff
    tests = [
        (constants.NIFTI_XFORM_ALIGNED_ANAT,
         constants.NIFTI_XFORM_ALIGNED_ANAT, constants.NIFTI_INTENT_NONE,
         'sform'),
        (constants.NIFTI_XFORM_ALIGNED_ANAT, constants.NIFTI_XFORM_UNKNOWN,
         constants.NIFTI_INTENT_NONE, 'sform'),
        (constants.NIFTI_XFORM_UNKNOWN, constants.NIFTI_XFORM_ALIGNED_ANAT,
         constants.NIFTI_INTENT_NONE, 'qform'),
        (constants.NIFTI_XFORM_ALIGNED_ANAT,
         constants.NIFTI_XFORM_ALIGNED_ANAT,
         constants.FSL_FNIRT_DISPLACEMENT_FIELD, 'sform'),
        (constants.NIFTI_XFORM_ALIGNED_ANAT,
         constants.NIFTI_XFORM_ALIGNED_ANAT,
         constants.FSL_CUBIC_SPLINE_COEFFICIENTS, 'scaling'),
        (constants.NIFTI_XFORM_UNKNOWN, constants.NIFTI_XFORM_UNKNOWN,
         constants.NIFTI_INTENT_NONE, 'scaling'),
    ]

    for sformcode, qformcode, intent, exp in tests:

        sform = affine.compose(np.random.random(3), np.random.random(3),
                               np.random.random(3))
        qform = affine.compose(np.random.random(3), np.random.random(3),
                               np.random.random(3))
        pixdims = np.random.randint(1, 10, 3)

        hdr = nib.Nifti1Header()
        hdr.set_data_shape((10, 10, 10))
        hdr.set_sform(sform, sformcode)
        hdr.set_qform(qform, qformcode)
        hdr.set_intent(intent)
        hdr.set_zooms(pixdims)

        # the randomly generated qform might
        # not be fully representable, so let
        # nibabel fix it for us
        sform = hdr.get_sform()
        qform = hdr.get_qform()

        got = fslimage.Nifti.determineAffine(hdr)

        if exp == 'sform': exp = sform
        elif exp == 'qform': exp = qform
        elif exp == 'scaling': exp = affine.scaleOffsetXform(pixdims, 0)

        assert np.all(np.isclose(got, exp))
Ejemplo n.º 13
0
def test_resampleToReference2():

    # More specific test - output
    # data gets transformed correctly
    # into reference space
    img = np.zeros((5, 5, 5), dtype=float)
    img[1, 1, 1] = 1
    img = fslimage.Image(img)

    refv2w = affine.scaleOffsetXform([1, 1, 1], [-1, -1, -1])
    ref = np.zeros((5, 5, 5), dtype=float)
    ref = fslimage.Image(ref, xform=refv2w)
    res = resample.resampleToReference(img, ref, order=0)

    exp = np.zeros((5, 5, 5), dtype=float)
    exp[2, 2, 2] = 1

    assert np.all(np.isclose(res[0], exp))
Ejemplo n.º 14
0
def swapdim(infile):
    basename = fslimage.removeExt(op.basename(infile))
    outfile = '{}_swapdim.nii.gz'.format(basename)
    img = fslimage.Image(infile)

    data = img.data
    xform = img.voxToWorldMat

    data = data.transpose((2, 0, 1))
    rot = affine.rotMatToAffine(
        affine.concat(affine.axisAnglesToRotMat(np.pi / 2, 0, 0),
                      affine.axisAnglesToRotMat(0, 0, 3 * np.pi / 2)))
    xform = affine.concat(xform, affine.scaleOffsetXform((1, -1, -1),
                                                         (0, 0, 0)), rot)

    fslimage.Image(data, xform=xform, header=img.header).save(outfile)

    return outfile
Ejemplo n.º 15
0
def test_resampleToReference3():

    # Test resampling image to ref
    # with mismatched dimensions
    imgdata = np.random.randint(0, 65536, (5, 5, 5))
    img = fslimage.Image(imgdata,
                         xform=affine.scaleOffsetXform((2, 2, 2),
                                                       (0.5, 0.5, 0.5)))

    # reference/expected data when
    # resampled to ref (using nn interp).
    # Same as image, upsampled by a
    # factor of 2
    refdata = np.repeat(np.repeat(np.repeat(imgdata, 2, 0), 2, 1), 2, 2)
    refdata = np.array([refdata] * 8).transpose((1, 2, 3, 0))
    ref = fslimage.Image(refdata)

    # We should be able to use a 4D reference
    resampled, xform = resample.resampleToReference(img,
                                                    ref,
                                                    order=0,
                                                    mode='nearest')
    assert np.all(resampled == ref.data[..., 0])

    # If resampling a 4D image with a 3D reference,
    # the fourth dimension should be passed through
    resampled, xform = resample.resampleToReference(ref,
                                                    img,
                                                    order=0,
                                                    mode='nearest')
    exp = np.array([imgdata] * 8).transpose((1, 2, 3, 0))
    assert np.all(resampled == exp)

    # When resampling 4D to 4D, only the
    # first 3 dimensions should be resampled
    imgdata = np.array([imgdata] * 15).transpose((1, 2, 3, 0))
    img = fslimage.Image(imgdata, xform=img.voxToWorldMat)
    exp = np.array([refdata[..., 0]] * 15).transpose((1, 2, 3, 0))
    resampled, xform = resample.resampleToReference(img,
                                                    ref,
                                                    order=0,
                                                    mode='nearest')
    assert np.all(resampled == exp)
Ejemplo n.º 16
0
def roi(fname, roi):

    base = fslimage.removeExt(op.basename(fname))
    outfile = '{}_roi_{}_{}_{}_{}_{}_{}'.format(base, *roi)

    img = fslimage.Image(fname)
    xs, xe, ys, ye, zs, ze = roi
    data = img[xs:xe, ys:ye, zs:ze, ...]

    xform = img.voxToWorldMat
    offset = [lo for lo in roi[::2]]
    offset = affine.scaleOffsetXform([1, 1, 1], offset)
    xform = affine.concat(xform, offset)

    img = fslimage.Image(data, xform=xform, header=img.header)

    img.save(outfile)

    return outfile
Ejemplo n.º 17
0
def updateShaderState(self):
    """Updates all variables used by the vertex/fragment shaders. The fragment
    shader is configured by the
    :func:`.gl21.glvector_funcs.updateFragmentShaderState` function.
    """

    shader = self.shader
    shader.load()

    changed = glvector_funcs.updateShaderState(self)
    image = self.vectorImage
    opts = self.opts

    # see comments in gl21/glvector_funcs.py
    if self.vectorImage.niftiDataType == constants.NIFTI_DT_RGB24:
        vvxMat = affine.scaleOffsetXform(2, -1)
    else:
        vvxMat = self.imageTexture.voxValXform

    directed = opts.directed
    unitLength = opts.unitLength
    lengthScale = opts.lengthScale / 100.0
    imageDims = image.pixdim[:3]
    d2vMat = opts.getTransform('display', 'voxel')
    v2dMat = opts.getTransform('voxel', 'display')
    xFlip = opts.orientFlip

    changed |= shader.set('vectorTexture', 4)
    changed |= shader.set('displayToVoxMat', d2vMat)
    changed |= shader.set('voxToDisplayMat', v2dMat)
    changed |= shader.set('voxValXform', vvxMat)
    changed |= shader.set('imageDims', imageDims)
    changed |= shader.set('directed', directed)
    changed |= shader.set('unitLength', unitLength)
    changed |= shader.set('lengthScale', lengthScale)
    changed |= shader.set('xFlip', xFlip)

    shader.unload()

    return changed
Ejemplo n.º 18
0
def test_resample_image_shape():
    with tempdir():
        img = Image(make_random_image('image.nii.gz', dims=(10, 10, 10)))
        resample_image.main('image resampled -s 20,20,20'.split())
        res = Image('resampled')

        expv2w = affine.concat(img.voxToWorldMat,
                               affine.scaleOffsetXform([0.5, 0.5, 0.5], 0))

        assert np.all(np.isclose(res.shape, (20, 20, 20)))
        assert np.all(np.isclose(res.pixdim, (0.5, 0.5, 0.5)))
        assert np.all(np.isclose(res.voxToWorldMat, expv2w))
        assert np.all(
            np.isclose(
                np.array(affine.axisBounds(res.shape, res.voxToWorldMat)) -
                0.25, affine.axisBounds(img.shape, img.voxToWorldMat)))

        resample_image.main('image resampled -s 20,20,20 -o corner'.split())
        res = Image('resampled')
        assert np.all(
            np.isclose(affine.axisBounds(res.shape, res.voxToWorldMat),
                       affine.axisBounds(img.shape, img.voxToWorldMat)))
Ejemplo n.º 19
0
def test_resampleToReference4():

    # the image and ref are out of
    # alignment, but this affine
    # will bring them into alignment
    img2ref = affine.scaleOffsetXform([2, 2, 2], [10, 10, 10])

    imgdata = np.random.randint(0, 65536, (5, 5, 5))
    refdata = np.zeros((5, 5, 5))
    img = fslimage.Image(imgdata)
    ref = fslimage.Image(refdata, xform=img2ref)

    # Without the affine, the image
    # will be out of the FOV of the
    # reference
    resampled, xform = resample.resampleToReference(img, ref)
    assert np.all(resampled == 0)

    # But applying the affine will
    # cause them to overlap
    # perfectly in world coordinates
    resampled, xform = resample.resampleToReference(img, ref, matrix=img2ref)
    assert np.all(resampled == imgdata)
Ejemplo n.º 20
0
def _test_NewImageAction(panel, overlayList, displayCtx):
    act = panel.frame.menuActions[newimage.NewImageAction]

    def check(ovl, shape, pixdim, dtype, affine):
        assert tuple( ovl.shape)        == tuple(shape)
        assert tuple( ovl.pixdim)       == tuple(pixdim)
        assert        ovl.dtype         == dtype
        assert np.all(ovl.voxToWorldMat == affine)

    tests = [
        ((100, 100, 100), (1,   1,   1),   np.float32, np.eye(4)),
        (( 50,  50,  50), (2,   2,   2),   np.uint8,   np.diag([2, 2, 2, 1])),
        (( 20,  30,  40), (1.5, 1.2, 1.3), np.int32,   fslaffine.scaleOffsetXform([2, 3, 4], [-4, -3, -2])),
        ((100, 100, 100), (1,   1,   1),   np.float64, fslaffine.compose((2, 3, 1), (1, 2, 3), (1, 1.5, 2))),
    ]

    with mock.patch('fsleyes.actions.newimage.NewImageDialog', MockNewImageDialog):
        MockNewImageDialog.ShowModalRet = wx.ID_CANCEL
        MockNewImageDialog.initOverride = False
        act()
        realYield()
        assert len(overlayList) == 0

        MockNewImageDialog.ShowModalRet = wx.ID_OK

        for shape, pixdim, dtype, affine in tests:
            MockNewImageDialog.shapeRet  = shape
            MockNewImageDialog.pixdimRet = pixdim
            MockNewImageDialog.dtypeRet  = dtype
            MockNewImageDialog.affineRet = affine
            act()
            realYield()
            assert len(overlayList) == 1
            check(overlayList[0], shape, pixdim, dtype, affine)
            overlayList.clear()
            realYield()
Ejemplo n.º 21
0
def test_scaleOffsetXform():

    # Test numerically
    testfile = op.join(datadir, 'test_transform_test_scaleoffsetxform.txt')
    lines = readlines(testfile)
    ntests = len(lines) // 5

    for i in range(ntests):

        lineoff = i * 5
        scales, offsets = lines[lineoff].decode('ascii').split(',')

        scales = [float(s) for s in scales.split()]
        offsets = [float(o) for o in offsets.split()]

        expected = lines[lineoff + 1:lineoff + 5]
        expected = [[float(v) for v in l.split()] for l in expected]
        expected = np.array(expected)

        result = affine.scaleOffsetXform(scales, offsets)

        assert np.all(np.isclose(result, expected))

    # Test that different input types work:
    #   - scalars
    #   - lists/tuples of length <= 3
    #   - numpy arrays
    a = np.array
    stests = [
        (5, [5, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]),
        ([5], [5, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]),
        ((5, ), [5, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]),
        (a([5]), [5, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]),
        ([5, 6], [5, 0, 0, 0, 0, 6, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]),
        ((5, 6), [5, 0, 0, 0, 0, 6, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]),
        (a([5, 6]), [5, 0, 0, 0, 0, 6, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]),
        ([5, 6, 7], [5, 0, 0, 0, 0, 6, 0, 0, 0, 0, 7, 0, 0, 0, 0, 1]),
        ((5, 6, 7), [5, 0, 0, 0, 0, 6, 0, 0, 0, 0, 7, 0, 0, 0, 0, 1]),
        (a([5, 6, 7]), [5, 0, 0, 0, 0, 6, 0, 0, 0, 0, 7, 0, 0, 0, 0, 1]),
    ]
    otests = [
        (5, [1, 0, 0, 5, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]),
        ([5], [1, 0, 0, 5, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]),
        ((5, ), [1, 0, 0, 5, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]),
        (a([5]), [1, 0, 0, 5, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]),
        ([5, 6], [1, 0, 0, 5, 0, 1, 0, 6, 0, 0, 1, 0, 0, 0, 0, 1]),
        ((5, 6), [1, 0, 0, 5, 0, 1, 0, 6, 0, 0, 1, 0, 0, 0, 0, 1]),
        (a([5, 6]), [1, 0, 0, 5, 0, 1, 0, 6, 0, 0, 1, 0, 0, 0, 0, 1]),
        ([5, 6, 7], [1, 0, 0, 5, 0, 1, 0, 6, 0, 0, 1, 7, 0, 0, 0, 1]),
        ((5, 6, 7), [1, 0, 0, 5, 0, 1, 0, 6, 0, 0, 1, 7, 0, 0, 0, 1]),
        (a([5, 6, 7]), [1, 0, 0, 5, 0, 1, 0, 6, 0, 0, 1, 7, 0, 0, 0, 1]),
    ]

    for (scale, expected) in stests:
        expected = np.array(expected).reshape(4, 4)
        result = affine.scaleOffsetXform(scale, 0)
        assert np.all(np.isclose(result, expected))
    for (offset, expected) in otests:
        expected = np.array(expected).reshape(4, 4)
        result = affine.scaleOffsetXform(1, offset)
        assert np.all(np.isclose(result, expected))
Ejemplo n.º 22
0
    def __setupTransforms(self):
        """Calculates transformation matrices between all of the possible
        spaces in which the overlay may be displayed.

        These matrices are accessible via the :meth:`getTransform` method.
        """

        image = self.overlay
        shape = np.array(image.shape[:3])

        voxToIdMat = np.eye(4)
        voxToPixdimMat = np.diag(list(image.pixdim[:3]) + [1.0])
        voxToPixFlipMat = image.voxToScaledVoxMat
        voxToWorldMat = image.voxToWorldMat
        voxToWorldMat = affine.concat(self.displayXform, voxToWorldMat)
        ds = self.displayCtx.displaySpace

        # The reference transforms depend
        # on the value of displaySpace
        if ds == 'world':
            voxToRefMat = voxToWorldMat
        elif ds is self.overlay:
            voxToRefMat = voxToPixFlipMat
        else:
            voxToRefMat = affine.concat(ds.voxToScaledVoxMat, ds.worldToVoxMat,
                                        voxToWorldMat)

        # When going from voxels to textures,
        # we add 0.5 to centre the voxel (see
        # the note on coordinate systems at
        # the top of this file).
        voxToTexMat = affine.scaleOffsetXform(tuple(1.0 / shape),
                                              tuple(0.5 / shape))

        idToVoxMat = affine.invert(voxToIdMat)
        idToPixdimMat = affine.concat(voxToPixdimMat, idToVoxMat)
        idToPixFlipMat = affine.concat(voxToPixFlipMat, idToVoxMat)
        idToWorldMat = affine.concat(voxToWorldMat, idToVoxMat)
        idToRefMat = affine.concat(voxToRefMat, idToVoxMat)
        idToTexMat = affine.concat(voxToTexMat, idToVoxMat)

        pixdimToVoxMat = affine.invert(voxToPixdimMat)
        pixdimToIdMat = affine.concat(voxToIdMat, pixdimToVoxMat)
        pixdimToPixFlipMat = affine.concat(voxToPixFlipMat, pixdimToVoxMat)
        pixdimToWorldMat = affine.concat(voxToWorldMat, pixdimToVoxMat)
        pixdimToRefMat = affine.concat(voxToRefMat, pixdimToVoxMat)
        pixdimToTexMat = affine.concat(voxToTexMat, pixdimToVoxMat)

        pixFlipToVoxMat = affine.invert(voxToPixFlipMat)
        pixFlipToIdMat = affine.concat(voxToIdMat, pixFlipToVoxMat)
        pixFlipToPixdimMat = affine.concat(voxToPixdimMat, pixFlipToVoxMat)
        pixFlipToWorldMat = affine.concat(voxToWorldMat, pixFlipToVoxMat)
        pixFlipToRefMat = affine.concat(voxToRefMat, pixFlipToVoxMat)
        pixFlipToTexMat = affine.concat(voxToTexMat, pixFlipToVoxMat)

        worldToVoxMat = affine.invert(voxToWorldMat)
        worldToIdMat = affine.concat(voxToIdMat, worldToVoxMat)
        worldToPixdimMat = affine.concat(voxToPixdimMat, worldToVoxMat)
        worldToPixFlipMat = affine.concat(voxToPixFlipMat, worldToVoxMat)
        worldToRefMat = affine.concat(voxToRefMat, worldToVoxMat)
        worldToTexMat = affine.concat(voxToTexMat, worldToVoxMat)

        refToVoxMat = affine.invert(voxToRefMat)
        refToIdMat = affine.concat(voxToIdMat, refToVoxMat)
        refToPixdimMat = affine.concat(voxToPixdimMat, refToVoxMat)
        refToPixFlipMat = affine.concat(voxToPixFlipMat, refToVoxMat)
        refToWorldMat = affine.concat(voxToWorldMat, refToVoxMat)
        refToTexMat = affine.concat(voxToTexMat, refToVoxMat)

        texToVoxMat = affine.invert(voxToTexMat)
        texToIdMat = affine.concat(voxToIdMat, texToVoxMat)
        texToPixdimMat = affine.concat(voxToPixdimMat, texToVoxMat)
        texToPixFlipMat = affine.concat(voxToPixFlipMat, texToVoxMat)
        texToWorldMat = affine.concat(voxToWorldMat, texToVoxMat)
        texToRefMat = affine.concat(voxToRefMat, texToVoxMat)

        self.__xforms['id', 'id'] = np.eye(4)
        self.__xforms['id', 'pixdim'] = idToPixdimMat
        self.__xforms['id', 'pixdim-flip'] = idToPixFlipMat
        self.__xforms['id', 'affine'] = idToWorldMat
        self.__xforms['id', 'reference'] = idToRefMat
        self.__xforms['id', 'texture'] = idToTexMat

        self.__xforms['pixdim', 'pixdim'] = np.eye(4)
        self.__xforms['pixdim', 'id'] = pixdimToIdMat
        self.__xforms['pixdim', 'pixdim-flip'] = pixdimToPixFlipMat
        self.__xforms['pixdim', 'affine'] = pixdimToWorldMat
        self.__xforms['pixdim', 'reference'] = pixdimToRefMat
        self.__xforms['pixdim', 'texture'] = pixdimToTexMat

        self.__xforms['pixdim-flip', 'pixdim-flip'] = np.eye(4)
        self.__xforms['pixdim-flip', 'id'] = pixFlipToIdMat
        self.__xforms['pixdim-flip', 'pixdim'] = pixFlipToPixdimMat
        self.__xforms['pixdim-flip', 'affine'] = pixFlipToWorldMat
        self.__xforms['pixdim-flip', 'reference'] = pixFlipToRefMat
        self.__xforms['pixdim-flip', 'texture'] = pixFlipToTexMat

        self.__xforms['affine', 'affine'] = np.eye(4)
        self.__xforms['affine', 'id'] = worldToIdMat
        self.__xforms['affine', 'pixdim'] = worldToPixdimMat
        self.__xforms['affine', 'pixdim-flip'] = worldToPixFlipMat
        self.__xforms['affine', 'reference'] = worldToRefMat
        self.__xforms['affine', 'texture'] = worldToTexMat

        self.__xforms['reference', 'reference'] = np.eye(4)
        self.__xforms['reference', 'id'] = refToIdMat
        self.__xforms['reference', 'pixdim'] = refToPixdimMat
        self.__xforms['reference', 'pixdim-flip'] = refToPixFlipMat
        self.__xforms['reference', 'affine'] = refToWorldMat
        self.__xforms['reference', 'texture'] = refToTexMat

        self.__xforms['texture', 'texture'] = np.eye(4)
        self.__xforms['texture', 'id'] = texToIdMat
        self.__xforms['texture', 'pixdim'] = texToPixdimMat
        self.__xforms['texture', 'pixdim-flip'] = texToPixFlipMat
        self.__xforms['texture', 'affine'] = texToWorldMat
        self.__xforms['texture', 'reference'] = texToRefMat
Ejemplo n.º 23
0
def prepareData(data,
                prefilter=None,
                prefilterRange=None,
                resolution=None,
                scales=None,
                normalise=None,
                normaliseRange=None):
    """This function prepares and returns the given ``data``, ready to be
    used as GL texture data.

    This process potentially involves:

      - Resampling to a different resolution (see the
        :func:`.routines.subsample` function).

      - Pre-filtering (see the ``prefilter`` parameter to
        :meth:`__init__`).

      - Normalising (if the ``normalise`` parameter to :meth:`__init__`
        was ``True``, or if the data type cannot be used as-is).

      - Casting to a different data type (if the data type cannot be used
        as-is).

    :returns: A tuple containing:

                - A ``numpy`` array containing the image data, ready to be
                   copied to the GPU.

                - An affine transformation matrix which encodes an offset
                  and a scale, which may be used to transform the texture
                  data from the range ``[0.0, 1.0]`` to its raw data
                  range.

                - Inverse of ``voxValXform``.
    """

    dtype = data.dtype
    floatTextures = canUseFloatTextures()

    if normalise: dmin, dmax = normaliseRange
    else: dmin, dmax = 0, 0

    if normalise                  and \
       prefilter      is not None and \
       prefilterRange is not None:
        dmin, dmax = prefilterRange(dmin, dmax)

    # Offsets/scales which can be used to transform from
    # the texture data (which may be offset or normalised)
    # back to the original voxel data
    if normalise: offset = dmin
    elif dtype == np.uint8: offset = 0
    elif dtype == np.int8: offset = -128
    elif dtype == np.uint16: offset = 0
    elif dtype == np.int16: offset = -32768
    elif floatTextures: offset = 0

    if normalise: scale = dmax - dmin
    elif dtype == np.uint8: scale = 255
    elif dtype == np.int8: scale = 255
    elif dtype == np.uint16: scale = 65535
    elif dtype == np.int16: scale = 65535
    elif floatTextures: scale = 1

    # If the data range is 0 (min == max)
    # we just set an identity xform
    if scale == 0:
        voxValXform = np.eye(4)
        invVoxValXform = np.eye(4)

    # Otherwise we save a transformation
    # from the texture values back to the
    # original data range. Note that if
    # storing floating point data, this
    # will be an identity transform.
    else:
        invScale = 1.0 / scale
        voxValXform = affine.scaleOffsetXform(scale, offset)
        invVoxValXform = affine.scaleOffsetXform(invScale, -offset * invScale)

    if resolution is not None:
        data = glroutines.subsample(data, resolution, pixdim=scales)[0]

    if prefilter is not None:
        data = prefilter(data)

    # TODO if FLOAT_TEXTURES, you should
    #      save normalised values as float32
    if normalise:

        log.debug('Normalising to range {} - {}'.format(dmin, dmax))

        if dmax != dmin:
            data = np.clip((data - dmin) / float(dmax - dmin), 0, 1)

        data = np.round(data * 65535)
        data = np.array(data, dtype=np.uint16)

    elif dtype == np.uint8:
        pass
    elif dtype == np.int8:
        data = np.array(data + 128, dtype=np.uint8)
    elif dtype == np.uint16:
        pass
    elif dtype == np.int16:
        data = np.array(data + 32768, dtype=np.uint16)
    elif floatTextures and data.dtype != np.float32:
        data = np.array(data, dtype=np.float32)

    return data, voxValXform, invVoxValXform
Ejemplo n.º 24
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  = affine.concat(view, t2dmat)
        ixform = affine.invert(xform)

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

        # Direction that the 'camera' is
        # pointing, normalied to unit length
        cdir = affine.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 = affine.scaleOffsetXform([1, 1, 0.5], [0, 0, 0.5])
        xform  = affine.concat(zscale, proj, xform)

        return rayStep, xform
Ejemplo n.º 25
0
def updateShaderState(self):
    """Updates the state of the vector vertex and fragment shaders - the
    fragment shader may may be either the ``glvolume`` or the ``glvector``
    shader.
    """

    opts = self.opts
    useVolumeFragShader = opts.colourImage is not None
    modLow, modHigh = self.getModulateRange()
    clipLow, clipHigh = self.getClippingRange()

    clipping = [clipLow, clipHigh, -1, -1]

    if np.isclose(modHigh, modLow):
        mod = [0, 0, 0, -1]
    else:
        mod = [modLow, modHigh, 1.0 / (modHigh - modLow), -1]

    # Inputs which are required by both the
    # glvolume and glvetor fragment shaders
    self.shader.setFragParam('clipping', clipping)

    clipCoordXform = self.getAuxTextureXform('clip')
    colourCoordXform = self.getAuxTextureXform('colour')
    modCoordXform = self.getAuxTextureXform('modulate')

    self.shader.setVertParam('clipCoordXform', clipCoordXform)
    self.shader.setVertParam('colourCoordXform', colourCoordXform)
    self.shader.setVertParam('modCoordXform', modCoordXform)

    if useVolumeFragShader:

        voxValXform = self.colourTexture.voxValXform
        cmapXform = self.cmapTexture.getCoordinateTransform()
        voxValXform = affine.concat(cmapXform, voxValXform)
        voxValXform = [voxValXform[0, 0], voxValXform[0, 3], 0, 0]

        self.shader.setFragParam('voxValXform', voxValXform)

        # settings expected by glvolume
        # frag shader, but not used
        self.shader.setFragParam('negCmap', [-1, 0, 0, 0])
        self.shader.setFragParam('modulate', [0, 0, -1, 1])

    else:

        colours, colourXform = self.getVectorColours()

        # See comments in gl21/glvector_funcs.py
        if self.vectorImage.niftiDataType == constants.NIFTI_DT_RGB24:
            voxValXform = affine.scaleOffsetXform(2, -1)
        else:
            voxValXform = self.imageTexture.voxValXform

        voxValXform = [voxValXform[0, 0], voxValXform[0, 3], 0, 0]

        self.shader.setFragParam('voxValXform', voxValXform)
        self.shader.setFragParam('mod', mod)
        self.shader.setFragParam('xColour', colours[0])
        self.shader.setFragParam('yColour', colours[1])
        self.shader.setFragParam('zColour', colours[2])
        self.shader.setFragParam('colourXform',
                                 [colourXform[0, 0], colourXform[0, 3], 0, 0])
    return True
Ejemplo n.º 26
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 = affine.scaleOffsetXform([scale] * 3, 0)
        rotate = affine.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 = affine.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 = affine.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
Ejemplo n.º 27
0
    def _draw(self):
        """Draws the scene to the canvas. """

        if self.destroyed:
            return

        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 = affine.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 = affine.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()

        if opts.showLight:
            self.__drawLight()

        # Testing click-to-near/far clipping plane transformation
        if hasattr(self, 'points'):
            colours = [(1, 0, 0, 1), (0, 0, 1, 1)]
            gl.glPointSize(5)

            gl.glBegin(gl.GL_LINES)
            for i, p in enumerate(self.points):
                gl.glColor4f(*colours[i % 2])
                p = affine.transform(p, self.viewMatrix)
                gl.glVertex3f(*p)
            gl.glEnd()
Ejemplo n.º 28
0
def updateShaderState(self, useSpline=False):
    """Updates the state of the vector vertex fragment shader. The fragment
    shader may be either the ``glvolume`` or the ``glvector`` shader.
    """

    changed = False
    opts = self.opts
    shader = self.shader
    imageShape = self.vectorImage.shape[:3]
    modLow, modHigh = self.getModulateRange()
    clipLow, clipHigh = self.getClippingRange()

    if opts.modulateImage is None: modShape = [1, 1, 1]
    else: modShape = opts.modulateImage.shape[:3]
    if opts.clipImage is None: clipShape = [1, 1, 1]
    else: clipShape = opts.clipImage.shape[:3]

    clipXform = self.getAuxTextureXform('clip')
    colourXform = self.getAuxTextureXform('colour')
    modXform = self.getAuxTextureXform('modulate')

    changed |= self.shader.set('clipCoordXform', clipXform)
    changed |= self.shader.set('colourCoordXform', colourXform)
    changed |= self.shader.set('modCoordXform', modXform)

    if self.useVolumeFragShader:

        voxValXform = self.colourTexture.voxValXform
        img2CmapXform = affine.concat(
            self.cmapTexture.getCoordinateTransform(), voxValXform)

        changed |= shader.set('clipTexture', 1)
        changed |= shader.set('imageTexture', 2)
        changed |= shader.set('colourTexture', 3)
        changed |= shader.set('negColourTexture', 3)
        changed |= shader.set('img2CmapXform', img2CmapXform)
        changed |= shader.set('imageShape', imageShape)
        changed |= shader.set('imageIsClip', False)
        changed |= shader.set('useNegCmap', False)
        changed |= shader.set('useSpline', useSpline)
        changed |= shader.set('clipLow', clipLow)
        changed |= shader.set('clipHigh', clipHigh)
        changed |= shader.set('invertClip', False)

    else:
        # If we are displaying an RGB24 image
        # as a vector, we map uint8 [0, 255] to
        # [-1, 1]. The integer values will be
        # automatically normalised to [0, 1],
        # so we transform from that range.
        if self.vectorImage.niftiDataType == constants.NIFTI_DT_RGB24:
            voxValXform = affine.scaleOffsetXform(2, -1)

        # Otherwise, if it's floating point,
        # it will not be normalised.
        else:
            voxValXform = self.imageTexture.voxValXform

        colours, colourXform = self.getVectorColours()

        changed |= shader.set('modulateTexture', 0)
        changed |= shader.set('clipTexture', 1)
        changed |= shader.set('vectorTexture', 4)
        changed |= shader.set('xColour', colours[0])
        changed |= shader.set('yColour', colours[1])
        changed |= shader.set('zColour', colours[2])
        changed |= shader.set('colourXform', colourXform)
        changed |= shader.set('voxValXform', voxValXform)
        changed |= shader.set('imageShape', imageShape)
        changed |= shader.set('modImageShape', modShape)
        changed |= shader.set('clipImageShape', clipShape)
        changed |= shader.set('clipLow', clipLow)
        changed |= shader.set('clipHigh', clipHigh)
        changed |= shader.set('modLow', modLow)
        changed |= shader.set('modHigh', modHigh)
        changed |= shader.set('useSpline', useSpline)

    return changed