diff --git a/fsl/data/fslimage.py b/fsl/data/fslimage.py
index 217903edac89ae7df300aa8b1757c48005ea1f8e..9c24bc971330a52a7bf80b86cb5520b7f4e4dee7 100644
--- a/fsl/data/fslimage.py
+++ b/fsl/data/fslimage.py
@@ -6,6 +6,8 @@
 # Author: Paul McCarthy <pauldmccarthy@gmail.com>
 #
 
+import os.path            as op
+
 import numpy              as np
 import nibabel            as nib
 import matplotlib.cm      as mplcm
@@ -39,7 +41,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()
+        self.name     = op.basename(image.get_filename())
 
         xdim,ydim,zdim = self.nibImage.get_shape()
         xlen,ylen,zlen = self.nibImage.get_header().get_zooms()
@@ -52,6 +54,10 @@ class Image(object):
         self.ylen  = ylen
         self.zlen  = zlen
 
+        # ImageDisplay instance used to describe
+        # how this image is to be displayed
+        self.display = ImageDisplay(self)
+
         # 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
@@ -61,7 +67,9 @@ class Image(object):
 
 class ImageDisplay(props.HasProperties):
     """
-    A class which describes how an image should be displayed.
+    A class which describes how an image should be displayed. There should
+    be no need to manually instantiate ImageDisplay objects - one is created
+    for each Image object, and is accessed via the Image.display attribute.
     """
 
     def updateColourMap(self, newVal):
@@ -81,7 +89,7 @@ class ImageDisplay(props.HasProperties):
             cmap.set_over( cmap(1.0), alpha=1.0) 
 
     # The display properties of an image
-    enabled    = props.Boolean()
+    enabled    = props.Boolean(default=True)
     alpha      = props.Double(minval=0.0, maxval=1.0, default=1.0)
     displayMin = props.Double()
     displayMax = props.Double()
@@ -125,13 +133,51 @@ class ImageDisplay(props.HasProperties):
 class ImageList(object):
     """
     Class representing a collection of images to be displayed together.
+    Provides basic list-like functionality, and a listener interface
+    allowing callback functions to be notified when the image collection
+    changes (e.g. an image is added or removed).
     """
 
-    def __init__(self, images=None, displays=None):
         
-        if images   is None: images   = []
-        if displays is None: displays = []
+    def __init__(self, images=None):
+        """
+        """
+        
+        if images is None: images = []
+
+        self._images    = images
+        self._listeners = []
+
+
+    def __len__     (self):        return self._images.__len__()
+    def __getitem__ (self, key):   return self._images.__getitem__(key)
+    def __iter__    (self):        return self._images.__iter__()
+    def __contains__(self, image): return self._images.__contains__(image)
+
+    
+    def append(self, image): 
+        self._images.append(image)
+        self.notify()
+
+        
+    def pop(self, index=-1):
+        self._images.pop(index)
+        self.notify()
+
+        
+    def insert(self, index, image):
+        self._images.insert(index, image)
+        self.notify()
+
+
+    def extend(self, images):
+        self._images.extend(images)
+        self.notify()
+
         
-        self.images   = images
-        self.displays = displays
+    def addListener   (self, listener): self._listeners.append(listener)
+    def removeListener(self, listener): self._listeners.remove(listener)
+    def notify        (self):
+        for listener in self._listeners:
+            listener(self)
 
diff --git a/fsl/props/widgets.py b/fsl/props/widgets.py
index 349bd768489a4b14fec8f584ccce7a8e5cb76b9b..a37ccd7f09288e7aea68627ccbdf00890878ceb0 100644
--- a/fsl/props/widgets.py
+++ b/fsl/props/widgets.py
@@ -315,6 +315,7 @@ def _Number(parent, hasProps, propObj, propVal):
     params['min']     = minval
     params['max']     = maxval
     params['initial'] = value
+    params['value']   = '{}'.format(value)
 
     # The minval and maxval attributes have not both
     # been set, so we create a spinbox instead of a slider.
diff --git a/fsl/utils/imageview.py b/fsl/utils/imageview.py
index 5e75097c264dfc8bad2661d138ecdabb44912e9c..4e5ccd459461bee6e5aacdcaa3c58a74262a2bef 100644
--- a/fsl/utils/imageview.py
+++ b/fsl/utils/imageview.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # imgshow.py - A wx/OpenGL widget for displaying and interacting with a
-# collection of 3D image. Displays three canvases, each of which shows
+# collection of 3D images. Displays three canvases, each of which shows
 # a slice of the images along each dimension.
 #
 # Author: Paul McCarthy <pauldmccarthy@gmail.com>
@@ -9,14 +9,15 @@
 
 import sys
 
-# import logging
-# logging.basicConfig(
-#     format='%(levelname)8s '\
-#            '%(filename)20s '\
-#            '%(lineno)4d: '\
-#            '%(funcName)s - '\
-#            '%(message)s',
-#     level=logging.DEBUG)
+if False:
+    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
@@ -32,15 +33,22 @@ class ImageView(wx.Panel):
     def __init__(self, parent, imageList, *args, **kwargs):
         """
         Creates three SliceCanvas objects, each displaying a
-        different axis of the given 3D numpy image.
+        different axis of the given image list.
         """
+        
+        if isinstance(imageList, fslimage.Image):
+            imageList = fslimage.ImageList(imageList)
+
+        if not isinstance(imageList, fslimage.ImageList):
+            raise TypeError(
+                'imageList must be a fsl.data.fslimage.ImageList instance')
 
         self.imageList = imageList
 
         wx.Panel.__init__(self, parent, *args, **kwargs)
         self.SetMinSize((300,100))
 
-        self.shape = imageList.images[0].data.shape
+        self.shape = imageList[0].data.shape
 
         self.canvasPanel = wx.Panel(self)
 
@@ -51,12 +59,12 @@ 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)):
+        
+        for image in imageList:
 
-            controlPanel = props.buildGUI(self.controlPanel, self.imageList.displays[i])
-            self.controlPanel.AddPage(controlPanel, '{}'.format(i))
+            displayProps = props.buildGUI(self.controlPanel, image.display)
+            self.controlPanel.AddPage(displayProps, image.name)
 
         self.mainSizer   = wx.BoxSizer(wx.VERTICAL)
         self.canvasSizer = wx.BoxSizer(wx.HORIZONTAL)
@@ -178,8 +186,7 @@ class ImageFrame(wx.Frame):
     def __init__(self, parent, imageList, title=None):
         wx.Frame.__init__(self, parent, title=title)
 
-        self.imageList = imageList
-        self.panel     = ImageView(self, imageList)
+        self.panel = ImageView(self, imageList)
         self.Layout()
 
 
@@ -191,8 +198,7 @@ if __name__ == '__main__':
 
     app       = wx.App()
     images    = map(fslimage.Image, sys.argv[1:])
-    displays  = map(fslimage.ImageDisplay, images)
-    imageList = fslimage.ImageList(images, displays)
+    imageList = fslimage.ImageList(images)
     
     frame  = ImageFrame(
         None,
diff --git a/fsl/utils/slicecanvas.py b/fsl/utils/slicecanvas.py
index 30de77b4ccd625c3b51272b4ea2699e7e5a99c71..5216b48f032910a2fea92c3a2f43e65c5eef6f80 100644
--- a/fsl/utils/slicecanvas.py
+++ b/fsl/utils/slicecanvas.py
@@ -211,9 +211,20 @@ class SliceCanvas(wxgl.GLCanvas):
 
         wxgl.GLCanvas.__init__(self, parent, **kwargs)
 
+        # Use the provided shared GL
+        # context, or create a new one
         if context is None: self.context = wxgl.GLContext(self)
         else:               self.context = context
 
+        # If a single image has been provided,
+        # make it into a list of length 1
+        if isinstance(imageList, fslimage.Image):
+            imageList = fslimage.ImageList(imageList)
+
+        if not isinstance(imageList, fslimage.ImageList):
+            raise TypeError(
+                'imageList must be a fsl.data.fslimage.ImageList instance') 
+
         # TODO Currently, the displayed x/horizontal and
         # y/vertical axes are defined by their order in
         # the image. Allow the caller to specify which
@@ -227,7 +238,7 @@ class SliceCanvas(wxgl.GLCanvas):
         self.zax       = zax
 
         # Currently all images must be of the same dimensions
-        ref = self.imageList.images[0]
+        ref = self.imageList[0]
 
         self.xdim = ref.data.shape[self.xax]
         self.ydim = ref.data.shape[self.yax]
@@ -248,55 +259,19 @@ class SliceCanvas(wxgl.GLCanvas):
 
         self._colourResolution = 256
 
-        # This flag is set by the _initGLData method when it
+        # These fields are set by the _initGLData method when it
         # has finished initialising the OpenGL data buffers
-        self.glReady = False
+        self.glReady     = False
+        self.glImageData = None
 
         self.Bind(wx.EVT_PAINT, self.draw)
 
-        for i in range(len(self.imageList.images)):
-            self._configDisplayListeners(i)
-
-
-    def _configDisplayListeners(self, idx):
-        """
-        """
-
-        image   = self.imageList.images  [idx]
-        display = self.imageList.displays[idx]
-
-        # Add a bunch of listeners to the image display
-        # object for each image, so we can update the
-        # view when image display properties are changed.
-        def refreshNeeded(*a):
-            self.Refresh()
-            
-        def colourUpdateNeeded(*a):
-            self.updateColourBuffer(idx)
-            self.Refresh()
-
-        lnrName = 'SliceCanvas_{{}}_{}'.format(id(self))
-
-        display.addListener(
-            'alpha', lnrName.format('alpha'), refreshNeeded)
-
-        display.addListener(
-            'displayMin', lnrName.format('displayMin'), colourUpdateNeeded)
-
-        display.addListener(
-            'displayMax', lnrName.format('displayMax'), colourUpdateNeeded)
-
-        display.addListener(
-            'rangeClip', lnrName.format('rangeClip'), colourUpdateNeeded)
-
-        display.addListener(
-            'cmap', lnrName.format('cmap'), colourUpdateNeeded) 
-
 
     def _initGLData(self):
         """
         Initialises the GL buffers which are copied to the GPU,
-        and used to render the voxel data.
+        and used to render the voxel data. This method is only
+        called once, on the first draw.
         """
 
         # A bit hacky. We can only set the GL context (and create
@@ -322,54 +297,65 @@ class SliceCanvas(wxgl.GLCanvas):
         self.alphaPos      = gl.glGetUniformLocation(self.shaders, 'alpha')
         self.colourMapPos  = gl.glGetUniformLocation(self.shaders, 'colourMap')
 
-        self.imageData = []
+        self.glImageData = []
         
-        for idx,image in enumerate(self.imageList.images):
+        for idx in range(len(self.imageList)):
+            self.glImageData.append(self._initGLImageData(idx))
+            self._configDisplayListeners(idx)
+            self.updateColourBuffer(idx)
 
-            # Data stored in the geometry buffer. Defines
-            # the geometry of a single voxel, rendered as
-            # a triangle strip.
-            geomData = np.array([0, 0,
-                                 1, 0,
-                                 0, 1,
-                                 1, 1], dtype=np.uint8)
+        self.glReady = True
 
-            # Data stored in the position buffer. Defines
-            # the location of every voxel in a single slice.
-            positionData = np.zeros((self.xdim*self.ydim, 2), dtype=np.uint16)
-            yidxs,xidxs  = np.meshgrid(np.arange(self.ydim),
-                                       np.arange(self.xdim),
-                                       indexing='ij')
-            positionData[:,0] = xidxs.ravel()
-            positionData[:,1] = yidxs.ravel()
 
-            geomBuffer     = vbo.VBO(geomData,     gl.GL_STATIC_DRAW)
-            positionBuffer = vbo.VBO(positionData, gl.GL_STATIC_DRAW)
+    def _initGLImageData(self, idx):
+        """
+        """
 
-            # The image buffer, containing the image data itself
-            imageBuffer = self._initImageBuffer(image)
+        image = self.imageList[idx]
+        
+        # Data stored in the geometry buffer. Defines
+        # the geometry of a single voxel, rendered as
+        # a triangle strip.
+        geomData = np.array([0, 0,
+                             1, 0,
+                             0, 1,
+                             1, 1], dtype=np.uint8)
 
-            # The colour buffer, containing a map of
-            # colours (stored on the GPU as a 1D texture)
-            colourBuffer = gl.glGenTextures(1)
+        # Data stored in the position buffer. Defines
+        # the location of every voxel in a single slice.
+        positionData = np.zeros((self.xdim*self.ydim, 2), dtype=np.uint16)
+        yidxs,xidxs  = np.meshgrid(np.arange(self.ydim),
+                                   np.arange(self.xdim),
+                                   indexing='ij')
+        positionData[:,0] = xidxs.ravel()
+        positionData[:,1] = yidxs.ravel()
 
-            imageData = GLImageData(
-                image, imageBuffer, colourBuffer, positionBuffer, geomBuffer)
+        geomBuffer     = vbo.VBO(geomData,     gl.GL_STATIC_DRAW)
+        positionBuffer = vbo.VBO(positionData, gl.GL_STATIC_DRAW)
 
-            self.imageData.append(imageData)
+        # The image buffer, containing the image data itself
+        imageBuffer = self._initImageBuffer(idx)
 
-            self.updateColourBuffer(idx)
+        # The colour buffer, containing a map of
+        # colours (stored on the GPU as a 1D texture)
+        colourBuffer = gl.glGenTextures(1)
+
+        glImageData = GLImageData(
+            image, imageBuffer, colourBuffer, positionBuffer, geomBuffer)
 
-        self.glReady = True
+
+        return glImageData
 
         
-    def _initImageBuffer(self, image):
+    def _initImageBuffer(self, idx):
         """
         Initialises the buffer used to store the image data. If a 'master'
         canvas was set when this SliceCanvas object was constructed, its
         image buffer is used instead.
         """
 
+        image = self.imageList[idx]
+
         # If a master canvas was passed to the
         # constructor, let's share its image data.
         if image.glBuffer is not None:
@@ -394,15 +380,52 @@ class SliceCanvas(wxgl.GLCanvas):
         return imageBuffer
 
 
+    def _configDisplayListeners(self, idx):
+        """
+        """
+
+        image   = self.imageList[idx]
+        display = image.display
+
+        # Add a bunch of listeners to the image display
+        # object for each image, so we can update the
+        # view when image display properties are changed.
+        def refreshNeeded(*a):
+            self.Refresh()
+            
+        def colourUpdateNeeded(*a):
+            self.updateColourBuffer(idx)
+            self.Refresh()
+
+        lnrName = 'SliceCanvas_{{}}_{}'.format(id(self))
+
+        display.addListener(
+            'alpha', lnrName.format('alpha'), refreshNeeded)
+        display.addListener(
+            'enabled', lnrName.format('enabled'), refreshNeeded) 
+
+        display.addListener(
+            'displayMin', lnrName.format('displayMin'), colourUpdateNeeded)
+
+        display.addListener(
+            'displayMax', lnrName.format('displayMax'), colourUpdateNeeded)
+
+        display.addListener(
+            'rangeClip', lnrName.format('rangeClip'), colourUpdateNeeded)
+
+        display.addListener(
+            'cmap', lnrName.format('cmap'), colourUpdateNeeded) 
+
+
     def updateColourBuffer(self, idx):
         """
-        Regenerates the colour buffer used to colour a slice. After
-        calling this method, you will need to call Refresh() for the
-        change to take effect.
+        Regenerates the colour buffer used to colour a slice of the
+        specified image. After calling this method, you will need to
+        call Refresh() for the change to take effect.
         """
 
-        iDisplay     = self.imageList.displays[idx]
-        colourBuffer = self.imageData[idx].colourBuffer
+        iDisplay     = self.imageList  [idx].display
+        colourBuffer = self.glImageData[idx].colourBuffer
 
         # Here we are creating a range of values to be passed
         # to the matplotlib.colors.Colormap instance of the
@@ -496,18 +519,21 @@ class SliceCanvas(wxgl.GLCanvas):
         gl.glEnable(gl.GL_BLEND)
         gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
 
-        for i in range(len(self.imageData)):
+        for i in range(len(self.glImageData)):
+
+            image          = self.imageList[i]
+            imageDisplay   = image.display
+            geomBuffer     = self.glImageData[i].geomBuffer
+            imageBuffer    = self.glImageData[i].imageBuffer
+            positionBuffer = self.glImageData[i].positionBuffer
+            colourBuffer   = self.glImageData[i].colourBuffer
 
-            image          = self.imageList.images[i]
-            imageDisplay   = self.imageList.displays[i]
-            geomBuffer     = self.imageData[i].geomBuffer
-            imageBuffer    = self.imageData[i].imageBuffer
-            positionBuffer = self.imageData[i].positionBuffer
-            colourBuffer   = self.imageData[i].colourBuffer
+            if not imageDisplay.enabled:
+                continue
 
             # Set up the colour buffer
             gl.glEnable(gl.GL_TEXTURE_1D)
-            gl.glActiveTexture(gl.GL_TEXTURE0)
+            gl.glActiveTexture(gl.GL_TEXTURE0) 
             gl.glBindTexture(gl.GL_TEXTURE_1D, colourBuffer)
             gl.glUniform1i(self.colourMapPos, 0)