diff --git a/fsl/data/fslimage.py b/fsl/data/fslimage.py
index 76cf9cfe62cb38b3be7c1afd32e21a5355a1b294..5ba3b0054b2c4e0f63c8e1c39372cb938540e669 100644
--- a/fsl/data/fslimage.py
+++ b/fsl/data/fslimage.py
@@ -15,8 +15,9 @@ import nibabel            as nib
 import matplotlib.cm      as mplcm
 import matplotlib.colors  as mplcolors
 
-import fsl.props          as props
-import fsl.data.imagefile as imagefile
+import fsl.props            as props
+import fsl.data.imagefile   as imagefile
+import fsl.utils.notifylist as notifylist
 
 
 class Image(object):
@@ -31,7 +32,7 @@ class Image(object):
         """
 
         # The image parameter may be the name of an image file
-        if isinstance(image, str):
+        if isinstance(image, basestring):
             image = nib.load(imagefile.addExt(image))
             
         # Or a numpy array - we wrap it in a nibabel image,
@@ -147,7 +148,7 @@ class ImageDisplay(props.HasProperties):
         self.displayMax = self.dataMax
 
         
-class ImageList(object):
+class ImageList(notifylist.NotifyList):
     """
     Class representing a collection of images to be displayed together.
     Provides basic list-like functionality, and a listener interface
@@ -165,42 +166,8 @@ class ImageList(object):
         if not isinstance(images, collections.Iterable):
             raise TypeError('images must be a sequence of images')
 
-        if not all(map(lambda img: isinstance(img, Image), images)):
-            raise TypeError('images must be a sequence of 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):
-        popped = self._images.pop(index)
-        self.notify()
-        return popped
-
-        
-    def insert(self, index, image):
-        self._images.insert(index, image)
-        self.notify()
-
-
-    def extend(self, images):
-        self._images.extend(images)
-        self.notify()
-
+        def validate(img):
+            if not isinstance(img, Image):
+                raise TypeError('images must be a sequence of images')
         
-    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)
+        notifylist.NotifyList.__init__(self, images, validate)
diff --git a/fsl/data/imagefile.py b/fsl/data/imagefile.py
index 4f08f9bb84f55139c66d0d9abc312d8a401ec49b..6b7a51f2e09a36dc56e419f548e40466147b543e 100644
--- a/fsl/data/imagefile.py
+++ b/fsl/data/imagefile.py
@@ -14,11 +14,37 @@ import os.path as op
 # to any of the functions in this module.
 _allowedExts = ['.nii', '.img', '.hdr', '.nii.gz', '.img.gz']
 
+_descriptions = ['NIFTI1 images',
+                 'ANALYZE75 images',
+                 'NIFTI1/ANALYZE75 headers',
+                 'Compressed NIFTI1 images',
+                 'Compressed ANALYZE75/NIFTI1 images']
+
 
 # The default file extension (TODO read this from $FSLOUTPUTTYPE)
 _defaultExt  = '.nii.gz'
 
 
+def wildcard(allowedExts=None):
+    """
+    """
+    
+    if allowedExts is None:
+        allowedExts  = _allowedExts
+        descs        = _descriptions
+    else:
+        descs        = allowedExts
+
+
+    exts = ['*{}'.format(ext) for ext in allowedExts]
+
+    wcParts = ['|'.join((desc,ext)) for (desc,ext) in zip(descs, exts)]
+
+    print  '|'.join(wcParts)
+    return '|'.join(wcParts)
+    
+
+
 def isSupported(filename, allowedExts=None):
     """
     Returns True if the given file has a supported extension, False
diff --git a/fsl/fslview/fslview.py b/fsl/fslview/fslview.py
new file mode 100644
index 0000000000000000000000000000000000000000..cc7465dfc11c42f5c22e0740e32beb80dba605e8
--- /dev/null
+++ b/fsl/fslview/fslview.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+#
+# fslview.py -
+#
+# Author: Paul McCarthy <pauldmccarthy@gmail.com>
+#
+
+import sys
+
+import wx
+
+import fsl.fslview.slicecanvas    as slicecanvas
+import fsl.fslview.orthopanel     as orthopanel
+import fsl.fslview.imagelistpanel as imagelistpanel
+
+import fsl.data.fslimage as fslimage
+
+
+class FslViewFrame(wx.Frame):
+
+    def __init__(self, imageList, title=''):
+        
+        wx.Frame.__init__(self, None, title=title)
+        self.imageList = imageList
+
+        self.orthoPanel = orthopanel    .OrthoPanel(    self, imageList)
+        self.listPanel  = imagelistpanel.ImageListPanel(self, imageList)
+
+        self.sizer = wx.BoxSizer(wx.VERTICAL)
+        self.SetSizer(self.sizer)
+
+        self.sizer.Add(self.orthoPanel, flag=wx.EXPAND, proportion=1)
+        self.sizer.Add(self.listPanel,  flag=wx.EXPAND)
+
+        self.Layout()
+
+        
+if __name__ == '__main__':
+
+    app       = wx.App()
+    images    = map(fslimage.Image, sys.argv[1:])
+    imageList = fslimage.ImageList(images)
+    frame     = FslViewFrame(imageList)
+
+    frame.Show()
+    app.MainLoop()
diff --git a/fsl/fslview/imagelistpanel.py b/fsl/fslview/imagelistpanel.py
new file mode 100644
index 0000000000000000000000000000000000000000..4614190f72f370486efaaccc603e568b959a50a0
--- /dev/null
+++ b/fsl/fslview/imagelistpanel.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python
+#
+# imagelistpanel.py - A panel which displays an image list, and a 'console'
+# allowing the display properties of each image to be changed, and images
+# to be added/removed from the list. 
+#
+# Author: Paul McCarthy <pauldmccarthy@gmail.com>
+#
+
+import os
+
+import wx
+
+import fsl.data.fslimage  as fslimage
+import fsl.data.imagefile as imagefile
+import fsl.utils.elistbox as elistbox
+import fsl.props          as props
+
+
+class ImageListPanel(wx.Panel):
+    """
+    """
+    
+    def __init__(self, parent, imageList):
+        """
+        """
+        
+        wx.Panel.__init__(self, parent)
+        self.imageList = imageList
+
+        imageNames = [img.name for img in imageList]
+
+        # list box containing the list of images
+        self.listBox = elistbox.EditableListBox(
+            self, imageNames, imageList, style=elistbox.ELB_REVERSE)
+
+        self.listBox.Bind(elistbox.EVT_ELB_SELECT_EVENT, self._imageSelected)
+        self.listBox.Bind(elistbox.EVT_ELB_MOVE_EVENT,   self._imageMoved)
+        self.listBox.Bind(elistbox.EVT_ELB_REMOVE_EVENT, self._imageRemoved)
+        self.listBox.Bind(elistbox.EVT_ELB_ADD_EVENT,    self._addImage)
+
+        self.sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.SetSizer(self.sizer)
+
+        self.sizer.Add(self.listBox, flag=wx.EXPAND, proportion=1)
+
+        # a panel for each image, containing widgets
+        # allowing the image display properties to be
+        # changed
+        for i,image in enumerate(imageList):
+            displayPanel = self._makeDisplayPanel(image)
+            displayPanel.Show(i == 0)
+
+        self.Layout()
+
+        
+    def _makeDisplayPanel(self, image):
+        """
+        """
+            
+        displayPanel = props.buildGUI(self, image.display)
+        self.sizer.Add(displayPanel, flag=wx.EXPAND, proportion=2)
+        image.setAttribute(
+            'displayPanel_{}'.format(id(self)), displayPanel)
+        return displayPanel
+
+        
+    def _imageMoved(self, ev):
+        """
+        Called when an image name is moved in the ListBox. Reorders the
+        ImageList to reflect the change.
+        """
+
+        self.imageList.move(ev.oldIdx, ev.newIdx)
+        self.Refresh()
+
+        
+    def _imageSelected(self, ev):
+        """
+        Called when an image is selected in the ListBox. Displays the
+        corresponding image display configuration panel.
+        """
+
+        for i,image in enumerate(self.imageList):
+            
+            displayPanel = image.getAttribute(
+                'displayPanel_{}'.format(id(self)))
+            
+            displayPanel.Show(i == ev.idx)
+        self.Layout()
+
+
+    def _addImage(self, ev):
+        
+        try:    lastDir = self._lastDir
+        except: lastDir = os.getcwd()
+
+        wildcard = imagefile.wildcard()
+        
+        dlg = wx.FileDialog(self.GetParent(),
+                            message='Open image file',
+                            defaultDir=lastDir,
+                            wildcard=wildcard,
+                            style=wx.FD_OPEN)
+
+        if dlg.ShowModal() != wx.ID_OK: return
+
+        image = fslimage.Image(dlg.GetPath())
+        self.imageList.insert(0, image)
+        self._makeDisplayPanel(image)
+        self.listBox.Insert(0, image.name, image)
+
+
+    def _imageRemoved(self, ev):
+        """
+        """
+
+        image        = self.imageList.pop(ev.idx)
+        displayPanel = image.getAttribute('displayPanel_{}'.format(id(self)))
+
+        displayPanel.Destroy()
diff --git a/fsl/fslview/slicecanvas.py b/fsl/fslview/slicecanvas.py
index 576bcc72359f9bbb472e77bc7cb9abade6e213d1..0fef081d92f70c031cc3b0a2d77aff0fc4783ed7 100644
--- a/fsl/fslview/slicecanvas.py
+++ b/fsl/fslview/slicecanvas.py
@@ -298,11 +298,6 @@ class SliceCanvas(wxgl.GLCanvas):
 
             glImageData = self._initGLImageData(image)
 
-            image.setAttribute('glImageData_{}'.format(id(self)), glImageData)
-            
-            self._configDisplayListeners(image)
-            self.updateColourBuffer(image)
-
         self.glReady = True
 
 
@@ -340,6 +335,10 @@ class SliceCanvas(wxgl.GLCanvas):
         glImageData = GLImageData(
             image, imageBuffer, colourBuffer, positionBuffer, geomBuffer)
 
+        image.setAttribute('glImageData_{}'.format(id(self)), glImageData)
+        self._configDisplayListeners(image)
+        self.updateColourBuffer(image)
+
         return glImageData
 
         
@@ -350,7 +349,8 @@ class SliceCanvas(wxgl.GLCanvas):
         image buffer is used instead.
         """
 
-        imageBuffer = image.getAttribute('glBuffer')
+        try:    imageBuffer = image.getAttribute('glBuffer')
+        except: imageBuffer = None
 
         if imageBuffer is not None:
             return imageBuffer
@@ -518,7 +518,14 @@ class SliceCanvas(wxgl.GLCanvas):
 
             image       = self.imageList[i]
             glImageData = image.getAttribute(
-                'glImageData_{}'.format(id(self))) 
+                'glImageData_{}'.format(id(self)))            
+
+            # try:
+            #     glImageData = image.getAttribute(
+            #         'glImageData_{}'.format(id(self)))
+            # except:
+            #     wx.CallAfter(lambda : self._initGLImageData(image))
+            #     return
             
             imageDisplay   = image.display
             geomBuffer     = glImageData.geomBuffer
diff --git a/fsl/utils/notifylist.py b/fsl/utils/notifylist.py
new file mode 100644
index 0000000000000000000000000000000000000000..0359f1c6d4b7c99a6dc6f7abfc8a443dabcda890
--- /dev/null
+++ b/fsl/utils/notifylist.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python
+#
+# notifylist.py -
+#
+# Author: Paul McCarthy <pauldmccarthy@gmail.com>
+#
+
+import collections
+import logging
+import unittest
+
+log = logging.getLogger(__name__)
+
+class NotifyList(object):
+
+    def __init__(self, items=None, validateFunc=None):
+        
+        if items        is None: items = []
+        if validateFunc is None: validateFunc = lambda v: v
+        
+        if not isinstance(items, collections.Iterable):
+            raise TypeError('items must be a sequence')
+
+        map(validateFunc, items)
+
+        self._validate  = validateFunc
+        self._items     = items
+        self._listeners = []
+
+        
+    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)
+        self._items.append(item)
+        self._notify()
+
+        
+    def pop(self, index=-1):
+        popped = self._items.pop(index)
+        self._notify()
+        return popped
+
+        
+    def insert(self, index, item):
+        self._validate(item)
+        self._items.insert(index, item)
+        self._notify()
+
+
+    def extend(self, items):
+        map(self._validate, items)
+        self._items.extend(items)
+        self._notify()
+
+
+    def move(self, from_, to):
+        """
+        Move the item from 'from_' to 'to'. 
+        """
+
+        item = self._items.pop(from_)
+        self._items.insert(to, item)
+        self._notify()
+
+        
+    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))
+ 
+
+class TestNotifyList(unittest.TestCase):
+
+    def setUp(self):
+        self.listlen = 5
+        self.thelist = NotifyList(range(self.listlen))
+
+    def test_move(self):
+
+        for i in range(self.listlen):
+            for j in range(self.listlen):
+
+                self.setUp()
+                self.thelist.move(i, j)
+
+                demo = range(self.listlen)
+
+                val = demo.pop(i)
+                demo.insert(j, val)
+
+                print '{} -> {}: {} <-> {}'.format(i, j, self.thelist, demo)
+        
+                self.assertEqual(self.thelist, demo)