Results 1 to 2 of 2

Thread: PyQt5 - Loading images asynchronously in ItemListView

  1. #1
    Join Date
    Apr 2017
    Posts
    2
    Thanks
    1
    Platforms
    Unix/X11 Windows

    Default PyQt5 - Loading images asynchronously in ItemListView

    Greetings Qt Center!
    I'm having trouble figuring out how to load images asynchronously in QListItemView. I have a database of actors where each row in the database has the following information [id,actor_name,actor_thumb_path].
    I've made a model that for test purpeses loads all the actors in the database, and a QListItemView with the setViewMode set to QListView.IconMode to display the actors names and thumbnails.
    Obviously doing this in a single thread hangs the UI therefore My goal is to draw a placeholder image in the view while another thread loads the image from the disk. When the image is loaded the main thread is notified and redraws the correct image in stead of the placeholder.

    To that end I created a custom delegate that receive a cache dict(), when the time comes to draw an image it checks if the actor id is present in the cache, if it is, it draws the image, if not it emits a signal sending the actor_id and the image path to the 'image_loader' thread which loads the image and places it in the common cache.

    While all of this seems to work, to the extent that the images are drawn correctly, there is no performance benefit to this method as the UI still hangs when the loader thread is loading the images.


    ActorModel.py:
    Qt Code:
    1. from PyQt5.QtCore import QAbstractListModel, Qt
    2.  
    3.  
    4. class ActorModel(QAbstractListModel):
    5. def __init__(self, db, parent=None):
    6. QAbstractListModel.__init__(self, parent)
    7. self.actors = None
    8. self.db = db
    9.  
    10. def update(self, actors):
    11. self.beginResetModel()
    12. self.actors = actors
    13. self.endResetModel()
    14.  
    15. def actor_search(self, search_string):
    16. actors = self.db.star_search("")
    17. self.update(actors)
    18.  
    19. def rowCount(self, parent=None, *args, **kwargs):
    20. return len(self.actors)
    21.  
    22. def data(self, QModelIndex, role=None):
    23. row = QModelIndex.row()
    24.  
    25. if role == Qt.UserRole + 1:
    26. return self.actors[row]["id"]
    27.  
    28. if role == Qt.DisplayRole:
    29. value = "{}".format(self.actors[row]["name"])
    30. return value
    31.  
    32. if role == Qt.UserRole:
    33. thumb_path = self.actors[row]["thumbnail"]
    34. return thumb_path
    To copy to clipboard, switch view to plain text mode 

    MainWindow:
    Qt Code:
    1. class MainWindow(QWidget):
    2. def __init__(self):
    3. super(MainWindow, self).__init__()
    4. self.db = Database(DB_PATH)
    5. self.image_cache = {}
    6. self.image_loader = ImageLoader(self.image_cache)
    7. self.actor_model = ActorModel(self.db)
    8. self.actor_model.actor_search("")
    9. self.actor_listview_delegate = ActorListviewDelegae(self.image_cache)
    10. self.setup_ui_elements()
    11. self.connect_signals()
    12. self.image_loader.start()
    13.  
    14. def setup_ui_elements(self):
    15. self.setGeometry(100, 100, 1280, 720)
    16. self.list_view = QListView()
    17. self.list_view.setViewMode(QListView.IconMode)
    18. self.list_view.setResizeMode(QListView.Adjust)
    19. self.list_view.setModel(self.actor_model)
    20. self.list_view.setItemDelegate(self.actor_listview_delegate)
    21. self.horizontal_layout = QVBoxLayout()
    22. self.horizontal_layout.addWidget(self.list_view)
    23. self.setLayout(self.horizontal_layout)
    24.  
    25. def connect_signals(self):
    26. self.actor_listview_delegate.load_image.connect(self.load_image)
    27. self.image_loader.finished_loading.connect(self.actor_listview_delegate.paint_override)
    28.  
    29.  
    30. @pyqtSlot(int, str, QSize)
    31. def load_image(self, actor_id, path, size):
    32. self.image_loader.add_to_queue(actor_id, path, size)
    To copy to clipboard, switch view to plain text mode 

    ActorListviewDelegate.py:
    Qt Code:
    1. class ActorListviewDelegae(QStyledItemDelegate):
    2.  
    3. def __init__(self, image_cache, parent=None):
    4. super(ActorListviewDelegae, self).__init__(parent)
    5. self.image_cache = image_cache
    6.  
    7. actor_name = QModelIndex.data(Qt.DisplayRole)
    8. actor_thumb_path = QModelIndex.data(Qt.UserRole)
    9. actor_id = QModelIndex.data(Qt.UserRole + 1)
    10. pic_rect = QRect(rect.left() - 5, rect.top() + 5, 195, 295)
    11.  
    12.  
    13. if not actor_thumb_path or not (actor_id in self.image_cache):
    14. self.load_image.emit(actor_id, actor_thumb_path, pic_rect.size())
    15. frame_color = QColor(255, 0, 0, 127)
    16. brush = QBrush(frame_color)
    17. QPainter.fillRect(pic_rect, brush) #Draws placeholder Rect
    18. else:
    19. pixmap = self.image_cache[actor_id]
    20. QPainter.drawPixmap(pic_rect, pixmap)
    21. text_rect = QRect(rect.left(), rect.top() + 300, 200, 20)
    22.  
    23. QPainter.drawText(text_rect, Qt.AlignCenter, actor_name)
    24.  
    25.  
    26. def sizeHint(self, QStyleOptionViewItem, QModelIndex):
    27. return QSize(200, 320)
    To copy to clipboard, switch view to plain text mode 

    ImageLoader:
    Qt Code:
    1. class ImageLoader(QThread):
    2.  
    3. def __init__(self, image_cache):
    4. super(ImageLoader, self).__init__()
    5. self.image_cache = image_cache
    6. self.queue = Queue()
    7.  
    8. def load_image(self, queue_item):
    9. actor_id = queue_item[0]
    10. image_path = queue_item[1]
    11. size = queue_item[2]
    12. if not (actor_id in self.image_cache):
    13. pixmap = QPixmap(image_path).scaled(size) #Actual Image Loading
    14. self.image_cache[actor_id] = pixmap
    15.  
    16.  
    17. def run(self):
    18. while True:
    19. current_item = self.queue.get()
    20. if current_item:
    21. self.load_image(current_item)
    22. else:
    23. break
    24.  
    25. def add_to_queue(self, actor_id, image_path):
    26. item = [actor_id, image_path, size]
    27. self.queue.put(item)
    To copy to clipboard, switch view to plain text mode 


    As I said before, my expectation when running this is that the UI thread would be completely free and would scroll smoothly through the list, however it is not the case. The UI hangs in exactly the same manner as when I was loading the image s in the UI thread.

    A thing to note is when I'm adding a small delay in the loader's thread 'run' function as follows:
    Qt Code:
    1. def run(self):
    2. while True:
    3. time.sleep(0.1)# <- Added delay
    4. current_item = self.queue.get()
    5. if current_item:
    6. self.load_image(current_item)
    7. else:
    8. break
    To copy to clipboard, switch view to plain text mode 
    The UI becomes more responsive. The UI responsiveness is in direct ration to the delay, when the delay is 1 second the UI is completely responsive. (Obviously this is bad because it only loads 1 image per second )

    I would really be happy if someone would point out to what am I doing wrong, or how am I to achieve the behavior I want.
    Thank You!

  2. #2
    Join Date
    Oct 2019
    Posts
    2
    Qt products
    Platforms
    MacOS X

    Default Re: PyQt5 - Loading images asynchronously in ItemListView

    do anyone know the solution, I have the same problem

Similar Threads

  1. Loading TIF images in Ubuntu
    By drhex in forum Qt Programming
    Replies: 6
    Last Post: 12th June 2015, 13:27
  2. Loading 2 images in Same label ->
    By 2lights in forum Qt Programming
    Replies: 3
    Last Post: 18th July 2013, 09:43
  3. Loading images from url in qt 4
    By shalini in forum Newbie
    Replies: 4
    Last Post: 15th September 2011, 17:21
  4. Slow loading images
    By abbapatris in forum Qt Programming
    Replies: 10
    Last Post: 5th March 2008, 16:52
  5. Loading images in QTextBrowser
    By nisha0609 in forum Qt Programming
    Replies: 1
    Last Post: 3rd January 2007, 10:44

Tags for this Thread

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
Digia, Qt and their respective logos are trademarks of Digia Plc in Finland and/or other countries worldwide.