From a5d42a3940819a94e4f8ea31933538775a4bc61d Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 24 Jan 2013 17:12:15 -0600 Subject: [PATCH 1/5] Add skimage.viewer.qt wrapper. This allows the viewer to use either PyQt4 or PySide as the toolkit. --- skimage/viewer/__init__.py | 5 +---- skimage/viewer/plugins/base.py | 11 +++-------- skimage/viewer/plugins/plotplugin.py | 6 +----- skimage/viewer/qt/QtCore.py | 9 +++++++++ skimage/viewer/qt/QtGui.py | 11 +++++++++++ skimage/viewer/qt/README.rst | 5 +++++ skimage/viewer/qt/__init__.py | 19 +++++++++++++++++++ skimage/viewer/utils/core.py | 5 +---- skimage/viewer/viewers/core.py | 10 +++------- skimage/viewer/widgets/core.py | 13 ++++--------- skimage/viewer/widgets/history.py | 6 ++---- 11 files changed, 59 insertions(+), 41 deletions(-) create mode 100644 skimage/viewer/qt/QtCore.py create mode 100644 skimage/viewer/qt/QtGui.py create mode 100644 skimage/viewer/qt/README.rst create mode 100644 skimage/viewer/qt/__init__.py diff --git a/skimage/viewer/__init__.py b/skimage/viewer/__init__.py index cfbeb0c0..e3681339 100644 --- a/skimage/viewer/__init__.py +++ b/skimage/viewer/__init__.py @@ -1,4 +1 @@ -try: - from viewers import ImageViewer, CollectionViewer -except ImportError: - print("Could not import PyQt4 -- ImageViewer not available.") +from viewers import ImageViewer, CollectionViewer diff --git a/skimage/viewer/plugins/base.py b/skimage/viewer/plugins/base.py index 5adf986e..aa6b585a 100644 --- a/skimage/viewer/plugins/base.py +++ b/skimage/viewer/plugins/base.py @@ -1,18 +1,13 @@ """ Base class for Plugins that interact with ImageViewer. """ -try: - from PyQt4 import QtGui - from PyQt4.QtCore import Qt - from PyQt4.QtGui import QDialog -except ImportError: - QDialog = object # hack to prevent nosetest and autodoc errors - print("Could not import PyQt4 -- skimage.viewer not available.") +from ..qt import QtGui +from ..qt.QtCore import Qt from ..utils import RequiredAttr, init_qtapp -class Plugin(QDialog): +class Plugin(QtGui.QDialog): """Base class for plugins that interact with an ImageViewer. A plugin connects an image filter (or another function) to an image viewer. diff --git a/skimage/viewer/plugins/plotplugin.py b/skimage/viewer/plugins/plotplugin.py index 850d0b71..c120756c 100644 --- a/skimage/viewer/plugins/plotplugin.py +++ b/skimage/viewer/plugins/plotplugin.py @@ -1,9 +1,5 @@ import numpy as np - -try: - from PyQt4 import QtGui -except ImportError: - print("Could not import PyQt4 -- skimage.viewer not available.") +from ..qt import QtGui from ..utils import new_plot from .base import Plugin diff --git a/skimage/viewer/qt/QtCore.py b/skimage/viewer/qt/QtCore.py new file mode 100644 index 00000000..897b8760 --- /dev/null +++ b/skimage/viewer/qt/QtCore.py @@ -0,0 +1,9 @@ +from . import qt_api + +if qt_api == 'pyside': + from PySide.QtCore import * +elif qt_api == 'pyqt': + from PyQt4.QtCore import * +else: + # Mock objects + Qt = None diff --git a/skimage/viewer/qt/QtGui.py b/skimage/viewer/qt/QtGui.py new file mode 100644 index 00000000..12e9837f --- /dev/null +++ b/skimage/viewer/qt/QtGui.py @@ -0,0 +1,11 @@ +from . import qt_api + +if qt_api == 'pyside': + from PySide.QtGui import * +elif qt_api == 'pyqt': + from PyQt4.QtGui import * +else: + # Mock objects + QMainWindow = object + QDialog = object + QWidget = object diff --git a/skimage/viewer/qt/README.rst b/skimage/viewer/qt/README.rst new file mode 100644 index 00000000..993ae1a5 --- /dev/null +++ b/skimage/viewer/qt/README.rst @@ -0,0 +1,5 @@ +This qt subpackage provides a wrapper to allow use of either PySide or PyQt4. +In addition, if neither package is available, some mock objects are created to +prevent errors in the TravisCI build. Only the objects used in the global +namespace need to be mocked (e.g., a Qt object that gets subclassed is used +in the global namespace). diff --git a/skimage/viewer/qt/__init__.py b/skimage/viewer/qt/__init__.py new file mode 100644 index 00000000..55cafcfe --- /dev/null +++ b/skimage/viewer/qt/__init__.py @@ -0,0 +1,19 @@ +import os +import warnings + +qt_api = os.environ.get('QT_API') + +if qt_api is None: + try: + import PySide + qt_api = 'pyside' + except ImportError: + try: + import PyQt4 + qt_api = 'pyqt' + except ImportError: + qt_api = 'none' + # Note that we don't want to raise an error because that would + # cause the TravisCI build to fail. + warnings.warn("Could not import PyQt4: ImageViewer not available!") + os.environ['QT_API'] = qt_api diff --git a/skimage/viewer/utils/core.py b/skimage/viewer/utils/core.py index 38bf7c6c..0dcd4176 100644 --- a/skimage/viewer/utils/core.py +++ b/skimage/viewer/utils/core.py @@ -14,10 +14,7 @@ except ImportError: LinearSegmentedColormap = object print("Could not import matplotlib -- skimage.viewer not available.") -try: - from PyQt4 import QtGui -except ImportError: - print("Could not import PyQt4 -- skimage.viewer not available.") +from ..qt import QtGui __all__ = ['init_qtapp', 'start_qtapp', 'RequiredAttr', 'figimage', diff --git a/skimage/viewer/viewers/core.py b/skimage/viewer/viewers/core.py index 193c5114..9d6261c6 100644 --- a/skimage/viewer/viewers/core.py +++ b/skimage/viewer/viewers/core.py @@ -1,12 +1,8 @@ """ ImageViewer class for viewing and interacting with images. """ -try: - from PyQt4 import QtGui, QtCore - from PyQt4.QtGui import QMainWindow -except ImportError: - QMainWindow = object # hack to prevent nosetest and autodoc errors - print("Could not import PyQt4 -- skimage.viewer not available.") +from ..qt import QtGui +from ..qt import QtCore from skimage import io, img_as_float from skimage.util.dtype import dtype_range @@ -32,7 +28,7 @@ def mpl_image_to_rgba(mpl_image): return img_as_float(image) -class ImageViewer(QMainWindow): +class ImageViewer(QtGui.QMainWindow): """Viewer for displaying images. This viewer is a simple container object that holds a Matplotlib axes diff --git a/skimage/viewer/widgets/core.py b/skimage/viewer/widgets/core.py index 14259f1e..2b0852c2 100644 --- a/skimage/viewer/widgets/core.py +++ b/skimage/viewer/widgets/core.py @@ -15,14 +15,9 @@ parameter type specified by its `ptype` attribute, which can be: property of the same name that updates the display. """ -try: - from PyQt4.QtCore import Qt - from PyQt4 import QtGui - from PyQt4 import QtCore - from PyQt4.QtGui import QWidget -except ImportError: - QWidget = object # hack to prevent nosetest and autodoc errors - print("Could not import PyQt4 -- skimage.viewer not available.") +from ..qt import QtGui +from ..qt import QtCore +from ..qt.QtCore import Qt from ..utils import RequiredAttr @@ -30,7 +25,7 @@ from ..utils import RequiredAttr __all__ = ['BaseWidget', 'Slider', 'ComboBox', 'Text'] -class BaseWidget(QWidget): +class BaseWidget(QtGui.QWidget): plugin = RequiredAttr("Widget is not attached to a Plugin.") diff --git a/skimage/viewer/widgets/history.py b/skimage/viewer/widgets/history.py index e3aaa938..ce20b559 100644 --- a/skimage/viewer/widgets/history.py +++ b/skimage/viewer/widgets/history.py @@ -1,9 +1,7 @@ from textwrap import dedent -try: - from PyQt4 import QtGui, QtCore -except ImportError: - print("Could not import PyQt4 -- skimage.viewer not available.") +from ..qt import QtGui +from ..qt import QtCore import numpy as np From c7fa6206cf1efef7af35ee98b825bf2251726399 Mon Sep 17 00:00:00 2001 From: tonysyu Date: Fri, 17 May 2013 17:12:52 -0500 Subject: [PATCH 2/5] Add PySide compatibility for save dialogs --- skimage/viewer/utils/dialogs.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/skimage/viewer/utils/dialogs.py b/skimage/viewer/utils/dialogs.py index 849fcad7..642166ae 100644 --- a/skimage/viewer/utils/dialogs.py +++ b/skimage/viewer/utils/dialogs.py @@ -1,9 +1,6 @@ import os -try: - from PyQt4 import QtGui -except ImportError: - print("Could not import PyQt4 -- skimage.viewer not available.") +from ..qt import QtGui def open_file_dialog(default_format='png'): @@ -16,7 +13,12 @@ def open_file_dialog(default_format='png'): def save_file_dialog(default_format='png'): """Return user-selected file path.""" - filename = str(QtGui.QFileDialog.getSaveFileName()) + filename = QtGui.QFileDialog.getSaveFileName() + # Handle discrepancy between PyQt4 and PySide APIs. + if isinstance(filename, tuple): + filename = filename[0] + filename = str(filename) + if len(filename) == 0: return None #TODO: io plugins should assign default image formats From 3baa7fe8f3ff7f7840f22647754783967657fe16 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sun, 19 May 2013 22:58:04 -0500 Subject: [PATCH 3/5] Fix file open dialog for PySide --- skimage/viewer/utils/dialogs.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/skimage/viewer/utils/dialogs.py b/skimage/viewer/utils/dialogs.py index 642166ae..f160531d 100644 --- a/skimage/viewer/utils/dialogs.py +++ b/skimage/viewer/utils/dialogs.py @@ -3,23 +3,30 @@ import os from ..qt import QtGui -def open_file_dialog(default_format='png'): - """Return user-selected file path.""" - filename = str(QtGui.QFileDialog.getOpenFileName()) +__all__ = ['open_file_dialog', 'save_file_dialog'] + + +def _format_filename(filename): + if isinstance(filename, tuple): + # Handle discrepancy between PyQt4 and PySide APIs. + filename = filename[0] if len(filename) == 0: return None + return str(filename) + + +def open_file_dialog(): + """Return user-selected file path.""" + filename = QtGui.QFileDialog.getOpenFileName() + filename = _format_filename(filename) return filename def save_file_dialog(default_format='png'): """Return user-selected file path.""" filename = QtGui.QFileDialog.getSaveFileName() - # Handle discrepancy between PyQt4 and PySide APIs. - if isinstance(filename, tuple): - filename = filename[0] - filename = str(filename) - - if len(filename) == 0: + filename = _format_filename(filename) + if filename is None: return None #TODO: io plugins should assign default image formats basename, ext = os.path.splitext(filename) From a49502a7b1aec7f2d7466606f41cee53d8d585ae Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sun, 26 May 2013 14:05:02 -0500 Subject: [PATCH 4/5] Remove `alignment` kwarg for compatibility with older PyQt4 --- skimage/viewer/widgets/core.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skimage/viewer/widgets/core.py b/skimage/viewer/widgets/core.py index 2b0852c2..f3d0a02a 100644 --- a/skimage/viewer/widgets/core.py +++ b/skimage/viewer/widgets/core.py @@ -160,9 +160,9 @@ class Slider(BaseWidget): self.editbox.setAlignment(align_value) self.editbox.editingFinished.connect(self._on_editbox_changed) - self.layout.addWidget(self.name_label, alignment=align_text) - self.layout.addWidget(self.slider, alignment=alignment) - self.layout.addWidget(self.editbox, alignment=align_value) + self.layout.addWidget(self.name_label) + self.layout.addWidget(self.slider) + self.layout.addWidget(self.editbox) def _on_slider_changed(self): """Call callback function with slider's name and value as parameters""" From d183cce16ca585a8345c1fcd575c6f1d131e8af8 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sun, 26 May 2013 18:28:12 -0500 Subject: [PATCH 5/5] Remove `alignment` kwarg (left out from previous commit). --- skimage/viewer/widgets/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/viewer/widgets/core.py b/skimage/viewer/widgets/core.py index f3d0a02a..ad69bfcf 100644 --- a/skimage/viewer/widgets/core.py +++ b/skimage/viewer/widgets/core.py @@ -236,7 +236,7 @@ class ComboBox(BaseWidget): self.layout = QtGui.QHBoxLayout(self) self.layout.addWidget(self.name_label) - self.layout.addWidget(self._combo_box, alignment=QtCore.Qt.AlignLeft) + self.layout.addWidget(self._combo_box) self._combo_box.currentIndexChanged.connect(self._value_changed) # self.connect(self._combo_box,