Search code examples
pythonimagenumpypyside2

Issue when drawing a numpy array into a QWidget


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_()

enter image description here 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_()

Solution

  • 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_()