From 37cdaf1f1a34e8212b39cd6eb80ab5899b8d3025 Mon Sep 17 00:00:00 2001
From: Paul McCarthy <pauld.mccarthy@gmail.com>
Date: Fri, 18 Mar 2016 16:18:43 +0000
Subject: [PATCH] Moved feat/melodic directory search routines to a separate
 generic module. Hopefully fixed TimeSeries confusion w.r.t. nested .feat/.ica
 directories

---
 fsl/data/featresults.py              | 46 ++++-----------------
 fsl/data/melodicresults.py           | 24 ++++++++++-
 fsl/fsleyes/views/timeseriespanel.py | 14 ++++++-
 fsl/utils/path.py                    | 62 ++++++++++++++++++++++++++++
 4 files changed, 106 insertions(+), 40 deletions(-)
 create mode 100644 fsl/utils/path.py

diff --git a/fsl/data/featresults.py b/fsl/data/featresults.py
index 2d1015f60..00fb3c722 100644
--- a/fsl/data/featresults.py
+++ b/fsl/data/featresults.py
@@ -48,6 +48,7 @@ import os.path             as op
 import numpy               as np
 
 import fsl.data.image      as fslimage
+import fsl.utils.path      as fslpath
 import fsl.utils.transform as transform
 
 
@@ -120,49 +121,20 @@ def getAnalysisDir(path):
     """If the given path is contained within a FEAT directory, the path
     to that FEAT directory is returned. Otherwise, ``None`` is returned.
     """
+    featdir = fslpath.deepest(path, ['.feat', '.gfeat'])
 
-    # This is basically the opposite to
-    # the getTopLevelAnalysisDir function
+    if featdir is not None and isFEATDir(featdir):
+        return featdir
 
-    path = path.strip()
-
-    if path == op.sep or path == '':
-        return None
-
-    path = path.rstrip(op.sep)
-
-    sufs = ['.feat', '.gfeat']
-
-    if any([path.endswith(suf) for suf in sufs]):
-        return path
-
-    return getAnalysisDir(op.dirname(path))
+    return None
 
 
 def getTopLevelAnalysisDir(path):
-    """If the given path is contained within a FEAT directory, or a
-    MELODIC directory, the path to the latter directory is returned.
-    Otherwise, ``None`` is returned.
+    """If the given path is contained within a hierarchy of FEAT or MELODIC
+    directories, the path to the highest-level (i.e. the shallowest in the
+    file system) directory is returned. Otherwise, ``None`` is returned.
     """
-
-    path = path.strip()
-
-    # We've reached the root of the file system
-    if path == op.sep or path == '':
-        return None
-
-    path   = path.rstrip(op.sep)
-    parent = getTopLevelAnalysisDir(op.dirname(path))
-
-    if parent is not None:
-        return parent
-
-    sufs = ['.ica', '.gica', '.feat', '.gfeat']
-
-    if any([path.endswith(suf) for suf in sufs]):
-        return path
-
-    return None
+    return fslpath.shallowest(path, ['.ica', '.gica', '.feat', '.gfeat'])
 
 
 def loadDesign(featdir):
diff --git a/fsl/data/melodicresults.py b/fsl/data/melodicresults.py
index c663aa9b4..4c46c57f5 100644
--- a/fsl/data/melodicresults.py
+++ b/fsl/data/melodicresults.py
@@ -16,6 +16,7 @@ following functions are provided:
 
    isMelodicImage
    isMelodicDir
+   getAnalysisDir
    getTopLevelAnalysisDir
    getDataFile
    getICFile
@@ -38,6 +39,7 @@ import numpy   as np
 
 import props
 
+import fsl.utils.path       as fslpath
 import fsl.data.image       as fslimage
 import fsl.data.featresults as featresults
 
@@ -91,7 +93,27 @@ def isMelodicDir(path):
     return True
 
 
-getTopLevelAnalysisDir = featresults.getTopLevelAnalysisDir
+def getAnalysisDir(path):
+    """If the given path is contained within a MELODIC directory, the path
+    to that MELODIC directory is returned. Otherwise, ``None`` is returned.
+    """
+
+    meldir = fslpath.deepest(path, ['.ica', '.gica'])
+
+    if meldir is not None and isMelodicDir(meldir):
+        return meldir
+    
+    return None
+
+
+def getTopLevelAnalysisDir(path):
+    """If the given path is contained within a hierarchy of FEAT or MELODIC
+    directories, the path to the highest-level (i.e. the shallowest in the
+    file system) directory is returned. Otherwise, ``None`` is returned.
+
+    See :func:`.featresults.getTopLevelAnalysisDir`.
+    """ 
+    return featresults.getTopLevelAnalysisDir(path)
 
     
 def getDataFile(meldir):
diff --git a/fsl/fsleyes/views/timeseriespanel.py b/fsl/fsleyes/views/timeseriespanel.py
index b68a7880b..9b38b0425 100644
--- a/fsl/fsleyes/views/timeseriespanel.py
+++ b/fsl/fsleyes/views/timeseriespanel.py
@@ -17,8 +17,9 @@ import                                                props
 
 import                                                plotpanel
 import fsl.data.featimage                          as fslfeatimage
-import fsl.data.featresults                        as featresults
 import fsl.data.melodicimage                       as fslmelimage
+import fsl.data.featresults                        as featresults
+import fsl.data.melodicresults                     as melresults
 import fsl.data.image                              as fslimage
 import fsl.fsleyes.actions                         as actions
 import fsl.fsleyes.plotting                        as plotting
@@ -252,9 +253,18 @@ class TimeSeriesPanel(plotpanel.OverlayPlotPanel):
 
         if overlay.dataSource is not None:
             featPath = featresults.getAnalysisDir(overlay.dataSource)
+            melPath  = melresults .getAnalysisDir(overlay.dataSource)
         else:
             featPath = None
-
+            melPath  = None
+
+        # If this overlay is from an .ica dir
+        # inside a .feat dir or vice versa,
+        # the longer/deeper path takes precedence.
+        if (featPath is not None) and (melPath is not None):
+            if len(melPath) > len(featPath): featPath = None
+            else:                            melPath  = None
+            
         # Is this a FEAT filtered_func_data image,
         # or an image in a FEAT directory?
         if isinstance(overlay, fslfeatimage.FEATImage) or featPath is not None:
diff --git a/fsl/utils/path.py b/fsl/utils/path.py
new file mode 100644
index 000000000..4c22cd410
--- /dev/null
+++ b/fsl/utils/path.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python
+#
+# path.py - Utility functions for working with file/directory paths.
+#
+# Author: Paul McCarthy <pauldmccarthy@gmail.com>
+#
+"""This module contains a few utility functions for working with file system
+paths.
+
+
+.. autosummary::
+   :nosignatures:
+
+   deepest
+   shallowest 
+"""
+
+
+import os.path as op
+
+
+def deepest(path, suffixes):
+    """Finds the deepest directory which ends with one of the given
+    sequence of suffixes, or returns ``None`` if no directories end
+    with any of the suffixes.
+    """
+
+    path = path.strip()
+
+    if path == op.sep or path == '':
+        return None
+
+    path = path.rstrip(op.sep)
+
+    if any([path.endswith(s) for s in suffixes]):
+        return path
+
+    return deepest(op.dirname(path), suffixes)
+
+
+def shallowest(path, suffixes):
+    """Finds the shallowest directory which ends with one of the given
+    sequence of suffixes, or returns ``None`` if no directories end
+    with any of the suffixes.
+    """ 
+    
+    path = path.strip()
+
+    # We've reached the root of the file system
+    if path == op.sep or path == '':
+        return None
+
+    path   = path.rstrip(op.sep)
+    parent = shallowest(op.dirname(path), suffixes)
+
+    if parent is not None:
+        return parent
+
+    if any([path.endswith(s) for s in suffixes]):
+        return path
+
+    return None 
-- 
GitLab