From 1a54c6c216512313a0aaad705884538cce2508db Mon Sep 17 00:00:00 2001
From: Paul McCarthy <pauld.mccarthy@gmail.com>
Date: Tue, 22 Apr 2014 13:42:51 +0100
Subject: [PATCH] ImageView supports multiple images. Pretty hacky at this
 stage, needs cleaning up. Have gone back to storing image data as uint8s.

---
 fsl/data/fslimage.py     | 25 ++++++++++++++++----
 fsl/utils/imageview.py   | 51 ++++++++++++++++++++++------------------
 fsl/utils/slicecanvas.py | 20 +++++++++-------
 3 files changed, 59 insertions(+), 37 deletions(-)

diff --git a/fsl/data/fslimage.py b/fsl/data/fslimage.py
index 3717e76a9..217903eda 100644
--- a/fsl/data/fslimage.py
+++ b/fsl/data/fslimage.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
-# fslimage.py - Classes representing a 3D image, the display
-# properties of a 3D image, and a collection of 3D images.
+# fslimage.py - Classes for representing 3D images, display
+# properties of 3D images, and collections of 3D images.
 #
 # Author: Paul McCarthy <pauldmccarthy@gmail.com>
 #
@@ -39,6 +39,7 @@ class Image(object):
         # otherwise, we assume that it is a nibabel image
         self.nibImage = image
         self.data     = image.get_data()
+        self.name     = image.get_filename()
 
         xdim,ydim,zdim = self.nibImage.get_shape()
         xlen,ylen,zlen = self.nibImage.get_header().get_zooms()
@@ -51,6 +52,10 @@ class Image(object):
         self.ylen  = ylen
         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
 
 
@@ -59,7 +64,6 @@ class ImageDisplay(props.HasProperties):
     A class which describes how an image should be displayed.
     """
 
-
     def updateColourMap(self, newVal):
         """
         When a colour property changes, this method is called -
@@ -76,6 +80,8 @@ class ImageDisplay(props.HasProperties):
             cmap.set_under(cmap(0.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)
     displayMin = props.Double()
     displayMax = props.Double()
@@ -85,9 +91,14 @@ class ImageDisplay(props.HasProperties):
     cmap       = props.ColourMap(default=mplcm.Greys_r,
                                  preNotifyFunc=updateColourMap)
     
-
-    _view   = props.VGroup(('displayMin', 'displayMax', 'alpha', 'rangeClip', 'cmap'))
+    _view   = props.VGroup(('enabled',
+                            'displayMin',
+                            'displayMax',
+                            'alpha',
+                            'rangeClip',
+                            'cmap'))
     _labels = {
+        'enabled'    : 'Enabled',
         'displayMin' : 'Min.',
         'displayMax' : 'Max.',
         'alpha'      : 'Opacity',
@@ -112,6 +123,9 @@ class ImageDisplay(props.HasProperties):
 
         
 class ImageList(object):
+    """
+    Class representing a collection of images to be displayed together.
+    """
 
     def __init__(self, images=None, displays=None):
         
@@ -120,3 +134,4 @@ class ImageList(object):
         
         self.images   = images
         self.displays = displays
+
diff --git a/fsl/utils/imageview.py b/fsl/utils/imageview.py
index d651bb597..5e75097c2 100644
--- a/fsl/utils/imageview.py
+++ b/fsl/utils/imageview.py
@@ -1,8 +1,8 @@
 #!/usr/bin/env python
 #
-# imgshow.py - A wx/OpenGL widget for displaying and interacting with a 3D
-# image. Displays three canvases, each of which shows a slice of the image
-# along each dimension.
+# imgshow.py - A wx/OpenGL widget for displaying and interacting with a
+# collection of 3D image. Displays three canvases, each of which shows
+# a slice of the images along each dimension.
 #
 # Author: Paul McCarthy <pauldmccarthy@gmail.com>
 #
@@ -29,27 +29,21 @@ LocationEvent, EVT_LOCATION_EVENT = wxevent.NewEvent()
 
 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
         different axis of the given 3D numpy image.
         """
 
-        if not isinstance(image, fslimage.Image):
-            image = fslimage.Image(image)
-
-        self.imageDisplay = fslimage.ImageDisplay(image)
-
-        imageList = fslimage.ImageList([image], [self.imageDisplay])
+        self.imageList = imageList
 
         wx.Panel.__init__(self, parent, *args, **kwargs)
         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.canvasPanel, imageList, zax=0)
         self.ycanvas = slicecanvas.SliceCanvas(
@@ -57,6 +51,13 @@ class ImageView(wx.Panel):
         self.zcanvas = slicecanvas.SliceCanvas(
             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.canvasSizer = wx.BoxSizer(wx.HORIZONTAL)
 
@@ -170,28 +171,32 @@ class ImageView(wx.Panel):
 
 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)
 
-        self.image = image
-        self.panel = ImageView(self, image)
+        self.imageList = imageList
+        self.panel     = ImageView(self, imageList)
         self.Layout()
 
 
 if __name__ == '__main__':
 
-    if len(sys.argv) != 2:
-        print 'usage: imageview.py filename'
+    if len(sys.argv) < 2:
+        print 'usage: imageview.py filename [filename]'
         sys.exit(1)
 
-    app    = wx.App()
-    image  = fslimage.Image(sys.argv[1])
+    app       = wx.App()
+    images    = map(fslimage.Image, sys.argv[1:])
+    displays  = map(fslimage.ImageDisplay, images)
+    imageList = fslimage.ImageList(images, displays)
+    
     frame  = ImageFrame(
         None,
-        image,
+        imageList,
         title=sys.argv[1])
     frame.Show()
 
diff --git a/fsl/utils/slicecanvas.py b/fsl/utils/slicecanvas.py
index 4f052fba6..30de77b4c 100644
--- a/fsl/utils/slicecanvas.py
+++ b/fsl/utils/slicecanvas.py
@@ -44,7 +44,7 @@ class GLImageData(object):
     slice).
 
     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
     containing colours.
@@ -375,11 +375,13 @@ class SliceCanvas(wxgl.GLCanvas):
         if image.glBuffer is not None:
             return image.glBuffer
 
-        # The image data is cast to single precision floating
-        # point, and normalised to lie between 0.0 and 1.0
+        # The image data is normalised to lie
+        # between 0 and 256, and cast to uint8
         imageData = np.array(image.data, dtype=np.float32)
-        imageData = (imageData       - imageData.min()) / \
-                    (imageData.max() - imageData.min())
+        imageData = 255.0*(imageData       - imageData.min()) / \
+                          (imageData.max() - imageData.min())
+        imageData = np.array(imageData, dtype=np.uint8)
+ 
 
         # Then flattened, with fortran dimension ordering,
         # so the data, as stored on the GPU, has its first
@@ -558,10 +560,10 @@ class SliceCanvas(wxgl.GLCanvas):
                 gl.glVertexAttribPointer(
                     self.voxelValuePos,
                     1,
-                    gl.GL_FLOAT,
-                    gl.GL_FALSE,
-                    imageStride*4,
-                    imageBuffer + imageOffset*4)
+                    gl.GL_UNSIGNED_BYTE,
+                    gl.GL_TRUE,
+                    imageStride,
+                    imageBuffer + imageOffset)
 
                 gl.glEnableVertexAttribArray(self.voxelValuePos)
                 arbia.glVertexAttribDivisorARB(self.voxelValuePos, 1)
-- 
GitLab