diff --git a/fsl/data/featresults.py b/fsl/data/featresults.py
index 719092a03771d78867d36c69ae7d241fa2231b59..138c0379e11563504f39f4aac6ece0cb44db0036 100644
--- a/fsl/data/featresults.py
+++ b/fsl/data/featresults.py
@@ -134,7 +134,7 @@ def loadDesign(featdir):
             if line.strip() == '/Matrix':
                 break
 
-        matrix = np.loadtxt(f)
+        matrix = np.loadtxt(f, ndmin=2)
 
     if matrix is None or matrix.size == 0:
         raise RuntimeError('{} does not appear to be a '
diff --git a/fsl/data/melodicimage.py b/fsl/data/melodicimage.py
index 1456386c9689ed78fe4a14e2ca8294b76fbb637a..3a2087da9a028717e832374be773ff89d3d07dfc 100644
--- a/fsl/data/melodicimage.py
+++ b/fsl/data/melodicimage.py
@@ -31,6 +31,7 @@ class MelodicImage(fslimage.Image):
        tr
        getComponentTimeSeries
        numComponents
+       getTopLevelAnalysisDir
        getDataFile
     """
 
@@ -98,6 +99,15 @@ class MelodicImage(fslimage.Image):
         return self.shape[3]
 
 
+    def getTopLevelAnalysisDir(self):
+        """Returns the top level analysis, if the melodic analysis for this
+        ``MelodicImage`` is contained within another analysis. Otherwise,
+        returnsa ``None``. See the
+        :func:`.melodicresults.getTopLevelAnalysisDir` function.
+        """
+        return melresults.getTopLevelAnalysisDir(self.__meldir)
+
+
     def getDataFile(self):
         """Returns the file name of the data image from which this
         ``MelodicImage`` was generated, if possible. See the
diff --git a/fsl/data/melodicresults.py b/fsl/data/melodicresults.py
index f020003fb06c67c371c91ff0a69f756c35b7ebbf..c31811723bd78e2ce8036072d3c5d3f0e19734d5 100644
--- a/fsl/data/melodicresults.py
+++ b/fsl/data/melodicresults.py
@@ -142,6 +142,10 @@ def getMixFile(meldir):
     return op.join(meldir, 'melodic_mix')
 
 
+def getReportFile(meldir):
+    pass
+
+
 def getNumComponents(meldir):
     """Returns the number of components generated in the melodic analysis
     contained in the given directrory.
diff --git a/fsl/data/strings.py b/fsl/data/strings.py
index 27ec5ed61ebbe336a80ad5fae945d5651d4660c7..ba3d4892673728f3355987b81ad0675045178298 100644
--- a/fsl/data/strings.py
+++ b/fsl/data/strings.py
@@ -325,13 +325,17 @@ labels = TypeDict({
     'OverlayInfoPanel.Image.transform'    : 'Transform/space',
     'OverlayInfoPanel.Image.orient'       : 'Orientation',
     
-    'OverlayInfoPanel.Image'              : 'NIFTI1 image',
-    'OverlayInfoPanel.FEATImage'          : 'NIFTI1 image (FEAT analysis)',
-    'OverlayInfoPanel.FEATImage.featInfo' : 'FEAT information',
-    'OverlayInfoPanel.Model'              : 'VTK model',
-    'OverlayInfoPanel.Model.numVertices'  : 'Number of vertices',
-    'OverlayInfoPanel.Model.numIndices'   : 'Number of indices',
-    'OverlayInfoPanel.dataSource'         : 'Data source',
+    'OverlayInfoPanel.Image'                    : 'NIFTI1 image',
+    'OverlayInfoPanel.FEATImage'                : 'NIFTI1 image '
+                                                  '(FEAT analysis)',
+    'OverlayInfoPanel.FEATImage.featInfo'       : 'FEAT information',
+    'OverlayInfoPanel.MelodicImage'             : 'NIFTI1 image '
+                                                  '(MELODIC analysis)', 
+    'OverlayInfoPanel.MelodicImage.melodicInfo' : 'MELODIC information',
+    'OverlayInfoPanel.Model'                    : 'VTK model',
+    'OverlayInfoPanel.Model.numVertices'        : 'Number of vertices',
+    'OverlayInfoPanel.Model.numIndices'         : 'Number of indices',
+    'OverlayInfoPanel.dataSource'               : 'Data source',
 })
 
 
@@ -708,4 +712,14 @@ feat = TypeDict({
     'numPoints'    : 'Number of volumes',
     'numEVs'       : 'Number of EVs',
     'numContrasts' : 'Number of contrasts',
+    'report'       : 'Link to report',
+})
+
+
+melodic = TypeDict({
+    'dataFile'       : 'Data file',
+    'partOfAnalysis' : 'Part of analysis',
+    'numComponents'  : 'Number of ICs',
+    'tr'             : 'TR time',
+    'report'         : 'Link to report',
 })
diff --git a/fsl/fsleyes/colourmaps.py b/fsl/fsleyes/colourmaps.py
index 46e1a2f1b07ceb429367d8cfc18b728280ac1d5f..0589151de45b3242394a6b55c768d8af5991bd9c 100644
--- a/fsl/fsleyes/colourmaps.py
+++ b/fsl/fsleyes/colourmaps.py
@@ -626,7 +626,7 @@ def randomBrightColour():
 def randomDarkColour():
     """Generates a random saturated and darkened RGB colour."""
 
-    return applyBricon(randomBrightColour(), 0.25, 0.5)
+    return applyBricon(randomBrightColour(), 0.35, 0.5)
 
 
 def complementaryColour(rgb):
diff --git a/fsl/fsleyes/controls/overlayinfopanel.py b/fsl/fsleyes/controls/overlayinfopanel.py
index 8f15f85342208b794a0aca449b20e9df794764a2..f44baf5900f0e7b90c77f85db9a6a8b65d74415f 100644
--- a/fsl/fsleyes/controls/overlayinfopanel.py
+++ b/fsl/fsleyes/controls/overlayinfopanel.py
@@ -30,14 +30,15 @@ class OverlayInfoPanel(fslpanel.FSLEyesPanel):
        :scale: 50%
        :align: center
 
-    Slightly different informtion is shown depending on the overlay type,
+    Slightly different information is shown depending on the overlay type,
     and is generated by the following methods:
 
-    =================== ==========================
-    :class:`.Image`     :meth:`__getImageInfo`
-    :class:`.FEATImage` :meth:`__getFEATImageInfo`
-    :class:`.Model`     :meth:`__getModelInfo`
-    =================== ==========================
+    ====================== =============================
+    :class:`.Image`        :meth:`__getImageInfo`
+    :class:`.FEATImage`    :meth:`__getFEATImageInfo`
+    :class:`.MelodicImage` :meth:`__getMelodicImageInfo`
+    :class:`.Model`        :meth:`__getModelInfo`
+    ====================== =============================
     """
 
 
@@ -293,6 +294,33 @@ class OverlayInfoPanel(fslpanel.FSLEyesPanel):
 
         return info
 
+
+    def __getMelodicImageInfo(self, overlay, display):
+        """Creates and returns an :class:`OverlayInfo` object containing
+        information about the given :class:`.MelodicImage` overlay.
+
+        :arg overlay: A :class:`.MelodicImage` instance.
+        :arg display: The :class:`.Display` instance assocated with the
+                      ``MelodicImage``.
+        """
+
+        info = self.__getImageInfo(overlay, display)
+
+        melInfo = collections.OrderedDict([
+            ('tr',             overlay.tr),
+            ('dataFile',       overlay.getDataFile()),
+            ('partOfAnalysis', overlay.getTopLevelAnalysisDir()),
+            ('numComponents',  overlay.numComponents()),
+        ])
+
+        secName = strings.labels[self, overlay, 'melodicInfo']
+        info.addSection(secName)
+
+        for k, v in melInfo.items():
+            info.addInfo(strings.melodic[k], v, section=secName)
+
+        return info
+
     
     def __getModelInfo(self, overlay, display):
         """Creates and returns an :class:`OverlayInfo` object containing
diff --git a/fsl/fsleyes/controls/timeserieslistpanel.py b/fsl/fsleyes/controls/timeserieslistpanel.py
index c3d500fb48be3bc98aa70c8d1b0947d43b7ea45d..c211d35b9c7f7d5c0d98ebf94ddf4c53ac4b15c4 100644
--- a/fsl/fsleyes/controls/timeserieslistpanel.py
+++ b/fsl/fsleyes/controls/timeserieslistpanel.py
@@ -183,7 +183,7 @@ class TimeSeriesListPanel(fslpanel.FSLEyesPanel):
             copy.setData(*ts.getData())
 
             # This is hacky, and is here in order to
-            # make the __onLIstSelect method work.
+            # make the __onListSelect method work.
             if isinstance(ts, timeseries.MelodicTimeSeries):
                 copy.tsLoc = 'volume'
                 copy.coord = ts.getComponent()
diff --git a/fsl/fsleyes/overlay.py b/fsl/fsleyes/overlay.py
index 62d6c1890340963d47df8ff687d053a4a68d9d83..053801d5f8db5d2e1667920a47045449a2a696af 100644
--- a/fsl/fsleyes/overlay.py
+++ b/fsl/fsleyes/overlay.py
@@ -182,12 +182,12 @@ class OverlayList(props.HasProperties):
         return self.overlays.insertAll(index, items) 
 
 
-def guessDataSourceType(filename):
+def guessDataSourceType(path):
     """A convenience function which, given the name of a file or directory,
     figures out a suitable overlay type.
 
     Returns a tuple containing two values - a type which should be able to
-    load the filename, and the filename, possibly adjusted. If the file type
+    load the path, and the path itself, possibly adjusted. If the type
     is unrecognised, the first tuple value will be ``None``.
     """
 
@@ -198,30 +198,53 @@ def guessDataSourceType(filename):
     import fsl.data.melodicresults as melresults
     import fsl.data.featresults    as featresults
 
-    filename = op.abspath(filename)
-
-    if filename.endswith('.vtk'):
-        return fslmodel.Model, filename
-
-    else:
-        if op.isdir(filename):
-            if featresults.isFEATDir(filename):
-                return fslfeatimage.FEATImage, filename
-            elif melresults.isMelodicDir(filename):
-                return fslmelimage.MelodicImage, filename 
-        else:
-            
-            try:               filename = fslimage.addExt(filename, True)
-            except ValueError: return None, filename
+    path = op.abspath(path)
+
+    # VTK files are easy
+    if path.endswith('.vtk'):
+        return fslmodel.Model, path
+
+    # Now, we check to see if the given
+    # path is part of a FEAT or MELODIC
+    # analysis. The way we go about this is
+    # a bit silly, but is necessary due to
+    # the fact thet a melodic analysis can
+    # be contained  within a feat analysis
+    # (or another melodic analysis). So we
+    # check for all analysis types and, if
+    # more than one analysis type matches,
+    # we return the one with the longest
+    # path name.
+    analyses = [
+        (fslfeatimage.FEATImage,    featresults.getFEATDir(   path)),
+        (fslmelimage .MelodicImage, melresults .getMelodicDir(path))]
+
+    # Remove the analysis types that didn't match
+    # (the get*Dir function returned None)
+    analyses = [(t, d) for (t, d) in analyses if d is not None]
+
+    # If we have one or more matches for
+    # an analysis directory, we return
+    # the one with the longest path
+    if len(analyses) > 0:
+
+        dirlens = map(len, [d for (t, d) in analyses])
+        maxidx  = dirlens.index(max(dirlens))
+        
+        return analyses[maxidx]
 
-            if featresults.isFEATDir(filename):
-                return fslfeatimage.FEATImage, filename
-            elif melresults.isMelodicDir(filename):
-                return fslmelimage.MelodicImage, filename
-            else:
-                return fslimage.Image, filename
+    # If the path is not an analysis directory,
+    # see if it is a regular nifti image
+    try:
+        path = fslimage.addExt(path, mustExist=True)
+        return fslimage.Image, path
+    
+    except ValueError:
+        pass
 
-    return None, filename
+    # Otherwise, I don't
+    # know what to do
+    return None, path
 
 
 def makeWildcard():
diff --git a/fsl/fsleyes/plotting/timeseries.py b/fsl/fsleyes/plotting/timeseries.py
index 094d450c8106f291ed802f94734940c254193a58..64c1631098279f21448f5f4de1bc096b6ba16545 100644
--- a/fsl/fsleyes/plotting/timeseries.py
+++ b/fsl/fsleyes/plotting/timeseries.py
@@ -26,9 +26,8 @@ import numpy as np
 
 import props
 
-import                          dataseries
-import fsl.data.strings      as strings
-import fsl.data.melodicimage as fslmelimage
+import                     dataseries
+import fsl.data.strings as strings
 
 
 class TimeSeries(dataseries.DataSeries):
@@ -72,8 +71,7 @@ class TimeSeries(dataseries.DataSeries):
         
     def getData(self, xdata=None, ydata=None):
         """Overrides :meth:`.DataSeries.getData`. Returns the data associated
-        with this ``TimeSeries`` instance, pre-processed according to the
-        current :class:`.TimeSeriesPanel` settings.
+        with this ``TimeSeries`` instance.
 
         The ``xdata`` and ``ydata`` arguments may be used by sub-classes to
         override the x/y data in the event that they have already performed
@@ -89,25 +87,7 @@ class TimeSeries(dataseries.DataSeries):
 
         xdata = np.array(xdata, dtype=np.float32)
         ydata = np.array(ydata, dtype=np.float32)
-
-        if self.tsPanel.usePixdim:
-            if isinstance(self.overlay, fslmelimage.MelodicImage):
-                xdata *= self.overlay.tr
-            else:
-                xdata *= self.overlay.pixdim[3]
         
-        if self.tsPanel.plotMode == 'demean':
-            ydata = ydata - ydata.mean()
-
-        elif self.tsPanel.plotMode == 'normalise':
-            ymin  = ydata.min()
-            ymax  = ydata.max()
-            ydata = 2 * (ydata - ymin) / (ymax - ymin) - 1
-            
-        elif self.tsPanel.plotMode == 'percentChange':
-            mean  = ydata.mean()
-            ydata =  100 * (ydata / mean) - 100
-            
         return xdata, ydata
 
 
@@ -503,7 +483,7 @@ class FEATTimeSeries(VoxelTimeSeries):
                     copenum)
 
 
-    def __plotPEFitChanged(self, evnum):
+    def __plotPEFitChanged(self, *a):
         """Called when the :attr:`plotPEFits` setting changes.
 
         If necessary, creates and caches one or more
diff --git a/fsl/fsleyes/views/plotpanel.py b/fsl/fsleyes/views/plotpanel.py
index 0a64d0e59dd5dcafbdf053cb7f0783f3806eb696..e80aa4b782ebd24f5dc04a026b658effb3eac796 100644
--- a/fsl/fsleyes/views/plotpanel.py
+++ b/fsl/fsleyes/views/plotpanel.py
@@ -355,13 +355,16 @@ class PlotPanel(viewpanel.ViewPanel):
         self.Refresh()
 
 
-    def drawDataSeries(self, extraSeries=None, **plotArgs):
+    def drawDataSeries(self, extraSeries=None, preproc=None, **plotArgs):
         """Plots all of the :class:`.DataSeries` instances in the
         :attr:`dataSeries` list
 
         :arg extraSeries: A sequence of additional ``DataSeries`` to be
                           plotted.
 
+        :arg preproc:     An optional preprocessing function - passed to the
+                          :meth:`__drawOneDataSeries` method.
+
         :arg plotArgs:    Passed through to the :meth:`__drawOneDataSeries`
                           method.
         """
@@ -397,7 +400,7 @@ class PlotPanel(viewpanel.ViewPanel):
         ylims = []
 
         for ds in toPlot:
-            xlim, ylim = self.__drawOneDataSeries(ds, **plotArgs)
+            xlim, ylim = self.__drawOneDataSeries(ds, preproc, **plotArgs)
             xlims.append(xlim)
             ylims.append(ylim)
 
@@ -474,16 +477,26 @@ class PlotPanel(viewpanel.ViewPanel):
         self.Refresh()
 
         
-    def __drawOneDataSeries(self, ds, **plotArgs):
+    def __drawOneDataSeries(self, ds, preproc=None, **plotArgs):
         """Plots a single :class:`.DataSeries` instance. This method is called
         by the :meth:`drawDataSeries` method.
 
         :arg ds:       The ``DataSeries`` instance.
 
+        :arg preproc:  An optional preprocessing function which must accept
+                       the ``DataSeries`` instance as its sole argument, and
+                       must return the ``(xdata, ydata)`` with any required
+                       processing applied.  The default preprocessing function
+                       returns the result of a call to
+                       :meth:`.DataSeries.getData`.
+
         :arg plotArgs: May be used to customise the plot - these
                        arguments are all passed through to the
                        ``Axis.plot`` function.
         """
+        
+        if preproc is None:
+            preproc = lambda s: s.getData()
 
         if ds.alpha == 0:
             return (0, 0), (0, 0)
@@ -491,7 +504,7 @@ class PlotPanel(viewpanel.ViewPanel):
         log.debug('Drawing {} for {}'.format(type(ds).__name__,
                                              ds.overlay))
 
-        xdata, ydata = ds.getData()
+        xdata, ydata = preproc(ds)
 
         if len(xdata) != len(ydata) or len(xdata) == 0:
             return (0, 0), (0, 0)
diff --git a/fsl/fsleyes/views/timeseriespanel.py b/fsl/fsleyes/views/timeseriespanel.py
index 5a879e526528b542ece4f59efe961faa81221d1b..d83221c9a1eee94064fc5a21b1cc3f4fa1f6351a 100644
--- a/fsl/fsleyes/views/timeseriespanel.py
+++ b/fsl/fsleyes/views/timeseriespanel.py
@@ -20,7 +20,7 @@ import fsl.data.featimage                          as fslfeatimage
 import fsl.data.melodicimage                       as fslmelimage
 import fsl.data.image                              as fslimage
 import fsl.fsleyes.colourmaps                      as fslcmaps
-import fsl.fsleyes.plotting.timeseries             as timeseries
+import fsl.fsleyes.plotting                        as plotting
 import fsl.fsleyes.controls.timeseriescontrolpanel as timeseriescontrolpanel
 import fsl.fsleyes.controls.timeserieslistpanel    as timeserieslistpanel
 
@@ -281,14 +281,15 @@ class TimeSeriesPanel(plotpanel.PlotPanel):
         tss = [ts for ts in tss if ts is not None]
 
         for i, ts in enumerate(list(tss)):
-            if isinstance(ts, timeseries.FEATTimeSeries):
+            if isinstance(ts, plotting.FEATTimeSeries):
                 tss.pop(i)
                 tss = tss[:i] + ts.getModelTimeSeries() + tss[i:]
 
         for ts in tss:
             ts.label = ts.makeLabel()
 
-        self.drawDataSeries(extraSeries=tss)
+        self.drawDataSeries(extraSeries=tss,
+                            preproc=self.__prepareTimeSeriesData)
 
 
     def getTimeSeries(self, overlay):
@@ -298,6 +299,39 @@ class TimeSeriesPanel(plotpanel.PlotPanel):
         return self.__currentTss.get(overlay)
 
 
+    def __prepareTimeSeriesData(self, ts):
+        """Given a :class:`.TimeSeries` instance, scales and normalises
+        the x and y data according to the current values of the
+        :attr:`usePixdim` and :attr:`plotMode` properties.
+
+        This method is used as a preprocessing function for all
+        :class:`.TimeSeries` instances that are plotted - see the
+        :meth:`.PlotPanel.drawDataSeries` method.
+        """
+
+        xdata, ydata = ts.getData()
+
+        if self.usePixdim:
+            if isinstance(ts.overlay, fslmelimage.MelodicImage):
+                xdata *= ts.overlay.tr
+            else:
+                xdata *= ts.overlay.pixdim[3]
+        
+        if self.plotMode == 'demean':
+            ydata = ydata - ydata.mean()
+
+        elif self.plotMode == 'normalise':
+            ymin  = ydata.min()
+            ymax  = ydata.max()
+            ydata = 2 * (ydata - ymin) / (ymax - ymin) - 1
+            
+        elif self.plotMode == 'percentChange':
+            mean  = ydata.mean()
+            ydata =  100 * (ydata / mean) - 100
+            
+        return xdata, ydata 
+
+
     def __overlayListChanged(self, *a):
         """Called when the :class:`.OverlayList` changes. Makes sure that
         there are no :class:`.TimeSeries` instances in the
@@ -402,18 +436,18 @@ class TimeSeriesPanel(plotpanel.PlotPanel):
             return None, None, None
 
         if isinstance(overlay, fslfeatimage.FEATImage):
-            ts = timeseries.FEATTimeSeries(self, overlay, self._displayCtx)
+            ts = plotting.FEATTimeSeries(self, overlay, self._displayCtx)
             targets   = [self._displayCtx]
             propNames = ['location']
             
         elif isinstance(overlay, fslmelimage.MelodicImage) and \
              self.plotMelodicICs:
-            ts = timeseries.MelodicTimeSeries(self, overlay, self._displayCtx)
+            ts = plotting.MelodicTimeSeries(self, overlay, self._displayCtx)
             targets   = [self._displayCtx.getOpts(overlay)]
             propNames = ['volume'] 
             
         else:
-            ts = timeseries.VoxelTimeSeries(self, overlay, self._displayCtx)
+            ts = plotting.VoxelTimeSeries(self, overlay, self._displayCtx)
             targets   = [self._displayCtx]
             propNames = ['location']