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

ImageView supports multiple images. Pretty hacky at this stage, needs cleaning...

ImageView supports multiple images. Pretty hacky at this stage, needs cleaning up. Have gone back to storing image data as uint8s.
parent 249a3357
No related branches found
No related tags found
No related merge requests found
#!/usr/bin/env python #!/usr/bin/env python
# #
# fslimage.py - Classes representing a 3D image, the display # fslimage.py - Classes for representing 3D images, display
# properties of a 3D image, and a collection of 3D images. # properties of 3D images, and collections of 3D images.
# #
# Author: Paul McCarthy <pauldmccarthy@gmail.com> # Author: Paul McCarthy <pauldmccarthy@gmail.com>
# #
...@@ -39,6 +39,7 @@ class Image(object): ...@@ -39,6 +39,7 @@ class Image(object):
# otherwise, we assume that it is a nibabel image # otherwise, we assume that it is a nibabel image
self.nibImage = image self.nibImage = image
self.data = image.get_data() self.data = image.get_data()
self.name = image.get_filename()
xdim,ydim,zdim = self.nibImage.get_shape() xdim,ydim,zdim = self.nibImage.get_shape()
xlen,ylen,zlen = self.nibImage.get_header().get_zooms() xlen,ylen,zlen = self.nibImage.get_header().get_zooms()
...@@ -51,6 +52,10 @@ class Image(object): ...@@ -51,6 +52,10 @@ class Image(object):
self.ylen = ylen self.ylen = ylen
self.zlen = zlen self.zlen = zlen
# This attribute may be used to point to an OpenGL
# buffer which is to be shared between multiple users
# (e.g. two SliceCanvas instances which are displaying
# a different view of the same image)
self.glBuffer = None self.glBuffer = None
...@@ -59,7 +64,6 @@ class ImageDisplay(props.HasProperties): ...@@ -59,7 +64,6 @@ class ImageDisplay(props.HasProperties):
A class which describes how an image should be displayed. A class which describes how an image should be displayed.
""" """
def updateColourMap(self, newVal): def updateColourMap(self, newVal):
""" """
When a colour property changes, this method is called - When a colour property changes, this method is called -
...@@ -76,6 +80,8 @@ class ImageDisplay(props.HasProperties): ...@@ -76,6 +80,8 @@ class ImageDisplay(props.HasProperties):
cmap.set_under(cmap(0.0), alpha=1.0) cmap.set_under(cmap(0.0), alpha=1.0)
cmap.set_over( cmap(1.0), alpha=1.0) cmap.set_over( cmap(1.0), alpha=1.0)
# The display properties of an image
enabled = props.Boolean()
alpha = props.Double(minval=0.0, maxval=1.0, default=1.0) alpha = props.Double(minval=0.0, maxval=1.0, default=1.0)
displayMin = props.Double() displayMin = props.Double()
displayMax = props.Double() displayMax = props.Double()
...@@ -85,9 +91,14 @@ class ImageDisplay(props.HasProperties): ...@@ -85,9 +91,14 @@ class ImageDisplay(props.HasProperties):
cmap = props.ColourMap(default=mplcm.Greys_r, cmap = props.ColourMap(default=mplcm.Greys_r,
preNotifyFunc=updateColourMap) preNotifyFunc=updateColourMap)
_view = props.VGroup(('enabled',
_view = props.VGroup(('displayMin', 'displayMax', 'alpha', 'rangeClip', 'cmap')) 'displayMin',
'displayMax',
'alpha',
'rangeClip',
'cmap'))
_labels = { _labels = {
'enabled' : 'Enabled',
'displayMin' : 'Min.', 'displayMin' : 'Min.',
'displayMax' : 'Max.', 'displayMax' : 'Max.',
'alpha' : 'Opacity', 'alpha' : 'Opacity',
...@@ -112,6 +123,9 @@ class ImageDisplay(props.HasProperties): ...@@ -112,6 +123,9 @@ class ImageDisplay(props.HasProperties):
class ImageList(object): class ImageList(object):
"""
Class representing a collection of images to be displayed together.
"""
def __init__(self, images=None, displays=None): def __init__(self, images=None, displays=None):
...@@ -120,3 +134,4 @@ class ImageList(object): ...@@ -120,3 +134,4 @@ class ImageList(object):
self.images = images self.images = images
self.displays = displays self.displays = displays
#!/usr/bin/env python #!/usr/bin/env python
# #
# imgshow.py - A wx/OpenGL widget for displaying and interacting with a 3D # imgshow.py - A wx/OpenGL widget for displaying and interacting with a
# image. Displays three canvases, each of which shows a slice of the image # collection of 3D image. Displays three canvases, each of which shows
# along each dimension. # a slice of the images along each dimension.
# #
# Author: Paul McCarthy <pauldmccarthy@gmail.com> # Author: Paul McCarthy <pauldmccarthy@gmail.com>
# #
...@@ -29,27 +29,21 @@ LocationEvent, EVT_LOCATION_EVENT = wxevent.NewEvent() ...@@ -29,27 +29,21 @@ LocationEvent, EVT_LOCATION_EVENT = wxevent.NewEvent()
class ImageView(wx.Panel): class ImageView(wx.Panel):
def __init__(self, parent, image, *args, **kwargs): def __init__(self, parent, imageList, *args, **kwargs):
""" """
Creates three SliceCanvas objects, each displaying a Creates three SliceCanvas objects, each displaying a
different axis of the given 3D numpy image. different axis of the given 3D numpy image.
""" """
if not isinstance(image, fslimage.Image): self.imageList = imageList
image = fslimage.Image(image)
self.imageDisplay = fslimage.ImageDisplay(image)
imageList = fslimage.ImageList([image], [self.imageDisplay])
wx.Panel.__init__(self, parent, *args, **kwargs) wx.Panel.__init__(self, parent, *args, **kwargs)
self.SetMinSize((300,100)) self.SetMinSize((300,100))
self.shape = image.data.shape self.shape = imageList.images[0].data.shape
self.canvasPanel = wx.Panel(self)
self.canvasPanel = wx.Panel(self)
self.controlPanel = props.buildGUI(self, self.imageDisplay)
self.xcanvas = slicecanvas.SliceCanvas( self.xcanvas = slicecanvas.SliceCanvas(
self.canvasPanel, imageList, zax=0) self.canvasPanel, imageList, zax=0)
self.ycanvas = slicecanvas.SliceCanvas( self.ycanvas = slicecanvas.SliceCanvas(
...@@ -57,6 +51,13 @@ class ImageView(wx.Panel): ...@@ -57,6 +51,13 @@ class ImageView(wx.Panel):
self.zcanvas = slicecanvas.SliceCanvas( self.zcanvas = slicecanvas.SliceCanvas(
self.canvasPanel, imageList, zax=2, context=self.xcanvas.context) self.canvasPanel, imageList, zax=2, context=self.xcanvas.context)
self.controlPanel = wx.Notebook(self)
for i in range(len(imageList.images)):
controlPanel = props.buildGUI(self.controlPanel, self.imageList.displays[i])
self.controlPanel.AddPage(controlPanel, '{}'.format(i))
self.mainSizer = wx.BoxSizer(wx.VERTICAL) self.mainSizer = wx.BoxSizer(wx.VERTICAL)
self.canvasSizer = wx.BoxSizer(wx.HORIZONTAL) self.canvasSizer = wx.BoxSizer(wx.HORIZONTAL)
...@@ -170,28 +171,32 @@ class ImageView(wx.Panel): ...@@ -170,28 +171,32 @@ class ImageView(wx.Panel):
class ImageFrame(wx.Frame): class ImageFrame(wx.Frame):
""" """
Convenience class for displaying an image in a standalone window. Convenience class for displaying a collection of images in a standalone
window.
""" """
def __init__(self, parent, image, title=None): def __init__(self, parent, imageList, title=None):
wx.Frame.__init__(self, parent, title=title) wx.Frame.__init__(self, parent, title=title)
self.image = image self.imageList = imageList
self.panel = ImageView(self, image) self.panel = ImageView(self, imageList)
self.Layout() self.Layout()
if __name__ == '__main__': if __name__ == '__main__':
if len(sys.argv) != 2: if len(sys.argv) < 2:
print 'usage: imageview.py filename' print 'usage: imageview.py filename [filename]'
sys.exit(1) sys.exit(1)
app = wx.App() app = wx.App()
image = fslimage.Image(sys.argv[1]) images = map(fslimage.Image, sys.argv[1:])
displays = map(fslimage.ImageDisplay, images)
imageList = fslimage.ImageList(images, displays)
frame = ImageFrame( frame = ImageFrame(
None, None,
image, imageList,
title=sys.argv[1]) title=sys.argv[1])
frame.Show() frame.Show()
......
...@@ -44,7 +44,7 @@ class GLImageData(object): ...@@ -44,7 +44,7 @@ class GLImageData(object):
slice). slice).
The third buffer, the 'image buffer' contains the image data itself, The third buffer, the 'image buffer' contains the image data itself,
scaled to lie between 0.0 and 1.0. It is used to calculate voxel colours. scaled to lie between 0 and 255. It is used to calculate voxel colours.
Finally, the texture, the 'colour buffer', is used to store a lookup table Finally, the texture, the 'colour buffer', is used to store a lookup table
containing colours. containing colours.
...@@ -375,11 +375,13 @@ class SliceCanvas(wxgl.GLCanvas): ...@@ -375,11 +375,13 @@ class SliceCanvas(wxgl.GLCanvas):
if image.glBuffer is not None: if image.glBuffer is not None:
return image.glBuffer return image.glBuffer
# The image data is cast to single precision floating # The image data is normalised to lie
# point, and normalised to lie between 0.0 and 1.0 # between 0 and 256, and cast to uint8
imageData = np.array(image.data, dtype=np.float32) imageData = np.array(image.data, dtype=np.float32)
imageData = (imageData - imageData.min()) / \ imageData = 255.0*(imageData - imageData.min()) / \
(imageData.max() - imageData.min()) (imageData.max() - imageData.min())
imageData = np.array(imageData, dtype=np.uint8)
# Then flattened, with fortran dimension ordering, # Then flattened, with fortran dimension ordering,
# so the data, as stored on the GPU, has its first # so the data, as stored on the GPU, has its first
...@@ -558,10 +560,10 @@ class SliceCanvas(wxgl.GLCanvas): ...@@ -558,10 +560,10 @@ class SliceCanvas(wxgl.GLCanvas):
gl.glVertexAttribPointer( gl.glVertexAttribPointer(
self.voxelValuePos, self.voxelValuePos,
1, 1,
gl.GL_FLOAT, gl.GL_UNSIGNED_BYTE,
gl.GL_FALSE, gl.GL_TRUE,
imageStride*4, imageStride,
imageBuffer + imageOffset*4) imageBuffer + imageOffset)
gl.glEnableVertexAttribArray(self.voxelValuePos) gl.glEnableVertexAttribArray(self.voxelValuePos)
arbia.glVertexAttribDivisorARB(self.voxelValuePos, 1) arbia.glVertexAttribDivisorARB(self.voxelValuePos, 1)
......
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