152 lines
4.8 KiB
Python
152 lines
4.8 KiB
Python
|
import numpy as np
|
||
|
from .util import prepare_for_display, window_manager
|
||
|
from ..._shared.utils import warn
|
||
|
|
||
|
from qtpy.QtWidgets import (QApplication, QLabel, QMainWindow, QWidget,
|
||
|
QGridLayout)
|
||
|
from qtpy.QtGui import QImage, QPixmap
|
||
|
from qtpy import QtCore
|
||
|
|
||
|
# We try to acquire the gui lock first or else the gui import might
|
||
|
# trample another GUI's PyOS_InputHook.
|
||
|
window_manager.acquire('qt')
|
||
|
|
||
|
app = None
|
||
|
|
||
|
|
||
|
class ImageLabel(QLabel):
|
||
|
def __init__(self, parent, arr):
|
||
|
QLabel.__init__(self)
|
||
|
|
||
|
# we need to hold a reference to
|
||
|
# arr because QImage doesn't copy the data
|
||
|
# and the buffer must be alive as long
|
||
|
# as the image is alive.
|
||
|
self.arr = arr
|
||
|
|
||
|
# we also need to pass in the row-stride to
|
||
|
# the constructor, because we can't guarantee
|
||
|
# that every row of the numpy data is
|
||
|
# 4-byte aligned. Which Qt would require
|
||
|
# if we didn't pass the stride.
|
||
|
self.img = QImage(arr.data, arr.shape[1], arr.shape[0],
|
||
|
arr.strides[0], QImage.Format_RGB888)
|
||
|
self.pm = QPixmap.fromImage(self.img)
|
||
|
self.setPixmap(self.pm)
|
||
|
self.setAlignment(QtCore.Qt.AlignTop)
|
||
|
self.setMinimumSize(100, 100)
|
||
|
|
||
|
def resizeEvent(self, evt):
|
||
|
width = self.width()
|
||
|
pm = QPixmap.fromImage(self.img)
|
||
|
self.pm = pm.scaledToWidth(width)
|
||
|
self.setPixmap(self.pm)
|
||
|
|
||
|
|
||
|
class ImageWindow(QMainWindow):
|
||
|
def __init__(self, arr, mgr):
|
||
|
QMainWindow.__init__(self)
|
||
|
self.setWindowTitle('skimage')
|
||
|
self.mgr = mgr
|
||
|
self.main_widget = QWidget()
|
||
|
self.layout = QGridLayout(self.main_widget)
|
||
|
self.setCentralWidget(self.main_widget)
|
||
|
|
||
|
self.label = ImageLabel(self, arr)
|
||
|
self.layout.addWidget(self.label, 0, 0)
|
||
|
self.layout.addLayout
|
||
|
self.mgr.add_window(self)
|
||
|
self.main_widget.show()
|
||
|
|
||
|
def closeEvent(self, event):
|
||
|
# Allow window to be destroyed by removing any
|
||
|
# references to it
|
||
|
self.mgr.remove_window(self)
|
||
|
|
||
|
|
||
|
def imread(filename):
|
||
|
"""
|
||
|
Read an image using QT's QImage.load
|
||
|
"""
|
||
|
qtimg = QImage()
|
||
|
if not qtimg.load(filename):
|
||
|
# QImage.load() returns false on failure, so raise an exception
|
||
|
raise IOError('Unable to load file %s' % filename)
|
||
|
if qtimg.depth() == 1:
|
||
|
raise IOError('1-bit images currently not supported')
|
||
|
# TODO: Warn about other odd formats we don't currently handle properly,
|
||
|
# such as the odd 16-bit packed formats QT supports
|
||
|
arrayptr = qtimg.bits()
|
||
|
# QT may pad the image, so we need to use bytesPerLine, not width for
|
||
|
# the conversion to a numpy array
|
||
|
bytes_per_pixel = qtimg.depth() // 8
|
||
|
pixels_per_line = qtimg.bytesPerLine() // bytes_per_pixel
|
||
|
img_size = pixels_per_line * qtimg.height() * bytes_per_pixel
|
||
|
arrayptr.setsize(img_size)
|
||
|
img = np.array(arrayptr)
|
||
|
# Reshape and trim down to correct dimensions
|
||
|
if bytes_per_pixel > 1:
|
||
|
img = img.reshape((qtimg.height(), pixels_per_line, bytes_per_pixel))
|
||
|
img = img[:, :qtimg.width(), :]
|
||
|
else:
|
||
|
img = img.reshape((qtimg.height(), pixels_per_line))
|
||
|
img = img[:, :qtimg.width()]
|
||
|
# Strip qt's false alpha channel if needed
|
||
|
# and reorder color axes as required
|
||
|
if bytes_per_pixel == 4 and not qtimg.hasAlphaChannel():
|
||
|
img = img[:, :, 2::-1]
|
||
|
elif bytes_per_pixel == 4:
|
||
|
img[:, :, 0:3] = img[:, :, 2::-1]
|
||
|
return img
|
||
|
|
||
|
def imshow(arr, fancy=False):
|
||
|
global app
|
||
|
if not app:
|
||
|
app = QApplication([])
|
||
|
|
||
|
arr = prepare_for_display(arr)
|
||
|
|
||
|
if not fancy:
|
||
|
iw = ImageWindow(arr, window_manager)
|
||
|
else:
|
||
|
from .skivi import SkiviImageWindow
|
||
|
iw = SkiviImageWindow(arr, window_manager)
|
||
|
|
||
|
iw.show()
|
||
|
|
||
|
|
||
|
def _app_show():
|
||
|
global app
|
||
|
if app and window_manager.has_windows():
|
||
|
app.exec_()
|
||
|
else:
|
||
|
print('No images to show. See `imshow`.')
|
||
|
|
||
|
|
||
|
def imsave(filename, img, format_str=None):
|
||
|
# we can add support for other than 3D uint8 here...
|
||
|
img = prepare_for_display(img)
|
||
|
qimg = QImage(img.data, img.shape[1], img.shape[0],
|
||
|
img.strides[0], QImage.Format_RGB888)
|
||
|
if _is_filelike(filename):
|
||
|
byte_array = QtCore.QByteArray()
|
||
|
qbuffer = QtCore.QBuffer(byte_array)
|
||
|
qbuffer.open(QtCore.QIODevice.ReadWrite)
|
||
|
saved = qimg.save(qbuffer, format_str.upper())
|
||
|
qbuffer.seek(0)
|
||
|
filename.write(qbuffer.readAll().data())
|
||
|
qbuffer.close()
|
||
|
else:
|
||
|
saved = qimg.save(filename)
|
||
|
if not saved:
|
||
|
from textwrap import dedent
|
||
|
msg = dedent(
|
||
|
'''The image was not saved. Allowable file formats
|
||
|
for the QT imsave plugin are:
|
||
|
BMP, JPG, JPEG, PNG, PPM, TIFF, XBM, XPM''')
|
||
|
raise RuntimeError(msg)
|
||
|
|
||
|
|
||
|
def _is_filelike(possible_filelike):
|
||
|
return callable(getattr(possible_filelike, 'write', None))
|