IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

PyQt Python Discussion :

Chargement d'images asynchrone


Sujet :

PyQt Python

  1. #1
    Invité
    Invité(e)
    Par défaut Chargement d'images asynchrone
    Bonjour a tous (et desole pour les accents, clavier japonais ^^)

    Je m'etais fait un petit programme il y a quelques temps, afin de trier un grand nombre de photos que j'avais sur mon disque.

    Lors des tests, j'avais utilise un echantillon de quelques milliers de cliches, sans aucun probleme.

    Le code correspondant a l'affichage etait a peu pres comme ca
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
     
    i = 0
    for uid, files in self.files.items():
        item = QtGui.QTableWidgetItem("%9d" % (uid))
        self.ui.tableWidget.setItem(i, 0, item)
        self.ui.tableWidget.scrollToItem(item)
        for j, f in enumerate(files):
            label = QtGui.QLabel()
            pixmap = QtGui.QPixmap()
            pixmap.load(f)
            label.setPixmap(pixmap.scaled(self.size_photo, self.size_photo,
                                        QtCore.Qt.KeepAspectRatio,
                                        QtCore.Qt.SmoothTransformation))
            label.setAlignment(QtCore.Qt.AlignCenter)
            self.ui.tableWidget.setCellWidget(i, j + 1, label)
        i += 1
        QtCore.QCoreApplication.processEvents()
        if i == limit or self.stopped:
            break
    Le probleme de cette approche est que mon UI restera bloquee tant que toutes les photos ne seront pas chargees.
    Avec quelques milliers de photos, j'ai un delai de l'ordre de la seconde, mais quand je passe a l'echelle l'interface freeze plus longtemps.
    Lorsque j'avais joue avec QML, j'avais vu qu'on pouvait charger des images de facon asynchrone, et c'est a peu pres ce resultat que j'aimerais avoir.

    Je suppose que cela implique de passer par un autre thread qui creera les pixmaps et enverra un signal au thread GUI afin de mettre a jour le tableau ?

    Quelqu'un aurait-il des conseils quant a la methode ?
    Par exemple, je pensais utiliser une liste de Pixmaps, qui serait partagee entre le thread GUI et le thread de travail, mais est-ce que c'est vraiment une approche propre ?
    Je suis un peu novice en programmation parallele ^^;

  2. #2
    Expert éminent

    Homme Profil pro
    Inscrit en
    Octobre 2008
    Messages
    4 300
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations forums :
    Inscription : Octobre 2008
    Messages : 4 300
    Points : 6 780
    Points
    6 780
    Par défaut
    Salut,

    C'est une solution qui fonctionne très bien, c'est comme cela que je l'ai implémentée dans mon appli Oqapy pour le navigateur d'image.

    Un thread est créé, il reçoit la liste d'images du dossier en cours de visionnage, crée chaque vignette (200x200) en tant que QImage et les enregistre dans un temporaire. A chaque vignette créée, il émet un signal récupéré par une fonction du main thread et qui doit juste créer la pixmap sans redimensionnement, donc.

    - L'interface ne se fige pas
    - L'utilisateur peut déjà visualiser ses images
    - Le navigateur se remplit très rapidement des premières vignettes

    C'est très simple à mettre en place, de plus.

  3. #3
    Membre chevronné

    Profil pro
    Account Manager
    Inscrit en
    Décembre 2006
    Messages
    2 301
    Détails du profil
    Informations personnelles :
    Localisation : France, Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Account Manager

    Informations forums :
    Inscription : Décembre 2006
    Messages : 2 301
    Points : 1 752
    Points
    1 752
    Par défaut
    Bonjour.

    Tout ceci a l'air très intéressant et aussi très pratique.

    Serait-il possible d'avoir ici un code minimal montrant comment il faut faire ?

  4. #4
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 462
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 462
    Points : 9 249
    Points
    9 249
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    En attendant un code plus spécifique, vous pouvez déjà utiliser ceci comme source d'inspiration:

    http://pyqt.developpez.com/tutoriels...rger-fichiers/

    C'est le même principe: il s'agit ici de télécharger un ficher du web, en mettant à jour une barre de progression. La partie téléchargement est dans un thread pour ne pas figer la fenêtre graphique.

    Il faut se rappeler qu'un thread ne doit pas toucher directement au graphique: il doit donc seulement envoyer des messages au graphique pour action: en principe une info d'avancement (dans votre cas pour visualiser les images au fur et à mesure de leur disponibilité) et une info de fin.

    Pour pinailler, on peut ajouter une pause et un arrêt avant la fin... Voire une possibilité de reprise d'un téléchargement partiel arrêté lors d'une session précédente.
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  5. #5
    Expert éminent

    Homme Profil pro
    Inscrit en
    Octobre 2008
    Messages
    4 300
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations forums :
    Inscription : Octobre 2008
    Messages : 4 300
    Points : 6 780
    Points
    6 780
    Par défaut
    Citation Envoyé par rambc Voir le message
    Bonjour.

    Tout ceci a l'air très intéressant et aussi très pratique.

    Serait-il possible d'avoir ici un code minimal montrant comment il faut faire ?
    Ouaip, minimaliste vraiment, aucune vérification de rien.

    Il crée un dossier "thumbnails" dans le dossier d'images que l'on ouvre (ligne 41)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
     
    # -*- coding: utf-8 -*-
     
    import sys
    import os
    import glob
    from threading import Thread
     
    from PyQt4 import QtCore, QtGui
     
    class Ui_MainWindow(object):
        def setupUi(self, MainWindow):
            MainWindow.setObjectName("MainWindow")
            MainWindow.resize(515, 372)
            MainWindow.setWindowTitle("Exemple Mini-Ma-Liste")
            self.centralwidget = QtGui.QWidget(MainWindow)
            self.gridLayout = QtGui.QGridLayout(self.centralwidget)
            self.gridLayout.setMargin(2)
            self.verticalLayout = QtGui.QVBoxLayout()
            self.listWidget = QtGui.QListWidget(self.centralwidget)
            self.listWidget.setIconSize(QtCore.QSize(160, 160))
            self.listWidget.setFlow(QtGui.QListView.LeftToRight)
            self.listWidget.setViewMode(QtGui.QListView.IconMode)
            self.verticalLayout.addWidget(self.listWidget)
            self.horizontalLayout = QtGui.QHBoxLayout()
            self.pushButton = QtGui.QPushButton(self.centralwidget)
            self.pushButton.setText("Folder")
            self.pushButton.clicked.connect(self.open_folder)
            self.horizontalLayout.addWidget(self.pushButton)
            spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, 
                                                        QtGui.QSizePolicy.Minimum)
            self.horizontalLayout.addItem(spacerItem)
            self.verticalLayout.addLayout(self.horizontalLayout)
            self.gridLayout.addLayout(self.verticalLayout, 0, 0, 1, 1)
            MainWindow.setCentralWidget(self.centralwidget)
            QtCore.QMetaObject.connectSlotsByName(MainWindow)
            self.thumbnails = []
     
        def open_folder(self):
            path = self.get_folder_name()
            l = glob.glob(os.path.join(path, "*.*"))
            self.thumb_folder = os.path.join(path, "thumbnails")
            if not os.path.isdir(self.thumb_folder):
                os.mkdir(self.thumb_folder)
            self.thumbs_builder = ThumbnailsBuilder(l, self.thumb_folder)
            self.thumbs_builder.thumbCreated.connect(self.show_thumbnail)
            self.thumbs_builder.start()
     
        def show_thumbnail(self, thumb):
            thumb = str(thumb)
            icon = self.create_icon(thumb)
            txt = self.create_text(thumb)
            obj = QtGui.QListWidgetItem(self.listWidget)
            obj.setIcon(QtGui.QIcon(icon))
            obj.setText(txt)
            obj.setTextAlignment(QtCore.Qt.AlignHCenter)
            obj.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
            self.thumbnails.append(obj) 
            QtCore.QCoreApplication.processEvents()
     
        def create_icon(self, img):
            icon = QtGui.QIcon()
            icon.addPixmap(QtGui.QPixmap(img), QtGui.QIcon.Normal, QtGui.QIcon.Off)
            return icon
     
        def create_text(self, name):
            t = os.path.split(name)[1]
            if len(t) > 20:
                t = "..." + t[-17:]
            return t
     
        def get_folder_name(self):
            return unicode(QtGui.QFileDialog.getExistingDirectory(None, 
                                            "Ouvrir un dossier",
                                            os.path.expanduser("~"),
                                            QtGui.QFileDialog.DontResolveSymlinks
                                            | QtGui.QFileDialog.ShowDirsOnly))
     
    class ThumbnailsBuilder(Thread):
        def __init__(self, files, temp):
            super(ThumbnailsBuilder, self).__init__()
            self.size = 200
            self.files = files
            self.temp = temp
            self._filesSaved = FileSaved()
            Thread.__init__(self)
     
        def thumbCreated():
            def fget(self):
                return self._filesSaved.thumbCreated
            return locals()
        thumbCreated = property(**thumbCreated())
     
     
        def run(self):
            for f in self.files:
                t = QtGui.QImage(f)
                thumb = t.scaled(self.size, self.size, QtCore.Qt.KeepAspectRatio, 
                                            QtCore.Qt.FastTransformation)
                if thumb.isNull():
                    print "Caramba ! pas une image ça : {0}".format(f)
                else:
                    dest = os.path.join(self.temp, os.path.split(f)[1])
                    thumb.save(dest, None, -1)
                    self.thumbCreated.emit(dest)
     
     
    class FileSaved(QtCore.QObject):
        thumbCreated = QtCore.pyqtSignal(str)
        def __init__(self):
            super(FileSaved, self).__init__()
     
    if __name__ == "__main__":
        import sys
        app = QtGui.QApplication(sys.argv)
        MainWindow = QtGui.QMainWindow()
        ui = Ui_MainWindow()
        ui.setupUi(MainWindow)
        MainWindow.show()
        sys.exit(app.exec_())
    Faut mettre ça au propre bien sur, aligner les vignettes sur leur base, leur mettre des toolTip, etc.

    Le principe est là.

  6. #6
    Expert éminent

    Homme Profil pro
    Inscrit en
    Octobre 2008
    Messages
    4 300
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations forums :
    Inscription : Octobre 2008
    Messages : 4 300
    Points : 6 780
    Points
    6 780
    Par défaut
    Un exemple plus soigné


  7. #7
    Invité
    Invité(e)
    Par défaut
    un autre bout de code, qui utilise plutot les QThreads de Qt4

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
     
    # -*- coding: utf-8 -*-
     
    import sip
    sip.setapi("QString", 2)
    sip.setapi("QVariant", 2)
    from PyQt4 import QtCore, QtGui, uic
    import os
     
    size_photo = 192
     
     
    class ImageLoader(QtCore.QObject):
        loaded = QtCore.pyqtSignal((int, int))
        allLoaded = QtCore.pyqtSignal()
     
        def __init__(self, *args):
            super(ImageLoader, self).__init__(*args)
            self.task = []
            self.pixmaps = {}
     
     
        def loadFile(self, i, j, filename):
            pixmap = QtGui.QPixmap()
            pixmap.load(filename)
            self.pixmaps[(i, j)] = pixmap
            self.loaded.emit(i, j)
     
     
        def loadAll(self):
            for (i, j, filename) in self.task:
                self.loadFile(i, j, filename)
                if self.stop:
                    break
            self.allLoaded.emit()
     
     
        def setTask(self, task):
            self.task = task
            self.stop = False
            self.pixmaps = {}
     
     
    class MainWindow(QtGui.QWidget):
     
        def __init__(self, *args):
            super(MainWindow, self).__init__(*args)
            self.ui = uic.loadUi("photo.ui", self)
            self.files = {}
            self.loadTask = []
            self.imageLoader = ImageLoader()
            self.workingThread = QtCore.QThread()
            self.workingThread.start()
            self.imageLoader.moveToThread(self.workingThread)
     
            self.ui.tableWidget.horizontalHeader().setResizeMode(QtGui.QHeaderView.Fixed)
            self.ui.tableWidget.horizontalHeader().setDefaultSectionSize(size_photo + 16)
            self.ui.tableWidget.horizontalHeader().setResizeMode(0, QtGui.QHeaderView.ResizeToContents)
            self.ui.tableWidget.verticalHeader().setResizeMode(QtGui.QHeaderView.Fixed)
            self.ui.tableWidget.verticalHeader().setDefaultSectionSize(size_photo + 16)
     
            self.imageLoader.loaded.connect(self.loadImage)
            self.imageLoader.allLoaded.connect(self.allLoaded)
            QtCore.QObject.connect(self.ui.refreshButton,
                                   QtCore.SIGNAL("clicked(bool)"),
                                   self.display)
            QtCore.QObject.connect(self.ui.folderButton,
                                   QtCore.SIGNAL("clicked(bool)"),
                                   self.setFolder)
     
     
        def setFolder(self):
            directory = QtGui.QFileDialog.getExistingDirectory(parent=self, caption="Open directory")
            if directory:
                self.ui.lineEdit.setText(directory)
                self.display()
     
     
        def allLoaded(self):
            self.ui.acceptButton.setEnabled(True)
            self.ui.rejectButton.setEnabled(True)
     
     
     
        def loadImage(self, i, j):
            label = QtGui.QLabel()
            pixmap = self.imageLoader.pixmaps[(i, j)]
            label.setPixmap(pixmap.scaled(size_photo, size_photo,
                            QtCore.Qt.KeepAspectRatio,
                            QtCore.Qt.SmoothTransformation))
            label.setAlignment(QtCore.Qt.AlignCenter)
            self.ui.tableWidget.setCellWidget(i, j + 1, label)    
     
     
        def display(self):
            self.ui.acceptButton.setEnabled(False)
            self.ui.rejectButton.setEnabled(False)
            self.ui.tableWidget.clear()
            self.ui.tableWidget.setRowCount(0)
            self.ui.tableWidget.setColumnCount(1)
            self.files = {}
            self.loadTask = []
            self.imageLoader.setTask(self.loadTask)
     
            real_dir = self.ui.lineEdit.text()
            if real_dirand os.path.exists(real_dir):            
                for f in os.listdir(real_dir):
                    if os.path.splitext(f)[1] == ".jpg":
                        uid = int(f.split("_", 1)[0])
                        self.files.setdefault(uid, []).append(os.path.join(real_dir, f))
     
                self.ui.tableWidget.setRowCount(len(self.files))
     
                m = 1
                for uid in self.files:
                    m = max(m, len(self.files[uid]) + 1)
                self.ui.tableWidget.setColumnCount(m)
     
                i = 0
                for uid, files in self.files.items():
                    item = QtGui.QTableWidgetItem("%9d" % (uid))
                    self.ui.tableWidget.setItem(i, 0, item)
                    for j, f in enumerate(files):
                        self.loadTask.append((i, j, f))
                    i += 1
                QtCore.QTimer.singleShot(0, self.imageLoader.loadAll)
     
     
    if __name__ == "__main__":
        import sys
        import traceback
     
        def handle_exception(exc_type, exc_value, exc_traceback):
            if issubclass(exc_type, KeyboardInterrupt):
                if QtGui.qApp:
                    QtGui.qApp.quit()
                return
            print "".join(traceback.format_exception(exc_type, exc_value, exc_traceback))
            sys.exit(1)
     
        sys.excepthook = handle_exception
     
        app = QtGui.QApplication(sys.argv)
        window = MainWindow()
        window.show()
        app.exec_()
        sys.exit()
    la specificite de mon cas est que mes fichiers sont de la forme uid_xxxxxx.jpg et que je voulais les trier par uid (une ligne => photos associees a un uid).
    mais a part ca le code est fonctionnel

    soit dit en passant, si quelqu'un voit dans mon code quelque chose de particulierement horrible qui pique les yeux, j'aimerais bien le savoir ! c'est comme ca qu'on s'ameliore apres tout...

  8. #8
    Expert éminent

    Homme Profil pro
    Inscrit en
    Octobre 2008
    Messages
    4 300
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations forums :
    Inscription : Octobre 2008
    Messages : 4 300
    Points : 6 780
    Points
    6 780
    Par défaut
    Ben on a un peu de mal à voir exactement à quoi te sert le QThread vu que tu crées directement des pixmaps, ce qui dans un thread devrait retourner une erreur et une pixmap nulle.

    Le but du jeu ici, était de faire le principal de la tâche dans un process séparé pour ne pas figer l'application pendant la création des vignettes.

    Mais si tu dis que ça marche, je te crois, hein.

  9. #9
    Invité
    Invité(e)
    Par défaut
    je ne vois pas le probleme pour etre honnete
    le "moveToThread" m'assure que si je l'appelle via le systeme de signal/slot ma fonction s'execute dans le thread
    j'ai essaye avec 500.000 images et l'affichage a debute de suite, pour s'achever 3 ou 4 minutes plus tard, sans aucun freeze ou quoi que ce soit

  10. #10
    Membre chevronné

    Profil pro
    Account Manager
    Inscrit en
    Décembre 2006
    Messages
    2 301
    Détails du profil
    Informations personnelles :
    Localisation : France, Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Account Manager

    Informations forums :
    Inscription : Décembre 2006
    Messages : 2 301
    Points : 1 752
    Points
    1 752
    Par défaut
    Bonjour.

    Merci pour les exemples !

Discussions similaires

  1. Chargement images asynchrones dans une ListView
    Par toufou dans le forum Composants graphiques
    Réponses: 3
    Dernier message: 07/02/2013, 22h52
  2. [xhtml][css] chargement d'image sur IE
    Par killgors dans le forum Mise en page CSS
    Réponses: 4
    Dernier message: 23/08/2005, 20h37
  3. chargement d'image
    Par bakonu dans le forum AWT/Swing
    Réponses: 2
    Dernier message: 20/06/2005, 22h40
  4. [JLabel] Chargement d'image dans une JFrame
    Par mr.t dans le forum Composants
    Réponses: 10
    Dernier message: 27/01/2005, 18h32
  5. Réponses: 21
    Dernier message: 29/04/2004, 15h45

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo