diff --git a/fsl/data/fixlabels.py b/fsl/data/fixlabels.py
new file mode 100644
index 0000000000000000000000000000000000000000..f948a307cdd5f08e0b2050e142cfefad8af6cd0a
--- /dev/null
+++ b/fsl/data/fixlabels.py
@@ -0,0 +1,281 @@
+#!/usr/bin/env python
+#
+# fixlabels.py - Functions for loading/saving FIX/ICA-AROMA label files.
+#
+# Author: Paul McCarthy <pauldmccarthy@gmail.com>
+#
+"""This module contains functions for loading/saving FIX/ICA-AROMA label files.
+
+.. autosummary::
+   :nosignatures:
+
+   loadLabelFile
+   saveLabelFile
+   isNoisyComponent
+   InvalidLabelFileError
+"""
+
+
+import os.path as op
+
+
+def loadLabelFile(filename, includeLabel=None, excludeLabel=None):
+    """Loads component labels from the specified file. The file is assuemd
+    to be of the format generated by FIX, Melview or ICA-AROMA; 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]
+
+
+    .. note:: This function will also parse files which only contain a
+              component list, e.g.::
+
+                  [2, 5, 6, 7]
+
+              The square brackets may or may not be present, i.e. the
+              following format is also accepted (this format is generated
+              by ICA-AROMA)::
+
+                  2, 5, 6, 7
+
+              In this case, the returned melodic directory path will be
+              ``None``.  The ``includeLabel`` and ``excludeLabel`` arguments
+              allow you to control the labels assigned to included/excluded
+              components.
+
+    
+    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.
+
+    :arg filename:     Name of the label file to load.
+
+    :arg includeLabel: If the file contains a single line containing a list
+                       component indices, this label will be used for the
+                       components in the list. Defaults to 'Unclassified
+                       noise' for FIX-like files, and 'Motion' for
+                       ICA-AROMA-like files.
+    
+    :arg excludeLabel: If the file contains a single line containing component
+                       indices, this label will be used for the components
+                       that are not in the list.  Defaults to 'Signal' for
+                       FIX-like files, and 'Unknown' for ICA-AROMA-like files.
+
+    :returns: A tuple containing the path to the melodic directory
+              as specified in the label file, and a list of lists, one
+              list per component, with each list containing the labels for
+              the corresponding component.
+    """
+
+    filename = op.abspath(filename)
+
+    with open(filename, 'rt') as f:
+        lines = f.readlines()
+
+    if len(lines) < 1:
+        raise InvalidLabelFileError('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 comma-separated list of noise
+    # components.
+    if len(lines) == 1:
+
+        line = lines[0]
+            
+        # if the list is contained in
+        # square brackets, we assume
+        # that it is a FIX output file,
+        # where included components have
+        # been classified as noise, and
+        # excluded components as signal.
+        # 
+        # Otherwise we assume that it
+        # is an AROMA file, where
+        # included components have
+        # been classified as being due
+        # to motion, and excluded
+        # components unclassified.
+        if includeLabel is None:
+            if line[0] == '[': includeLabel = 'Unclassified noise'
+            else:              includeLabel = 'Movement'
+                
+        if excludeLabel is None:
+            if line[0] == '[': excludeLabel = 'Signal'
+            else:              excludeLabel = 'Unknown'
+
+        # Remove any leading/trailing
+        # whitespace or brackets.
+        line = lines[0].strip(' []')
+
+        melDir     = None 
+        noisyComps = [int(i) for i in line.split(',')]
+        allLabels  = []
+
+        for i in range(max(noisyComps)):
+            if (i + 1) in noisyComps: allLabels.append([includeLabel])
+            else:                     allLabels.append([excludeLabel])
+
+    # Otherwise, we assume that
+    # it is a full label file.
+    else:
+
+        melDir     = lines[0]
+        noisyComps = lines[-1].strip(' []').split(',')
+        noisyComps = [c      for c in noisyComps if c != '']
+        noisyComps = [int(c) for c in noisyComps]
+        
+        # The melodic directory path should
+        # either be an absolute path, or
+        # be specified relative to the location
+        # of the label file.
+        if not op.isabs(melDir):
+            melDir = op.join(op.dirname(filename), melDir)
+        
+        # Parse the labels for every component
+        allLabels = []
+        for i, compLine in enumerate(lines[1:-1]):
+
+            tokens = compLine.split(',')
+            tokens = [t.strip() for t in tokens]
+
+            if len(tokens) < 3:
+                raise InvalidLabelFileError(
+                    'Invalid FIX classification file - '
+                    'line {}: {}'.format(i + 1, compLine))
+
+            try:
+                compIdx = int(tokens[0])
+                
+            except:
+                raise InvalidLabelFileError(
+                    'Invalid FIX classification file - '
+                    'line {}: {}'.format(i + 1, compLine))
+                    
+            compLabels = tokens[1:-1]
+
+            if compIdx != i + 1:
+                raise InvalidLabelFileError(
+                    'Invalid FIX classification file - wrong component '
+                    'number at line {}: {}'.format(i + 1, compLine))
+
+            allLabels.append(compLabels)
+
+    # Validate the labels against
+    # the noisy list - all components
+    # in the noisy list should not
+    # have 'signal' or 'unknown' labels
+    for i, labels in enumerate(allLabels):
+
+        comp  = i + 1
+        noise = isNoisyComponent(labels)
+
+        if noise and (comp not in noisyComps):
+            raise InvalidLabelFileError('Noisy component {} has invalid '
+                                        'labels: {}'.format(comp, labels))
+
+    for comp in noisyComps:
+        
+        i      = comp - 1
+        labels = allLabels[i]
+        noise  = isNoisyComponent(labels)
+        
+        if not noise:
+            raise InvalidLabelFileError('Noisy component {} is missing '
+                                        'a noise label'.format(comp)) 
+
+    return melDir, allLabels
+
+
+def saveLabelFile(allLabels, filename, dirname=None, listBad=True):
+    """Saves the given classification labels to the specified file. The
+    classifications are saved in the format described in the
+    :func:`loadLabelFile` method.
+
+    :arg allLabels: A list of lists, one list for each component, where
+                    each list contains the labels for the corresponding
+                    component.
+
+    :arg filename:  Name of the file to which the labels should be saved.
+
+    :arg dirname:   If provided, is output as the first line of the file.
+                    Intended to be a relative path to the MELODIC analysis
+                    directory with which this label file is associated.
+
+    :arg listBad:   If ``True`` (the default), the last line of the file
+                    will contain a comma separated list of components which
+                    are deemed 'noisy' (see :func:`isNoisyComponent`).
+    """
+    
+    lines      = []
+    noisyComps = []
+
+    # The first line - the melodic directory name
+    if dirname is not None:
+        lines.append(op.abspath(dirname))
+
+    # A line for each component
+    for i, labels in enumerate(allLabels):
+
+        comp   = i + 1
+        noise  = isNoisyComponent(labels)
+
+        # Make sure there are no
+        # commas in any label names
+        labels = [l.replace(',', '_') for l in labels]
+        tokens = [str(comp)] + labels + [str(noise)]
+
+        lines.append(', '.join(tokens))
+
+        if noise:
+            noisyComps.append(comp)
+
+    # A line listing the bad components
+    if listBad:
+        lines.append('[' + ', '.join([str(c) for c in noisyComps]) + ']')
+
+    with open(filename, 'wt') as f:
+        f.write('\n'.join(lines) + '\n')
+
+
+def isNoisyComponent(labels):
+    """Given a set of component labels, returns ``True`` if the component
+    is ultimately classified as noise, ``False`` otherwise.
+    """
+
+    labels = [l.lower() for l in labels]
+    noise  = ('signal' not in labels) and ('unknown' not in labels)
+
+    return noise
+    
+
+class InvalidLabelFileError(Exception):
+    """Exception raised by the :func:`loadLabelFile` function when an attempt
+    is made to load an invalid label file.
+    """
+    pass
diff --git a/fsl/data/melodicimage.py b/fsl/data/melodicimage.py
index 0dada8dc9c37b49eafbefd4dd9884c16cf81aaa2..948248ccc1f292c24bd4fddeeef10901d5aa1093 100644
--- a/fsl/data/melodicimage.py
+++ b/fsl/data/melodicimage.py
@@ -36,7 +36,6 @@ class MelodicImage(fslimage.Image):
        getMelodicDir
        getTopLevelAnalysisDir
        getDataFile
-       getICClassification
 
 
     The :attr:`tr` time of the ``MelodicImage`` may not be known when it is
@@ -70,7 +69,6 @@ class MelodicImage(fslimage.Image):
         self.__meldir     = meldir
         self.__melmix     = melanalysis.getComponentTimeSeries(  meldir)
         self.__melFTmix   = melanalysis.getComponentPowerSpectra(meldir)
-        self.__melICClass = mellabels  .MelodicClassification(   self)
 
         # Automatically set the
         # TR value if possible
@@ -83,10 +81,6 @@ class MelodicImage(fslimage.Image):
             if dataImage.is4DImage():
                 self.__tr = dataImage.pixdim[3]
 
-        # TODO load classifications if present
-        for i in range(self.numComponents()):
-            self.__melICClass.addLabel(i, 'Unknown')
-
 
     @property
     def tr(self):
@@ -163,10 +157,3 @@ class MelodicImage(fslimage.Image):
         function.
         """
         return melanalysis.getMeanFile(self.__meldir) 
-
-
-    def getICClassification(self):
-        """Return the :class:`.MelodicClassification` instance associated with
-        this ``MelodicImage``.
-        """
-        return self.__melICClass
diff --git a/fsl/data/melodiclabels.py b/fsl/data/melodiclabels.py
deleted file mode 100644
index add891dc51688a7a2a891058660526352b92b422..0000000000000000000000000000000000000000
--- a/fsl/data/melodiclabels.py
+++ /dev/null
@@ -1,549 +0,0 @@
-#!/usr/bin/env python
-#
-# melodiclabels.py - Loading/saving/managing melodic IC labels.
-#
-# Author: Paul McCarthy <pauldmccarthy@gmail.com>
-#
-"""This module provides the :class:`MelodicClassification` class, which is
-used to manage component classifications of a :class:`.MelodicImage`.
-
-A handful of standalone IO functions are also contained in this module, for
-saving/loading component classifications to/from file:
-
-.. autosummary::
-   :nosignatures:
-
-   loadLabelFile
-   saveLabelFile
-"""
-
-
-import logging
-import os.path as op
-
-import fsl.utils.notifier as notifier
-
-
-log = logging.getLogger(__name__)
-
-
-class MelodicClassification(notifier.Notifier):
-    """The ``MelodicClassification`` class is a convenience class for managing
-    a collection of component classification labels.
-
-
-    .. autosummary::
-       :nosignatures:
-
-       hasLabel
-       hasComponent
-       getLabels
-       getComponents
-       addLabel
-       addComponent
-       removeLabel
-       removeComponent
-       clearLabels
-       clearComponents
-
-    The ``MelodicClassification`` class uses the :class:`.Notifier` interface
-    to notify listeners about changes to the labels. Listeners can be 
-    registered to be notified on the following topics:
-
-      - ``added``:   A new label was added to a component.
-      - ``removed``: A label was removed from a component.
-
-    When either of these events occur, the value passed to registered
-    listeners will contain a list of ``(component, label)``) tuples,
-    which specify the labels that were added/removed.
-
-    .. note:: All component labels are internally stored as lower case;
-              their cased version (whatever is initially used) is accessible
-              via the :meth:`getDisplayLabel` method.
-    """
-
-    
-    def __init__(self, melimage):
-        """Create a ``MelodicClassification`` instance.
-
-        :arg melimage: A :class:`.MelodicImage` instance.
-        """
-
-        self.__melimage      = melimage
-        self.__ncomps        = melimage.numComponents()
-        self.__displayLabels = {}
-
-        # __labels is a list of lists, one list
-        # for each component, containing the
-        # labels for that component.
-        #
-        # __components is a dictionary of
-        #
-        #   { label : [component] } mappings
-        # 
-        # containing the same information, but
-        # making lookup by label a bit quicker.
-        #
-        # These are initialised in clear()
-        self.__labels        = None
-        self.__components    = None
-
-        self.clear()
-
-
-    def getDisplayLabel(self, label):
-        """Returns the display name for the given label. """
-        return self.__displayLabels.get(label.lower(), label)
-
-
-    def clear(self):
-        """Removes all labels from all components. """
-
-        self.__components = {}
-        self.__labels     = [[] for i in range(self.__ncomps)]
-        
-
-    def load(self, filename):
-        """Loads component labels from the specified file. See the
-        :func:`loadLabelFile` function.
-
-        .. note:: This method adds to, but does not replace, any existing
-                  component classifications stored by this
-                  ``MelodicClassification``. Call the :meth:`clear` method,
-                  before calling ``load``, if you want to discard any existing
-                  classifications.
-        """
-
-        # Read the labels in
-        _, allLabels = loadLabelFile(filename)
-
-        # More labels in the file than there are in
-        # melodic_IC - that doesn't make any sense.
-        if len(allLabels) > self.__ncomps:
-            raise InvalidLabelFileError(
-                '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
-        with self.skipAll(topic='added'):
-            for i, labels in enumerate(allLabels):
-                for label in labels:
-                    self.addLabel(i, label)
-
-    
-    def save(self, filename):
-        """Saves the component classifications stored by this
-        ``MeloidicClassification`` to the specified file. See the
-        :func:`saveMelodicLabelFile` function.
-        """
-
-        allLabels = []
-
-        for c in range(self.__ncomps):
-            labels = [self.getDisplayLabel(l) for l in self.getLabels(c)]
-            allLabels.append(labels)
-
-        saveLabelFile(self.__melimage.getMelodicDir(),
-                      allLabels,
-                      filename)
-
-
-    def getLabels(self, component):
-        """Returns all labels of the specified component. """
-        return list(self.__labels[component])
-
-
-    def getAllLabels(self):
-        """Returns all labels that are currently associated with any
-        component.
-        """
-        return list(self.__components.keys())
-
-
-    def hasLabel(self, component, label):
-        """Returns ``True`` if the specified component has the specified label,
-        ``False`` otherwise.
-        """
-        label = label.lower()
-        return label in self.__labels[component]
-    
-
-    def addLabel(self, component, label, notify=True):
-        """Adds the given label to the given component.
-
-        :arg notify: If ``True`` (the default), the :meth:`.Notifier.notify`
-                     method will be called, with the ``'added'`` topic.
-                     This parameter is only intended for uses interal to the
-                     ``MelodicClassification`` class.
-
-        :returns: ``True`` if the label was added, ``False`` if the label was
-                  already present.
-        """
-
-        display = label
-        label   = label.lower()
-        labels  = list(self.__labels[component])
-        comps   = list(self.__components.get(label, []))
-        
-        if label in labels:
-            return False
-
-        labels.append(label)
-        comps .append(component)
-
-        self.__displayLabels[label]     = display
-        self.__components[   label]     = comps        
-        self.__labels[       component] = labels
-
-        log.debug('Label added to component: {} <-> {}'.format(component,
-                                                               label))
-
-        if notify:
-            self.notify(topic='added', value=[(component, label)])
-
-        return True
- 
-
-    def removeLabel(self, component, label, notify=True):
-        """Removes the given label from the given component.
-
-        :returns: ``True`` if the label was removed, ``False`` if the 
-                  component did not have this label.
-        """
-
-        label  = label.lower()
-        labels = list(self.__labels[component])
-        comps  = list(self.__components.get(label, []))
-
-        if label not in labels:
-            return False
-
-        labels.remove(label)
-        comps .remove(component)
-
-        self.__components[label]     = comps        
-        self.__labels[    component] = labels
-
-        log.debug('Label removed from component: {} <-> {}'.format(component,
-                                                                   label))
-
-        if notify:
-            self.notify(topic='removed', value=[(component, label)])
-
-        return True
-
-
-    def clearLabels(self, component):
-        """Removes all labels from the given component. """
-        
-        labels  = self.getLabels(component)
-        removed = []
-
-        for label in labels:
-            if self.removeLabel(component, label, notify=False):
-                removed.append((component, label))
-
-        log.debug('Labels cleared from component: {}'.format(component))
-
-        if len(removed) > 0:
-            self.notify(topic='removed', value=removed)
-
-    
-    def getComponents(self, label):
-        """Returns a list of all components which have the given label. """
-        label = label.lower()
-        return list(self.__components.get(label, []))
-
-    
-    def hasComponent(self, label, component):
-        """Returns ``True`` if the given compoennt has the given label,
-        ``False`` otherwise.
-        """
-        return component in self.getComponents(label)
-
-    
-    def addComponent(self, label, component):
-        """Adds the given label to the given component. """
-        self.addLabel(component, label)
-
-
-    def removeComponent(self, label, component, notify=True):
-        """Removes the given label from the given component. """
-        self.removeLabel(component, label, notify)
-
-    
-    def clearComponents(self, label):
-        """Removes the given label from all components. """
-        
-        components = self.getComponents(label)
-        removed    = []
-
-        for comp in components:
-            if self.removeComponent(label, comp, notify=False):
-                removed.append((comp, label))
-
-        if len(removed) > 0:
-            self.notify(topic='removed', value=removed)
-
-
-def loadLabelFile(filename, includeLabel=None, excludeLabel=None):
-    """Loads component labels from the specified file. The file is assuemd
-    to be of the format generated by FIX, Melview or ICA-AROMA; 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]
-
-
-    .. note:: This function will also parse files which only contain a
-              component list, e.g.::
-
-                  [2, 5, 6, 7]
-
-              The square brackets may or may not be present, i.e. the
-              following format is also accepted (this format is generated
-              by ICA-AROMA)::
-
-                  2, 5, 6, 7
-
-              In this case, the returned melodic directory path will be
-              ``None``.  The ``includeLabel`` and ``excludeLabel`` arguments
-              allow you to control the labels assigned to included/excluded
-              components.
-
-    
-    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.
-
-    :arg filename:     Name of the label file to load.
-
-    :arg includeLabel: If the file contains a single line containing a list
-                       component indices, this label will be used for the
-                       components in the list. Defaults to 'Unclassified
-                       noise' for FIX-like files, and 'Motion' for
-                       ICA-AROMA-like files.
-    
-    :arg excludeLabel: If the file contains a single line containing component
-                       indices, this label will be used for the components
-                       that are not in the list.  Defaults to 'Signal' for
-                       FIX-like files, and 'Unknown' for ICA-AROMA-like files.
-
-    :returns: A tuple containing the path to the melodic directory
-              as specified in the label file, and a list of lists, one
-              list per component, with each list containing the labels for
-              the corresponding component.
-    """
-
-    filename = op.abspath(filename)
-
-    with open(filename, 'rt') as f:
-        lines = f.readlines()
-
-    if len(lines) < 1:
-        raise InvalidLabelFileError('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 comma-separated list of noise
-    # components.
-    if len(lines) == 1:
-
-        line = lines[0]
-            
-        # if the list is contained in
-        # square brackets, we assume
-        # that it is a FIX output file,
-        # where included components have
-        # been classified as noise, and
-        # excluded components as signal.
-        # 
-        # Otherwise we assume that it
-        # is an AROMA file, where
-        # included components have
-        # been classified as being due
-        # to motion, and excluded
-        # components unclassified.
-        if includeLabel is None:
-            if line[0] == '[': includeLabel = 'Unclassified noise'
-            else:              includeLabel = 'Movement'
-                
-        if excludeLabel is None:
-            if line[0] == '[': excludeLabel = 'Signal'
-            else:              excludeLabel = 'Unknown'
-
-        # Remove any leading/trailing
-        # whitespace or brackets.
-        line = lines[0].strip(' []')
-
-        melDir     = None 
-        noisyComps = [int(i) for i in line.split(',')]
-        allLabels  = []
-
-        for i in range(max(noisyComps)):
-            if (i + 1) in noisyComps: allLabels.append([includeLabel])
-            else:                     allLabels.append([excludeLabel])
-
-    # Otherwise, we assume that
-    # it is a full label file.
-    else:
-
-        melDir     = lines[0]
-        noisyComps = lines[-1].strip(' []').split(',')
-        noisyComps = [c      for c in noisyComps if c != '']
-        noisyComps = [int(c) for c in noisyComps]
-        
-        # The melodic directory path should
-        # either be an absolute path, or
-        # be specified relative to the location
-        # of the label file.
-        if not op.isabs(melDir):
-            melDir = op.join(op.dirname(filename), melDir)
-        
-        # Parse the labels for every component
-        # We do not add the labels as we go
-        # because, 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 InvalidLabelFileError(
-                    'Invalid FIX classification file - '
-                    'line {}: {}'.format(i + 1, compLine))
-
-            try:
-                compIdx = int(tokens[0])
-                
-            except:
-                raise InvalidLabelFileError(
-                    'Invalid FIX classification file - '
-                    'line {}: {}'.format(i + 1, compLine))
-                    
-            compLabels = tokens[1:-1]
-
-            if compIdx != i + 1:
-                raise InvalidLabelFileError(
-                    'Invalid FIX classification file - wrong component '
-                    'number at line {}: {}'.format(i + 1, compLine))
-
-            allLabels.append(compLabels)
-
-    # Validate the labels against
-    # the noisy list - all components
-    # in the noisy list should not
-    # have 'signal' or 'unknown' labels
-    for i, labels in enumerate(allLabels):
-
-        comp  = i + 1
-        noise = isNoisyComponent(labels)
-
-        if noise and (comp not in noisyComps):
-            raise InvalidLabelFileError('Noisy component {} has invalid '
-                                        'labels: {}'.format(comp, labels))
-
-    for comp in noisyComps:
-        
-        i      = comp - 1
-        labels = allLabels[i]
-        noise  = isNoisyComponent(labels)
-        
-        if not noise:
-            raise InvalidLabelFileError('Noisy component {} is missing '
-                                        'a noise label'.format(comp)) 
-
-    return melDir, allLabels
-
-
-def saveLabelFile(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:`loadLabelFile` 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
-        noise  = isNoisyComponent(labels)
-
-        # Make sure there are no
-        # commas in any label names
-        labels = [l.replace(',', '_') for l in labels]
-        tokens = [str(comp)] + labels + [str(noise)]
-
-        lines.append(', '.join(tokens))
-
-        if noise:
-            noisyComps.append(comp)
-
-    # A line listing the bad components
-    lines.append('[' + ', '.join([str(c) for c in noisyComps]) + ']')
-
-    with open(filename, 'wt') as f:
-        f.write('\n'.join(lines) + '\n')
-
-
-def isNoisyComponent(labels):
-    """Given a set of component labels, returns ``True`` if the component
-    is ultimately classified as noise, ``False`` otherwise.
-    """
-
-    labels = [l.lower() for l in labels]
-    noise  = ('signal' not in labels) and ('unknown' not in labels)
-
-    return noise
-    
-
-class InvalidLabelFileError(Exception):
-    """Exception raised by the :meth:`MelodicClassification.load` method and
-    the :func:`loadMelodicLabelFile` function when an attempt is made to load
-    an invalid label file.
-    """
-    pass
diff --git a/fsl/data/volumelabels.py b/fsl/data/volumelabels.py
new file mode 100644
index 0000000000000000000000000000000000000000..cf95150c40cefdb523bdcbec31d564813a8980c6
--- /dev/null
+++ b/fsl/data/volumelabels.py
@@ -0,0 +1,295 @@
+#!/usr/bin/env python
+#
+# volumelabels.py - The VolumeLabels class.
+#
+# Author: Paul McCarthy <pauldmccarthy@gmail.com>
+#
+"""This module provides the :class:`VolumeLabels` class, which is used to
+manage labels associated with the volumes of an :class:`.Image`.  This class
+is primarily used for managing component classifications of a
+:class:`.MelodicImage`.
+"""
+
+
+import logging
+
+import fsl.utils.notifier as notifier
+import fsl.data.fixlabels as fixlabels
+
+
+log = logging.getLogger(__name__)
+
+
+class VolumeLabels(notifier.Notifier):
+    """The ``VolumeLabels`` class is a convenience class for managing a
+    collection of classification labels fgor the volumes of an :class:`.Image`.
+    The ``VolumeLabels`` class refers to *volumes* as *components*, because
+    it was originally written to manage classification labels associated wirh
+    the components of a Melodic analysis.
+
+    The following methods are available on a ``VolumeLabels`` instance:
+
+    .. autosummary::
+       :nosignatures:
+
+       clear
+       load
+       save
+       hasLabel
+       hasComponent
+       getLabels
+       getAllLabels
+       getDisplayLabel
+       getComponents
+       addLabel
+       addComponent
+       removeLabel
+       removeComponent
+       clearLabels
+       clearComponents
+
+    The ``VolumeLabels`` class uses the :class:`.Notifier` interface
+    to notify listeners about changes to the labels. Listeners can be 
+    registered to be notified on the following topics:
+
+      - ``added``:   A new label was added to a component.
+      - ``removed``: A label was removed from a component.
+
+    When either of these events occur, the value passed to registered
+    listeners will contain a list of ``(component, label)``) tuples,
+    which specify the labels that were added/removed.
+
+    .. note:: All component labels are internally stored as lower case;
+              however, their cased version (whatever is initially used) is
+              accessible via the :meth:`getDisplayLabel` method.
+    """
+
+    
+    def __init__(self, nvolumes):
+        """Create a ``VolumeLabels`` instance.
+
+        :arg nvolumes: Number of volumes to be classified. This cannot be
+                       changed.
+        """
+
+        self.__ncomps        = nvolumes
+        self.__displayLabels = {}
+
+        # __labels is a list of lists, one list
+        # for each component, containing the
+        # labels for that component.
+        #
+        # __components is a dictionary of
+        #
+        #   { label : [component] } mappings
+        # 
+        # containing the same information, but
+        # making lookup by label a bit quicker.
+        #
+        # These are initialised in clear()
+        self.__labels     = None
+        self.__components = None
+
+        self.clear()
+
+
+    def getDisplayLabel(self, label):
+        """Returns the display name for the given label. """
+        return self.__displayLabels.get(label.lower(), label)
+
+
+    def clear(self):
+        """Removes all labels from all components. """
+
+        self.__components = {}
+        self.__labels     = [[] for i in range(self.__ncomps)]
+        
+
+    def load(self, filename):
+        """Loads component labels from the specified file. See the
+        :func:`.fixlabels.loadLabelFile` function.
+
+        .. note:: This method adds to, but does not replace, any existing
+                  component classifications stored by this
+                  ``VolumeLabels`` instance. Call the :meth:`clear` method,
+                  before calling ``load``, if you want to discard any existing
+                  classifications.
+        """
+
+        # Read the labels in
+        _, allLabels = fixlabels.loadLabelFile(filename)
+
+        # More labels in the file than there are in
+        # this labels instance - that doesn't make
+        # any sense.
+        if len(allLabels) > self.__ncomps:
+            raise fixlabels.InvalidLabelFileError(
+                'Wrong number of components in {}!'.format(filename))
+
+        # Less labels in the file than there are in
+        # this labels instance - 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 object
+        with self.skipAll(topic='added'):
+            for i, labels in enumerate(allLabels):
+                for label in labels:
+                    self.addLabel(i, label)
+
+    
+    def save(self, filename, dirname=None):
+        """Saves the component classifications stored by this ``VolumeLabels``
+        instance to the specified file. See the
+        :func:`.fixlabels.saveLabelFile` function.
+        """
+
+        allLabels = []
+
+        for c in range(self.__ncomps):
+            labels = [self.getDisplayLabel(l) for l in self.getLabels(c)]
+            allLabels.append(labels)
+
+        fixlabels.saveLabelFile(allLabels, filename, dirname=dirname)
+
+
+    def getLabels(self, component):
+        """Returns all labels of the specified component. """
+        return list(self.__labels[component])
+
+
+    def getAllLabels(self):
+        """Returns all labels that are currently associated with any
+        component.
+        """
+        return list(self.__components.keys())
+
+
+    def hasLabel(self, component, label):
+        """Returns ``True`` if the specified component has the specified label,
+        ``False`` otherwise.
+        """
+        label = label.lower()
+        return label in self.__labels[component]
+    
+
+    def addLabel(self, component, label, notify=True):
+        """Adds the given label to the given component.
+
+        :arg notify: If ``True`` (the default), the :meth:`.Notifier.notify`
+                     method will be called, with the ``'added'`` topic.
+                     This parameter is only intended for uses interal to the
+                     ``VolumeLabels`` class.
+
+        :returns: ``True`` if the label was added, ``False`` if the label was
+                  already present.
+        """
+
+        display = label
+        label   = label.lower()
+        labels  = list(self.__labels[component])
+        comps   = list(self.__components.get(label, []))
+        
+        if label in labels:
+            return False
+
+        labels.append(label)
+        comps .append(component)
+
+        self.__displayLabels[label]     = display
+        self.__components[   label]     = comps        
+        self.__labels[       component] = labels
+
+        log.debug('Label added to component: {} <-> {}'.format(component,
+                                                               label))
+
+        if notify:
+            self.notify(topic='added', value=[(component, label)])
+
+        return True
+ 
+
+    def removeLabel(self, component, label, notify=True):
+        """Removes the given label from the given component.
+
+        :returns: ``True`` if the label was removed, ``False`` if the 
+                  component did not have this label.
+        """
+
+        label  = label.lower()
+        labels = list(self.__labels[component])
+        comps  = list(self.__components.get(label, []))
+
+        if label not in labels:
+            return False
+
+        labels.remove(label)
+        comps .remove(component)
+
+        self.__components[label]     = comps        
+        self.__labels[    component] = labels
+
+        log.debug('Label removed from component: {} <-> {}'.format(component,
+                                                                   label))
+
+        if notify:
+            self.notify(topic='removed', value=[(component, label)])
+
+        return True
+
+
+    def clearLabels(self, component):
+        """Removes all labels from the given component. """
+        
+        labels  = self.getLabels(component)
+        removed = []
+
+        for label in labels:
+            if self.removeLabel(component, label, notify=False):
+                removed.append((component, label))
+
+        log.debug('Labels cleared from component: {}'.format(component))
+
+        if len(removed) > 0:
+            self.notify(topic='removed', value=removed)
+
+    
+    def getComponents(self, label):
+        """Returns a list of all components which have the given label. """
+        label = label.lower()
+        return list(self.__components.get(label, []))
+
+    
+    def hasComponent(self, label, component):
+        """Returns ``True`` if the given compoennt has the given label,
+        ``False`` otherwise.
+        """
+        return component in self.getComponents(label)
+
+    
+    def addComponent(self, label, component):
+        """Adds the given label to the given component. """
+        self.addLabel(component, label)
+
+
+    def removeComponent(self, label, component, notify=True):
+        """Removes the given label from the given component. """
+        self.removeLabel(component, label, notify)
+
+    
+    def clearComponents(self, label):
+        """Removes the given label from all components. """
+        
+        components = self.getComponents(label)
+        removed    = []
+
+        for comp in components:
+            if self.removeComponent(label, comp, notify=False):
+                removed.append((comp, label))
+
+        if len(removed) > 0:
+            self.notify(topic='removed', value=removed)