From 064608c6833b0342fb17f562fcfcd30e354f831f Mon Sep 17 00:00:00 2001
From: Paul McCarthy <pauld.mccarthy@gmail.com>
Date: Mon, 4 Apr 2016 13:45:52 +0100
Subject: [PATCH] Python 2-3 compatibility.

---
 fsl/data/atlases.py      |  6 +++---
 fsl/data/featimage.py    |  8 ++++----
 fsl/data/featresults.py  | 22 +++++++++-------------
 fsl/data/image.py        |  7 ++++---
 fsl/data/melodicimage.py |  4 ++--
 fsl/data/model.py        |  6 +++++-
 fsl/data/tensorimage.py  | 14 ++++++++------
 fsl/utils/async.py       |  7 ++++---
 fsl/utils/dialog.py      | 23 +++++++++++++++++++----
 fsl/utils/imagepanel.py  | 19 +++++++++++++++----
 fsl/utils/memoize.py     |  4 +++-
 fsl/utils/notifier.py    |  2 +-
 fsl/utils/path.py        | 14 +++++++-------
 fsl/utils/platform.py    | 25 ++++++++++++++++++++-----
 fsl/utils/runwindow.py   |  4 +++-
 fsl/utils/typedict.py    |  6 ++++--
 16 files changed, 111 insertions(+), 60 deletions(-)

diff --git a/fsl/data/atlases.py b/fsl/data/atlases.py
index 4c53811dc..a17ca031b 100644
--- a/fsl/data/atlases.py
+++ b/fsl/data/atlases.py
@@ -100,7 +100,7 @@ log = logging.getLogger(__name__)
 
 
 def listAtlases(refresh=False):
-    """Returns a dictionary containing :class:`AtlasDescription` objects for
+    """Returns a list containing :class:`AtlasDescription` objects for
     all available atlases.
 
     :arg refresh: If ``True``, or if the atlas desriptions have not
@@ -119,7 +119,7 @@ def listAtlases(refresh=False):
         refresh = True
 
     if not refresh:
-        return ATLAS_DESCRIPTIONS.values()
+        return list(ATLAS_DESCRIPTIONS.values())
 
     atlasFiles = glob.glob(op.join(ATLAS_DIR, '*.xml'))
     atlasDescs = map(AtlasDescription, atlasFiles)
@@ -131,7 +131,7 @@ def listAtlases(refresh=False):
         desc.index                       = i
         ATLAS_DESCRIPTIONS[desc.atlasID] = desc
 
-    return atlasDescs
+    return list(atlasDescs)
 
 
 def getAtlasDescription(atlasID):
diff --git a/fsl/data/featimage.py b/fsl/data/featimage.py
index 59cc9ce89..7befd0d06 100644
--- a/fsl/data/featimage.py
+++ b/fsl/data/featimage.py
@@ -9,12 +9,12 @@
 """
 
 
-import os.path as op
+import os.path      as op
 
-import numpy   as np
+import numpy        as np
 
-import image   as fslimage
-import            featresults
+from . import image as fslimage
+from . import          featresults
 
 
 class FEATImage(fslimage.Image):
diff --git a/fsl/data/featresults.py b/fsl/data/featresults.py
index 4772f05b0..df2acbe50 100644
--- a/fsl/data/featresults.py
+++ b/fsl/data/featresults.py
@@ -414,7 +414,7 @@ def loadClusterResults(featdir, settings, contrast):
         # empty lines
         lines = f.readlines()
         lines = [l.strip() for l in lines]
-        lines = filter(lambda l: l != '', lines)
+        lines = [l         for l in lines if l != '']
 
         # the first line should contain column
         # names, and each other line should
@@ -554,19 +554,15 @@ def getEVNames(settings):
     :arg settings: A FEAT settings dictionary (see :func:`loadSettings`). 
     """
 
-    numEVs = int(settings['evs_real'])
+    numEVs    = int(settings['evs_real'])
+    titleKeys = [s for s in settings.keys() if s.startswith('evtitle')]
+    derivKeys = [s for s in settings.keys() if s.startswith('deriv_yn')]
 
-    titleKeys = filter(lambda s: s.startswith('evtitle'),  settings.keys())
-    derivKeys = filter(lambda s: s.startswith('deriv_yn'), settings.keys())
-
-    def _cmp(key1, key2):
-        key1 = ''.join([c for c in key1 if c.isdigit()])
-        key2 = ''.join([c for c in key2 if c.isdigit()])
-
-        return cmp(int(key1), int(key2))
-
-    titleKeys = sorted(titleKeys, cmp=_cmp)
-    derivKeys = sorted(derivKeys, cmp=_cmp)
+    def key(k):
+        return int(''.join([c for c in k if c.isdigit()]))
+        
+    titleKeys = sorted(titleKeys, key=key)
+    derivKeys = sorted(derivKeys, key=key)
     evnames  = []
 
     for titleKey, derivKey in zip(titleKeys, derivKeys):
diff --git a/fsl/data/image.py b/fsl/data/image.py
index cc8ab09f8..a9975f5a7 100644
--- a/fsl/data/image.py
+++ b/fsl/data/image.py
@@ -38,6 +38,7 @@ import               os
 import os.path    as op
 import subprocess as sp
 
+import               six 
 import numpy      as np
 import nibabel    as nib
 
@@ -117,7 +118,7 @@ class Nifti1(object):
             header = header.copy()
 
         # The image parameter may be the name of an image file
-        if isinstance(image, basestring):
+        if isinstance(image, six.string_types):
             
             nibImage, filename = loadImage(addExt(image))
             self.nibImage      = nibImage
@@ -380,7 +381,7 @@ class Image(Nifti1, props.HasProperties):
             
         # Or, if this image was loaded 
         # from disk, use the file name
-        elif isinstance(image, basestring):
+        elif isinstance(image, six.string_types):
             self.name  = removeExt(op.basename(self.dataSource))
             self.saved = True
             
@@ -594,7 +595,7 @@ def looksLikeImage(filename, allowedExts=None):
     # TODO A much more robust approach would be
     #      to try loading the file using nibabel.
 
-    return any(map(lambda ext: filename.endswith(ext), allowedExts))
+    return any([filename.endswith(ext) for ext in allowedExts])
 
 
 def removeExt(filename):
diff --git a/fsl/data/melodicimage.py b/fsl/data/melodicimage.py
index 21bc8005e..fc0cb0670 100644
--- a/fsl/data/melodicimage.py
+++ b/fsl/data/melodicimage.py
@@ -14,8 +14,8 @@ import os.path as op
 
 import props
 
-import image          as fslimage
-import melodicresults as melresults
+from . import image          as fslimage
+from . import melodicresults as melresults
 
 
 class MelodicImage(fslimage.Image):
diff --git a/fsl/data/model.py b/fsl/data/model.py
index 3def2dff3..ededc41a5 100644
--- a/fsl/data/model.py
+++ b/fsl/data/model.py
@@ -21,6 +21,8 @@ import logging
 import os.path as op
 import numpy   as np
 
+import six
+
 
 log = logging.getLogger(__name__)
 
@@ -43,7 +45,7 @@ class Model(object):
         :arg indices: A list of indices into the vertex data.
         """
 
-        if isinstance(data, basestring):
+        if isinstance(data, six.string_types):
             infile = data
             data, lengths, indices = loadVTKPolydataFile(infile)
 
@@ -131,6 +133,7 @@ def loadVTKPolydataFile(infile):
     for i in range(nVertices):
         vertLine       = lines[i + 5]
         vertices[i, :] = map(float, vertLine.split())
+        vertices[i, :] = [float(w) for w in vertLine.split()]
 
     indexOffset = 0
     for i in range(nPolygons):
@@ -141,6 +144,7 @@ def loadVTKPolydataFile(infile):
         start              = indexOffset
         end                = indexOffset + polygonLengths[i]
         indices[start:end] = map(int, polyLine[1:])
+        indices[start:end] = [int(w) for w in polyLine[1:]]
 
         indexOffset        += polygonLengths[i]
 
diff --git a/fsl/data/tensorimage.py b/fsl/data/tensorimage.py
index 4d194b480..86ae221cb 100644
--- a/fsl/data/tensorimage.py
+++ b/fsl/data/tensorimage.py
@@ -9,12 +9,14 @@ the diffusion tensor data generated by the FSL ``dtifit`` tool.
 """
 
 
-import                   logging
-import                   re
-import                   glob
-import os.path        as op
+import                 logging
+import                 re
+import                 glob
+import os.path      as op
 
-import fsl.data.image as fslimage
+import                 six
+
+from . import image as fslimage
 
 
 log = logging.getLogger(__name__)
@@ -101,7 +103,7 @@ class TensorImage(fslimage.Nifti1):
                    eigenvalues.
         """
 
-        dtifitDir = isinstance(path, basestring)
+        dtifitDir = isinstance(path, six.string_types)
 
         if dtifitDir:
 
diff --git a/fsl/utils/async.py b/fsl/utils/async.py
index 801d0debb..672102102 100644
--- a/fsl/utils/async.py
+++ b/fsl/utils/async.py
@@ -33,11 +33,12 @@ the task (via :func:`idle`).
 
 
 import time
-import Queue
 import logging
 import threading
 import collections
 
+try:    import queue
+except: import Queue as queue
 
 log = logging.getLogger(__name__)
 
@@ -114,7 +115,7 @@ in the :func:`idle` function.
 """
 
 
-_idleQueue = Queue.Queue()
+_idleQueue = queue.Queue()
 """A ``Queue`` of functions which are to be run on the ``wx.EVT_IDLE``
 loop.
 """
@@ -129,7 +130,7 @@ def _wxIdleLoop(ev):
 
     try:
         task, schedtime, timeout, args, kwargs = _idleQueue.get_nowait()
-    except Queue.Empty:
+    except queue.Empty:
         return
 
     name    = getattr(task, '__name__', '<unknown>')
diff --git a/fsl/utils/dialog.py b/fsl/utils/dialog.py
index a6e277f63..0a482defc 100644
--- a/fsl/utils/dialog.py
+++ b/fsl/utils/dialog.py
@@ -22,7 +22,7 @@ import threading
 
 import wx
 
-from fsl.utils.platform import platform as fslplatform
+from .platform import platform as fslplatform
 
 
 class SimpleMessageDialog(wx.Dialog):
@@ -455,7 +455,12 @@ class TextEditDialog(wx.Dialog):
         if icon is not None:
             
             icon = wx.ArtProvider.GetMessageBoxIcon(icon)
-            bmp  = wx.EmptyBitmap(icon.GetWidth(), icon.GetHeight())
+
+            if fslplatform.wxFlavour == fslplatform.WX_PHOENIX:
+                bmp = wx.Bitmap()
+            else:
+                bmp = wx.EmptyBitmap(icon.GetWidth(), icon.GetHeight())
+                
             bmp.CopyFromIcon(icon)
             self.__icon = wx.StaticBitmap(self)
             self.__icon.SetBitmap(bmp)
@@ -634,7 +639,12 @@ class FSLDirDialog(wx.Dialog):
         self.__skip    = wx.Button(      self, id=wx.ID_CANCEL)
 
         icon = wx.ArtProvider.GetMessageBoxIcon(wx.ICON_EXCLAMATION)
-        bmp  = wx.EmptyBitmap(icon.GetWidth(), icon.GetHeight())
+
+        if fslplatform.wxFlavour == fslplatform.WX_PYTHON:
+            bmp  = wx.EmptyBitmap(icon.GetWidth(), icon.GetHeight())
+        else:
+            bmp = wx.Bitmap()
+            
         bmp.CopyFromIcon(icon)
 
         self.__icon.SetBitmap(bmp)
@@ -756,7 +766,12 @@ class CheckBoxMessageDialog(wx.Dialog):
         if icon is not None:
             icon = wx.ArtProvider.GetMessageBoxIcon(icon) 
             self.__icon = wx.StaticBitmap(self)
-            bmp  = wx.EmptyBitmap(icon.GetWidth(), icon.GetHeight())
+
+            if fslplatform.wxFlavour == fslplatform.WX_PYTHON:
+                bmp = wx.EmptyBitmap(icon.GetWidth(), icon.GetHeight())
+            else:
+                bmp = wx.Bitmap()
+                
             bmp.CopyFromIcon(icon)
             self.__icon.SetBitmap(bmp)
         else:
diff --git a/fsl/utils/imagepanel.py b/fsl/utils/imagepanel.py
index cf74b453f..5352bd100 100644
--- a/fsl/utils/imagepanel.py
+++ b/fsl/utils/imagepanel.py
@@ -12,12 +12,18 @@ import logging
 
 import wx
 
+from fsl.utils.platform import platform as fslplatform
+
 
 log = logging.getLogger(__name__)
 
 
-class ImagePanel(wx.PyPanel):
-    """A :class:`wx.PyPanel` which may be used to display a resizeable
+if fslplatform.wxFlavour == fslplatform.WX_PHOENIX: ImagePanelBase = wx.Panel
+else:                                               ImagePanelBase = wx.PyPanel
+
+
+class ImagePanel(ImagePanelBase):
+    """A :class:`wx.Panel` which may be used to display a resizeable
     :class:`wx.Image`. The image is scaled to the size of the panel.
     """
 
@@ -32,7 +38,7 @@ class ImagePanel(wx.PyPanel):
         :arg image:  The :class:`wx.Image` object to display.
         """
 
-        wx.PyPanel.__init__(self, parent)
+        ImagePanelBase.__init__(self, parent)
 
         self.Bind(wx.EVT_PAINT, self.Draw)
         self.Bind(wx.EVT_SIZE,  self.__onSize)
@@ -46,6 +52,11 @@ class ImagePanel(wx.PyPanel):
         :arg image: The :class:`wx.Image` object to display.
         """
         self.__image = image
+
+
+        if image is not None: self.SetMinSize(image.GetSize())
+        else:                 self.SetMinSize((0, 0))
+        
         self.Refresh()
 
         
@@ -86,6 +97,6 @@ class ImagePanel(wx.PyPanel):
         if width == 0 or height == 0:
             return
 
-        bitmap = wx.BitmapFromImage(self.__image.Scale(width, height))
+        bitmap = self.__image.Scale(width, height).ConvertToBitmap()
         
         dc.DrawBitmap(bitmap, 0, 0, False)
diff --git a/fsl/utils/memoize.py b/fsl/utils/memoize.py
index 07f6f1242..9de2ef726 100644
--- a/fsl/utils/memoize.py
+++ b/fsl/utils/memoize.py
@@ -18,6 +18,7 @@ a function:
 
 import hashlib
 import functools
+import six
 
 
 def memoizeMD5(func):
@@ -35,7 +36,8 @@ def memoizeMD5(func):
         hashobj = hashlib.md5()
 
         for arg in args:
-            hashobj.update(str(arg))
+            arg = six.u(arg).encode('utf-8')
+            hashobj.update(arg)
 
         digest = hashobj.hexdigest()
         cached = cache.get(digest)
diff --git a/fsl/utils/notifier.py b/fsl/utils/notifier.py
index 154f67521..faa3fb7e6 100644
--- a/fsl/utils/notifier.py
+++ b/fsl/utils/notifier.py
@@ -34,7 +34,7 @@ class Notifier(object):
         """Initialises a dictionary of listeners on a new ``Notifier``
         instance.
         """
-        new             = object.__new__(cls, *args, **kwargs)
+        new             = object.__new__(cls)
         new.__listeners = collections.OrderedDict()
 
         return new
diff --git a/fsl/utils/path.py b/fsl/utils/path.py
index cacc25b0f..408b77c23 100644
--- a/fsl/utils/path.py
+++ b/fsl/utils/path.py
@@ -91,8 +91,8 @@ def addExt(prefix, allowedExts, mustExist=True, defaultExt=None):
     if not mustExist:
 
         # the provided file name already
-        # ends with a supported extension 
-        if any(map(lambda ext: prefix.endswith(ext), allowedExts)):
+        # ends with a supported extension
+        if any([prefix.endswith(ext) for ext in allowedExts]):
             return prefix
 
         if defaultExt is not None: return prefix + defaultExt
@@ -100,16 +100,16 @@ def addExt(prefix, allowedExts, mustExist=True, defaultExt=None):
 
     # If the provided prefix already ends with a
     # supported extension , check to see that it exists
-    if any(map(lambda ext: prefix.endswith(ext), allowedExts)):
+    if any([prefix.endswith(ext) for ext in allowedExts]):
         extended = [prefix]
         
     # Otherwise, make a bunch of file names, one per
     # supported extension, and test to see if exactly
     # one of them exists.
     else:
-        extended = map(lambda ext: prefix + ext, allowedExts)
+        extended = [prefix + ext for ext in allowedExts]
 
-    exists = map(op.isfile, extended)
+    exists = [op.isfile(e) for e in extended]
 
     # Could not find any supported file
     # with the specified prefix
@@ -119,7 +119,7 @@ def addExt(prefix, allowedExts, mustExist=True, defaultExt=None):
 
     # Ambiguity! More than one supported
     # file with the specified prefix
-    if len(filter(bool, exists)) > 1:
+    if sum(exists) > 1:
         raise ValueError('More than one file with prefix {}'.format(prefix))
 
     # Return the full file name of the
@@ -139,7 +139,7 @@ def removeExt(filename, allowedExts):
     """
 
     # figure out the extension of the given file
-    extMatches = map(lambda ext: filename.endswith(ext), allowedExts)
+    extMatches = [filename.endswith(ext) for ext in allowedExts]
 
     # the file does not have a supported extension
     if not any(extMatches):
diff --git a/fsl/utils/platform.py b/fsl/utils/platform.py
index 2510f8830..9b95faf96 100644
--- a/fsl/utils/platform.py
+++ b/fsl/utils/platform.py
@@ -8,17 +8,14 @@
 of information about the current platform we are running on. A single
 ``Platform`` instance is created when this module is first imported, and
 is available as a module attribute called :attr:`platform`.
-
-.. note:: The ``Platform`` class only contains information which is not
-          already accessible from the built-in ``platform`` module
-          (e.g. operating system information), or the ``six`` module (e.g.
-          python 2 vs 3).
 """
 
 
 import logging
 
 import os
+import sys
+import platform as builtin_platform
 
 import fsl.utils.notifier as notifier
 
@@ -67,6 +64,8 @@ class Platform(notifier.Notifier):
 
     .. autosummary::
 
+       os
+       frozen
        fsldir
        haveGui
        wxPlatform
@@ -125,6 +124,22 @@ class Platform(notifier.Notifier):
                 log.warning('Could not determine wx platform from '
                             'information: {}'.format(pi))
 
+                
+    @property
+    def os(self):
+        """The operating system name. Whatever is returned by the built-in
+        ``platform.system`` function.
+        """
+        return builtin_platform.system()
+
+    
+    @property
+    def frozen(self):
+        """``True`` if we are running in a compiled/frozen application,
+        ``False`` otherwise.
+        """
+        return getattr(sys, 'frozen', False)
+
 
     @property
     def haveGui(self):
diff --git a/fsl/utils/runwindow.py b/fsl/utils/runwindow.py
index f344c1483..a26969e9f 100644
--- a/fsl/utils/runwindow.py
+++ b/fsl/utils/runwindow.py
@@ -26,7 +26,9 @@ import logging
 
 import subprocess as subp
 import threading  as thread
-import Queue      as queue
+
+try:    import queue
+except: import Queue as queue
 
 import wx
 
diff --git a/fsl/utils/typedict.py b/fsl/utils/typedict.py
index 0a498ca74..0ac4cdcde 100644
--- a/fsl/utils/typedict.py
+++ b/fsl/utils/typedict.py
@@ -8,6 +8,8 @@
 """
 
 
+import six
+
 import collections
 
 
@@ -164,7 +166,7 @@ class TypeDict(object):
         string, or a combination of strings and types, into a tuple.
         """
 
-        if isinstance(key, basestring):
+        if isinstance(key, six.string_types):
             if '.' in key: return tuple(key.split('.'))
             else:          return key
 
@@ -174,7 +176,7 @@ class TypeDict(object):
             key   = []
 
             for tk in tKeys:
-                if   isinstance(tk, basestring):           key.append(tk)
+                if   isinstance(tk, six.string_types):     key.append(tk)
                 elif isinstance(tk, collections.Sequence): key += list(tk)
                 else:                                      key.append(tk)
             
-- 
GitLab