From c4983874591d324ea25a4632e891400bee5b2b77 Mon Sep 17 00:00:00 2001
From: unknown <geo_beja@PC-12-3465.geo.hu-berlin.de>
Date: Tue, 10 Nov 2015 14:53:49 +0100
Subject: [PATCH] included qimage2ndarray from
 https://github.com/hmeine/qimage2ndarray

---
 .idea/vcs.xml                       |   2 +-
 qimage2ndarray/LICENSE.txt          |  30 +++
 qimage2ndarray/__init__.py          | 403 ++++++++++++++++++++++++++++
 qimage2ndarray/dynqt.py             |   4 +
 qimage2ndarray/qimageview_python.py |  60 +++++
 qimage2ndarray/qt_driver.py         | 125 +++++++++
 6 files changed, 623 insertions(+), 1 deletion(-)
 create mode 100644 qimage2ndarray/LICENSE.txt
 create mode 100644 qimage2ndarray/__init__.py
 create mode 100644 qimage2ndarray/dynqt.py
 create mode 100644 qimage2ndarray/qimageview_python.py
 create mode 100644 qimage2ndarray/qt_driver.py

diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 94a25f7f..35eb1ddf 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
   <component name="VcsDirectoryMappings">
-    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+    <mapping directory="" vcs="Git" />
   </component>
 </project>
\ No newline at end of file
diff --git a/qimage2ndarray/LICENSE.txt b/qimage2ndarray/LICENSE.txt
new file mode 100644
index 00000000..96d599da
--- /dev/null
+++ b/qimage2ndarray/LICENSE.txt
@@ -0,0 +1,30 @@
+Copyright (c) 2009, Hans Meine <hans_meine@gmx.net>
+All rights reserved. 
+ 
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ 
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+ 
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the
+   distribution.
+ 
+3. Neither the name of the University of Hamburg nor the names of its
+   contributors may be used to endorse or promote products derived
+   from this software without specific prior written permission.
+ 
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/qimage2ndarray/__init__.py b/qimage2ndarray/__init__.py
new file mode 100644
index 00000000..6e56e0e0
--- /dev/null
+++ b/qimage2ndarray/__init__.py
@@ -0,0 +1,403 @@
+import sys as _sys
+import numpy as _np
+
+from .dynqt import QtGui as _qt
+
+from .dynqt import qt as _qt_driver
+if _qt_driver.name() == 'PythonQt':
+    from .qimageview import QImage2ndarray as _temp
+    _qimageview = _temp.qimageview
+else:
+    from .qimageview_python import qimageview as _qimageview
+
+__version__ = "1.5"
+
+if _sys.byteorder == 'little':
+    _bgra = (0, 1, 2, 3)
+else:
+    _bgra = (3, 2, 1, 0)
+
+_bgra_fields = {'b': (_np.uint8, _bgra[0], 'blue'),
+                'g': (_np.uint8, _bgra[1], 'green'),
+                'r': (_np.uint8, _bgra[2], 'red'),
+                'a': (_np.uint8, _bgra[3], 'alpha')}
+
+bgra_dtype = _np.dtype(_bgra_fields)
+"""Complex dtype offering the named fields 'r','g','b', and 'a' and
+corresponding long names, conforming to QImage_'s 32-bit memory layout."""
+
+try:
+    _basestring = basestring
+except NameError:
+    # 'basestring' undefined, must be Python 3
+    _basestring = str
+
+def _qimage_or_filename_view(qimage):
+    if isinstance(qimage, _basestring):
+        qimage = _qt.QImage(qimage)
+    return _qimageview(qimage)
+
+def raw_view(qimage):
+    """Returns raw 2D view of the given QImage_'s memory.  The result
+    will be a 2-dimensional numpy.ndarray with an appropriately sized
+    integral dtype.  (This function is not intented to be used
+    directly, but used internally by the other -- more convenient --
+    view creation functions.)
+
+    :param qimage: image whose memory shall be accessed via NumPy
+    :type qimage: QImage_
+    :rtype: numpy.ndarray_ with shape (height, width)"""
+    return _qimage_or_filename_view(qimage)
+
+
+def byte_view(qimage, byteorder = 'little'):
+    """Returns raw 3D view of the given QImage_'s memory.  This will
+    always be a 3-dimensional numpy.ndarray with dtype numpy.uint8.
+    
+    Note that for 32-bit images, the last dimension will be in the
+    [B,G,R,A] order (if little endian) due to QImage_'s memory layout
+    (the alpha channel will be present for Format_RGB32 images, too).
+
+    For 8-bit (indexed) images, the array will still be 3-dimensional,
+    i.e. shape will be (height, width, 1).
+
+    The order of channels in the last axis depends on the `byteorder`,
+    which defaults to 'little', i.e. BGRA order.  You may set the
+    argument `byteorder` to 'big' to get ARGB, or use None which means
+    sys.byteorder here, i.e. return native order for the machine the
+    code is running on.
+
+    For your convenience, `qimage` may also be a filename, see
+    `Loading and Saving Images`_ in the documentation.
+
+    :param qimage: image whose memory shall be accessed via NumPy
+    :type qimage: QImage_
+    :param byteorder: specify order of channels in last axis
+    :rtype: numpy.ndarray_ with shape (height, width, 1 or 4) and dtype uint8"""
+    raw = _qimage_or_filename_view(qimage)
+    result = raw.view(_np.uint8).reshape(raw.shape + (-1, ))
+    if byteorder and byteorder != _sys.byteorder:
+        result = result[...,::-1]
+    return result
+
+
+def rgb_view(qimage, byteorder = 'big'):
+    """Returns RGB view of a given 32-bit color QImage_'s memory.
+    Similarly to byte_view(), the result is a 3D numpy.uint8 array,
+    but reduced to the rgb dimensions (without alpha), and reordered
+    (using negative strides in the last dimension) to have the usual
+    [R,G,B] order.  The image must have 32 bit pixel size, i.e. be
+    RGB32, ARGB32, or ARGB32_Premultiplied.  (Note that in the latter
+    case, the values are of course premultiplied with alpha.)
+
+    The order of channels in the last axis depends on the `byteorder`,
+    which defaults to 'big', i.e. RGB order.  You may set the argument
+    `byteorder` to 'little' to get BGR, or use None which means
+    sys.byteorder here, i.e. return native order for the machine the
+    code is running on.
+
+    For your convenience, `qimage` may also be a filename, see
+    `Loading and Saving Images`_ in the documentation.
+
+    :param qimage: image whose memory shall be accessed via NumPy
+    :type qimage: QImage_ with 32-bit pixel type
+    :param byteorder: specify order of channels in last axis
+    :rtype: numpy.ndarray_ with shape (height, width, 3) and dtype uint8"""
+    if byteorder is None:
+        byteorder = _sys.byteorder
+    bytes = byte_view(qimage, byteorder)
+    if bytes.shape[2] != 4:
+        raise ValueError("For rgb_view, the image must have 32 bit pixel size (use RGB32, ARGB32, or ARGB32_Premultiplied)")
+
+    if byteorder == 'little':
+        return bytes[...,:3] # strip A off BGRA
+    else:
+        return bytes[...,1:] # strip A off ARGB
+
+
+def alpha_view(qimage):
+    """Returns alpha view of a given 32-bit color QImage_'s memory.
+    The result is a 2D numpy.uint8 array, equivalent to
+    byte_view(qimage)[...,3].  The image must have 32 bit pixel size,
+    i.e. be RGB32, ARGB32, or ARGB32_Premultiplied.  Note that it is
+    not enforced that the given qimage has a format that actually
+    *uses* the alpha channel -- for Format_RGB32, the alpha channel
+    usually contains 255 everywhere.
+
+    For your convenience, `qimage` may also be a filename, see
+    `Loading and Saving Images`_ in the documentation.
+
+    :param qimage: image whose memory shall be accessed via NumPy
+    :type qimage: QImage_ with 32-bit pixel type
+    :rtype: numpy.ndarray_ with shape (height, width) and dtype uint8"""
+    bytes = byte_view(qimage, byteorder = None)
+    if bytes.shape[2] != 4:
+        raise ValueError("For alpha_view, the image must have 32 bit pixel size (use RGB32, ARGB32, or ARGB32_Premultiplied)")
+    return bytes[...,_bgra[3]]
+
+
+def recarray_view(qimage):
+    """Returns recarray_ view of a given 32-bit color QImage_'s
+    memory.
+
+    The result is a 2D array with a complex record dtype, offering the
+    named fields 'r','g','b', and 'a' and corresponding long names.
+    Thus, each color components can be accessed either via string
+    indexing or via attribute lookup (through numpy.recarray_):
+
+    For your convenience, `qimage` may also be a filename, see
+    `Loading and Saving Images`_ in the documentation.
+
+    >>> from PyQt4.QtGui import QImage, qRgb
+    >>> qimg = QImage(320, 240, QImage.Format_ARGB32)
+    >>> qimg.fill(qRgb(12,34,56))
+    >>>
+    >>> import qimage2ndarray
+    >>> v = qimage2ndarray.recarray_view(qimg)
+    >>>
+    >>> red = v["r"]
+    >>> red[10,10]
+    12
+    >>> pixel = v[10,10]
+    >>> pixel["r"]
+    12
+    >>> (v.g == v["g"]).all()
+    True
+    >>> (v.alpha == 255).all()
+    True
+
+    :param qimage: image whose memory shall be accessed via NumPy
+    :type qimage: QImage_ with 32-bit pixel type
+    :rtype: numpy.ndarray_ with shape (height, width) and dtype :data:`bgra_dtype`"""
+    raw = _qimage_or_filename_view(qimage)
+    if raw.itemsize != 4:
+        raise ValueError("For rgb_view, the image must have 32 bit pixel size (use RGB32, ARGB32, or ARGB32_Premultiplied)")
+    return raw.view(bgra_dtype, _np.recarray)
+
+
+
+def _normalize255(array, normalize, clip = (0, 255)):
+    if normalize:
+        if normalize is True:
+            normalize = array.min(), array.max()
+            if clip == (0, 255):
+                clip = None
+        elif _np.isscalar(normalize):
+            normalize = (0, normalize)
+
+        nmin, nmax = normalize
+
+        if nmin:
+            array = array - nmin
+
+        if nmax != nmin:
+            scale = 255. / (nmax - nmin)
+            if scale != 1.0:
+                array = array * scale
+
+    if clip:
+        low, high = clip
+        _np.clip(array, low, high, array)
+
+    return array
+
+
+def gray2qimage(gray, normalize = False):
+    """Convert the 2D numpy array `gray` into a 8-bit, indexed QImage_
+    with a gray colormap.  The first dimension represents the vertical
+    image axis.
+
+    The parameter `normalize` can be used to normalize an image's
+    value range to 0..255:
+
+    `normalize` = (nmin, nmax):
+      scale & clip image values from nmin..nmax to 0..255
+
+    `normalize` = nmax:
+      lets nmin default to zero, i.e. scale & clip the range 0..nmax
+      to 0..255
+
+    `normalize` = True:
+      scale image values to 0..255 (same as passing (gray.min(),
+      gray.max()))
+
+    If the source array `gray` contains masked values, the result will
+    have only 255 shades of gray, and one color map entry will be used
+    to make the corresponding pixels transparent.
+
+    A full alpha channel cannot be supported with indexed images;
+    instead, use `array2qimage` to convert into a 32-bit QImage.
+
+    :param gray: image data which should be converted (copied) into a QImage_
+    :type gray: 2D or 3D numpy.ndarray_ or `numpy.ma.array <masked arrays>`_
+    :param normalize: normalization parameter (see above, default: no value changing)
+    :type normalize: bool, scalar, or pair
+    :rtype: QImage_ with RGB32 or ARGB32 format"""
+    if _np.ndim(gray) != 2:
+        raise ValueError("gray2QImage can only convert 2D arrays" +
+                         " (try using array2qimage)" if _np.ndim(gray) == 3 else "")
+
+    h, w = gray.shape
+    result = _qt.QImage(w, h, _qt.QImage.Format_Indexed8)
+
+    if not _np.ma.is_masked(gray):
+        for i in range(256):
+            result.setColor(i, _qt.qRgb(i,i,i))
+
+        _qimageview(result)[:] = _normalize255(gray, normalize)
+    else:
+        # map gray value 1 to gray value 0, in order to make room for
+        # transparent colormap entry:
+        result.setColor(0, _qt.qRgb(0,0,0))
+        for i in range(2, 256):
+            result.setColor(i-1, _qt.qRgb(i,i,i))
+
+        _qimageview(result)[:] = _normalize255(gray, normalize, clip = (1, 255)) - 1
+
+        result.setColor(255, 0)
+        _qimageview(result)[gray.mask] = 255
+
+    return result
+
+
+def array2qimage(array, normalize = False):
+    """Convert a 2D or 3D numpy array into a 32-bit QImage_.  The
+    first dimension represents the vertical image axis; the optional
+    third dimension is supposed to contain 1-4 channels:
+
+    ========= ===================
+    #channels interpretation
+    ========= ===================
+            1 scalar/gray
+            2 scalar/gray + alpha
+            3 RGB
+            4 RGB + alpha
+    ========= ===================
+
+    Scalar data will be converted into corresponding gray RGB triples;
+    if you want to convert to an (indexed) 8-bit image instead, use
+    `gray2qimage` (which cannot support an alpha channel though).
+
+    The parameter `normalize` can be used to normalize an image's
+    value range to 0..255:
+
+    `normalize` = (nmin, nmax):
+      scale & clip image values from nmin..nmax to 0..255
+
+    `normalize` = nmax:
+      lets nmin default to zero, i.e. scale & clip the range 0..nmax
+      to 0..255
+
+    `normalize` = True:
+      scale image values to 0..255 (same as passing (array.min(),
+      array.max()))
+
+    If `array` contains masked values, the corresponding pixels will
+    be transparent in the result.  Thus, the result will be of
+    QImage.Format_ARGB32 if the input already contains an alpha
+    channel (i.e. has shape (H,W,4)) or if there are masked pixels,
+    and QImage.Format_RGB32 otherwise.
+
+    :param array: image data which should be converted (copied) into a QImage_
+    :type array: 2D or 3D numpy.ndarray_ or `numpy.ma.array <masked arrays>`_
+    :param normalize: normalization parameter (see above, default: no value changing)
+    :type normalize: bool, scalar, or pair
+    :rtype: QImage_ with RGB32 or ARGB32 format"""
+    if _np.ndim(array) == 2:
+        array = array[...,None]
+    elif _np.ndim(array) != 3:
+        raise ValueError("array2qimage can only convert 2D or 3D arrays (got %d dimensions)" % _np.ndim(array))
+    if array.shape[2] not in (1, 2, 3, 4):
+        raise ValueError("array2qimage expects the last dimension to contain exactly one (scalar/gray), two (gray+alpha), three (R,G,B), or four (R,G,B,A) channels")
+
+    h, w, channels = array.shape
+
+    hasAlpha = _np.ma.is_masked(array) or channels in (2, 4)
+    fmt = _qt.QImage.Format_ARGB32 if hasAlpha else _qt.QImage.Format_RGB32
+
+    result = _qt.QImage(w, h, fmt)
+
+    array = _normalize255(array, normalize)
+
+    if channels >= 3:
+        rgb_view(result)[:] = array[...,:3]
+    else:
+        rgb_view(result)[:] = array[...,:1] # scalar data
+
+    alpha = alpha_view(result)
+
+    if channels in (2, 4):
+        alpha[:] = array[...,-1]
+    else:
+        alpha[:] = 255
+
+    if _np.ma.is_masked(array):
+        alpha[:]  *= _np.logical_not(_np.any(array.mask, axis = -1))
+
+    return result
+
+
+def imread(filename, masked = False):
+    """Convenience function that uses the QImage_ constructor to read an
+    image from the given file and return an `rgb_view` of the result.
+    This is intentionally similar to scipy.ndimage.imread (which uses
+    PIL), scipy.misc.imread, or matplotlib.pyplot.imread (using PIL
+    for non-PNGs).
+
+    For grayscale images, return 2D array (even if it comes from a 32-bit
+    representation; this is a consequence of the QImage API).
+
+    For images with an alpha channel, the resulting number of channels
+    will be 2 (grayscale+alpha) or 4 (RGB+alpha).  Alternatively, one may
+    pass `masked = True' in order to get `numpy.ma.array <masked
+    arrays>`_ back.  Note that only fully transparent pixels are masked
+    (and that masked arrays only support binary masks).  The value of
+    `masked` is ignored when the loaded image has no alpha channel
+    (i.e., one would not get a masked array in that case).
+
+    This function has been added in version 1.3.
+
+    """
+    qImage = _qt.QImage(filename)
+
+    isGray = qImage.isGrayscale()
+    if isGray and qImage.depth() == 8:
+        return byte_view(qImage)[...,0]
+
+    hasAlpha = qImage.hasAlphaChannel()
+
+    if hasAlpha:
+        targetFormat = _qt.QImage.Format_ARGB32
+    else:
+        targetFormat = _qt.QImage.Format_RGB32
+    if qImage.format() != targetFormat:
+        qImage = qImage.convertToFormat(targetFormat)
+
+    result = rgb_view(qImage)
+    if isGray:
+        result = result[...,0]
+    if hasAlpha:
+        if masked:
+            mask = (alpha_view(qImage) == 0)
+            if _np.ndim(result) == 3:
+                mask = _np.repeat(mask[...,None], 3, axis = 2)
+            result = _np.ma.masked_array(result, mask)
+        else:
+            result = _np.dstack((result, alpha_view(qImage)))
+    return result
+
+
+def imsave(filename, image, normalize = False, format = None, quality = -1):
+    """Convenience function that uses QImage.save to save an image to the
+    given file.  This is intentionally similar to scipy.misc.imsave.
+    However, it supports different optional arguments:
+
+    :param normalize: see :func:`array2qimage` (which is used internally)
+    :param format: image filetype (e.g. 'PNG'),  (default: check filename's suffix)
+    :param quality: see QImage.save (0 = small .. 100 = uncompressed, -1 = default compression)
+    :returns: boolean success, see QImage.save
+    
+    This function has been added in version 1.4.
+    """
+    qImage = array2qimage(image, normalize = normalize)
+    return qImage.save(filename, format, quality)
diff --git a/qimage2ndarray/dynqt.py b/qimage2ndarray/dynqt.py
new file mode 100644
index 00000000..a4f1a18b
--- /dev/null
+++ b/qimage2ndarray/dynqt.py
@@ -0,0 +1,4 @@
+from .qt_driver import QtDriver
+
+qt = QtDriver()
+QtGui = qt.QtGui
diff --git a/qimage2ndarray/qimageview_python.py b/qimage2ndarray/qimageview_python.py
new file mode 100644
index 00000000..83efb2dd
--- /dev/null
+++ b/qimage2ndarray/qimageview_python.py
@@ -0,0 +1,60 @@
+import numpy as np
+from qimage2ndarray.dynqt import qt, QtGui
+
+def PyQt_data(image):
+    # PyQt4/PyQt5's QImage.bits() returns a sip.voidptr that supports
+    # conversion to string via asstring(size) or getting its base
+    # address via int(...):
+    return (int(image.bits()), False)
+
+def _re_buffer_address_match(buf_repr):
+    import re
+    _re_buffer_address = re.compile('<read-write buffer ptr 0x([0-9a-fA-F]*),')
+    global _re_buffer_address_match
+    _re_buffer_address_match = _re_buffer_address.match
+    return _re_buffer_address_match(buf_repr)
+
+def PySide_data(image):
+    # PySide's QImage.bits() returns a buffer object like this:
+    # <read-write buffer ptr 0x7fc3f4821600, size 76800 at 0x111269570>
+    ma = _re_buffer_address_match(repr(image.bits()))
+    assert ma, 'could not parse address from %r' % (image.bits(), )
+    return (int(ma.group(1), 16), False)
+
+getdata = dict(
+    PyQt4 = PyQt_data,
+    PyQt5 = PyQt_data,
+    PySide = PySide_data,
+)[qt.name()]
+
+
+def qimageview(image):
+    if not isinstance(image, QtGui.QImage):
+        raise TypeError("image argument must be a QImage instance")
+
+    shape = image.height(), image.width()
+    strides0 = image.bytesPerLine()
+
+    format = image.format()
+    if format == QtGui.QImage.Format_Indexed8:
+        dtype = "|u1"
+        strides1 = 1
+    elif format in (QtGui.QImage.Format_RGB32, QtGui.QImage.Format_ARGB32, QtGui.QImage.Format_ARGB32_Premultiplied):
+        dtype = "|u4"
+        strides1 = 4
+    elif format == QtGui.QImage.Format_Invalid:
+        raise ValueError("qimageview got invalid QImage")
+    else:
+        raise ValueError("qimageview can only handle 8- or 32-bit QImages")
+
+    image.__array_interface__ = {
+        'shape': shape,
+        'typestr': dtype,
+        'data': getdata(image),
+        'strides': (strides0, strides1),
+        'version': 3,
+    }
+
+    result = np.asarray(image)
+    del image.__array_interface__
+    return result
diff --git a/qimage2ndarray/qt_driver.py b/qimage2ndarray/qt_driver.py
new file mode 100644
index 00000000..483cd287
--- /dev/null
+++ b/qimage2ndarray/qt_driver.py
@@ -0,0 +1,125 @@
+#  Copyright 2014-2014 Hans Meine <hans_meine@gmx.net>
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+"""This module contains a wrapper around three different Qt python bindings.
+It will dynamically decide which one to use:
+
+* First, the environment variable QT_DRIVER is checked
+  (may be one of 'PyQt5', 'PyQt4', 'PySide', 'PythonQt').
+* If unset, previously imported binding modules are detected (in sys.modules).
+* If no bindings are loaded, the environment variable QT_API is checked
+  (used by ETS and ipython, may be 'pyside' or 'pyqt').
+
+In order to have compatible behavior between the different bindings,
+PyQt4 (if used) is configured as follows::
+
+    sip.setapi("QString", 2)
+    sip.setapi("QVariant", 2)
+
+Furthermore, there is a 'getprop' function that solves the following
+problem: PythonQt exports Qt properties as Python properties *and*
+gives the precedence over getters with the same name.  Instead of
+calling getters with parentheses (which must not be used in PythonQt,
+but are required in PyQt and PySide), one may e.g. write
+`getprop(widget.width)`.
+"""
+
+import sys, os
+
+def getprop_PythonQt(prop):
+    """getprop(property_or_getter)
+
+    Used on getters that have the same name as a corresponding
+    property.  For PythonQt, this version will just return the
+    argument, which is assumed to be (the value of) a python property
+    through which PythonQt exposes Qt properties."""
+    return prop
+
+def getprop_other(getter):
+    """getprop(property_or_getter)
+
+    Used on getters that have the same name as a corresponding
+    property.  For Qt bindings other than PythonQt, this version will
+    return the result of calling the argument, which is assumed to be
+    a Qt getter function.  (With PythonQt, properties override getters
+    and no calling must be done.)"""
+    return getter()
+
+class QtDriver(object):
+    DRIVERS = ('PyQt5', 'PyQt4', 'PySide', 'PythonQt')
+    DEFAULT = 'PyQt4'
+    
+    @classmethod
+    def detect_qt(cls):
+        for drv in cls.DRIVERS:
+            if drv in sys.modules:
+                return drv
+        if '_PythonQt' in sys.modules:
+            return 'PythonQt'
+        return None
+
+    def name(self):
+        return self._drv
+
+    def getprop(self):
+        return getprop_PythonQt if self._drv == 'PythonQt' else getprop_other
+
+    def __init__(self, drv = os.environ.get('QT_DRIVER')):
+        """Supports QT_API (used by ETS and ipython)"""
+        if drv is None:
+            drv = self.detect_qt()
+        if drv is None:
+            drv = os.environ.get('QT_API')
+        if drv is None:
+            drv = self.DEFAULT
+        drv = {'pyside' : 'PySide', 'pyqt' : 'PyQt4', 'pyqt5' : 'PyQt5'}.get(drv, drv) # map ETS syntax
+        assert drv in self.DRIVERS
+        self._drv = drv
+
+    @staticmethod
+    def _initPyQt4():
+        """initialize PyQt4 to be compatible with PySide"""
+        if 'PyQt4.QtCore' in sys.modules:
+            # too late to configure API
+            pass
+        else:
+            import sip
+            sip.setapi("QString", 2)
+            sip.setapi("QVariant", 2)
+
+    @staticmethod
+    def requireCompatibleAPI():
+        """If PyQt4's API should be configured to be compatible with PySide's
+        (i.e. QString and QVariant should not be explicitly exported,
+        cf. documentation of sip.setapi()), call this function to check that
+        the PyQt4 was properly imported.  (It will always be configured this
+        way by this module, but it could have been imported before we got a
+        hand on doing so.)
+        """
+        if 'PyQt4.QtCore' in sys.modules:
+            import sip
+            for api in ('QVariant', 'QString'):
+                if sip.getapi(api) != 2:
+                    raise RuntimeError('%s API already set to V%d, but should be 2' % (api, sip.getapi(api)))
+            
+    def importMod(self, mod):
+        if self._drv == 'PyQt4':
+            self._initPyQt4()
+        qt = __import__('%s.%s' % (self._drv, mod))
+        return getattr(qt, mod)
+
+    def __getattr__(self, name):
+        if name.startswith('Qt'):
+            return self.importMod(name)
+        return super(QtDriver, self).__getattr__(name)
-- 
GitLab