diff --git a/doc/source/install.rst b/doc/source/install.rst index 5747f72f00..ec4e83d09f 100644 --- a/doc/source/install.rst +++ b/doc/source/install.rst @@ -10,11 +10,11 @@ This table summarizes the support matrix of silx: +------------+--------------+--------------------------------+ | System | Python vers. | Qt and its bindings | +------------+--------------+--------------------------------+ -| `Windows`_ | 3.7-3.10 | PyQt5.6+, PySide6.4+, PyQt6.3+ | +| `Windows`_ | 3.7-3.10 | PyQt5.9+, PySide6.4+, PyQt6.3+ | +------------+--------------+--------------------------------+ -| `MacOS`_ | 3.7-3.10 | PyQt5.6+, PySide6.4+, PyQt6.3+ | +| `MacOS`_ | 3.7-3.10 | PyQt5.9+, PySide6.4+, PyQt6.3+ | +------------+--------------+--------------------------------+ -| `Linux`_ | 3.7-3.10 | PyQt5.3+, PySide6.4+, PyQt6.3+ | +| `Linux`_ | 3.7-3.10 | PyQt5.9+, PySide6.4+, PyQt6.3+ | +------------+--------------+--------------------------------+ For the description of *silx* dependencies, see the Dependencies_ section. diff --git a/src/silx/gui/_glutils/font.py b/src/silx/gui/_glutils/font.py index 2fd5653072..c86f9d20c0 100644 --- a/src/silx/gui/_glutils/font.py +++ b/src/silx/gui/_glutils/font.py @@ -109,7 +109,7 @@ def rasterTextQt( font = qt.QFont(font, size, weight, italic) # get text size - image = qt.QImage(1, 1, qt.QImage.Format_RGB888) + image = qt.QImage(1, 1, qt.QImage.Format_Grayscale8) painter = qt.QPainter() painter.begin(image) painter.setPen(qt.Qt.white) @@ -133,11 +133,11 @@ def rasterTextQt( # align line size to 32 bits to ease conversion to numpy array width = 4 * ((width + 3) // 4) image = qt.QImage( - int(width), int(bounds.height() * devicePixelRatio + 2), qt.QImage.Format_RGB888 + int(width), + int(bounds.height() * devicePixelRatio + 2), + qt.QImage.Format_Grayscale8, ) image.setDevicePixelRatio(devicePixelRatio) - - # TODO if Qt5 use Format_Grayscale8 instead image.fill(0) # Raster text @@ -150,9 +150,6 @@ def rasterTextQt( array = convertQImageToArray(image) - # RGB to R - array = numpy.ascontiguousarray(array[:, :, 0]) - # Remove leading and trailing empty columns/rows but one on each side filled_rows = numpy.nonzero(numpy.sum(array, axis=1))[0] filled_columns = numpy.nonzero(numpy.sum(array, axis=0))[0] diff --git a/src/silx/gui/utils/image.py b/src/silx/gui/utils/image.py index 3dbb949254..8b3c843030 100644 --- a/src/silx/gui/utils/image.py +++ b/src/silx/gui/utils/image.py @@ -84,30 +84,41 @@ def convertArrayToQImage(array: numpy.ndarray) -> qt.QImage: def convertQImageToArray(image: qt.QImage) -> numpy.ndarray: """Convert a QImage to a numpy array. - If QImage format is not Format_RGB888, Format_RGBA8888 or Format_ARGB32, - it is first converted to one of this format depending on - the presence of an alpha channel. + If QImage format is not one of: + - Format_Grayscale8 + - Format_RGB888 + - Format_RGBA8888 + - Format_ARGB32, + it is first converted to one of this format. The created numpy array is using a copy of the QImage data. :param QImage image: The QImage to convert. - :return: The image array of RGB or RGBA channels of shape - (height, width, channels (3 or 4)) + :return: Image array of uint8 of shape: + - (height, width) for grayscale images + - (height, width, channels (3 or 4)) for RGB and RGBA images """ - rgba8888 = getattr(qt.QImage, 'Format_RGBA8888', None) # Only in Qt5 + supportedFormats = ( + qt.QImage.Format_Grayscale8, + qt.QImage.Format_ARGB32, + qt.QImage.Format_RGB888, + qt.QImage.Format_RGBA8888, + ) # Convert to supported format if needed - if image.format() not in (qt.QImage.Format_ARGB32, - qt.QImage.Format_RGB888, - rgba8888): + if image.format() not in supportedFormats: if image.hasAlphaChannel(): - image = image.convertToFormat( - rgba8888 if rgba8888 is not None else qt.QImage.Format_ARGB32) + image = image.convertToFormat(qt.QImage.Format_RGBA8888) else: image = image.convertToFormat(qt.QImage.Format_RGB888) format_ = image.format() - channels = 3 if format_ == qt.QImage.Format_RGB888 else 4 + if format_ == qt.QImage.Format_Grayscale8: + channels = 1 + elif format_ == qt.QImage.Format_RGB888: + channels = 3 + else: + channels = 4 ptr = image.bits() if qt.BINDING == 'PyQt5': @@ -133,6 +144,9 @@ def convertQImageToArray(image: qt.QImage) -> numpy.ndarray: else: # big endian: ARGB -> RGBA view = view[:, :, (1, 2, 3, 0)] + if channels == 1: # Remove channel dimension + view = view[:, :, 0] + # Format_RGB888 and Format_RGBA8888 do not need reshuffling channels: # They are byte-ordered and already in the right order diff --git a/src/silx/gui/utils/test/test_image.py b/src/silx/gui/utils/test/test_image.py index dc0ae54e9a..6158020eac 100644 --- a/src/silx/gui/utils/test/test_image.py +++ b/src/silx/gui/utils/test/test_image.py @@ -1,6 +1,6 @@ # /*########################################################################## # -# Copyright (c) 2017-2018 European Synchrotron Radiation Facility +# Copyright (c) 2017-2023 European Synchrotron Radiation Facility # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -28,50 +28,59 @@ __date__ = "16/01/2017" import numpy +import pytest from silx.gui import qt -from silx.utils.testutils import ParametricTestCase -from silx.gui.utils.testutils import TestCaseQt from silx.gui.utils.image import convertArrayToQImage, convertQImageToArray -class TestQImageConversion(TestCaseQt, ParametricTestCase): - """Tests conversion of QImage to/from numpy array.""" +@pytest.mark.parametrize( + "format_, channels", + [ + (qt.QImage.Format_RGB888, 3), # Native support + (qt.QImage.Format_ARGB32, 4), # Native support + ] +) +def testConvertArrayToQImage(format_, channels): + """Test conversion of numpy array to QImage""" + image = numpy.arange( + 3*3*channels, dtype=numpy.uint8).reshape(3, 3, channels) + qimage = convertArrayToQImage(image) - def testConvertArrayToQImage(self): - """Test conversion of numpy array to QImage""" - for format_, channels in [('Format_RGB888', 3), - ('Format_ARGB32', 4)]: - with self.subTest(format_): - image = numpy.arange( - 3*3*channels, dtype=numpy.uint8).reshape(3, 3, channels) - qimage = convertArrayToQImage(image) + assert (qimage.height(), qimage.width()) == image.shape[:2] + assert qimage.format() == format_ - self.assertEqual(qimage.height(), image.shape[0]) - self.assertEqual(qimage.width(), image.shape[1]) - self.assertEqual(qimage.format(), getattr(qt.QImage, format_)) + for row in range(3): + for col in range(3): + # Qrgb has no alpha channel, not compared + # Qt uses x,y while array is row,col... + assert qt.QColor(qimage.pixel(col, row)) == qt.QColor(*image[row, col, :3]) - for row in range(3): - for col in range(3): - # Qrgb has no alpha channel, not compared - # Qt uses x,y while array is row,col... - self.assertEqual(qt.QColor(qimage.pixel(col, row)), - qt.QColor(*image[row, col, :3])) +@pytest.mark.parametrize( + "format_, channels", + [ + (qt.QImage.Format_RGB888, 3), # Native support + (qt.QImage.Format_ARGB32, 4), # Native support + (qt.QImage.Format_RGB32, 3), # Conversion to RGB + ] +) +def testConvertQImageToArray(format_, channels): + """Test conversion of QImage to numpy array""" + color = numpy.arange(channels) # RGB(A) values + qimage = qt.QImage(3, 3, format_) + qimage.fill(qt.QColor(*color)) + image = convertQImageToArray(qimage) - def testConvertQImageToArray(self): - """Test conversion of QImage to numpy array""" - for format_, channels in [ - ('Format_RGB888', 3), # Native support - ('Format_ARGB32', 4), # Native support - ('Format_RGB32', 3)]: # Conversion to RGB - with self.subTest(format_): - color = numpy.arange(channels) # RGB(A) values - qimage = qt.QImage(3, 3, getattr(qt.QImage, format_)) - qimage.fill(qt.QColor(*color)) - image = convertQImageToArray(qimage) + assert (qimage.height(), qimage.width(), len(color)) == image.shape + assert numpy.all(numpy.equal(image, color)) - self.assertEqual(qimage.height(), image.shape[0]) - self.assertEqual(qimage.width(), image.shape[1]) - self.assertEqual(image.shape[2], len(color)) - self.assertTrue(numpy.all(numpy.equal(image, color))) + +def testConvertQImageToArrayGrayscale(): + """Test conversion of grayscale QImage to numpy array""" + qimage = qt.QImage(3, 3, qt.QImage.Format_Grayscale8) + qimage.fill(1) + image = convertQImageToArray(qimage) + + assert (qimage.height(), qimage.width()) == image.shape + assert numpy.all(numpy.equal(image, 1))