PyQt: отображение диалогового окна файла после создания pixMap приводит к ошибке сегментации

Я разрабатываю графический интерфейс с PyQt для визуального анализа данных, собранных во время некоторых экспериментов. Графический интерфейс просит пользователя указать каталог, в котором находятся данные для анализа:

class ExperimentAnalyzer(QtGui.QMainWindow):
    ## other stuff here

    def loadExperiment(self):
        directory = QtGui.QFileDialog.getExistingDirectory(self,
                                                           "Select Directory")
        ## load data from directory here

Графический интерфейс предоставляет функцию play, с помощью которой пользователь может видеть, как экспериментальные данные меняются с течением времени. Это реализовано с помощью QTimer:

  def playOrPause(self):
      ## play
      if self.appStatus.timer is None:
          self.appStatus.timer = QtCore.QTimer(self)
          self.appStatus.timer.connect(self.appStatus.timer,
                                       QtCore.SIGNAL("timeout()"),
                                       self.nextFrame)

          self.appStatus.timer.start(40)

       ## pause
       else:
          self.appStatus.timer.stop()
          self.appStatus.timer = None

Если я воспроизводлю временную последовательность данных, затем приостанавливаю, а затем пытаюсь изменить каталог для загрузки данных новый эксперимент, я столкнулся с ошибкой сегментации.

Используя некоторые отладочные отпечатки, я обнаружил, что приложение вылетает, когда я звоню

    QtGui.QFileDialog.getExistingDirectory(self, "Select Directory")

в методе loadExperiment.

Я новичок в Qt и думаю, что неправильно обращаюсь с таймером.
Я использую PyQt 4.9, Python 2.7.3 в Ubuntu 10.04.

Изменить-1:

После ответа Люка я вернулся к своему коду.
Вот метод nextFrame, который вызывается каждый раз, когда таймер выдает тайм-аут сигнал. Он обновляет элемент QGraphicsScene, содержащийся в графическом интерфейсе:

def nextFrame(self):
    image = Image.open("<some jpg>")
    w, h = image.size
    imageQt = ImageQt.ImageQt(image)
    pixMap = QtGui.QPixmap.fromImage(imageQt)

    self.scene.clear()
    self.scene.addPixmap(pixMap)
    self.view.fitInView(QtCore.QRectF(0, 0, w, h),
                        QtCore.Qt.KeepAspectRatio)

где объекты self.scene и self.view ранее были созданы в конструкторе GUI как

self.view = QtGui.QGraphicsView(self)
self.scene = QtGui.QGraphicsScene()
self.view.setScene(self.scene)

Я узнал, что комментируя эту строку:

    # self.scene.addPixmap(pixMap)

и повторение той же последовательности операций, о которой сообщалось выше, ошибка сегментации больше не возникает.

Правка-2:

Запустив приложение с gdb (с python-dbg), выясняется, что ошибка сегментации возникает после вызова QPainter::drawPixmap.

(gdb) bt
#0  0xb6861f1d in ?? () from /usr/lib/i386-linux-gnu/libQtGui.so.4
#1  0xb685d491 in ?? () from /usr/lib/i386-linux-gnu/libQtGui.so.4
#2  0xb693bcd3 in ?? () from /usr/lib/i386-linux-gnu/libQtGui.so.4
#3  0xb69390bc in ?? () from /usr/lib/i386-linux-gnu/libQtGui.so.4
#4  0xb6945c77 in ?? () from /usr/lib/i386-linux-gnu/libQtGui.so.4
#5  0xb68bd424 in QPainter::drawPixmap(QPointF const&, QPixmap const&) () from   /usr/lib/i386-linux-gnu/libQtGui.so.4

Следовательно, это не проблема, связанная с обработкой таймера, как я полагал в первую очередь.
Ошибка сегментации возникает из-за того, что я делаю что-то не так с pixMap.


person Loris Fichera    schedule 22.02.2013    source источник


Ответы (2)


Извините, мне не удалось воспроизвести segfaults, которые вы видите. Вот полный исходный код приложения, с помощью которого я пытался воспроизвести ваш сбой (Qt 4.8.1, PyQt 4.9.1, Python 2.7.3, Kubuntu Precise):

#!/usr/bin/env python

import sys
from PyQt4 import QtCore, QtGui

class AppStatus(object):
    def __init__(self):
        self.timer = None

class ExperimentAnalyzer(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(ExperimentAnalyzer, self).__init__(parent)
        elayout = QtGui.QVBoxLayout()

        self.count = 0
        self.appStatus = AppStatus()

        everything = QtGui.QWidget(self)
        everything.setLayout(elayout)

        button = QtGui.QPushButton("Start/stop timer", self)
        self.connect(button, QtCore.SIGNAL("clicked()"), self.playOrPause)
        elayout.addWidget(button)

        button = QtGui.QPushButton("Load experiment", self)
        self.connect(button, QtCore.SIGNAL("clicked()"), self.loadExperiment)
        elayout.addWidget(button)

        self.setCentralWidget(everything)

    def loadExperiment(self):
        directory = QtGui.QFileDialog.getExistingDirectory(self, "Select Directory")

    def nextFrame(self):
        self.count += 1
        print self.count

    def playOrPause(self):
        if self.appStatus.timer is None:
            self.appStatus.timer = QtCore.QTimer(self)
            self.appStatus.timer.connect(self.appStatus.timer,
                                       QtCore.SIGNAL("timeout()"),
                                       self.nextFrame)

            self.appStatus.timer.start(40)

        else:
            self.appStatus.timer.stop()
            self.appStatus.timer = None

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    mainwin = ExperimentAnalyzer(None)
    mainwin.show()
    app.exec_()

Если это тестовое приложение у вас не падает, значит, проблема в коде, который вы нам не показали.

person Luke Woodward    schedule 22.02.2013
comment
Что ж, это работает. Проблема, как вы сказали, должна быть где-то в другом. Я постараюсь разобраться и обновить вопрос. - person Loris Fichera; 22.02.2013

Хорошо, в конце концов я понял, в чем проблема.
Я изменил метод nextFrame таким образом, чтобы сохранить ссылку на ImageQt объект, то есть:

def nextFrame(self):
    ## load the image from file

    self.appStatus.imageQt = ImageQt.ImageQt(image)
    pixMap = QtGui.QPixmap.fromImage(self.appStatus.imageQt)

    ## update the QGraphicsView

и ошибка сегментации исчезла.

Мое объяснение этому следующее: Qt содержит внутри себя указатель на объект ImageQt, который используется каждый раз, когда Запускается paintEvent (и, следовательно, pixMap должен быть перерисован).
Не сохраняется ссылка на ImageQt объект позволяет сборщику мусора Python собрать его. В результате Qt попытается прочитать из освобожденной области памяти, вызвав ошибку сегментации.

person Loris Fichera    schedule 23.02.2013