Skip to content
Snippets Groups Projects
Commit 6914abc5 authored by Paul McCarthy's avatar Paul McCarthy
Browse files

Added clipping functionaliy (out-of-range values are transparent). Image...

Added clipping functionaliy (out-of-range values are transparent). Image display properties are now defined in a separate class, fsl.data.ImageDisplay.
parent a12ec8be
No related branches found
No related tags found
No related merge requests found
......@@ -13,18 +13,7 @@ import matplotlib.colors as mplcolors
import fsl.props as props
import fsl.data.imagefile as imagefile
class Image(props.HasProperties):
alpha = props.Double(minval=0.0, maxval=1.0, default=1.0)
displayMin = props.Double()
displayMax = props.Double()
_view = props.VGroup(('displayMin', 'displayMax', 'alpha'))
_labels = {
'displayMin' : 'Min.',
'displayMax' : 'Max.',
'alpha' : 'Opacity'
}
class Image(object):
def __init__(self, image):
......@@ -53,11 +42,49 @@ class Image(props.HasProperties):
self.ylen = ylen
self.zlen = zlen
class ImageDisplay(props.HasProperties):
"""
"""
alpha = props.Double(minval=0.0, maxval=1.0, default=1.0)
displayMin = props.Double()
displayMax = props.Double()
rangeClip = props.Boolean(default=False)
_view = props.VGroup(('displayMin', 'displayMax', 'alpha', 'rangeClip'))
_labels = {
'displayMin' : 'Min.',
'displayMax' : 'Max.',
'alpha' : 'Opacity',
'rangeClip' : 'Clipping'
}
def __init__(self, image):
self.image = image
# Attributes controlling image display
self.cmap = mplcm.Greys_r
self.alpha = 1.0
self.datamin = self.data.min()
self.datamax = self.data.max()
self.displayMin = self.datamin # use cal_min/cal_max instead?
self.displayMax = self.datamax
self.dataMin = self.image.data.min()
self.dataMax = self.image.data.max()
self.displayMin = self.dataMin # use cal_min/cal_max instead?
self.displayMax = self.dataMax
self.addListener(
'rangeClip',
'ImageRangeClip_{}'.format(id(self)),
self.updateColourMap)
def updateColourMap(self, *a):
if self.rangeClip:
self.cmap.set_under(self.cmap(0.0), alpha=0.0)
self.cmap.set_over( self.cmap(1.0), alpha=0.0)
else:
self.cmap.set_under(self.cmap(0.0), alpha=1.0)
self.cmap.set_over( self.cmap(1.0), alpha=1.0)
......@@ -693,8 +693,6 @@ def buildGUI(parent,
if hasattr(hasProps, '_tooltips'): tooltips = hasProps._tooltips
else: tooltips = {}
print labels
propGui = PropGUI()
view = _prepareView(view, labels, tooltips)
mainPanel = _create(parent, view, hasProps, propGui)
......
......@@ -9,6 +9,15 @@
import sys
# import logging
# logging.basicConfig(
# format='%(levelname)8s '\
# '%(filename)20s '\
# '%(lineno)4d: '\
# '%(funcName)s - '\
# '%(message)s',
# level=logging.DEBUG)
import wx
import wx.lib.newevent as wxevent
......@@ -26,20 +35,25 @@ class ImageView(wx.Panel):
different axis of the given 3D numpy image.
"""
if not isinstance(image, fslimage.Image):
image = fslimage.Image(image)
self.imageDisplay = fslimage.ImageDisplay(image)
wx.Panel.__init__(self, parent, *args, **kwargs)
self.SetMinSize((300,100))
self.shape = image.data.shape
self.canvasPanel = wx.Panel(self)
self.controlPanel = props.buildGUI(self, image)
self.controlPanel = props.buildGUI(self, self.imageDisplay)
self.xcanvas = slicecanvas.SliceCanvas(
self.canvasPanel, image, zax=0)
self.canvasPanel, self.imageDisplay, zax=0)
self.ycanvas = slicecanvas.SliceCanvas(
self.canvasPanel, image, zax=1, master=self.xcanvas)
self.canvasPanel, self.imageDisplay, zax=1, master=self.xcanvas)
self.zcanvas = slicecanvas.SliceCanvas(
self.canvasPanel, image, zax=2, master=self.xcanvas)
self.canvasPanel, self.imageDisplay, zax=2, master=self.xcanvas)
self.mainSizer = wx.BoxSizer(wx.VERTICAL)
self.canvasSizer = wx.BoxSizer(wx.HORIZONTAL)
......@@ -99,7 +113,6 @@ class ImageView(wx.Panel):
self.setLocation(self.xcanvas.zpos, self.ycanvas.zpos, z)
def _setCanvasPosition(self, ev):
"""
Called on mouse movement and left clicks. The currently
......
......@@ -9,8 +9,11 @@
import itertools as it
import numpy as np
import matplotlib.colors as colors
import wx
import wx.glcanvas as wxgl
import OpenGL.GL as gl
import OpenGL.GL.shaders as shaders
import OpenGL.arrays.vbo as vbo
......@@ -81,8 +84,15 @@ varying float fragVoxelValue;
void main(void) {
vec3 color = texture1D(colourMap, fragVoxelValue).rgb;
gl_FragColor = vec4(color, alpha);
vec4 voxTexture = texture1D(colourMap, fragVoxelValue);
vec3 voxColour = voxTexture.rgb;
float voxAlpha = voxTexture.a;
if (voxAlpha > alpha) {
voxAlpha = alpha;
}
gl_FragColor = vec4(voxColour, voxAlpha);
}
"""
......@@ -90,7 +100,7 @@ void main(void) {
class SliceCanvas(wxgl.GLCanvas):
"""
A wx.glcanvas.GLCanvas which may be used to display a single
2D slice from a 3D numpy array.
2D slice from a 3D image (see fsl.data.fslimage.Image).
"""
@property
......@@ -199,9 +209,21 @@ class SliceCanvas(wxgl.GLCanvas):
GL context and the image buffer data.
"""
if not isinstance(image, fslimage.Image):
image = fslimage.Image(image)
realImage = None
imageDisplay = None
if isinstance(image, fslimage.ImageDisplay):
realImage = image.image
imageDisplay = image
elif isinstance(image, fslimage.Image):
realImage = image
imageDisplay = fslimage.ImageDisplay(image)
elif isinstance(image, np.ndarray):
realImage = fslimage.Image(image)
imageDisplay = fslimage.ImageDisplay(realImage)
wxgl.GLCanvas.__init__(self, parent, **kwargs)
if master is not None: context = master.context
......@@ -217,10 +239,11 @@ class SliceCanvas(wxgl.GLCanvas):
dims = range(3)
dims.pop(zax)
self.image = image
self.xax = dims[0]
self.yax = dims[1]
self.zax = zax
self.image = realImage
self.imageDisplay = imageDisplay
self.xax = dims[0]
self.yax = dims[1]
self.zax = zax
self.xdim = self.image.data.shape[self.xax]
self.ydim = self.image.data.shape[self.yax]
......@@ -249,13 +272,29 @@ class SliceCanvas(wxgl.GLCanvas):
self.Bind(wx.EVT_PAINT, self.draw)
def alphaChanged(newAlpha, *a):
def refreshNeeded(*a):
self.Refresh()
def colourUpdateNeeded(*a):
self.updateColourBuffer()
self.Refresh()
self.image.addListener(
self.imageDisplay.addListener(
'alpha',
'SliceCanvasAlpha_{}'.format(id(self)),
alphaChanged)
refreshNeeded)
self.imageDisplay.addListener(
'displayMin',
'SliceCanvasDisplayMin_{}'.format(id(self)),
colourUpdateNeeded)
self.imageDisplay.addListener(
'displayMax',
'SliceCanvasDisplayMax_{}'.format(id(self)),
colourUpdateNeeded)
self.imageDisplay.addListener(
'rangeClip',
'SliceCanvasRangeClip_{}'.format(id(self)),
colourUpdateNeeded)
def _initGLData(self):
......@@ -350,19 +389,27 @@ class SliceCanvas(wxgl.GLCanvas):
change to take effect.
"""
iDisplay = self.imageDisplay
normMin = (iDisplay.displayMin - iDisplay.dataMin) / \
(iDisplay.dataMax - iDisplay.dataMin)
normMax = (iDisplay.displayMax - iDisplay.dataMin) / \
(iDisplay.dataMax - iDisplay.dataMin)
normalStep = 1.0 / (self.colourResolution - 1)
newStep = normalStep / (normMax - normMin)
normalRange = np.linspace(0.0, 1.0, self.colourResolution)
newRange = (normalRange - normMin) * (newStep / normalStep)
# Create [self.colourResolution] rgb values,
# spanning the entire range of the image
# colour map (see fsl.data.fslimage.Image)
colourmap = self.image.cmap(
np.linspace(0.0, 1.0, self.colourResolution))
# Strip the alpha values (we use an image wide
# alpha constant - fsl.data.fslimage.Image.alpha),
colourmap = colourmap[:,:3]
colourmap = np.floor(colourmap * 255)
colourmap = iDisplay.cmap(newRange)
# The colour data is stored on
# the GPU as 8 bit rgb triplets
colourmap = np.floor(colourmap * 255)
colourmap = np.array(colourmap, dtype=np.uint8)
colourmap = colourmap.ravel(order='C')
......@@ -377,10 +424,10 @@ class SliceCanvas(wxgl.GLCanvas):
gl.glTexImage1D(gl.GL_TEXTURE_1D,
0,
gl.GL_RGB8,
gl.GL_RGBA8,
self.colourResolution,
0,
gl.GL_RGB,
gl.GL_RGBA,
gl.GL_UNSIGNED_BYTE,
colourmap)
......@@ -433,7 +480,7 @@ class SliceCanvas(wxgl.GLCanvas):
gl.glBindTexture(gl.GL_TEXTURE_1D, self.colourBuffer)
gl.glUniform1i(self.colourMapPos, 0)
gl.glUniform1f(self.alphaPos, self.image.alpha)
gl.glUniform1f(self.alphaPos, self.imageDisplay.alpha)
# We draw each horizontal row of voxels one at a time.
# This is necessary because, in order to allow image
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment