diff --git a/fsl/data/strings.py b/fsl/data/strings.py
index 4e0d63e118577e3ef3a00ff5b5a5abf6d7767ec0..dac954427aa157fa06bb2e2f5ea237991597e2f2 100644
--- a/fsl/data/strings.py
+++ b/fsl/data/strings.py
@@ -78,7 +78,12 @@ messages = TypeDict({
 
     'NewLutDialog.newLut' : 'Enter a name for the new LUT',
 
-    'ClusterPanel.notFEAT'  : 'Choose a FEAT overlay', 
+    'ClusterPanel.noOverlays'  : 'Add a FEAT overlay',
+    'ClusterPanel.notFEAT'     : 'Choose a FEAT overlay',
+    'ClusterPanel.noClusters'  : 'No cluster results exist '
+                                 'in this FEAT analysis',
+    'ClusterPanel.badData'     : 'Cluster data could not be parsed - '
+                                 'check your cluster_*.txt files.', 
     
 })
 
@@ -234,6 +239,20 @@ labels = TypeDict({
     'FEATReducedTimeSeries.pe'   : 'Reduced against PE{}',
 
     'FEATResidualTimeSeries'     : 'Residuals',
+
+    'ClusterPanel.clustName'     : 'Z statistics for COPE {} ({})',
+    
+    'ClusterPanel.index'         : 'Cluster index',
+    'ClusterPanel.nvoxels'       : 'Size (voxels)',
+    'ClusterPanel.p'             : 'P',
+    'ClusterPanel.logp'          : '-log10(P)',
+    'ClusterPanel.zmax'          : 'Z Max',
+    'ClusterPanel.zmaxcoords'    : 'Z Max location',
+    'ClusterPanel.zcogcoords'    : 'Z Max COG location',
+    'ClusterPanel.copemax'       : 'COPE Max',
+    'ClusterPanel.copemaxcoords' : 'COPE Max location',
+    'ClusterPanel.copemean'      : 'COPE mean', 
+    
 })
 
 
diff --git a/fsl/fslview/controls/clusterpanel.py b/fsl/fslview/controls/clusterpanel.py
index bfc37ba1c51c7eee5c02edea262d92c12488b1bb..770453fcaa054447f1ef80c2ecc20170eaa1acaf 100644
--- a/fsl/fslview/controls/clusterpanel.py
+++ b/fsl/fslview/controls/clusterpanel.py
@@ -5,13 +5,18 @@
 # Author: Paul McCarthy <pauldmccarthy@gmail.com>
 #
 
-import wx
+import                        logging
+import                        wx
+import wx.grid             as wxgrid
 
 import fsl.fslview.panel   as fslpanel
 import fsl.data.strings    as strings
 import fsl.data.featimage  as featimage
 
 
+log = logging.getLogger(__name__)
+
+
 class ClusterPanel(fslpanel.FSLViewPanel):
 
     def __init__(self, parent, overlayList, displayCtx):
@@ -22,10 +27,29 @@ class ClusterPanel(fslpanel.FSLViewPanel):
             style=(wx.ALIGN_CENTRE_HORIZONTAL |
                    wx.ALIGN_CENTRE_VERTICAL))
 
+        self.__overlayName = wx    .StaticText(self)
+        self.__statSelect  = wx    .ComboBox(  self, style=wx.CB_READONLY)
+        self.__clusterList = wxgrid.Grid(      self)
+        
+        self.__clusterList.CreateGrid(0, 10)
+        self.__clusterList.HideRowLabels()
+        
         self.__sizer = wx.BoxSizer(wx.VERTICAL)
         self.SetSizer(self.__sizer)
 
+        self.__topSizer  = wx.BoxSizer(wx.HORIZONTAL)
+        self.__mainSizer = wx.BoxSizer(wx.VERTICAL)
+ 
+        self.__topSizer.Add(self.__overlayName, flag=wx.EXPAND, proportion=1)
+        self.__topSizer.Add(self.__statSelect,  flag=wx.EXPAND, proportion=1)
+
+        self.__mainSizer.Add(self.__topSizer,    flag=wx.EXPAND)
+        self.__mainSizer.Add(self.__clusterList, flag=wx.EXPAND, proportion=1)
+
+        # Only one of the disabledText or
+        # mainSizer are shown at any one time
         self.__sizer.Add(self.__disabledText, flag=wx.EXPAND, proportion=1)
+        self.__sizer.Add(self.__mainSizer,    flag=wx.EXPAND, proportion=1)
 
         overlayList.addListener('overlays',
                                 self._name,
@@ -34,30 +58,159 @@ class ClusterPanel(fslpanel.FSLViewPanel):
                                 self._name,
                                 self.__selectedOverlayChanged)
 
+        self.__statSelect.Bind(wx.EVT_COMBOBOX, self.__statSelected)
+
+        self.__selectedOverlay = None
         self.__selectedOverlayChanged()
+
+
+    def destroy(self):
+        self._overlayList.removeListener('overlays',        self._name)
+        self._displayCtx .removeListener('selectedOverlay', self ._name)
+
+        if self.__selctedOverlay is not None:
+            try:
+                display = self._displayCtx.getDisplay(self.__selectedOverlay)
+                display.removeListener('name', self._name)
+            except:
+                pass
+
+            
+    def __disable(self, message):
+
+        self.__disabledText.SetLabel(message)
+        self.__sizer.Show(self.__disabledText, True)
+        self.__sizer.Show(self.__mainSizer,    False)
+        self.Layout() 
         
 
     def __selectedOverlayChanged(self, *a):
 
+        self.__statSelect .Clear()
+        self.__clusterList.ClearGrid()
+
+        # No overlays are loaded
         if len(self._overlayList) == 0:
-            # hide other things
-            self.__sizer.Show(self.__disabledText, False)
-            self.Layout()
+            self.__disable(strings.messages[self, 'noOverlays'])
             return
 
+        if self.__selectedOverlay is not None:
+            display = self._displayCtx.getDisplay(self.__selectedOverlay)
+            display.removeListener('name', self._name)
+            self.__selectedOverlay = None
+
         overlay = self._displayCtx.getSelectedOverlay()
-        
+        display = self._displayCtx.getDisplay(overlay)
+
+        # Not a FEAT image, can't 
+        # do anything with that
         if not isinstance(overlay, featimage.FEATImage):
+            self.__disable(strings.messages[self, 'notFEAT'])
+            return
+
+        self.__selectedOverlay = overlay
+
+        self.__sizer.Show(self.__disabledText, False)
+        self.__sizer.Show(self.__mainSizer,    True)
+
+        numCons  =  overlay.numContrasts()
+        conNames =  overlay.contrastNames()
 
-            self.__disabledText.SetLabel(
-                strings.messages[self, 'notFEAT'])
+        try:
+            # clusts is a list of (contrast, clusterList) tuples 
+            clusts = [(c, overlay.clusterResults(c)) for c in range(numCons)]
+            clusts = filter(lambda (con, clust): clust is not None, clusts)
 
-            # hide other things
-            self.__sizer.Show(self.__disabledText)
-            self.Layout()
+        # Error parsing the cluster data
+        except Exception as e:
+            log.warning('Error parsing cluster data for '
+                        '{}: {}'.format(overlay.name, str(e)), exc_info=True)
+            self.__disable(strings.messages[self, 'badData'])
             return
 
+        # No cluster results exist
+        # for any contrast
+        if len(clusts) == 0:
+            self.__disable(strings.messages[self, 'noClusters'])
+            return
 
-        self.__sizer.Show(self.__disabledText, False)
+        for contrast, clusterList in clusts:
+            name = conNames[contrast]
+            name = strings.labels[self, 'clustName'].format(contrast, name)
+
+            self.__statSelect.Append(name, clusterList)
+            
+        self.__overlayName.SetLabel(display.name)
+        self.__statSelect.SetSelection(0)
+        self.__displayClusterData(clusts[0][1])
+
+        # Update displayed name if
+        # overlay name is changed
+        def nameChanged(*a):
+            self.__overlayName.setLabel(display.name)
+
+        display.addListener('name', self._name, nameChanged)
+        
         self.Layout()
         return
+
+    
+    def __statSelected(self, ev):
+        idx  = self.__statSelect.GetSelection()
+        data = self.__statSelect.GetClientData(idx)
+        self.__displayClusterData(data)
+
+        
+    def __displayClusterData(self, clusters):
+
+        cols = {'index'         : 0,
+                'nvoxels'       : 1,
+                'p'             : 2,
+                'logp'          : 3,
+                'zmax'          : 4,
+                'zmaxcoords'    : 5,
+                'zcogcoords'    : 6,
+                'copemax'       : 7,
+                'copemaxcoords' : 8,
+                'copemean'      : 9}
+
+        grid = self.__clusterList
+
+        nrows = grid.GetNumberRows()
+
+        if nrows > 0:
+            grid.DeleteRows(0, nrows)
+        grid.InsertRows(0, len(clusters))
+
+        for col, i in cols.items():
+            grid.SetColLabelValue(i, strings.labels[self, col])
+
+        for i, clust in enumerate(clusters):
+            f = lambda v: '{}'.format(v)
+            grid.SetCellValue(i, cols['index'],   f(clust.index))
+            grid.SetCellValue(i, cols['nvoxels'], f(clust.nvoxels))
+            grid.SetCellValue(i, cols['p'],       f(clust.p))
+            grid.SetCellValue(i, cols['logp'],    f(clust.logp))
+            grid.SetCellValue(i, cols['zmax'],    f(clust.zmax))
+            
+            grid.SetCellValue(i, cols['zmaxcoords'],
+                              '[{} {} {}]'.format(clust.zmaxx,
+                                                  clust.zmaxy,
+                                                  clust.zmaxz))
+            
+            grid.SetCellValue(i, cols['zcogcoords'],
+                              '[{} {} {}]'.format(clust.zcogx,
+                                                  clust.zcogy,
+                                                  clust.zcogz))
+            
+            grid.SetCellValue(i, cols['copemax'], f(clust.copemax))
+            
+            grid.SetCellValue(i, cols['copemaxcoords'],
+                              '[{} {} {}]'.format(clust.copemaxx,
+                                                  clust.copemaxy,
+                                                  clust.copemaxz))
+            
+            grid.SetCellValue(i, cols['copemean'], f(clust.copemean))
+
+        grid.AutoSize()
+        grid.Refresh()