diff --git a/fsl/data/fslimage.py b/fsl/data/fslimage.py
index 9e4b846729969cd3ea6fc0a5085b9f4cb151f378..e8e0bf4f80027a3b739393099ab6081003cd295f 100644
--- a/fsl/data/fslimage.py
+++ b/fsl/data/fslimage.py
@@ -6,8 +6,9 @@
 # Author: Paul McCarthy <pauldmccarthy@gmail.com>
 #
 
-import collections
+import sys
 import logging
+import collections
 
 import os.path            as op
 
@@ -18,7 +19,6 @@ import matplotlib.colors  as mplcolors
 
 import fsl.props            as props
 import fsl.data.imagefile   as imagefile
-import fsl.utils.notifylist as notifylist
 
 
 log = logging.getLogger(__name__)
@@ -146,7 +146,7 @@ class ImageDisplay(props.HasProperties):
         self.displayMax = self.dataMax
 
         
-class ImageList(notifylist.NotifyList):
+class ImageList(object):
     """
     Class representing a collection of images to be displayed together.
     Provides basic list-like functionality, and a listener interface
@@ -154,6 +154,7 @@ class ImageList(notifylist.NotifyList):
     changes (e.g. an image is added or removed).
     """
 
+
     def __init__(self, images=None):
         """
         Create an ImageList object from the given sequence of Image objects.
@@ -164,8 +165,102 @@ class ImageList(notifylist.NotifyList):
         if not isinstance(images, collections.Iterable):
             raise TypeError('images must be a sequence of images')
 
-        def validate(img):
-            if not isinstance(img, Image):
-                raise TypeError('images must be a sequence of images')
+        map(self._validate, images)
+
+        self._items     = images
+        self._listeners = []
+
+        self._updateImageAttributes()
+
+        
+    def _updateImageAttributes(self):
+        """
+        Called whenever an item is added or removed from the list.
+        Updates the xyz bounds.
+        """
+
+        # TODO support negative space
+
+        maxBounds = 3 * [-sys.float_info.max]
+        minBounds = [0.0, 0.0, 0.0]
+        
+        for img in self._items:
+
+            if img.shape[0] > maxBounds[0]: maxBounds[0] = img.shape[0]
+            if img.shape[1] > maxBounds[1]: maxBounds[1] = img.shape[1]
+            if img.shape[2] > maxBounds[2]: maxBounds[2] = img.shape[2] 
+
+        self.minBounds = minBounds
+        self.maxBounds = maxBounds
+
+        
+    def _validate(self, img):
+        """
+        Called whenever an item is added to the list. Raises
+        a TypeError if said item is not an Image object.
+        """
+        if not isinstance(img, Image):
+            raise TypeError('item must be a fsl.data.fslimage.Image') 
+
+            
+    def __len__     (self):        return self._items.__len__()
+    def __getitem__ (self, key):   return self._items.__getitem__(key)
+    def __iter__    (self):        return self._items.__iter__()
+    def __contains__(self, item):  return self._items.__contains__(item)
+    def __eq__      (self, other): return self._items.__eq__(other)
+    def __str__     (self):        return self._items.__str__()
+    def __repr__    (self):        return self._items.__repr__()
+
+ 
+    def append(self, item):
+        self._validate(item)
+        log.debug('Item appended: {}'.format(item))
+        self._items.append(item)
+        self._updateImageAttributes()
+        self._notify()
+
+        
+    def pop(self, index=-1):
+        item = self._items.pop(index)
+        log.debug('Item popped: {} (index {})'.format(item, index))
+        self._updateImageAttributes()
+        self._notify()
+        return item
+
+        
+    def insert(self, index, item):
+        self._validate(item)
+        self._items.insert(index, item)
+        log.debug('Item inserted: {} (index {})'.format(item, index))
+        self._updateImageAttributes()
+        self._notify()
+
+
+    def extend(self, items):
+        map(self._validate, items)
+        self._items.extend(items)
+        log.debug('List extended: {}'.format(', '.join([str(i) for i in item])))
+        self._updateImageAttributes()
+        self._notify()
+
+
+    def move(self, from_, to):
+        """
+        Move the item from 'from_' to 'to'. 
+        """
+
+        item = self._items.pop(from_)
+        self._items.insert(to, item)
+        log.debug('Image moved: {} (from: {} to: {})'.format(item, from_, to))
+        self._notify()
+
         
-        notifylist.NotifyList.__init__(self, images, validate)
+    def addListener   (self, listener): self._listeners.append(listener)
+    def removeListener(self, listener): self._listeners.remove(listener)
+    def _notify       (self):
+        for listener in self._listeners:
+            try:
+                listener(self)
+            except e:
+                log.debug('Listener raised exception: {}'.format(e.message))
+ 
diff --git a/fsl/fslview/orthopanel.py b/fsl/fslview/orthopanel.py
index 0a06e317fce85e82c5fcbbc48971a33e1543ec05..89158a42690316b1ebebf52c9f8b3202e7694c5c 100644
--- a/fsl/fslview/orthopanel.py
+++ b/fsl/fslview/orthopanel.py
@@ -48,8 +48,6 @@ class OrthoPanel(wx.Panel):
         wx.Panel.__init__(self, parent)
         self.SetMinSize((300,100))
 
-        self.shape = imageList[0].data.shape
-
         self.xcanvas = slicecanvas.SliceCanvas(self, imageList, zax=0)
         self.ycanvas = slicecanvas.SliceCanvas(self, imageList, zax=1,
                                                context=self.xcanvas.context)
@@ -127,33 +125,12 @@ class OrthoPanel(wx.Panel):
         y = self.ycanvas.zpos
         z = self.zcanvas.zpos
 
-        if source == self.xcanvas:
-
-            mx = mx * self.shape[1] / float(w)
-            my = my * self.shape[2] / float(h)
-            y,z = mx,my
-
-        elif source == self.ycanvas:
-            mx = mx * self.shape[0] / float(w)
-            my = my * self.shape[2] / float(h)
-            x,z = mx,my
-
-        elif source == self.zcanvas:
-            mx = mx * self.shape[0] / float(w)
-            my = my * self.shape[1] / float(h)
-            x,y = mx,my
-
-        x = int(x)
-        y = int(y)
-        z = int(z)
-
-        if x < 0: x = 0
-        if y < 0: y = 0
-        if z < 0: z = 0
+        mx = mx * (source.xmax - source.xmin) / float(w)
+        my = my * (source.ymax - source.ymin) / float(h)
 
-        if x >= self.shape[0]: x = self.shape[0]-1
-        if y >= self.shape[1]: y = self.shape[1]-1
-        if z >= self.shape[2]: z = self.shape[2]-1 
+        if   source == self.xcanvas: y,z = mx,my
+        elif source == self.ycanvas: x,z = mx,my
+        elif source == self.zcanvas: x,y = mx,my
 
         self.setLocation(x,y,z)
 
diff --git a/fsl/fslview/slicecanvas.py b/fsl/fslview/slicecanvas.py
index bdda0dada2811e867defef5011de427cd41bdf1d..f3142eeee2d2c7baf911daf75b905a2df000adaf 100644
--- a/fsl/fslview/slicecanvas.py
+++ b/fsl/fslview/slicecanvas.py
@@ -6,7 +6,7 @@
 # Author: Paul McCarthy <pauldmccarthy@gmail.com>
 #
 
-import itertools         as it
+import sys
 
 import numpy             as np
 import matplotlib.colors as mplcolors
@@ -78,6 +78,8 @@ class GLImageData(object):
         self.ylen = image.pixdim[canvas.yax]
         self.zlen = image.pixdim[canvas.zax]
 
+        # TODO origin
+
         dsize = image.data.dtype.itemsize
 
         self.xstride = image.data.strides[canvas.xax] / dsize
@@ -339,10 +341,8 @@ class SliceCanvas(wxgl.GLCanvas):
         Refresh() after changing the zpos.
         """
 
-        zpos = int(round(zpos))
-
-#        if   zpos >= self.zdim: zpos = self.zdim - 1
-#        elif zpos <  0:         zpos = 0
+        if   zpos > self.zmax: zpos = self.zmax
+        elif zpos < self.zmin: zpos = self.zmin
 
         self._zpos = zpos
 
@@ -360,10 +360,8 @@ class SliceCanvas(wxgl.GLCanvas):
         Refresh() after changing the xpos.
         """ 
 
-        xpos = int(round(xpos))
-
-#        if   xpos >= self.xdim: xpos = self.xdim - 1
-#        elif xpos <  0:         xpos = 0
+        if   xpos > self.xmax: xpos = self.xmax
+        elif xpos < self.xmin: xpos = self.xmin 
 
         self._xpos = xpos
 
@@ -380,20 +378,18 @@ class SliceCanvas(wxgl.GLCanvas):
         Change the y cursor position. You will need to manually call
         Refresh() after changing the ypos.
         """ 
-
-        ypos = int(round(ypos))
-
-#        if   ypos >= self.ydim: ypos = self.ydim - 1
-#        elif ypos <  0:         ypos = 0
+        
+        if   ypos > self.ymax: ypos = self.ymax
+        elif ypos < self.ymin: ypos = self.ymin 
 
         self._ypos = ypos
 
 
-    def __init__(
-            self, parent, imageList, zax=0, zpos=None, context=None, **kwargs):
+    def __init__(self, parent, imageList, zax=0, context=None):
         """
-        Creates a canvas object. The OpenGL data buffers are set up in
-        _initGLData the first time that the canvas is displayed/drawn.
+        Creates a canvas object. The OpenGL data buffers are set up the
+        first time that the canvas is displayed/drawn.
+        
         Parameters:
         
           parent    - WX parent object
@@ -404,57 +400,109 @@ class SliceCanvas(wxgl.GLCanvas):
                       (the 'depth' axis), default 0.
 
           context   - wx.glcanvas.GLContext object. If None, one is created.
-        
-          zpos      - Initial slice to be displayed. If not provided, the
-                      middle slice is used.
         """
 
-        wxgl.GLCanvas.__init__(self, parent, **kwargs)
+        if not isinstance(imageList, fslimage.ImageList):
+            raise TypeError(
+                'imageList must be a fsl.data.fslimage.ImageList instance') 
 
-        self.name = 'SliceCanvas_{}'.format(id(self))
+        wxgl.GLCanvas.__init__(self, parent)
 
         # 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 not isinstance(imageList, fslimage.ImageList):
-            raise TypeError(
-                'imageList must be a fsl.data.fslimage.ImageList instance') 
+        self.imageList = imageList
+        self.name      = 'SliceCanvas_{}'.format(id(self))
 
+        # These attributes map from the image axes to
+        # the display axes. xax is horizontal, yax
+        # is vertical, and zax is depth.
+        #
         # TODO Currently, the displayed x/horizontal and
         # y/vertical axes are defined by their order in
-        # the image. Allow the caller to specify which
-        # axes should be horizontal/vertical.
+        # the image. We could allow the caller to specify
+        # which axes should be horizontal/vertical.
         dims = range(3)
         dims.pop(zax)
-
-        self.imageList = imageList
-        self.xax       = dims[0]
-        self.yax       = dims[1]
-        self.zax       = zax
-        # This flag is set by the _initGLData method when it
-        # has finished initialising the OpenGL data buffers
+        self.xax = dims[0]
+        self.yax = dims[1]
+        self.zax = zax
+
+        # These attributes define the current location
+        # of the cursor, and the displayed slice. They
+        # are initialised in the _imageListChanged
+        # method.
+        self._xpos = None
+        self._ypos = None
+        self._zpos = None 
+
+        # These attributes define the spatial data
+        # limits of all displayed images. They are
+        # set by the _imageListChanged method, and
+        # updated whenever an image is added/removed
+        # from the list.
+        self.xmin = None
+        self.xmax = None
+        self.ymin = None
+        self.ymax = None
+        self.zmin = None
+        self.zmax = None
+        
+        # This flag is set by the _initGLData method
+        # when it has finished initialising the OpenGL
+        # shaders
         self.glReady = False
 
         # All the work is done by the draw method
         self.Bind(wx.EVT_PAINT, self.draw)
 
-        # TODO Fix these numbers
-        self._xpos = 200
-        self._ypos = 200
-        self._zpos = 200
+        # When the image list changes, refresh the
+        # display, and update the display bounds
+        self.imageList.addListener(lambda il: self._imageListChanged())
 
-        # When the image list changes, refresh the display
-        #
-        # TODO When image list changes, update local attributes
-        # xdim and ydim, so we know how big to set the viewport
-        # in the resize method
-        #
-        self.imageList.addListener(lambda il: self.Refresh())
 
+    def _imageListChanged(self):
+        """
+        This method is called once by _initGLData on the first draw, and
+        then again every time an image is added or removed from the
+        image list. For newly added images, it creates a GLImageData
+        object, which initialises the OpenGL data necessary to render
+        the image. This method also updates the canvas bounds (i.e.
+        the min/max x/y/z coordinates across all images being displayed).
+        """
+
+        # Create a GLImageData object
+        # for any new images
+        for image in self.imageList:
+            try:
+                glData = image.getAttribute(self.name)
+            except: 
+                glData = GLImageData(image, self)
+                image.setAttribute(self.name, glData)
+
+        # Update the minimum/maximum
+        # image bounds along each axis
+        self.xmin = self.imageList.minBounds[self.xax]
+        self.ymin = self.imageList.minBounds[self.yax]
+        self.zmin = self.imageList.minBounds[self.zax]
+
+        self.xmax = self.imageList.maxBounds[self.xax]
+        self.ymax = self.imageList.maxBounds[self.yax]
+        self.zmax = self.imageList.maxBounds[self.zax]
+        
+        # initialise the cursor location and displayed
+        # slice if they do not yet have values
+        if not all((self._xpos, self._ypos, self._zpos)):
+            self.xpos = (self.xmax - self.xmin) / 2
+            self.ypos = (self.ymax - self.ymin) / 2
+            self.zpos = (self.zmax - self.zmin) / 2
 
-    def _initGLShaders(self):
+        self.Refresh()
+
+
+    def _initGLData(self):
         """
         Compiles the vertex and fragment shader programs, and
         stores references to the shader variables as attributes
@@ -464,13 +512,13 @@ class SliceCanvas(wxgl.GLCanvas):
 
         # A bit hacky. We can only set the GL context (and create
         # the GL data) once something is actually displayed on the
-        # screen. The _initGLShaders method is called (asynchronously)
+        # screen. The _initGLData method is called (asynchronously)
         # by the draw() method if it sees that the glReady flag has
         # not yet been set. But draw() may be called mored than once
-        # before _initGLShaders is called. Here, to prevent
-        # _initGLShaders from running more than once, the first time
+        # before _initGLData is called. Here, to prevent
+        # _initGLData from running more than once, the first time
         # it is called it simply overwrites itself with a dummy method.
-        self._initGLShaders = lambda s: s
+        self._initGLData = lambda s: s
  
         self.context.SetCurrent(self)
 
@@ -485,6 +533,10 @@ class SliceCanvas(wxgl.GLCanvas):
         self.alphaPos      = gl.glGetUniformLocation(self.shaders, 'alpha')
         self.colourMapPos  = gl.glGetUniformLocation(self.shaders, 'colourMap')
 
+        # Initialise data for the images that
+        # are already in the image list 
+        self._imageListChanged()
+
         self.glReady = True
 
         
@@ -501,8 +553,7 @@ class SliceCanvas(wxgl.GLCanvas):
         gl.glViewport(0, 0, size.width, size.height)
         gl.glMatrixMode(gl.GL_PROJECTION)
         gl.glLoadIdentity()
-        # TODO fix these numbers (see notes in __init__)
-        gl.glOrtho(0, 450, 0, 450, 0, 1)
+        gl.glOrtho(self.xmin, self.xmax, self.ymin, self.ymax, 0, 1)
         gl.glMatrixMode(gl.GL_MODELVIEW)
         gl.glLoadIdentity()
 
@@ -514,7 +565,7 @@ class SliceCanvas(wxgl.GLCanvas):
 
         # image data has not been initialised.
         if not self.glReady:
-            wx.CallAfter(self._initGLShaders)
+            wx.CallAfter(self._initGLData)
             return
 
         self.context.SetCurrent(self)
@@ -529,15 +580,15 @@ 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.imageList)):
-
-            image = self.imageList[i]
+        for image in self.imageList:
 
-            try:
-                glImageData = image.getAttribute(self.name)
-            except:
-                glImageData = GLImageData(image, self)
-                image.setAttribute(glImageData, self.name)
+            # The GL data is stored as an attribute of the image,
+            # and is created in the _imageListChanged method when
+            # images are added to the image. If there's no data
+            # here, ignore it; hopefully by the time draw() is
+            # called again, it will have been created.
+            try:    glImageData = image.getAttribute(self.name)
+            except: continue
             
             imageDisplay   = image.display
             
@@ -551,9 +602,16 @@ class SliceCanvas(wxgl.GLCanvas):
             zdim    = glImageData.zdim
             xstride = glImageData.xstride
             ystride = glImageData.ystride
-            zstride = glImageData.zstride 
+            zstride = glImageData.zstride
+
+            # Figure out which slice we are drawing
+            # TODO origin and scaling by zlen
+            zi = int(round(self.zpos)) 
 
             if not imageDisplay.enabled:
+                continue 
+
+            if zi < 0 or zi >= zdim:
                 continue
 
             # Set up the colour buffer
@@ -575,7 +633,8 @@ class SliceCanvas(wxgl.GLCanvas):
             # drawing columns, for reasons unknown to me.
             for yi in range(ydim):
 
-                imageOffset = self.zpos * zstride + yi * ystride
+                # TODO zpos is not necessarily in image coords
+                imageOffset = zi * zstride + yi * ystride
                 imageStride = xstride 
                 posOffset   = yi * xdim * 4
 
@@ -631,17 +690,15 @@ class SliceCanvas(wxgl.GLCanvas):
         gl.glUseProgram(0)
 
         # A vertical line at xpos, and a horizontal line at ypos
-        x = self.xpos + 0.5
-        y = self.ypos + 0.5
+        x = self.xpos
+        y = self.ypos
 
-        # TODO Fix these numbers (see __init__ notes)
-        
         gl.glBegin(gl.GL_LINES)
         gl.glColor3f(0, 1, 0)
-        gl.glVertex2f(x,         0)
-        gl.glVertex2f(x,         450)
-        gl.glVertex2f(0,         y)
-        gl.glVertex2f(450, y)
+        gl.glVertex2f(x,         self.ymin)
+        gl.glVertex2f(x,         self.ymax)
+        gl.glVertex2f(self.xmin, y)
+        gl.glVertex2f(self.xmax, y)
         gl.glEnd()
 
         self.SwapBuffers()