diff --git a/fsl/data/model.py b/fsl/data/model.py new file mode 100644 index 0000000000000000000000000000000000000000..31327fa2445d961a6e3272c898aa1598a5e45580 --- /dev/null +++ b/fsl/data/model.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python +# +# model.py - +# +# Author: Paul McCarthy <pauldmccarthy@gmail.com> +# + + +import numpy as np + + +def loadVTKPolydataFile(infile): + + lines = None + + with open(infile, 'rt') as f: + lines = f.readlines() + + lines = [l.strip() for l in lines] + + if lines[3] != 'DATASET POLYDATA': + raise ValueError('') + + nVertices = int(lines[4].split()[1]) + nPolygons = int(lines[5 + nVertices].split()[1]) + nIndices = int(lines[5 + nVertices].split()[2]) - nPolygons + + vertices = np.zeros((nVertices, 3), dtype=np.float32) + polygonLengths = np.zeros( nPolygons, dtype=np.uint32) + indices = np.zeros( nIndices, dtype=np.uint32) + + for i in range(nVertices): + vertLine = lines[i + 5] + vertices[i, :] = map(float, vertLine.split()) + + indexOffset = 0 + for i in range(nPolygons): + + polyLine = lines[6 + nVertices + i].split() + polygonLengths[i] = int(polyLine[0]) + + start = indexOffset + end = indexOffset + polygonLengths[i] + indices[start:end] = map(int, polyLine[1:]) + + indexOffset += polygonLengths[i] + + + return vertices, polygonLengths, indices + + +class PolygonModel(object): + + def __init__(self, data, indices=None): + """ + """ + + if isinstance(data, str): + infile = data + data, lengths, indices = loadVTKPolydataFile(infile) + + if np.any(lengths != 3): + raise RuntimeError('All polygons in VTK file must be ' + 'triangles ({})'.format(infile)) + + if indices is None: + indices = np.arange(data.shape[0], dtype=np.uint32) + + self.vertices = np.array(data, dtype=np.float32) + self.indices = indices + + + def getBounds(self): + return (self.vertices.min(axis=0), + self.vertices.max(axis=0)) diff --git a/fsl/fslview/displaycontext/modelopts.py b/fsl/fslview/displaycontext/modelopts.py new file mode 100644 index 0000000000000000000000000000000000000000..93acf16835f3e146e85745d4f80b2d4f4d26c6aa --- /dev/null +++ b/fsl/fslview/displaycontext/modelopts.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +# +# modelopts.py - +# +# Author: Paul McCarthy <pauldmccarthy@gmail.com> +# + +import numpy as np + +import props + +import display as fsldisplay + + +class ModelOpts(fsldisplay.DisplayOpts): + + colour = props.Colour() + outline = props.Boolean(default=True) + + + def __init__(self, *args, **kwargs): + + fsldisplay.DisplayOpts.__init__(self, *args, **kwargs) + + colour = np.random.random(4) + colour[3] = 1.0 + self.colour = colour diff --git a/fsl/fslview/gl/glmodel.py b/fsl/fslview/gl/glmodel.py new file mode 100644 index 0000000000000000000000000000000000000000..7095b0e470fdcefc0711d55a49a8dd7eeafe9647 --- /dev/null +++ b/fsl/fslview/gl/glmodel.py @@ -0,0 +1,269 @@ +#!/usr/bin/env python +# +# glvtkobject.py - +# +# Author: Paul McCarthy <pauldmccarthy@gmail.com> +# + + +import numpy as np +import OpenGL.GL as gl + +import globject +import fsl.fslview.gl.routines as glroutines + + +class GLModel(globject.GLObject): + + def __init__(self, overlay, display): + + globject.GLObject.__init__(self) + + self.overlay = overlay + self.display = display + self.opts = display.getDisplayOpts() + + + def destroy(self): + pass + + + def getDisplayBounds(self): + return self.overlay.getBounds() + + + def setAxes(self, xax, yax): + self.xax = xax + self.yax = yax + self.zax = 3 - xax - yax + + self._prepareOutlineVertices() + + + + def _prepareOutlineVertices(self): + + verts = self.overlay.vertices + mean = verts.mean(axis=0) + + verts = verts - mean + + verts[:, self.xax] *= 0.9 + verts[:, self.yax] *= 0.9 + + verts += mean + + self.outlineVertices = verts + + + def preDraw(self): + + pass + + + def draw(self, zpos, xform=None): + + if self.opts.outline: self.drawOutline(zpos, xform) + else: self.drawFilled( zpos, xform) + + + def drawFilled(self, zpos, xform): + + xax = self.xax + yax = self.yax + zax = self.zax + + lo, hi = self.getDisplayBounds() + + xmin = lo[xax] + ymin = lo[yax] + xmax = hi[xax] + ymax = hi[yax] + + clipPlaneVerts = np.zeros((4, 3), dtype=np.float32) + clipPlaneVerts[0, [xax, yax]] = [xmin, ymin] + clipPlaneVerts[1, [xax, yax]] = [xmin, ymax] + clipPlaneVerts[2, [xax, yax]] = [xmax, ymax] + clipPlaneVerts[3, [xax, yax]] = [xmax, ymin] + clipPlaneVerts[:, zax] = zpos + + planeEq = glroutines.planeEquation(clipPlaneVerts[0, :], + clipPlaneVerts[1, :], + clipPlaneVerts[2, :]) + + vertices = self.overlay.vertices + indices = self.overlay.indices + + gl.glEnableClientState(gl.GL_VERTEX_ARRAY) + gl.glEnable(gl.GL_CLIP_PLANE0) + gl.glEnable(gl.GL_CULL_FACE) + gl.glEnable(gl.GL_STENCIL_TEST) + + gl.glClipPlane(gl.GL_CLIP_PLANE0, planeEq) + + gl.glClear(gl.GL_STENCIL_BUFFER_BIT) + + gl.glColorMask(gl.GL_FALSE, gl.GL_FALSE, gl.GL_FALSE, gl.GL_FALSE) + + # first pass - render front faces + gl.glStencilFunc(gl.GL_ALWAYS, 0, 0) + gl.glStencilOp(gl.GL_KEEP, gl.GL_KEEP, gl.GL_INCR) + gl.glCullFace(gl.GL_BACK) + + gl.glVertexPointer(3, gl.GL_FLOAT, 0, vertices) + gl.glDrawElements(gl.GL_TRIANGLES, + len(indices), + gl.GL_UNSIGNED_INT, + indices) + + # Second pass - render back faces + gl.glStencilOp(gl.GL_KEEP, gl.GL_KEEP, gl.GL_DECR) + gl.glCullFace(gl.GL_FRONT) + + gl.glVertexPointer(3, gl.GL_FLOAT, 0, vertices) + gl.glDrawElements(gl.GL_TRIANGLES, + len(indices), + gl.GL_UNSIGNED_INT, + indices) + + # third pass - render the intersection + # of the front and back faces from the + # stencil buffer + gl.glColorMask(gl.GL_TRUE, gl.GL_TRUE, gl.GL_TRUE, gl.GL_TRUE) + + gl.glDisable(gl.GL_CLIP_PLANE0) + gl.glDisable(gl.GL_CULL_FACE) + + gl.glStencilFunc(gl.GL_NOTEQUAL, 0, 255) + + colour = self.opts.colour + colour[3] = self.display.alpha + + gl.glColor(*colour) + gl.glBegin(gl.GL_QUADS) + + gl.glVertex3f(*clipPlaneVerts[0, :]) + gl.glVertex3f(*clipPlaneVerts[1, :]) + gl.glVertex3f(*clipPlaneVerts[2, :]) + gl.glVertex3f(*clipPlaneVerts[3, :]) + gl.glEnd() + + gl.glDisable(gl.GL_STENCIL_TEST) + gl.glDisableClientState(gl.GL_VERTEX_ARRAY) + + + def drawOutline(self, zpos, xform): + xax = self.xax + yax = self.yax + zax = self.zax + + lo, hi = self.getDisplayBounds() + + xmin = lo[xax] + ymin = lo[yax] + xmax = hi[xax] + ymax = hi[yax] + + clipPlaneVerts = np.zeros((4, 3), dtype=np.float32) + clipPlaneVerts[0, [xax, yax]] = [xmin, ymin] + clipPlaneVerts[1, [xax, yax]] = [xmin, ymax] + clipPlaneVerts[2, [xax, yax]] = [xmax, ymax] + clipPlaneVerts[3, [xax, yax]] = [xmax, ymin] + clipPlaneVerts[:, zax] = zpos + + planeEq = glroutines.planeEquation(clipPlaneVerts[0, :], + clipPlaneVerts[1, :], + clipPlaneVerts[2, :]) + + vertices = self.overlay.vertices + olVertices = self.outlineVertices + indices = self.overlay.indices + + gl.glEnableClientState(gl.GL_VERTEX_ARRAY) + gl.glEnable(gl.GL_CLIP_PLANE0) + gl.glEnable(gl.GL_CULL_FACE) + gl.glEnable(gl.GL_STENCIL_TEST) + + gl.glClipPlane(gl.GL_CLIP_PLANE0, planeEq) + + gl.glClear(gl.GL_STENCIL_BUFFER_BIT) + + gl.glColorMask(gl.GL_FALSE, gl.GL_FALSE, gl.GL_FALSE, gl.GL_FALSE) + + # first pass - render front faces + gl.glStencilFunc(gl.GL_ALWAYS, 0, 0) + gl.glStencilOp(gl.GL_KEEP, gl.GL_KEEP, gl.GL_INCR) + gl.glCullFace(gl.GL_BACK) + + gl.glVertexPointer(3, gl.GL_FLOAT, 0, vertices) + gl.glDrawElements(gl.GL_TRIANGLES, + len(indices), + gl.GL_UNSIGNED_INT, + indices) + + gl.glStencilOp(gl.GL_KEEP, gl.GL_KEEP, gl.GL_INCR) + gl.glVertexPointer(3, gl.GL_FLOAT, 0, olVertices) + gl.glDrawElements(gl.GL_TRIANGLES, + len(indices), + gl.GL_UNSIGNED_INT, + indices) + + # Second pass - render back faces + gl.glStencilOp(gl.GL_KEEP, gl.GL_KEEP, gl.GL_INCR) + gl.glCullFace(gl.GL_FRONT) + + gl.glVertexPointer(3, gl.GL_FLOAT, 0, vertices) + gl.glDrawElements(gl.GL_TRIANGLES, + len(indices), + gl.GL_UNSIGNED_INT, + indices) + + gl.glStencilOp(gl.GL_KEEP, gl.GL_KEEP, gl.GL_INCR) + gl.glVertexPointer(3, gl.GL_FLOAT, 0, olVertices) + gl.glDrawElements(gl.GL_TRIANGLES, + len(indices), + gl.GL_UNSIGNED_INT, + indices) + + # third pass - render the intersection + # of the front and back faces from the + # stencil buffer + gl.glColorMask(gl.GL_TRUE, gl.GL_TRUE, gl.GL_TRUE, gl.GL_TRUE) + + gl.glDisable(gl.GL_CLIP_PLANE0) + gl.glDisable(gl.GL_CULL_FACE) + + gl.glStencilFunc(gl.GL_EQUAL, 3, 255) + + colour = self.opts.colour + colour[3] = self.display.alpha + + gl.glColor(*colour) + gl.glBegin(gl.GL_QUADS) + + gl.glVertex3f(*clipPlaneVerts[0, :]) + gl.glVertex3f(*clipPlaneVerts[1, :]) + gl.glVertex3f(*clipPlaneVerts[2, :]) + gl.glVertex3f(*clipPlaneVerts[3, :]) + gl.glEnd() + + gl.glStencilFunc(gl.GL_EQUAL, 1, 255) + + colour = self.opts.colour + colour[3] = self.display.alpha + + gl.glColor(*colour) + gl.glBegin(gl.GL_QUADS) + + gl.glVertex3f(*clipPlaneVerts[0, :]) + gl.glVertex3f(*clipPlaneVerts[1, :]) + gl.glVertex3f(*clipPlaneVerts[2, :]) + gl.glVertex3f(*clipPlaneVerts[3, :]) + gl.glEnd() + + gl.glDisable(gl.GL_STENCIL_TEST) + gl.glDisableClientState(gl.GL_VERTEX_ARRAY) + + + def postDraw(self): + pass