<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Python GUIs - pyqg6</title><link href="https://www.pythonguis.com/" rel="alternate"/><link href="https://www.pythonguis.com/feeds/pyqg6.tag.atom.xml" rel="self"/><id>https://www.pythonguis.com/</id><updated>2020-05-11T09:00:00+00:00</updated><subtitle>Create GUI applications with Python and Qt</subtitle><entry><title>Fixing Crashes When Using NumPy Arrays with QImage in Qt Threads — How to safely pass image data between threads when streaming video or updating displays</title><link href="https://www.pythonguis.com/faq/crashing-in-qrunner-threads-with-numpy/" rel="alternate"/><published>2020-05-11T09:00:00+00:00</published><updated>2020-05-11T09:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2020-05-11:/faq/crashing-in-qrunner-threads-with-numpy/</id><summary type="html">I'm using a threaded runner to stream a live video feed by converting a NumPy array to a &lt;code&gt;QImage&lt;/code&gt;, then to a &lt;code&gt;QPixmap&lt;/code&gt;, and displaying it on a &lt;code&gt;QLabel&lt;/code&gt;. But I'm frequently encountering crashes when the label is resized too quickly or the scroll area is scrolled. Could this be a problem with the &lt;code&gt;QImage&lt;/code&gt; memory buffer getting cleared before the &lt;code&gt;QPixmap&lt;/code&gt; can update? Is this fixable, or is it a fundamental issue with threads in Python/Qt?</summary><content type="html">
            &lt;blockquote&gt;
&lt;p&gt;I'm using a threaded runner to stream a live video feed by converting a NumPy array to a &lt;code&gt;QImage&lt;/code&gt;, then to a &lt;code&gt;QPixmap&lt;/code&gt;, and displaying it on a &lt;code&gt;QLabel&lt;/code&gt;. But I'm frequently encountering crashes when the label is resized too quickly or the scroll area is scrolled. Could this be a problem with the &lt;code&gt;QImage&lt;/code&gt; memory buffer getting cleared before the &lt;code&gt;QPixmap&lt;/code&gt; can update? Is this fixable, or is it a fundamental issue with threads in Python/Qt?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is a common problem when working with NumPy arrays and &lt;code&gt;QImage&lt;/code&gt; across threads. The good news is that it's fixable. The crashes come from how &lt;code&gt;QImage&lt;/code&gt; handles the underlying memory of a NumPy array.&lt;/p&gt;
&lt;h2 id="why-the-crash-happens"&gt;Why the crash happens&lt;/h2&gt;
&lt;p&gt;When you create a &lt;code&gt;QImage&lt;/code&gt; from a NumPy array, &lt;code&gt;QImage&lt;/code&gt; doesn't copy the data. Instead it holds a reference to the original memory buffer provided by the NumPy array.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import numpy as np
from PyQt6.QtGui import QImage

# Create a NumPy array (e.g. a video frame)
array = np.random.randint(0, 255, (480, 640, 3), dtype=np.uint8)

# QImage points to the same memory &amp;mdash; no copy is made
image = QImage(
    array.data,
    array.shape[1],
    array.shape[0],
    array.strides[0],
    QImage.Format.Format_RGB8888,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This is efficient, but it creates a dangerous situation in a multithreaded application. If the NumPy array is modified or goes out of scope in the worker thread while the GUI thread is still using the &lt;code&gt;QImage&lt;/code&gt; to paint, the memory that &lt;code&gt;QImage&lt;/code&gt; is pointing to may no longer be valid. The result is a segfault or (worse) a silent crash without any information about what has happened.&lt;/p&gt;
&lt;p&gt;This is especially likely when frames are arriving quickly (as with a live video feed) and the GUI is being redrawn frequently &amp;mdash; for example, during a resize or a scroll.&lt;/p&gt;
&lt;h2 id="the-fix-copy-the-image-data"&gt;The fix: copy the image data&lt;/h2&gt;
&lt;p&gt;The simplest and most reliable fix is to make sure the &lt;code&gt;QImage&lt;/code&gt; owns its own copy of the pixel data before you pass it to the GUI thread. You can do this by calling &lt;code&gt;.copy()&lt;/code&gt; on the &lt;code&gt;QImage&lt;/code&gt;:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;image = QImage(
    array.data,
    array.shape[1],
    array.shape[0],
    array.strides[0],
    QImage.Format.Format_RGB888,
).copy()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;.copy()&lt;/code&gt; call creates a new &lt;code&gt;QImage&lt;/code&gt; with its own independent memory buffer. Now it doesn't matter if the original NumPy array changes or disappears &amp;mdash; the &lt;code&gt;QImage&lt;/code&gt; is safe to use from the GUI thread.&lt;/p&gt;
&lt;p&gt;If you've already tried using &lt;code&gt;.copy()&lt;/code&gt; and it hasn't worked, bear in mind that &lt;em&gt;where&lt;/em&gt; you use the copy matters just as much as using it at all.&lt;/p&gt;
&lt;h2 id="where-to-copy-matters"&gt;Where to copy matters&lt;/h2&gt;
&lt;p&gt;If you create the &lt;code&gt;QImage&lt;/code&gt; in the worker thread and emit it via a &lt;a href="https://www.pythonguisi.com/tutorials/pyqt6-signals-slots-events/"&gt;signal&lt;/a&gt;, the copy needs to happen &lt;strong&gt;before&lt;/strong&gt; the signal is emitted. If the signal carries a reference to the original (non-copied) &lt;code&gt;QImage&lt;/code&gt;, the data might still be invalidated before the GUI thread processes it.&lt;/p&gt;
&lt;p&gt;Here's the pattern that works:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;# In the worker thread
image = QImage(
    frame.data,
    frame.shape[1],
    frame.shape[0],
    frame.strides[0],
    QImage.Format.Format_RGB888,
).copy()  # Copy immediately, before emitting

self.signals.result.emit(image)  # Now safe to send to GUI thread
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;And in the main thread, connect that signal to a slot that updates the display:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;def update_display(self, image):
    pixmap = QPixmap.fromImage(image)
    self.label.setPixmap(pixmap)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Because the &lt;code&gt;QImage&lt;/code&gt; was copied before it crossed the thread boundary, the GUI thread has full ownership of the data and can paint it safely.&lt;/p&gt;
&lt;h2 id="use-signals-to-control-the-update-flow"&gt;Use signals to control the update flow&lt;/h2&gt;
&lt;p&gt;Another source of crashes is calling GUI methods directly from a worker thread. In Qt, &lt;strong&gt;all GUI updates must happen on the main thread&lt;/strong&gt;. If you're calling &lt;code&gt;label.setPixmap(...)&lt;/code&gt; from inside a worker or a callback running on a background thread, that's undefined behavior and will eventually crash.&lt;/p&gt;
&lt;p&gt;The solution is to always use &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-signals-slots-events/"&gt;signals and slots&lt;/a&gt; to communicate between threads. Emit a signal from the worker carrying the processed image, and connect it to a slot on the main thread that performs the GUI update.&lt;/p&gt;
&lt;p&gt;This also gives you a natural way to throttle updates. If frames are arriving faster than the GUI can paint them, you can use a flag to skip frames that arrive while the previous one is still being displayed.&lt;/p&gt;
&lt;h2 id="complete-working-example"&gt;Complete working example&lt;/h2&gt;
&lt;p&gt;Here's a full example that simulates a video feed using a &lt;code&gt;QRunnable&lt;/code&gt; and a &lt;code&gt;QThreadPool&lt;/code&gt;. It generates random NumPy frames in a background thread and safely displays them on a &lt;code&gt;QLabel&lt;/code&gt;. If you're new to running background tasks with &lt;code&gt;QThreadPool&lt;/code&gt;, see our &lt;a href="https://www.pythonguis.com/tutorials/multithreading-pyqt6-applications-qthreadpool/"&gt;detailed guide to multithreading PyQt6 applications&lt;/a&gt;.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys
import time

import numpy as np
from PyQt6.QtCore import (
    QObject,
    QRunnable,
    QThreadPool,
    pyqtSignal,
    pyqtSlot,
)
from PyQt6.QtGui import QImage, QPixmap
from PyQt6.QtWidgets import (
    QApplication,
    QLabel,
    QMainWindow,
    QScrollArea,
    QVBoxLayout,
    QWidget,
)


class WorkerSignals(QObject):
    frame_ready = pyqtSignal(QImage)
    finished = pyqtSignal()


class VideoWorker(QRunnable):
    def __init__(self):
        super().__init__()
        self.signals = WorkerSignals()
        self.running = True

    @pyqtSlot()
    def run(self):
        while self.running:
            # Simulate a video frame (e.g. from a camera or stream)
            frame = np.random.randint(
                0, 255, (480, 640, 3), dtype=np.uint8
            )

            # Create QImage and copy it immediately so it owns its data
            image = QImage(
                frame.data,
                frame.shape[1],
                frame.shape[0],
                frame.strides[0],
                QImage.Format.Format_RGB888,
            ).copy()

            # Emit the safe, copied image to the main thread
            self.signals.frame_ready.emit(image)

            # Simulate ~30 fps
            time.sleep(1 / 30)

        self.signals.finished.emit()

    def stop(self):
        self.running = False


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Threaded Video Display")

        self.label = QLabel("Waiting for frames...")
        self.label.setScaledContents(True)

        scroll_area = QScrollArea()
        scroll_area.setWidget(self.label)
        scroll_area.setWidgetResiizable(True)

        container = QWidget()
        layout = QVBoxLayout(container)
        layout.addWidget(scroll_area)
        self.setCentralWidget(container)

        self.resize(700, 520)

        # Set up threading
        self.threadpool = QThreadPool()
        self.worker = VideoWorker()
        self.worker.signals.frame_ready.connect(self.update_display)
        self.worker.signals.finished.connect(self.on_finished)
        self.threadpool.start(self.worker)

    def update_display(self, image):
        """Runs on the main thread &amp;mdash; safe to update the GUI here."""
        pixmap = QPixmap.fromImage(image)
        self.label.setPixmap(pixmap)

    def on_finished(self):
        print("Worker finished.")

    def closeEvent(self, event):
        self.worker.stop()
        self.threadpool.waitForDone(2000)
        event.accept()


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;When you run this, you'll see a window displaying rapidly changing random noise &amp;mdash; a stand-in for a real video stream. You can resize the window and scroll around without crashes, because the image data is safely copied before it crosses the thread boundary, and all GUI updates happen on the main thread via signals.&lt;/p&gt;
&lt;h2 id="recap"&gt;Recap&lt;/h2&gt;
&lt;p&gt;When working with NumPy arrays and &lt;code&gt;QImage&lt;/code&gt; across threads, keep these three things in mind:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;QImage&lt;/code&gt; does not copy NumPy data.&lt;/strong&gt; It points to the original array's memory buffer. If that buffer changes or is freed, the &lt;code&gt;QImage&lt;/code&gt; becomes invalid.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Call &lt;code&gt;.copy()&lt;/code&gt; on the &lt;code&gt;QImage&lt;/code&gt; before emitting it across threads.&lt;/strong&gt; This gives the &lt;code&gt;QImage&lt;/code&gt; its own memory, independent of the NumPy array.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Always update the GUI from the main thread.&lt;/strong&gt; Use &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-signals-slots-events/"&gt;signals&lt;/a&gt; to send data from background workers to slots connected on the main thread, where it's safe to call &lt;code&gt;setPixmap()&lt;/code&gt; and other GUI methods.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With these practices in place, you can stream video or display rapidly changing image data without encountering the mysterious crashes that come from shared memory across threads.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt6 see my book, &lt;a href="https://www.martinfitzpatrick.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="python"/><category term="pyqg6"/><category term="pyside6"/><category term="threads"/><category term="numpy"/><category term="qimage"/><category term="concurrency"/><category term="qt"/><category term="qt6"/><category term="data-science"/><category term="pyside6-data-science"/><category term="pyside6-concurrency"/></entry></feed>