From 8b19e44950b481d897a56b787b498e833be9d129 Mon Sep 17 00:00:00 2001
From: Paul McCarthy <pauld.mccarthy@gmail.com>
Date: Tue, 18 Apr 2017 14:23:38 +0100
Subject: [PATCH] Fixes and adjustments to fixlabels.load/saveLabelFile -
 ability to specify labels which correspond to 'signal'.

---
 fsl/data/fixlabels.py    | 70 ++++++++++++++++++++++++++--------------
 fsl/data/melodicimage.py |  1 -
 2 files changed, 46 insertions(+), 25 deletions(-)

diff --git a/fsl/data/fixlabels.py b/fsl/data/fixlabels.py
index f948a307c..8cc0a99c9 100644
--- a/fsl/data/fixlabels.py
+++ b/fsl/data/fixlabels.py
@@ -62,8 +62,9 @@ def loadLabelFile(filename, includeLabel=None, excludeLabel=None):
       - One or more labels for the component (multiple labels must be
         comma-separated).
       - ``'True'`` if the component has been classified as *bad*,
-        ``'False'`` otherwise.
-
+        ``'False'`` otherwise. This field is optional - if the last
+        comma-separated token on a line is not equal (case-insensitive)
+        to ``True`` or ``False``, it is interpreted as a component label.
     
     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
@@ -74,7 +75,7 @@ def loadLabelFile(filename, includeLabel=None, excludeLabel=None):
     :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
+                       noise' for FIX-like files, and 'Movement' for
                        ICA-AROMA-like files.
     
     :arg excludeLabel: If the file contains a single line containing component
@@ -88,7 +89,8 @@ def loadLabelFile(filename, includeLabel=None, excludeLabel=None):
               the corresponding component.
     """
 
-    filename = op.abspath(filename)
+    signalLabels = None
+    filename     = op.abspath(filename)
 
     with open(filename, 'rt') as f:
         lines = f.readlines()
@@ -128,6 +130,8 @@ def loadLabelFile(filename, includeLabel=None, excludeLabel=None):
         if excludeLabel is None:
             if line[0] == '[': excludeLabel = 'Signal'
             else:              excludeLabel = 'Unknown'
+        else:
+            signalLabels = [excludeLabel]
 
         # Remove any leading/trailing
         # whitespace or brackets.
@@ -176,8 +180,11 @@ def loadLabelFile(filename, includeLabel=None, excludeLabel=None):
                 raise InvalidLabelFileError(
                     'Invalid FIX classification file - '
                     'line {}: {}'.format(i + 1, compLine))
-                    
-            compLabels = tokens[1:-1]
+
+            if tokens[-1].lower() in ('true', 'false'):
+                compLabels = tokens[1:-1]
+            else:
+                compLabels = tokens[1:]
 
             if compIdx != i + 1:
                 raise InvalidLabelFileError(
@@ -193,9 +200,10 @@ def loadLabelFile(filename, includeLabel=None, excludeLabel=None):
     for i, labels in enumerate(allLabels):
 
         comp  = i + 1
-        noise = isNoisyComponent(labels)
+        noise = isNoisyComponent(labels, signalLabels)
 
         if noise and (comp not in noisyComps):
+            print(signalLabels)
             raise InvalidLabelFileError('Noisy component {} has invalid '
                                         'labels: {}'.format(comp, labels))
 
@@ -203,7 +211,7 @@ def loadLabelFile(filename, includeLabel=None, excludeLabel=None):
         
         i      = comp - 1
         labels = allLabels[i]
-        noise  = isNoisyComponent(labels)
+        noise  = isNoisyComponent(labels, signalLabels)
         
         if not noise:
             raise InvalidLabelFileError('Noisy component {} is missing '
@@ -212,24 +220,31 @@ def loadLabelFile(filename, includeLabel=None, excludeLabel=None):
     return melDir, allLabels
 
 
-def saveLabelFile(allLabels, filename, dirname=None, listBad=True):
+def saveLabelFile(allLabels,
+                  filename,
+                  dirname=None,
+                  listBad=True,
+                  signalLabels=None):
     """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 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 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 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`).
 
-    :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`).
+    :arg signalLabels: Labels which should be deemed 'signal' - see the
+                       :func:`isNoisyComponent` function.
     """
     
     lines      = []
@@ -243,7 +258,7 @@ def saveLabelFile(allLabels, filename, dirname=None, listBad=True):
     for i, labels in enumerate(allLabels):
 
         comp   = i + 1
-        noise  = isNoisyComponent(labels)
+        noise  = isNoisyComponent(labels, signalLabels)
 
         # Make sure there are no
         # commas in any label names
@@ -263,13 +278,20 @@ def saveLabelFile(allLabels, filename, dirname=None, listBad=True):
         f.write('\n'.join(lines) + '\n')
 
 
-def isNoisyComponent(labels):
+def isNoisyComponent(labels, signalLabels=None):
     """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)
+    :arg signalLabels: Labels which are deemed signal. If a component has
+                       no labels in this list, it is deemed noise. Defaults
+                       to ``['Signal', 'Unknown']`.
+    """
+    if signalLabels is None:
+        signalLabels = ['signal', 'unknown']
+        
+    signalLabels = [l.lower() for l in signalLabels]
+    labels       = [l.lower() for l in labels]
+    noise        = not any([sl in labels for sl in signalLabels])
 
     return noise
     
diff --git a/fsl/data/melodicimage.py b/fsl/data/melodicimage.py
index 948248ccc..61931a3e4 100644
--- a/fsl/data/melodicimage.py
+++ b/fsl/data/melodicimage.py
@@ -14,7 +14,6 @@ import os.path as op
 
 from . import image           as fslimage
 from . import melodicanalysis as melanalysis
-from . import melodiclabels   as mellabels
 
 
 class MelodicImage(fslimage.Image):
-- 
GitLab