Of course! The combination of Python, NumPy (mat), and QImage is a cornerstone of data visualization and image processing in the PyQt or PySide frameworks. It allows you to efficiently convert numerical data (like matrices) into visual images that can be displayed in a GUI.

Let's break down the process, the concepts, and provide clear, practical examples.
The Core Idea: The Bridge
The fundamental challenge is that numpy.ndarray (your "mat") and QImage are two completely different data structures:
numpy.ndarray: A multi-dimensional array of numbers. It's highly efficient for numerical computation but has no inherent concept of color or display.QImage: A Qt object that represents an image. It's designed for display, painting, and storage. It expects data in a specific, memory-efficient format.
The "bridge" between them is the QImage constructor that can accept raw data in a specific format.
Key Concept: Pixel Formats (QImage.Format)
When you create a QImage from a NumPy array, you must specify how the numbers in the array map to pixels. The most common format is QImage.Format_RGB888.

Format_RGB888: This format stores each pixel as 3 consecutive bytes, representing Red, Green, and Blue values.- This means a 100x100 image in
Format_RGB888requires a data buffer of100 * 100 * 3 = 30,000bytes.
- This means a 100x100 image in
Format_Grayscale8: For single-channel (grayscale) images. Each pixel is 1 byte (0-255).Format_Indexed8: Uses a color palette (aQColorTable). Good for images with a limited number of colors.Format_RGBA8888: Similar toRGB888but with an extra Alpha channel for transparency (4 bytes per pixel).
The conversion process involves reshaping and reinterpreting your NumPy array to match the memory layout of one of these formats.
Step-by-Step Conversion Guide
Here is the standard workflow for converting a NumPy array to a QImage.
Step 1: Ensure Correct Data Type
The QImage constructor expects a contiguous block of memory. A standard numpy.ndarray might not be. Use np.ascontiguousarray() to fix this.
The data type is also critical. For Format_RGB888, you typically want an 8-bit unsigned integer array (np.uint8).

import numpy as np # Example: A 100x100x3 array of random colors # Ensure dtype is uint8 for QImage rgb_array = np.random.randint(0, 256, (100, 100, 3), dtype=np.uint8) # Make sure the array is contiguous in memory contiguous_rgb_array = np.ascontiguousarray(rgb_array)
Step 2: Reshape the Array (If Necessary)
The QImage constructor expects the data to be a flat, one-dimensional array. For an RGB image, the dimensions should be (height, width, channels). The constructor can handle this shape directly if the format is Format_RGB888.
For a grayscale image (height, width), no reshaping is needed.
Step 3: Create the QImage
Now, use the QImage constructor with three key arguments:
data: The NumPy array itself (the contiguous one).width: The width of the image in pixels.height: The height of the image in pixels.format: TheQImage.Formatenum value (e.g.,QImage.Format_RGB888).
The constructor will reinterpret the memory of your NumPy array as image data.
from PyQt5.QtGui import QImage
# Get dimensions from the array
height, width, _ = contiguous_rgb_array.shape
# Create the QImage
q_image = QImage(
contiguous_rgb_array.data,
width,
height,
contiguous_rgb_array.strides[0], # This is the bytes per line (row)
QImage.Format_RGB888
)
Note on strides[0]: The third argument is the number of bytes per scanline (row). For a standard (height, width, 3) array, array.strides[0] gives you this value directly (width * 3).
Complete, Runnable Example
Here is a full example using PyQt5 to display a NumPy-generated image in a window.
import sys
import numpy as np
from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QVBoxLayout, QWidget
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtCore import Qt
class ImageDisplayer(QMainWindow):
def __init__(self, image_array):
super().__init__()
self.setWindowTitle("NumPy to QImage Display")
# --- Conversion Logic ---
# 1. Ensure data is uint8 and contiguous
if image_array.dtype != np.uint8:
# Normalize and scale to 0-255 if it's a float array
if image_array.max() <= 1.0:
image_array = (image_array * 255).astype(np.uint8)
else:
image_array = image_array.astype(np.uint8)
contiguous_array = np.ascontiguousarray(image_array)
# 2. Determine QImage format based on array shape
if contiguous_array.ndim == 2: # Grayscale
height, width = contiguous_array.shape
bytes_per_line = width
q_image = QImage(
contiguous_array.data,
width,
height,
bytes_per_line,
QImage.Format_Grayscale8
)
elif contiguous_array.ndim == 3 and contiguous_array.shape[2] == 3: # RGB
height, width, _ = contiguous_array.shape
bytes_per_line = contiguous_array.strides[0]
q_image = QImage(
contiguous_array.data,
width,
height,
bytes_per_line,
QImage.Format_RGB888
)
else:
raise ValueError("Unsupported array shape for QImage conversion")
# --- GUI Setup ---
label = QLabel()
pixmap = QPixmap.fromImage(q_image)
label.setPixmap(pixmap.scaled(400, 400, Qt.KeepAspectRatio, Qt.SmoothTransformation))
self.setCentralWidget(label)
self.resize(450, 450)
if __name__ == '__main__':
app = QApplication(sys.argv)
# --- Example 1: Create a simple RGB gradient ---
width, height = 300, 200
x = np.linspace(0, 255, width, dtype=np.uint8)
y = np.linspace(0, 255, height, dtype=np.uint8)
xx, yy = np.meshgrid(x, y)
# Create an RGB image
rgb_data = np.zeros((height, width, 3), dtype=np.uint8)
rgb_data[:, :, 0] = xx # Red channel varies with x
rgb_data[:, :, 1] = yy # Green channel varies with y
rgb_data[:, :, 2] = 128 # Blue channel is constant
window = ImageDisplayer(rgb_data)
window.show()
sys.exit(app.exec_())
Handling Grayscale Images (2D Arrays)
The process is simpler for 2D arrays because each value maps directly to a single pixel intensity.
# Create a 2D NumPy array (e.g., from a mathematical function)
size = 256
x = np.linspace(-2, 2, size)
y = np.linspace(-2, 2, size)
xx, yy = np.meshgrid(x, y)
grayscale_data = np.sin(xx**2 + yy**2) * 128 + 128 # Create a pattern
grayscale_data = grayscale_data.astype(np.uint8) # Convert to uint8
# Convert to QImage
height, width = grayscale_data.shape
q_image_grayscale = QImage(
grayscale_data.data,
width,
height,
grayscale_data.strides[0], # bytes per line is just width for 2D array
QImage.Format_Grayscale8
)
Important Pitfalls and Solutions
-
Data Type (
dtype): If your array isfloat64orint32, theQImageconstructor will misinterpret the data, leading to a garbled or incorrect image. Always convert tonp.uint8before creating theQImage. Remember to normalize your data to the 0-255 range first if it's floating-point. -
Memory Contiguity: If you get a
TypeErrorabout the buffer being the wrong size or not C-contiguous, usenp.ascontiguousarray(). This is a very common source of errors. -
Array Shape: The shape must be
(height, width)for grayscale or(height, width, 3)for RGB. If your data is(width, height, 3), you must transpose it:array = array.transpose(1, 0, 2).
The Reverse: QImage to NumPy Array
It's just as useful to go the other way, for example, to process an image loaded from a file.
from PyQt5.QtGui import QPixmap
# Load an image from a file
pixmap = QPixmap("my_image.png")
q_image = pixmap.toImage()
# Get the raw data
width = q_image.width()
height = q_image.height()
# .bits() returns a PyCapsule object, which can be converted to a buffer
ptr = q_image.bits()
# The data is not contiguous by default, so we make a copy
ptr.setsize(height * q_image.bytesPerLine())
numpy_array = np.array(ptr).reshape(height, width, -1)
# If it's an RGB image, the last dimension will be 3
if q_image.format() == QImage.Format_RGB888:
# The array will be of type np.ubyte (uint8)
rgb_array_from_qimage = numpy_array[:, :, :3] # Ensure it's exactly 3 channels
print(f"Successfully converted QImage to NumPy array of shape: {rgb_array_from_qimage.shape}") 