Ejemplo n.º 1
0
def test_normalise(seed):

    vectors = -100 + 200 * np.random.random((200, 3))

    def parallel(v1, v2):
        v1 = v1 / affine.veclength(v1)
        v2 = v2 / affine.veclength(v2)

        return np.isclose(np.dot(v1, v2), 1)

    for v in vectors:

        vtype = random.choice((list, tuple, np.array))
        v = vtype(v)
        vn = affine.normalise(v)
        vl = affine.veclength(vn)

        assert np.isclose(vl, 1.0)
        assert parallel(v, vn)

    # normalise should also be able
    # to do multiple vectors at once
    results = affine.normalise(vectors)
    lengths = affine.veclength(results)
    pars = np.zeros(200)
    for i in range(200):

        v = vectors[i]
        r = results[i]

        pars[i] = parallel(v, r)

    assert np.all(np.isclose(lengths, 1))
    assert np.all(pars)
Ejemplo n.º 2
0
def calcVertexNormals(vertices, indices, fnormals):
    """Calculates vertex normals for the mesh described by ``vertices``
    and ``indices``.

    :arg vertices: A ``(n, 3)`` array containing the mesh vertices.
    :arg indices:  A ``(m, 3)`` array containing the mesh triangles.
    :arg fnormals: A ``(m, 3)`` array containing the face/triangle normals.
    :returns:      A ``(n, 3)`` array containing normals for every vertex in
                   the mesh.
    """

    vnormals = np.zeros((vertices.shape[0], 3), dtype=float)

    # TODO make fast. I can't figure
    # out how to use np.add.at to
    # accumulate the face normals for
    # each vertex.
    for i in range(indices.shape[0]):

        v0, v1, v2 = indices[i]

        vnormals[v0, :] += fnormals[i]
        vnormals[v1, :] += fnormals[i]
        vnormals[v2, :] += fnormals[i]

    # normalise to unit length
    return affine.normalise(vnormals)
Ejemplo n.º 3
0
def needsFixing(vertices, indices, fnormals, loBounds, hiBounds):
    """Determines whether the triangle winding order, for the mesh described by
    ``vertices`` and ``indices``, needs to be flipped.

    If this function returns ``True``, the given ``indices`` and ``fnormals``
    need to be adjusted so that all face normals are facing outwards from the
    centre of the mesh. The necessary adjustments are as follows::

        indices[:, [1, 2]] = indices[:, [2, 1]]
        fnormals           = fnormals * -1

    :arg vertices: A ``(n, 3)`` array containing the mesh vertices.
    :arg indices:  A ``(m, 3)`` array containing the mesh triangles.
    :arg fnormals: A ``(m, 3)`` array containing the face/triangle normals.
    :arg loBounds: A ``(3, )`` array contaning the low vertex bounds.
    :arg hiBounds: A ``(3, )`` array contaning the high vertex bounds.

    :returns:      ``True`` if the ``indices`` and ``fnormals`` need to be
                   adjusted, ``False`` otherwise.
    """

    # Define a viewpoint which is
    # far away from the mesh.
    camera = loBounds - (hiBounds - loBounds)

    # Find the nearest vertex
    # to the viewpoint
    dists = np.sqrt(np.sum((vertices - camera) ** 2, axis=1))
    ivert = np.argmin(dists)
    vert  = vertices[ivert]

    # Get all the triangles
    # that this vertex is in
    # and their face normals
    itris = np.where(indices == ivert)[0]
    norms = fnormals[itris, :]

    # Calculate the angle between each
    # normal, and a vector from the
    # vertex to the camera. If more than
    # 50% of the angles are negative
    # (== more than 90 degrees == the
    # face is facing away from the
    # camera), assume that we need to
    # flip the triangle winding order.
    angles = np.dot(norms, affine.normalise(camera - vert))
    return ((angles >= 0).sum() / len(itris)) < 0.5
Ejemplo n.º 4
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 = affine.concat(d2tmat, viewmat)
        cdir = np.array([0, 0, 1])
        cdir = affine.transform(cdir, xform, vector=True)
        cdir = affine.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
Ejemplo n.º 5
0
def calcFaceNormals(vertices, indices):
    """Calculates face normals for the mesh described by ``vertices`` and
    ``indices``.

    :arg vertices: A ``(n, 3)`` array containing the mesh vertices.
    :arg indices:  A ``(m, 3)`` array containing the mesh triangles.
    :returns:      A ``(m, 3)`` array containing normals for every triangle in
                   the mesh.
    """

    v0 = vertices[indices[:, 0]]
    v1 = vertices[indices[:, 1]]
    v2 = vertices[indices[:, 2]]

    fnormals = np.cross((v1 - v0), (v2 - v0))
    fnormals = affine.normalise(fnormals)

    return np.atleast_2d(fnormals)
Ejemplo n.º 6
0
def planeEquation2(origin, normal):
    """Calculates the equation of a plane equation from a normal vector
    and a single point on the plane.

    Returns a ``numpy`` array containing four values, the coefficients of the
    equation:

    See also :func:`planeEquation`.
    """

    normal = affine.normalise(normal)
    ax, by, cz = np.array(origin) * normal

    eqn = np.zeros(4, dtype=np.float64)
    eqn[:3] = normal
    eqn[3] = -np.sum((ax, by, cz))

    return eqn
Ejemplo n.º 7
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     = affine.axisAnglesToRotMat(incline, 0, 0)
        rot2     = affine.axisAnglesToRotMat(0, 0, azimuth)
        rotation = affine.concat(rot2, rot1)

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

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

        return origin, normal
Ejemplo n.º 8
0
CUBE_TRIANGLES_CCW[:, [1, 2]] = CUBE_TRIANGLES_CCW[:, [2, 1]]

CUBE_CCW_FACE_NORMALS = np.array([
    [ 0,  0, -1], [ 0,  0, -1],
    [ 0,  0,  1], [ 0,  0,  1],
    [ 0, -1,  0], [ 0, -1,  0],
    [ 0,  1,  0], [ 0,  1,  0],
    [-1,  0,  0], [-1,  0,  0],
    [ 1,  0,  0], [ 1,  0,  0],
])

CUBE_CCW_VERTEX_NORMALS = np.zeros((8, 3))
for i in range(8):
    faces = np.where(CUBE_TRIANGLES_CCW == i)[0]
    CUBE_CCW_VERTEX_NORMALS[i] = CUBE_CCW_FACE_NORMALS[faces].sum(axis=0)
CUBE_CCW_VERTEX_NORMALS = affine.normalise(CUBE_CCW_VERTEX_NORMALS)


def test_mesh_create():

    verts = np.array(CUBE_VERTICES)
    tris  = np.array(CUBE_TRIANGLES_CCW)

    mesh = fslmesh.Mesh(tris, vertices=verts)

    print(str(mesh))

    assert mesh.name       == 'mesh'
    assert mesh.dataSource is None
    assert mesh.nvertices  == 8
    assert np.all(np.isclose(mesh.vertices, verts))
Ejemplo n.º 9
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.º 10
0
    def _pickModeLeftMouseDown(self, ev, canvas, mousePos, canvasPos):
        """Called on mouse down events in ``pick`` mode.

        Updates the :attr:`DisplayContext.location` property.
        """

        from fsl.data.mesh import Mesh

        displayCtx = self.displayCtx
        ovl = displayCtx.getSelectedOverlay()

        if ovl is None:
            return

        # The canvasPos is located on the near clipping
        # plane (see Scene3DCanvas.canvasToWorld).
        # We also need the corresponding point on the
        # far clipping plane.
        farPos = canvas.canvasToWorld(mousePos[0], mousePos[1], near=False)

        # For non-mesh overlays, we select a point which
        # is in between the near/far clipping planes.
        if not isinstance(ovl, Mesh):

            posDir = farPos - canvasPos
            dist = affine.veclength(posDir)
            posDir = affine.normalise(posDir)
            midPos = canvasPos + 0.5 * dist * posDir

            self.displayCtx.location.xyz = midPos

        else:
            opts = self.displayCtx.getOpts(ovl)
            rayOrigin = canvasPos
            rayDir = affine.normalise(farPos - canvasPos)

            # transform location from display into model space
            rayOrigin = opts.transformCoords(rayOrigin, 'display', 'mesh')
            rayDir = opts.transformCoords(rayDir,
                                          'display',
                                          'mesh',
                                          vector=True)
            loc, tri = ovl.rayIntersection([rayOrigin], [rayDir],
                                           vertices=True)

            if len(loc) == 0:
                return

            loc = loc[0]
            tri = ovl.indices[int(tri[0]), :]

            # The rayIntersection method gives us a
            # point on one of the mesh triangles -
            # we want the vertex on that triangle
            # which is nearest to the intersection.
            triVerts = ovl.vertices[tri, :]
            triDists = affine.veclength(loc - triVerts)
            vertIdx = np.argsort(triDists)[0]

            loc = ovl.vertices[tri[vertIdx], :]
            loc = opts.transformCoords(loc, 'mesh', 'display')

            self.displayCtx.location.xyz = loc