I'm trying to code a preview widget that is able to display a 2D numpy array image. This widget has a fixed size (square), but the image can have any shape.
It seems to work for some image shapes, but for other shapes it displays non-sense, and for some other shapes it crashes without any error message.
Do you see an obvious mistake in my code?
from silx.gui import qt
import numpy
GRAY_COLORTABLE = []
for i in range(256):
GRAY_COLORTABLE.append(qt.qRgb(i, i, i))
class PreviewImageWidget(qt.QWidget):
"""Preview image"""
def __init__(self, parent=None):
super().__init__(parent)
self.pixmap = qt.QPixmap()
self.setFixedSize(350, 350)
def paintEvent(self, event):
painter = qt.QPainter(self)
painter.drawPixmap(self.rect(), self.pixmap)
def setImage(self, img_array):
# TODO : adjust colortable to actual dtype (autoscale to min - max ??)
if img_array is None:
self.pixmap = qt.QPixmap()
else:
if img_array.dtype != numpy.uint8:
max_value = img_array.max()
img_array = 256. / max_value * img_array
img_array = img_array.astype(numpy.uint8)
# binary images are of dtype uint8
if img_array.max() == 1:
img_array = img_array * 255
image = qt.QImage(img_array,
img_array.shape[1], img_array.shape[0],
qt.QImage.Format_Indexed8)
image.setColorTable(GRAY_COLORTABLE)
self.pixmap = qt.QPixmap.fromImage(image)
self.update()
if __name__ == '__main__':
app = qt.QApplication([])
allPreviewWidgets = []
for sh in [(610, 500), (450, 700), (550, 600),
(500, 500), (510, 500), (500, 520)]:
img_array = numpy.zeros(sh, dtype=numpy.uint8)
img_array[200:350, 250:300] = 1
previewWidget = PreviewImageWidget()
previewWidget.setWindowTitle(str(img_array.shape))
previewWidget.show()
previewWidget.setImage(img_array)
allPreviewWidgets.append(previewWidget)
app.exec_()
The shapes that are almost square don't work. The rectangle ones work fine.
In the documentation of QPainter
, it says:
Note: The image is scaled to fit the rectangle, if both the image and rectangle size disagree.
An example of shape that makes the program crash: (2000, 500)
Edit: here is another example showing the same problem without a QPainter and without resizing the pixmap. I think this narrows it down to an issue with how QImage is decoding the numpy array.
from silx.gui import qt
import numpy
GRAY_COLORTABLE = []
for i in range(256):
GRAY_COLORTABLE.append(qt.qRgb(i, i, i))
def array2qpixmap(img_array):
if img_array.max() == 1:
img_array = img_array * 255
image = qt.QImage(img_array.astype(numpy.uint8),
img_array.shape[1], img_array.shape[0],
qt.QImage.Format_Indexed8)
image.setColorTable(GRAY_COLORTABLE)
return qt.QPixmap.fromImage(image)
if __name__ == '__main__':
app = qt.QApplication([])
labels = []
for sh in [(610, 500), (450, 700), (550, 600),
(500, 500), (510, 500), (200, 520)]:
img_array = numpy.zeros(sh, dtype=numpy.uint8)
img_array[200:350, 250:300] = 1
lab = qt.QLabel()
lab.setFixedSize(700, 700)
lab.setWindowTitle(str(sh))
lab.show()
lab.setPixmap(array2qpixmap(img_array))
labels.append(lab)
app.exec_()
I have only been able to reproduce the problem in the second case and I have found that the problem is the memory that since you are using the same object in all the transformations, in some cases the memory is being eliminated, the solution is to copy the data:
from PySide2 import QtCore, QtGui, QtWidgets
import numpy
GRAY_COLORTABLE = []
for i in range(256):
GRAY_COLORTABLE.append(QtGui.qRgb(i, i, i))
def array2qpixmap(img_array):
height, width = img_array.shape
bytesPerLine, _ = img_array.strides
image = QtGui.QImage(
img_array.data.tobytes(),
width,
height,
bytesPerLine,
QtGui.QImage.Format_Indexed8,
)
image.setColorTable(GRAY_COLORTABLE)
return QtGui.QPixmap.fromImage(image.copy())
if __name__ == "__main__":
app = QtWidgets.QApplication([])
labels = []
for sh in [
(610, 500),
(450, 700),
(550, 600),
(500, 500),
(510, 500),
(200, 520),
]:
img_array = numpy.zeros(sh, dtype=numpy.uint8)
img_array[200:350, 250:300] = 255
lab = QtWidgets.QLabel()
lab.resize(700, 700)
lab.setWindowTitle(str(sh))
lab.show()
lab.setPixmap(array2qpixmap(img_array.copy()))
labels.append(lab)
app.exec_()