How to correctly load images asynchronously in PyQt5?

2024/9/20 22:42:55

I'm trying to figure out how to accomplish an async image load correctly, in PyQt Qlistview.

My main widget consists of a Qlistview and a QLineEdit textbox. I have a database of actors which I query using a subclass of QAbstractListModel When text is entered in the textbox, the database is queried and the Model is populated with the results. Results are then displayed in the Qlistview. (A result for each Actor contains the actors name and a path to an image.)

Like so: enter image description here

The problem begins when the result set is too large (Larger than 50), loading the images from disk is taking it's toll and hanging the UI. The behaviour I wish to achieve is to initially load a placeholder image for all the results and then in a different thread load the specific image from disk and when it's loaded update the Qlistview item with the newly loaded image.

To that end I created a custom QItemDelegate class that has a cache of all images that needs to be loaded. If the image is not in the cache then it draws the placeholder image and sends a signal to a another thread that loads that image and puts it in the cache.

My Delegate class:

class MyDelegate(QStyledItemDelegate):t1 = pyqtSignal(str, str, dict)def __init__(self, image_cache, loader_thread, parent=None):super(MyDelegate, self).__init__(parent)self.placeholder_image = QPixmap(PLACEHOLDER_IMAGE_PATH).scaled(200, 300)self.image_cache = image_cacheself.loader_thread = loader_threadself.t1.connect(self.loader_thread.insert_into_queue)def paint(self, QPainter, QStyleOptionViewItem, QModelIndex):rect = QStyleOptionViewItem.rectactor_name = QModelIndex.data(Qt.DisplayRole)actor_thumb = QModelIndex.data(Qt.UserRole)pic_rect = QRect(rect.left(), rect.top(), 200, 300)text_rect = QRect(rect.left(), rect.top() + 300, 200, 20)try:cached_thumb = self.image_cache[actor_name]print("Got image: {} from cache".format(actor_name)except KeyError as e:self.t1.emit(actor_name, actor_thumb, self.image_cache)cached_thumb = self.placeholder_imageprint("Drawing placeholder image for {}".format(actor_name)QPainter.drawPixmap(pic_rect, cached_thumb)QPainter.drawText(text_rect, Qt.AlignCenter, actor_name)if QStyleOptionViewItem.state & QStyle.State_Selected:highlight_color = QStyleOptionViewItem.palette.highlight().color()highlight_color.setAlpha(50)highlight_brush = QBrush(highlight_color)QPainter.fillRect(rect, highlight_brush)def sizeHint(self, QStyleOptionViewItem, QModelIndex):return QSize(200, 320)

LoaderThread:

class LoaderThread(QObject):def __init__(self):super(LoaderThread, self).__init__()@pyqtSlot(str, str, dict)def insert_into_queue(self, name, thumb_path, image_cache):print("Got signal, loading image for {} from disk".format(name))pixmap = QPixmap(thumb_path).scaled(200, 300)image_cache[name] = pixmapprint("Image for {} inserted to cache".format(name))

Relevant part of the main window __init__ method:

image_cache = {}
lt = loader_tread.LoaderThread()
self.thread = QThread()
lt.moveToThread(self.thread)
self.thread.start()
self.delegate = MyDelegate(image_cache, lt)

While this approach seems to work as so far as the images are loading correctly, the UI is hanging when multiple calls to self.t1.emit(actor_name, actor_thumb, self.image_cache) in MyDelegate are made.

In fact, the delay is almost identical to when the images are loaded in the same thread like so:

 try:cached_thumb = self.image_cache[actor_name]print("Got image: {} from cache".format(QModelIndex.data(Qt.DisplayRole)))except KeyError as e:# self.t1.emit(actor_name, actor_thumb, self.image_cache)pixmap = QPixmap(actor_thumb).scaled(200,300)self.image_cache[actor_name] = pixmapcached_thumb = self.image_cache[actor_name]

If someone has any pointers about what I am doing wrong or about how the desired behavior can be achieved they will be well received.

P.S I'm aware that I can limit the result set in the database query, however this is not what I wish to do.

Answer

I just had the same problem today : Trying to load thumbnails QPixmaps on a separate Qthread, and it was hanging the UI.

I figured out that for some reason... using QPixmap to load image from disk will always "freeze" the main GUI Thread :

pixmap = QPixmap(thumb_path).scaled(200, 300)  # Will hang UI

One solution ; load the image using a QImage object, then generate the QPixmap from image, The following code do the job for me :

image = QImage(thumb_path)
pixmap = QPixmap.fromImage(image).scaled(200, 300)

Scrolling is smooth, tested with 500+ thumbnails.

https://en.xdnf.cn/q/72451.html

Related Q&A

How to print results of Python ThreadPoolExecutor.map immediately?

I am running a function for several sets of iterables, returning a list of all results as soon as all processes are finished.def fct(variable1, variable2):# do an operation that does not necessarily ta…

Python dir equivalent in perl?

The dir command in Python 2.7.x lists all accessible symbols from a module. Is there an equivalent in Perl 5.x to list all accessible symbols from a package?

Entire JSON into One SQLite Field with Python

I have what is likely an easy question. Im trying to pull a JSON from an online source, and store it in a SQLite table. In addition to storing the data in a rich table, corresponding to the many fiel…

Python scipy module import error due to missing ._ufuncs dll

I have some troubles with sub-module integrate from scipy in python. I have a 64 bits architecture, and it seems, according to the first lines of the python interpreter (see below) that I am also using…

How can I call python program from VBA?

Just as the title goes.I have a python program which processes some data file I downloaded from email.I am writing a vba script which can download the email attachments and execute the python program t…

Embedding CPython: how do you constuct Python callables to wrap C callback pointers?

Suppose I am embedding the CPython interpreter into a larger program, written in C. The C component of the program occasionally needs to call functions written in Python, supplying callback functions …

python - beautifulsoup - TypeError: sequence item 0: expected string, Tag found

Im using beautifulsoup to extract images and links from a html string. It all works perfectly fine, however with some links that have a tag in the link contents it is throwing an error.Example Link:<…

Python evdev detect device unplugged

Im using the great "evdev" library to listen to a USB barcode reader input and I need to detect if the device suddenly gets unplugged/unresponsive because otherwise the python script reading …

python: urllib2 using different network interface

I have the following code:f = urllib2.urlopen(url) data = f.read() f.close()Its running on a machine with two network interfaces. Id like to specify which interface I want the code to use. Specifically…

RuntimeError: as_numpy_iterator() is not supported while tracing functions

while i was using function as_numpy_iterator() got error--------------------------------------------------------------------------- RuntimeError Traceback (most recent call…