diff --git a/fsl/data/melodicresults.py b/fsl/data/melodicresults.py
index 89b45a913507d1640da16907158090f582fa753e..6de7dc48f146b196d4ec6f83a271fd37afd09b30 100644
--- a/fsl/data/melodicresults.py
+++ b/fsl/data/melodicresults.py
@@ -229,35 +229,8 @@ class MelodicClassification(props.HasProperties):
         
 
     def load(self, filename):
-        """Loads component labels from the specified file. The file is assuemd
-        to be of the format generated by FIX or Melview; such a file should
-        have a structure resembling the following::
-
-            filtered_func_data.ica
-            1, Signal, False
-            2, Unclassified Noise, True
-            3, Unknown, False
-            4, Signal, False
-            5, Unclassified Noise, True
-            6, Unclassified Noise, True
-            7, Unclassified Noise, True
-            8, Signal, False
-            [2, 5, 6, 7]
-
-        The first line of the file contains the name of the melodic directory.
-        Then, one line is present for each component, containing the following,
-        separated by commas:
-        
-          - The component index (starting from 1).
-          - One or more labels for the component (multiple labels must be
-            comma-separated).
-          - ``'True'`` if the component has been classified as *bad*,
-            ``'False'`` otherwise.
-            
-        The last line of the file contains the index (starting from 1) of all
-        *bad* components, i.e. those components which are not classified as
-        signal or unknown.
-
+        """Loads component labels from the specified file. See the
+        :func:`loadMelodicLabelFile` function.
 
         .. note:: This method adds to, but does not replace, any existing
                   component classifications stored by this
@@ -266,57 +239,27 @@ class MelodicClassification(props.HasProperties):
                   classifications.
         """
 
-        with open(filename, 'rt') as f:
-            lines = f.readlines()
-
-        if len(lines) < 3:
-            raise InvalidFixFileError('Invalid FIX classification '
-                                      'file - not enough lines')
-
-        lines = [l.strip() for l in lines]
-
-        # Ignore the first and last
-        # lines - we're only interested
-        # in the component labels
-        compLines = lines[1:-1]
-
-        if len(compLines) != self.__ncomps:
-            raise InvalidFixFileError('Invalid FIX classification '
-                                      'file - number of components '
-                                      'do not match')
-
-        # Parse the labels for every component
-        # We dot not add the labels as we go
-        # as, if something is wrong with the
-        # file contents, we don't want this
-        # MelodicClassification instance to
-        # be modified. So we'll assign the
-        # labels afterwards
-        allLabels = []
-        for i, compLine in enumerate(compLines):
-            
-            tokens = compLine.split(',')
-            tokens = [t.strip() for t in tokens]
-            
-            if len(tokens) < 3:
-                raise InvalidFixFileError('Invalid FIX classification '
-                                          'file - component line {} does '
-                                          'not have enough '
-                                          'tokens'.format(i + 1))
-
-            compIdx    = int(tokens[0])
-            compLabels = tokens[1:-1]
-
-            if compIdx != i + 1:
-                raise InvalidFixFileError('Invalid FIX classification '
-                                          'file - component line {} has '
-                                          'wrong component number '
-                                          '({})'.format(i, compIdx))
-
-            allLabels.append(compLabels)
-
-        # Now that all the labels are
-        # read in, we can store them
+        # Read the labels in
+        _, allLabels = loadMelodicLabelFile(filename)
+
+        # More labels in the file than there are in
+        # melodic_IC - that doesn't make any sense.
+        if len(allLabels) > self.__ncomps:
+            raise InvalidFixFileError('The number of components in {} does '
+                                      'not match the number of components in '
+                                      '{}!'.format(filename,
+                                                   self.__melimage.dataSource))
+
+        # Less labels in the file than there are in
+        # the melodic_IC image - this is ok, as the
+        # file may have only contained a list of
+        # noisy components. We'll label the remaining
+        # components as 'Unknown'.
+        elif len(allLabels) < self.__ncomps:
+            for i in range(len(allLabels), self.__ncomps):
+                allLabels.append(['Unknown'])
+
+        # Add the labels to this melclass object
         notifState = self.getNotificationState('labels')
         self.disableNotification('labels')
 
@@ -330,44 +273,19 @@ class MelodicClassification(props.HasProperties):
     
     def save(self, filename):
         """Saves the component classifications stored by this
-        ``MeloidicClassification`` to the specified file. The classifications
-        are saved in the format described in the :meth:`load` method.
-
-        .. TODO:: Accept a dictionary of ``{label : display label}`` mappings,
-                  so we can output cased labels (e.g. ``'Signal'`` instead of
-                  ``'signal'``).
+        ``MeloidicClassification`` to the specified file. See the
+        :func:`saveMelodicLabelFile` function.
         """
 
-        lines    = []
-        badComps = []
-        image    = self.__melimage
-
-        # The first line - the melodic directory name
-        lines.append(op.basename(image.getMelodicDir()))
-
-        # A line for each component
-        for comp in range(self.__ncomps):
-            
-            noise  = not (self.hasLabel(comp, 'signal') or
-                          self.hasLabel(comp, 'unknown'))
-
-            # Make sure there are no
-            # commas in any label names
-            labels = [self.getDisplayLabel(l) for l in self.getLabels(comp)]
-            labels = [l.replace(',', '_') for l in labels]
-            
-            tokens = [str(comp + 1)] + labels + [str(noise)]
-
-            lines.append(', '.join(tokens))
-
-            if noise:
-                badComps.append(comp)
+        allLabels = []
 
-        # A line listing the bad components
-        lines.append('[' + ', '.join([str(c + 1) for c in badComps]) + ']')
+        for c in range(self.__ncomps):
+            labels = [self.getDisplayLabel(l) for l in self.labels[c]]
+            allLabels.append(labels)
 
-        with open(filename, 'wt') as f:
-            f.write('\n'.join(lines) + '\n')
+        saveMelodicLabelFile(self.__melImage.getMelodicDir(),
+                             allLabels,
+                             filename)
 
 
     def getLabels(self, component):
@@ -483,7 +401,162 @@ class MelodicClassification(props.HasProperties):
         self.notify('labels')
 
 
-class InvalidFixFileError(Exception):
+def loadMelodicLabelFile(filename):
+    """Loads component labels from the specified file. The file is assuemd
+    to be of the format generated by FIX or Melview; such a file should
+    have a structure resembling the following::
+
+        filtered_func_data.ica
+        1, Signal, False
+        2, Unclassified Noise, True
+        3, Unknown, False
+        4, Signal, False
+        5, Unclassified Noise, True
+        6, Unclassified Noise, True
+        7, Unclassified Noise, True
+        8, Signal, False
+        [2, 5, 6, 7]
+
+    The first line of the file contains the name of the melodic directory.
+    Then, one line is present for each component, containing the following,
+    separated by commas:
+
+      - The component index (starting from 1).
+      - One or more labels for the component (multiple labels must be
+        comma-separated).
+      - ``'True'`` if the component has been classified as *bad*,
+        ``'False'`` otherwise.
+
+    The last line of the file contains the index (starting from 1) of all
+    *bad* components, i.e. those components which are not classified as
+    signal or unknown.
     """
+
+    with open(filename, 'rt') as f:
+        lines = f.readlines()
+
+    if len(lines) < 1:
+        raise InvalidFixFileError('Invalid FIX classification '
+                                  'file - not enough lines')
+
+    lines = [l.strip() for l in lines]
+    lines = [l for l in lines if l != '']
+
+    # If the file contains a single
+    # line, we assume that it is just
+    # a list of noise components.
+    if len(lines) == 1:
+
+        melDir     = None 
+        noisyComps = map(int, lines[0][1:-1].split(', '))
+        allLabels  = []
+
+        for i in range(max(noisyComps)):
+            if (i + 1) in noisyComps: allLabels.append(['Unclassified noise'])
+            else:                     allLabels.append(['Signal'])
+
+    # Otherwise, we assume that
+    # it is a full label file.
+    else:
+
+        melDir     = lines[0]
+        noisyComps = map(int, lines[-1][1:-1].split(', '))
+
+        # Parse the labels for every component
+        # We dot not add the labels as we go
+        # as, if something is wrong with the
+        # file contents, we don't want this
+        # MelodicClassification instance to
+        # be modified. So we'll assign the
+        # labels afterwards
+        allLabels = []
+        for i, compLine in enumerate(lines[1:-1]):
+
+            tokens = compLine.split(',')
+            tokens = [t.strip() for t in tokens]
+
+            if len(tokens) < 3:
+                raise InvalidFixFileError('Invalid FIX classification '
+                                          'file - component line {} does '
+                                          'not have enough '
+                                          'tokens'.format(i + 1))
+
+            compIdx    = int(tokens[0])
+            compLabels = tokens[1:-1]
+
+            if compIdx != i + 1:
+                raise InvalidFixFileError('Invalid FIX classification '
+                                          'file - component line {} has '
+                                          'wrong component number '
+                                          '({})'.format(i, compIdx))
+
+            allLabels.append(compLabels)
+
+
+    # Validate the labels against
+    # the noisy list - all components
+    # in the noisy list should have
+    # the label 'unclassified noise'.
+    for i, labels in enumerate(allLabels):
+
+        for label in labels:
+            if label.lower() == 'unclassified noise' and \
+               (i + 1) not in noisyComps:
+
+                raise InvalidFixFileError('Noisy component {} has an invalid '
+                                          'label: {}'.format(i + 1, label))
+
+    for comp in noisyComps:
+        labels = allLabels[comp - 1]
+        labels = [l.lower() for l in labels]
+        
+        if 'unclassified noise' not in labels:
+            raise InvalidFixFileError('Noisy component {} is missing '
+                                      'a noise label'.format(i)) 
+
+    return melDir, allLabels
+
+
+def saveMelodicLabelFile(melDir, allLabels, filename):
+    """Saves the component classifications stored by this
+    ``MeloidicClassification`` to the specified file. The classifications
+    are saved in the format described in the :meth:`load` method.
+    """
+    
+    lines      = []
+    noisyComps = []
+
+    # The first line - the melodic directory name
+    lines.append(op.abspath(melDir))
+
+    # A line for each component
+    for i, labels in enumerate(allLabels):
+
+        comp    = i + 1
+        lowered = [l.lower() for l in labels]
+        noise   = 'signal' not in lowered and 'unknown' not in lowered
+
+        # Make sure there are no
+        # commas in any label names
+        labels = [l.replace(',', '_') for l in labels]
+        
+        tokens = [str(comp + 1)] + labels + [str(noise)]
+
+        lines.append(', '.join(tokens))
+
+        if noise:
+            noisyComps.append(comp)
+
+    # A line listing the bad components
+    lines.append('[' + ', '.join([str(c + 1) for c in noisyComps]) + ']')
+
+    with open(filename, 'wt') as f:
+        f.write('\n'.join(lines) + '\n')
+
+
+class InvalidFixFileError(Exception):
+    """Exception raised by the :meth:`MelodicClassification.load` method and
+    the :func:`loadMelodicLabelFile` function when an attempt is made to load
+    an invalid FIX label file.
     """
     pass
diff --git a/fsl/fsleyes/luts/melodic-classes.lut b/fsl/fsleyes/luts/melodic-classes.lut
index aaf2c07fce35959dea4e16af2be7324c78148f4b..841c3cf0f6a01b8f52a58e4eeaa69f08907f4563 100644
--- a/fsl/fsleyes/luts/melodic-classes.lut
+++ b/fsl/fsleyes/luts/melodic-classes.lut
@@ -1,11 +1,11 @@
-1   0.419608  0.619608  0.992157  Signal
-2   0.490196  0.827451  0.000000  Unknown
-3   0.980392  0.478431  0.588235  Unclassified noise
-4   0.843137  0.776471  0.000000  Movement
-5   0.850980  0.435294  0.000000  Cardiac
-6   0.494118  0.964706  0.984314  White matter
-7   0.984313  0.564706  0.850980  Non-brain
-8   0.858824  0.854902  0.847059  MRI
-9   0.666667  0.972549  0.745098  Susceptibility-motion
-10  0.780392  0.470588  0.870588  Sagittal sinus
-11  0.427450  0.725490  0.603922  Respiratory
+1  0.39216 0.82745 0.00000 Signal
+2  0.91765 0.86275 0.00000 Unknown
+3  1.00000 0.37647 0.26275 Unclassified noise
+4  0.08235 0.82353 0.83922 Movement
+5  0.70588 0.27059 1.00000 Cardiac
+6  0.49412 0.96471 0.98431 White matter
+7  0.98431 0.56471 0.85098 Non-brain
+8  0.85882 0.85490 0.84706 MRI
+9  0.66667 0.97255 0.74510 Susceptibility-motion
+10 0.78039 0.47059 0.87059 Sagittal sinus
+11 0.42745 0.72549 0.60392 Respiratory
diff --git a/fsl/fsleyes/perspectives.py b/fsl/fsleyes/perspectives.py
index 03a4a6f537a434898b41b1fd73c44889c0cea801..20db6f42f6c8a209950a8c18e2cb079404b351e7 100644
--- a/fsl/fsleyes/perspectives.py
+++ b/fsl/fsleyes/perspectives.py
@@ -404,13 +404,13 @@ BUILT_IN_PERSPECTIVES = collections.OrderedDict((
     ('melodic',
      textwrap.dedent("""
                      LightBoxPanel,TimeSeriesPanel,PowerSpectrumPanel,
-                     layout2|name=LightBoxPanel 1;caption=Lightbox View 1;state=67377088;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=853;besth=-1;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=TimeSeriesPanel 2;caption=Time series 2;state=67377148;dir=3;layer=0;row=0;pos=0;prop=100000;bestw=-1;besth=472;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=PowerSpectrumPanel 3;caption=Power spectra 3;state=67377148;dir=3;layer=0;row=0;pos=1;prop=100000;bestw=-1;besth=472;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|dock_size(5,0,0)=22|dock_size(3,0,0)=493|
-                     OverlayListPanel,LightBoxToolBar,OverlayDisplayToolBar,LocationPanel,MelodicClassificationPanel,LookupTablePanel,
-                     layout2|name=Panel;caption=;state=768;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=20;besth=20;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=OverlayListPanel;caption=Overlay list;state=67373052;dir=3;layer=0;row=0;pos=0;prop=100000;bestw=197;besth=80;minw=197;minh=80;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=197;floath=96;notebookid=-1;transparent=255|name=LightBoxToolBar;caption=Lightbox view toolbar;state=67382012;dir=1;layer=10;row=0;pos=0;prop=100000;bestw=757;besth=43;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=OverlayDisplayToolBar;caption=Display toolbar;state=67382012;dir=1;layer=11;row=0;pos=0;prop=100000;bestw=860;besth=49;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=LocationPanel;caption=Location;state=67373052;dir=3;layer=0;row=0;pos=1;prop=100000;bestw=440;besth=109;minw=440;minh=109;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=440;floath=125;notebookid=-1;transparent=255|name=MelodicClassificationPanel;caption=Melodic IC classification;state=67373052;dir=2;layer=0;row=0;pos=1;prop=100000;bestw=400;besth=100;minw=400;minh=100;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=400;floath=116;notebookid=-1;transparent=255|name=LookupTablePanel;caption=Lookup tables;state=67373052;dir=2;layer=0;row=0;pos=0;prop=100000;bestw=358;besth=140;minw=358;minh=140;maxw=-1;maxh=-1;floatx=3614;floaty=658;floatw=358;floath=156;notebookid=-1;transparent=255|dock_size(5,0,0)=22|dock_size(3,0,0)=130|dock_size(1,10,0)=45|dock_size(1,11,0)=10|dock_size(2,0,0)=402|
+                     layout2|name=LightBoxPanel 1;caption=Lightbox View 1;state=67377088;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=853;besth=-1;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=TimeSeriesPanel 2;caption=Time series 2;state=67377148;dir=3;layer=0;row=0;pos=0;prop=100000;bestw=-1;besth=472;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=PowerSpectrumPanel 3;caption=Power spectra 3;state=67377148;dir=3;layer=0;row=0;pos=1;prop=100000;bestw=-1;besth=472;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|dock_size(5,0,0)=22|dock_size(3,0,0)=195|
+                     OverlayListPanel,OverlayDisplayToolBar,LocationPanel,LightBoxToolBar,MelodicClassificationPanel,
+                     layout2|name=Panel;caption=;state=768;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=20;besth=20;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=OverlayListPanel;caption=Overlay list;state=67373052;dir=3;layer=0;row=0;pos=0;prop=100000;bestw=204;besth=80;minw=197;minh=80;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=204;floath=96;notebookid=-1;transparent=255|name=OverlayDisplayToolBar;caption=Display toolbar;state=67382012;dir=1;layer=11;row=0;pos=0;prop=100000;bestw=810;besth=49;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=LocationPanel;caption=Location;state=67373052;dir=3;layer=0;row=0;pos=1;prop=100000;bestw=440;besth=111;minw=440;minh=109;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=440;floath=127;notebookid=-1;transparent=255|name=LightBoxToolBar;caption=Lightbox view toolbar;state=67382012;dir=1;layer=10;row=0;pos=0;prop=100000;bestw=753;besth=43;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=MelodicClassificationPanel;caption=Melodic IC classification;state=67373052;dir=2;layer=0;row=0;pos=0;prop=100000;bestw=400;besth=100;minw=400;minh=100;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=400;floath=116;notebookid=-1;transparent=255|dock_size(5,0,0)=22|dock_size(3,0,0)=130|dock_size(1,10,0)=45|dock_size(1,11,0)=51|dock_size(2,0,0)=402|
                      ,
                      layout2|name=FigureCanvasWxAgg;caption=;state=768;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=640;besth=480;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|dock_size(5,0,0)=642|
                      ,
-                     layout2|name=FigureCanvasWxAgg;caption=;state=768;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=640;besth=480;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|dock_size(5,0,0)=642|
+                     layout2|name=FigureCanvasWxAgg;caption=;state=768;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=640;besth=480;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|dock_size(5,0,0)=642|'
                      """)),
 
     ('feat',