diff --git a/libs/qimage2ndarray/LICENSE.txt b/libs/qimage2ndarray/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..96d599dac8fc563eb78b9305e428df72944a6678 --- /dev/null +++ b/libs/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/libs/qimage2ndarray/__init__.py b/libs/qimage2ndarray/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6e56e0e06dd75127d37a7b5918acf431b9824c32 --- /dev/null +++ b/libs/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/libs/qimage2ndarray/dynqt.py b/libs/qimage2ndarray/dynqt.py new file mode 100644 index 0000000000000000000000000000000000000000..a4f1a18bce006d81d848dfd98d881901e3af5bda --- /dev/null +++ b/libs/qimage2ndarray/dynqt.py @@ -0,0 +1,4 @@ +from .qt_driver import QtDriver + +qt = QtDriver() +QtGui = qt.QtGui diff --git a/libs/qimage2ndarray/qimageview_python.py b/libs/qimage2ndarray/qimageview_python.py new file mode 100644 index 0000000000000000000000000000000000000000..83efb2ddf0f9351992aef016f7ce1f4ac2eb83c9 --- /dev/null +++ b/libs/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/libs/qimage2ndarray/qt_driver.py b/libs/qimage2ndarray/qt_driver.py new file mode 100644 index 0000000000000000000000000000000000000000..483cd287af1976d6c665860fa84d3304ece7273d --- /dev/null +++ b/libs/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) diff --git a/libs/qrangeslider-0.1.1/PKG-INFO b/libs/qrangeslider-0.1.1/PKG-INFO new file mode 100644 index 0000000000000000000000000000000000000000..5374908bfa8050c16057076ebc3c909efc4b6441 --- /dev/null +++ b/libs/qrangeslider-0.1.1/PKG-INFO @@ -0,0 +1,10 @@ +Metadata-Version: 1.0 +Name: qrangeslider +Version: 0.1.1 +Summary: The QRangeSlider class implements a horizontal PyQt range slider widget. +Home-page: http://github.com/rsgalloway/qrangeslider +Author: Ryan Galloway +Author-email: ryan@rsgalloway.com +License: UNKNOWN +Description: UNKNOWN +Platform: UNKNOWN diff --git a/libs/qrangeslider-0.1.1/README b/libs/qrangeslider-0.1.1/README new file mode 100644 index 0000000000000000000000000000000000000000..8048e6bbc834e6f1fca94eccc5fb61b1c9bf0790 --- /dev/null +++ b/libs/qrangeslider-0.1.1/README @@ -0,0 +1,34 @@ +QRangeSlider + +Thu Apr 28 00:08:07 PDT 2011 + + +1 Overview +~~~~~~~~~~ + +The QRangeSlider class implements a horizontal PyQt range slider widget. + + * README this file + * LICENSE the license under which QRangeSlider is released + * qrangeslider.py qrangeslider python module + * examples.py some usage examples + + +2 Installation +~~~~~~~~~~~~~~ + + $ sudo easy_install qrangeslider + +or download the source and run + + $ sudo python setup.py install + + +3 Basic Usage +~~~~~~~~~~~~~ + + >>> from qrangeslider import QRangeSlider + >>> app = QtGui.QApplication(sys.argv) + >>> slider = QRangeSlider() + >>> slider.show() + >>> app.exec_() \ No newline at end of file diff --git a/libs/qrangeslider-0.1.1/qrangeslider.py b/libs/qrangeslider-0.1.1/qrangeslider.py new file mode 100644 index 0000000000000000000000000000000000000000..1267a1a459b364bab0196f53979fa351ec40dff6 --- /dev/null +++ b/libs/qrangeslider-0.1.1/qrangeslider.py @@ -0,0 +1,521 @@ +#!/usr/bin/env python +# --------------------------------------------------------------------------------------------- +# Copyright (c) 2011-2014, Ryan Galloway (ryan@rsgalloway.com) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# - Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# - 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. +# +# - Neither the name of the software 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 HOLDER 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. +# +# --------------------------------------------------------------------------------------------- +# docs and latest version available for download at +# http://rsgalloway.github.com/qrangeslider +# --------------------------------------------------------------------------------------------- + +__author__ = "Ryan Galloway <ryan@rsgalloway.com>" +__version__ = "0.1.1" + + +# --------------------------------------------------------------------------------------------- +# SUMMARY +# --------------------------------------------------------------------------------------------- +"""The QRangeSlider class implements a horizontal range slider widget. + +""" + +# --------------------------------------------------------------------------------------------- +# TODO +# --------------------------------------------------------------------------------------------- + +""" + - smoother mouse move event handler + - support splits and joins + - verticle sliders + - ticks + +""" + +# --------------------------------------------------------------------------------------------- +# IMPORTS +# --------------------------------------------------------------------------------------------- +import os +import sys + +from PyQt4 import QtCore +from PyQt4 import QtGui +from PyQt4 import uic + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +__all__ = ['QRangeSlider'] + +DEFAULT_CSS = """ +QRangeSlider * { + border: 0px; + padding: 0px; +} +QRangeSlider #Head { + background: #222; +} +QRangeSlider #Span { + background: #393; +} +QRangeSlider #Span:active { + background: #282; +} +QRangeSlider #Tail { + background: #222; +} +QRangeSlider > QSplitter::handle { + background: #393; +} +QRangeSlider > QSplitter::handle:vertical { + height: 4px; +} +QRangeSlider > QSplitter::handle:pressed { + background: #ca5; +} + +""" + +def scale(val, src, dst): + """ + Scale the given value from the scale of src to the scale of dst. + """ + return int(((val - src[0]) / float(src[1]-src[0])) * (dst[1]-dst[0]) + dst[0]) + +class Ui_Form(object): + """default range slider form""" + + def setupUi(self, Form): + Form.setObjectName(_fromUtf8("QRangeSlider")) + Form.resize(300, 30) + Form.setStyleSheet(_fromUtf8(DEFAULT_CSS)) + self.gridLayout = QtGui.QGridLayout(Form) + self.gridLayout.setMargin(0) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self._splitter = QtGui.QSplitter(Form) + self._splitter.setMinimumSize(QtCore.QSize(0, 0)) + self._splitter.setMaximumSize(QtCore.QSize(16777215, 16777215)) + self._splitter.setOrientation(QtCore.Qt.Horizontal) + self._splitter.setObjectName(_fromUtf8("splitter")) + self._head = QtGui.QGroupBox(self._splitter) + self._head.setTitle(_fromUtf8("")) + self._head.setObjectName(_fromUtf8("Head")) + self._handle = QtGui.QGroupBox(self._splitter) + self._handle.setTitle(_fromUtf8("")) + self._handle.setObjectName(_fromUtf8("Span")) + self._tail = QtGui.QGroupBox(self._splitter) + self._tail.setTitle(_fromUtf8("")) + self._tail.setObjectName(_fromUtf8("Tail")) + self.gridLayout.addWidget(self._splitter, 0, 0, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + encoding = QtGui.QApplication.UnicodeUTF8 + Form.setWindowTitle(QtGui.QApplication.translate("QRangeSlider", + "QRangeSlider", + None, encoding)) + + +class Element(QtGui.QGroupBox): + + def __init__(self, parent, main): + super(Element, self).__init__(parent) + self.main = main + + def setStyleSheet(self, style): + """redirect style to parent groupbox""" + self.parent().setStyleSheet(style) + + def textColor(self): + """text paint color""" + return getattr(self, '__textColor', QtGui.QColor(125, 125, 125)) + + def setTextColor(self, color): + """set the text paint color""" + if type(color) == tuple and len(color) == 3: + color = QtGui.QColor(color[0], color[1], color[2]) + elif type(color) == int: + color = QtGui.QColor(color, color, color) + setattr(self, '__textColor', color) + + def paintEvent(self, event): + """overrides paint event to handle text""" + qp = QtGui.QPainter() + qp.begin(self) + if self.main.drawValues(): + self.drawText(event, qp) + qp.end() + + +class Head(Element): + """area before the handle""" + + def __init__(self, parent, main): + super(Head, self).__init__(parent, main) + + def drawText(self, event, qp): + qp.setPen(self.textColor()) + qp.setFont(QtGui.QFont('Arial', 10)) + qp.drawText(event.rect(), QtCore.Qt.AlignLeft, str(self.main.min())) + + +class Tail(Element): + """area after the handle""" + + def __init__(self, parent, main): + super(Tail, self).__init__(parent, main) + + def drawText(self, event, qp): + qp.setPen(self.textColor()) + qp.setFont(QtGui.QFont('Arial', 10)) + qp.drawText(event.rect(), QtCore.Qt.AlignRight, str(self.main.max())) + + +class Handle(Element): + """handle area""" + + def __init__(self, parent, main): + super(Handle, self).__init__(parent, main) + + def drawText(self, event, qp): + qp.setPen(self.textColor()) + qp.setFont(QtGui.QFont('Arial', 10)) + qp.drawText(event.rect(), QtCore.Qt.AlignLeft, str(self.main.start())) + qp.drawText(event.rect(), QtCore.Qt.AlignRight, str(self.main.end())) + + def mouseMoveEvent(self, event): + event.accept() + mx = event.globalX() + _mx = getattr(self, '__mx', None) + + if not _mx: + setattr(self, '__mx', mx) + dx = 0 + else: + dx = mx - _mx + + setattr(self, '__mx', mx) + + if dx == 0: + event.ignore() + return + elif dx > 0: + dx = 1 + elif dx < 0: + dx = -1 + + s = self.main.start() + dx + e = self.main.end() + dx + if s >= self.main.min() and e <= self.main.max(): + self.main.setRange(s, e) + + +class QRangeSlider(QtGui.QWidget, Ui_Form): + """ + The QRangeSlider class implements a horizontal range slider widget. + + Inherits QWidget. + + Methods + + * __init__ (self, QWidget parent = None) + * bool drawValues (self) + * int end (self) + * (int, int) getRange (self) + * int max (self) + * int min (self) + * int start (self) + * setBackgroundStyle (self, QString styleSheet) + * setDrawValues (self, bool draw) + * setEnd (self, int end) + * setStart (self, int start) + * setRange (self, int start, int end) + * setSpanStyle (self, QString styleSheet) + + Signals + + * endValueChanged (int) + * maxValueChanged (int) + * minValueChanged (int) + * startValueChanged (int) + + Customizing QRangeSlider + + You can style the range slider as below: + :: + QRangeSlider * { + border: 0px; + padding: 0px; + } + QRangeSlider #Head { + background: #222; + } + QRangeSlider #Span { + background: #393; + } + QRangeSlider #Span:active { + background: #282; + } + QRangeSlider #Tail { + background: #222; + } + + Styling the range slider handles follows QSplitter options: + :: + QRangeSlider > QSplitter::handle { + background: #393; + } + QRangeSlider > QSplitter::handle:vertical { + height: 4px; + } + QRangeSlider > QSplitter::handle:pressed { + background: #ca5; + } + + """ + endValueChanged = QtCore.pyqtSignal(int) + maxValueChanged = QtCore.pyqtSignal(int) + minValueChanged = QtCore.pyqtSignal(int) + startValueChanged = QtCore.pyqtSignal(int) + + # define splitter indices + _SPLIT_START = 1 + _SPLIT_END = 2 + + # signals + minValueChanged = QtCore.pyqtSignal(int) + maxValueChanged = QtCore.pyqtSignal(int) + startValueChanged = QtCore.pyqtSignal(int) + endValueChanged = QtCore.pyqtSignal(int) + + def __init__(self, parent=None): + """Create a new QRangeSlider instance. + + :param parent: QWidget parent + :return: New QRangeSlider instance. + + """ + super(QRangeSlider, self).__init__(parent) + self.setupUi(self) + self.setMouseTracking(False) + + #self._splitter.setChildrenCollapsible(False) + self._splitter.splitterMoved.connect(self._handleMoveSplitter) + + # head layout + self._head_layout = QtGui.QHBoxLayout() + self._head_layout.setSpacing(0) + self._head_layout.setMargin(0) + self._head.setLayout(self._head_layout) + self.head = Head(self._head, main=self) + self._head_layout.addWidget(self.head) + + # handle layout + self._handle_layout = QtGui.QHBoxLayout() + self._handle_layout.setSpacing(0) + self._handle_layout.setMargin(0) + self._handle.setLayout(self._handle_layout) + self.handle = Handle(self._handle, main=self) + self.handle.setTextColor((150, 255, 150)) + self._handle_layout.addWidget(self.handle) + + # tail layout + self._tail_layout = QtGui.QHBoxLayout() + self._tail_layout.setSpacing(0) + self._tail_layout.setMargin(0) + self._tail.setLayout(self._tail_layout) + self.tail = Tail(self._tail, main=self) + self._tail_layout.addWidget(self.tail) + + # defaults + self.setMin(0) + self.setMax(99) + self.setStart(0) + self.setEnd(99) + self.setDrawValues(True) + + def min(self): + """:return: minimum value""" + return getattr(self, '__min', None) + + def max(self): + """:return: maximum value""" + return getattr(self, '__max', None) + + def setMin(self, value): + """sets minimum value""" + assert type(value) is int + setattr(self, '__min', value) + self.minValueChanged.emit(value) + + def setMax(self, value): + """sets maximum value""" + assert type(value) is int + setattr(self, '__max', value) + self.maxValueChanged.emit(value) + + def start(self): + """:return: range slider start value""" + return getattr(self, '__start', None) + + def end(self): + """:return: range slider end value""" + return getattr(self, '__end', None) + + def _setStart(self, value): + """stores the start value only""" + setattr(self, '__start', value) + self.startValueChanged.emit(value) + + def setStart(self, value): + """sets the range slider start value""" + assert type(value) is int + v = self._valueToPos(value) + self._splitter.splitterMoved.disconnect() + self._splitter.moveSplitter(v, self._SPLIT_START) + self._splitter.splitterMoved.connect(self._handleMoveSplitter) + self._setStart(value) + + def _setEnd(self, value): + """stores the end value only""" + setattr(self, '__end', value) + self.endValueChanged.emit(value) + + def setEnd(self, value): + """set the range slider end value""" + assert type(value) is int + v = self._valueToPos(value) + self._splitter.splitterMoved.disconnect() + self._splitter.moveSplitter(v, self._SPLIT_END) + self._splitter.splitterMoved.connect(self._handleMoveSplitter) + self._setEnd(value) + + def drawValues(self): + """:return: True if slider values will be drawn""" + return getattr(self, '__drawValues', None) + + def setDrawValues(self, draw): + """sets draw values boolean to draw slider values""" + assert type(draw) is bool + setattr(self, '__drawValues', draw) + + def getRange(self): + """:return: the start and end values as a tuple""" + return (self.start(), self.end()) + + def setRange(self, start, end): + """set the start and end values""" + self.setStart(start) + self.setEnd(end) + + def keyPressEvent(self, event): + """overrides key press event to move range left and right""" + key = event.key() + if key == QtCore.Qt.Key_Left: + s = self.start()-1 + e = self.end()-1 + elif key == QtCore.Qt.Key_Right: + s = self.start()+1 + e = self.end()+1 + else: + event.ignore() + return + event.accept() + if s >= self.min() and e <= self.max(): + self.setRange(s, e) + + def setBackgroundStyle(self, style): + """sets background style""" + self._tail.setStyleSheet(style) + self._head.setStyleSheet(style) + + def setSpanStyle(self, style): + """sets range span handle style""" + self._handle.setStyleSheet(style) + + def _valueToPos(self, value): + """converts slider value to local pixel x coord""" + return scale(value, (self.min(), self.max()), (0, self.width())) + + def _posToValue(self, xpos): + """converts local pixel x coord to slider value""" + return scale(xpos, (0, self.width()), (self.min(), self.max())) + + def _handleMoveSplitter(self, xpos, index): + """private method for handling moving splitter handles""" + hw = self._splitter.handleWidth() + + def _lockWidth(widget): + width = widget.size().width() + widget.setMinimumWidth(width) + widget.setMaximumWidth(width) + + def _unlockWidth(widget): + widget.setMinimumWidth(0) + widget.setMaximumWidth(16777215) + + v = self._posToValue(xpos) + + if index == self._SPLIT_START: + _lockWidth(self._tail) + if v >= self.end(): + return + + offset = -20 + w = xpos + offset + self._setStart(v) + + elif index == self._SPLIT_END: + _lockWidth(self._head) + if v <= self.start(): + return + + offset = -40 + w = self.width() - xpos + offset + self._setEnd(v) + + _unlockWidth(self._tail) + _unlockWidth(self._head) + _unlockWidth(self._handle) + + +#------------------------------------------------------------------------------- +# MAIN +#------------------------------------------------------------------------------- + +if __name__ == '__main__': + app = QtGui.QApplication(sys.argv) + rs = QRangeSlider() + rs.show() + rs.setRange(15, 35) + rs.setBackgroundStyle('background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #222, stop:1 #333);') + rs.handle.setStyleSheet('background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #282, stop:1 #393);') + app.exec_() diff --git a/libs/qrangeslider-0.1.1/setup.py b/libs/qrangeslider-0.1.1/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..76fd96a6274e1b39ae0bcd207f0d4dac5ca4d190 --- /dev/null +++ b/libs/qrangeslider-0.1.1/setup.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +# +# Copyright (C) 2011-2014 Ryan Galloway (ryan@rsgalloway.com) +# +# This module is part of Shotman and is released under +# the BSD License: http://www.opensource.org/licenses/bsd-license.php + +from distutils.core import setup +from qrangeslider import __version__ +setup(name='qrangeslider', + version=__version__, + description='The QRangeSlider class implements a horizontal PyQt range slider widget.', + author='Ryan Galloway', + author_email='ryan@rsgalloway.com', + url='http://github.com/rsgalloway/qrangeslider', + py_modules=['qrangeslider'] + ) diff --git a/sensecarbon_tsv.py b/sensecarbon_tsv.py index e673346fe15607c2ba7ba52d28d4c57d79215da4..703708a510dd4b0263a5ae7febfe110825f8b84d 100644 --- a/sensecarbon_tsv.py +++ b/sensecarbon_tsv.py @@ -55,9 +55,12 @@ if os.path.exists(path): pluginDir = os.path.dirname(__file__) sys.path.append(pluginDir) -sys.path.append(os.path.join(pluginDir, 'qimage2ndarray')) +sys.path.append(os.path.join(pluginDir, 'libs')) +#sys.path.append(os.path.join(pluginDir, *['libs','qimage2ndarray'])) +sys.path.append(os.path.join(pluginDir, *['libs','qrangeslider-0.1.1'])) import qimage2ndarray +import qrangeslider from PyQt4.QtCore import * from PyQt4.QtGui import * @@ -1296,6 +1299,11 @@ class SenseCarbon_TSV: TS = TimeSeries.loadFromFile(path) self.init_TimeSeries(TS) self.ua_datumAdded() + + if len(self.BAND_VIEWS) == 0: + self.ua_addBandView([3, 2, 1]) + self.ua_addBandView([4, 5, 3]) + self.check_enabled() def ua_saveTSFile(self):