def draw3D(self, xform=None, bbox=None): """Draws the image in 3D on the canvas. :arg self: The :class:`.GLVolume` object which is managing the image to be drawn. :arg xform: A 4*4 transformation matrix to be applied to the vertex data. :arg bbox: An optional bounding box. """ opts = self.opts canvas = self.canvas copts = canvas.opts tex = self.renderTexture1 proj = self.canvas.projectionMatrix vertices, voxCoords, texCoords = self.generateVertices3D(bbox) rayStep, texform = opts.calculateRayCastSettings(xform, proj) rayStep = affine.transformNormal( rayStep, self.imageTexture.texCoordXform(self.overlay.shape)) texform = affine.concat( texform, self.imageTexture.invTexCoordXform(self.overlay.shape)) # If lighting is enabled, we specify the light # position in image texture coordinates, to make # life easier for the shader if copts.light: lxform = opts.getTransform('display', 'texture') lightPos = affine.transform(canvas.lightPos, lxform) else: lightPos = [0, 0, 0] if xform is not None: vertices = affine.transform(vertices, xform) self.shader.set('lighting', copts.light) self.shader.set('tex2ScreenXform', texform) self.shader.set('rayStep', rayStep) self.shader.set('lightPos', lightPos) self.shader.setAtt('vertex', vertices) self.shader.setAtt('texCoord', texCoords) self.shader.loadAtts() tex.bindAsRenderTarget() gl.glDrawArrays(gl.GL_TRIANGLES, 0, 36) tex.unbindAsRenderTarget() self.shader.unloadAtts() self.shader.unload()
def test_transformNormal(seed): normals = -100 + 200 * np.random.random((50, 3)) def tn(n, xform): xform = npla.inv(xform[:3, :3]).T return np.dot(xform, n) for n in normals: scales = -10 + np.random.random(3) * 10 offsets = -100 + np.random.random(3) * 200 rotations = -np.pi + np.random.random(3) * 2 * np.pi origin = -100 + np.random.random(3) * 200 xform = affine.compose(scales, offsets, rotations, origin) expected = tn(n, xform) result = affine.transformNormal(n, xform) assert np.all(np.isclose(expected, result))
def draw3D(self, xform=None, bbox=None): """Draws the image in 3D on the canvas. :arg self: The :class:`.GLVolume` object which is managing the image to be drawn. :arg xform: A 4*4 transformation matrix to be applied to the vertex data. :arg bbox: An optional bounding box. """ opts = self.opts tex = self.renderTexture1 proj = self.canvas.projectionMatrix vertices, voxCoords, texCoords = self.generateVertices3D(bbox) rayStep, texform = opts.calculateRayCastSettings(xform, proj) rayStep = affine.transformNormal( rayStep, self.imageTexture.texCoordXform(self.overlay.shape)) texform = affine.concat( texform, self.imageTexture.invTexCoordXform(self.overlay.shape)) if xform is not None: vertices = affine.transform(vertices, xform) self.shader.set('tex2ScreenXform', texform) self.shader.set('rayStep', rayStep) self.shader.setAtt('vertex', vertices) self.shader.setAtt('texCoord', texCoords) self.shader.loadAtts() tex.bindAsRenderTarget() gl.glDrawArrays(gl.GL_TRIANGLES, 0, 36) tex.unbindAsRenderTarget() self.shader.unloadAtts() self.shader.unload()
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
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 = affine.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 # modalpha not applied in 3D modAlpha = 1 if opts.modulateAlpha else -1 imageIsMod = 1 if opts.modulateImage is None else -1 modXform = self.getModulateValueXform() 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] modulate = [modXform[0, 0], modXform[0, 3], modAlpha, imageIsMod] negCmap = [useNegCmap, texZero, modAlpha, 0] # disable clip image/modalpha for 3D if self.threedee: clipping[3] = 1 modulate[2] = -1 modulate[3] = 1 changed = False changed |= shader.setFragParam('voxValXform', voxValXform) changed |= shader.setFragParam('clipping', clipping) changed |= shader.setFragParam('modulate', modulate) 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 = affine.transform(origin, d2tmat) normal = affine.transformNormal(normal, d2tmat) clipPlanes[i, :] = glroutines.planeEquation2(origin, normal) changed |= shader.setFragParam('clipPlanes', clipPlanes) self.shader.unload() return changed
def draw3D(self, xform=None, bbox=None): """Draws the image in 3D on the canvas. :arg self: The :class:`.GLVolume` object which is managing the image to be drawn. :arg xform: A 4*4 transformation matrix to be applied to the vertex data. :arg bbox: An optional bounding box. """ opts = self.opts canvas = self.canvas display = self.display shader = self.shader shape = self.image.shape proj = canvas.projectionMatrix src = self.renderTexture1 dest = self.renderTexture2 w, h = src.shape vertices, voxCoords, texCoords = self.generateVertices3D(bbox) rayStep, texform = opts.calculateRayCastSettings(xform, proj) rayStep = affine.transformNormal(rayStep, self.imageTexture.texCoordXform(shape)) texform = affine.concat(texform, self.imageTexture.invTexCoordXform(shape)) if xform is not None: vertices = affine.transform(vertices, xform) vertices = np.array(vertices, dtype=np.float32).ravel('C') outerLoop = opts.getNumOuterSteps() screenSize = [1.0 / w, 1.0 / h, 0, 0] rayStep = list(rayStep) + [0] texform = texform[2, :] settings = [(1 - opts.blendFactor)**2, 0, 0, display.alpha / 100.0] gl.glVertexPointer(3, gl.GL_FLOAT, 0, vertices) shader.setAtt('texCoord', texCoords) shader.setFragParam('rayStep', rayStep) shader.setFragParam('screenSize', screenSize) shader.setFragParam('tex2ScreenXform', texform) # Disable blending - we want each # loop to replace the contents of # the texture, not blend into it! with glroutines.enabled((gl.GL_VERTEX_ARRAY)), \ glroutines.disabled((gl.GL_BLEND)): for i in range(outerLoop): settings = list(settings) dtex = src.depthTexture settings[1] = i * opts.numInnerSteps if i == outerLoop - 1: settings[2] = 1 else: settings[2] = -1 shader.setFragParam('settings', settings) dest.bindAsRenderTarget() src.bindTexture(gl.GL_TEXTURE5) dtex.bindTexture(gl.GL_TEXTURE6) gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT) gl.glDrawArrays(gl.GL_TRIANGLES, 0, 36) src.unbindTexture() dtex.unbindTexture() dest.unbindAsRenderTarget() dest, src = src, dest shader.unloadAtts() shader.unload() self.renderTexture1 = src self.renderTexture2 = dest
def updateShaderState(self): """Updates the parameters used by the shader programs, reflecting the current display properties. """ if not self.ready(): return opts = self.opts display = self.display shader = self.shader imageIsClip = opts.clipImage is None imageIsMod = opts.modulateImage is None # The clipping options are in the voxel value # range, but the shader needs them to be in image # texture value range (0.0 - 1.0). So let's scale # them. imgXform = self.imageTexture.invVoxValXform if imageIsClip: clipXform = imgXform else: clipXform = self.clipTexture.invVoxValXform modAlpha = opts.modulateAlpha modXform = self.getModulateValueXform() modScale = modXform[0, 0] modOffset = modXform[0, 3] clipLow = opts.clippingRange[0] * clipXform[0, 0] + clipXform[0, 3] clipHigh = opts.clippingRange[1] * clipXform[0, 0] + clipXform[0, 3] texZero = 0.0 * imgXform[0, 0] + imgXform[0, 3] imageShape = self.image.shape[:3] texShape = self.imageTexture.shape[:3] if len(texShape) == 2: texShape = list(texShape) + [1] if imageIsClip: clipImageShape = imageShape else: clipImageShape = opts.clipImage.shape[:3] if imageIsMod: modImageShape = imageShape else: modImageShape = opts.modulateImage.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 = affine.concat(self.colourTexture.getCoordinateTransform(), self.imageTexture.voxValXform) shader.load() # disable clipimage/modalpha in 3D if self.threedee: imageIsClip = True imageIsMod = True modAlpha = False changed = False changed |= shader.set('useSpline', opts.interpolation == 'spline') changed |= shader.set('imageShape', imageShape) changed |= shader.set('texShape', texShape) changed |= shader.set('clipLow', clipLow) changed |= shader.set('clipHigh', clipHigh) changed |= shader.set('modScale', modScale) changed |= shader.set('modOffset', modOffset) changed |= shader.set('texZero', texZero) changed |= shader.set('invertClip', opts.invertClipping) changed |= shader.set('useNegCmap', opts.useNegativeCmap) changed |= shader.set('imageIsClip', imageIsClip) changed |= shader.set('imageIsMod', imageIsMod) changed |= shader.set('img2CmapXform', img2CmapXform) changed |= shader.set('clipImageShape', clipImageShape) changed |= shader.set('modImageShape', modImageShape) changed |= shader.set('modulateAlpha', modAlpha) changed |= shader.set('imageTexture', 0) changed |= shader.set('colourTexture', 1) changed |= shader.set('negColourTexture', 2) changed |= shader.set('clipTexture', 3) changed |= shader.set('modulateTexture', 4) if self.threedee: blendFactor = (1 - opts.blendFactor)**2 clipPlanes = np.zeros((opts.numClipPlanes, 4), dtype=np.float32) d2tmat = opts.getTransform('display', 'texture') if opts.clipMode == 'intersection': clipMode = 1 elif opts.clipMode == 'union': clipMode = 2 elif opts.clipMode == 'complement': clipMode = 3 else: clipMode = 0 for i in range(opts.numClipPlanes): origin, normal = self.get3DClipPlane(i) origin = affine.transform(origin, d2tmat) normal = affine.transformNormal(normal, d2tmat) clipPlanes[i, :] = glroutines.planeEquation2(origin, normal) changed |= shader.set('numClipPlanes', opts.numClipPlanes) changed |= shader.set('clipMode', clipMode) changed |= shader.set('clipPlanes', clipPlanes, opts.numClipPlanes) changed |= shader.set('blendFactor', blendFactor) changed |= shader.set('stepLength', 1.0 / opts.getNumSteps()) changed |= shader.set('alpha', display.alpha / 100.0) shader.unload() return changed