为什么即使我将外包给工作线程,我的GUI也没有响应?

时间:2012-07-18 21:12:59

标签: python multithreading pyqt4

我有一个PyQt4 GUI,我需要将一堆Numpy数组保存为* .tif图像。这似乎需要花费大量时间,导致我的GUI无法响应多达几分钟,具体取决于堆栈中的图像数量。

大部分处理发生在图像的循环中:

for i in range(0, np.shape(dataStack)[2]):

        print('Saving slice ' + str(i + 1))

        #Save tumor stack
        im1 = Image.fromarray(tumorStack[:,:,i]*255)
        im1.save(saveLocationStr + 'Slice' + str(i+1) + '.tif')

        #Set up correct number of subplots for review plot.
        if T0 is not None:
            plt.subplot(141)
        else:
            plt.subplot(131)

        #Plot current brain slice in 1st position
        plt.imshow(dataStack[:,:,i], cmap=mpl.cm.bone)
        plt.axis('off')

        plt.title(patient + '\n' + date + '\n' + 'Slice ' + str(i+1) + ' of ' + str(int(np.shape(dataStack)[2])))

        #Select correct next subplot
        if T0 is not None:
            plt.subplot(142)
        else:
            plt.subplot(132)

        #Get a masked copy of the tumorStack 
        tempTumorStack = copy.deepcopy(tumorStack[:,:,i])
        tempTumorStack = np.ma.masked_where(tempTumorStack == 0, tempTumorStack)

        #Plot masked tumor stack over brain data
        plt.imshow(dataStack[:,:,i], cmap=mpl.cm.bone)
        plt.imshow(tempTumorStack, cmap=mpl.cm.jet_r, interpolation='nearest')
        plt.axis('off')

        plt.title(modality + ' Region')

        #Get the auto-zoomed region and plot it
        (x, y) = tumorStack[:,:,i].nonzero()
        if( int(np.shape(x)[0]) == 0 or int(np.shape(y)[0]) == 0):
            if T0 is not None:
                plt.subplot(143)
                plt.imshow(T0[:,:,i], cmap=mpl.cm.bone)
                plt.axis('off')

                plt.title('T0')

                #Plot autozoomed with perimiter over brain data
                plt.subplot(144)
                plt.imshow(np.zeros(np.shape(dataStack[:,:,i])), cmap=mpl.cm.bone)

                plt.title('Perimiter of \n' + modality + ' + T0 for SA')

                plt.axis('off')

            else:
                plt.subplot(133)
                plt.imshow(np.zeros(np.shape(dataStack[:,:,i])), cmap=mpl.cm.bone)

                plt.title('Perimiter of \n' + modality + ' for SA')

                plt.axis('off')

        else:
            minX = np.min(x)
            minY = np.min(y)
            maxX = np.max(x)
            maxY = np.max(y)

            zoomedXmin =  minX - (minX * .10)
            zoomedXmax = (maxX * .10) + maxX
            zoomedYmin =  minY - (minY * .10)
            zoomedYmax =  (maxY * .10) + maxY

            widthOf = zoomedXmax - zoomedXmin
            heigthOf = zoomedYmax - zoomedYmin

            #Get perimiter of tumor for autozoomed region
            #Can do n=8 if we want
            #tempTumorStack = bwperim(tempTumorStack,n=8)
            tempTumorStack = mahotas.labeled.borders(tempTumorStack)
            tempTumorStack = np.where(tempTumorStack == np.max(tempTumorStack), 1, np.nan)

            #Plot T0 then auto-zoomed if user wanted T0
            if T0 is not None:
                plt.subplot(143)
                plt.imshow(T0[:,:,i], cmap=mpl.cm.bone)
                plt.axis('off')

                plt.title('T0')

                #Plot autozoomed with perimiter over brain data
                plt.subplot(144)
                plt.imshow(dataStack[int(zoomedXmin):int(zoomedXmax), int(zoomedYmin):int(zoomedYmax), i ], cmap=mpl.cm.bone)
                plt.imshow(tempTumorStack[int(zoomedXmin):int(zoomedXmax), int(zoomedYmin):int(zoomedYmax) ], cmap=mpl.cm.jet_r)
                #plt.xlim(minX, maxX-minX)
                #plt.ylim(minY, maxY-minY)

                plt.title('Perimiter of \n' + modality + ' + T0 for SA')

                plt.axis('off')

            #Just plot autozoomed
            else:
                plt.subplot(133)
                plt.imshow(dataStack[int(zoomedXmin):int(zoomedXmax), int(zoomedYmin):int(zoomedYmax), i ], cmap=mpl.cm.bone)
                plt.imshow(tempTumorStack[int(zoomedXmin):int(zoomedXmax), int(zoomedYmin):int(zoomedYmax) ], cmap=mpl.cm.jet_r)

                plt.title('Perimiter of \n' + modality + ' for SA')

                plt.axis('off')

        #Finish setting up plot to specs, render, and save it
        plt.subplots_adjust(wspace=.5)
        plt.axis('off')
        plt.tick_params(bottom='off', top='off', left='off', right='off')
        plt.draw()
        plt.savefig(saveLocationStr + 'MRI_Comparison\\Slice' + str(i+1) + '.png', dpi=200)

我想我会将昂贵的工作外包给PyQt4工作者QThread,所以按照http://diotavelli.net/PyQtWiki/Threading,_Signals_and_Slots的例子我创建了一个继承自QtCore.QThread的工作类,在此方法的run方法中调用了上述所有代码class,并创建并启动()编辑此类的实例,其中* .tif堆栈保存需要在主窗口中进行。

GUI仍像以前一样没有响应,但线程似乎成功执行了代码。我担心上面代码中使用的某个包可能没有正确地释放GIL(这里引用http://www.riverbankcomputing.com/pipermail/pyqt/2011-August/030470.html)。有没有办法手动确保这有效?

我已经注释掉并用time.sleep延迟替换了* .tif堆栈保存代码,并且GUI仍然没有响应,所以也许我正在实现QThread错误?

我错过了别的什么吗?我提供了足够的信息吗?

编辑:

我在主窗口中创建线程:

# create instance of saveImageStackWorker(QThread) thread class
self.saveThread = brain_io.saveImageStackWorker()
self.saveThread.populate(self.tumorBrain.pixData, self.tumorBrain.tumor, saveLocationStr, self.measurer, self.tumorBrain.patient, dateForPlots, imageModality, T0pass)

这是我定义的整个工作线程类,其中函数saveImageStack(args)与上面提供的* .tif保存代码相同:

class saveImageStackWorker(QThread):
"""

"""

def __init__(self, parent=None):

    QThread.__init__(self, parent)

def __del__(self):

    self.wait()

def populate(self, dataStack, tumorStack, saveLocationStr, measurer, patient, date, modality, T0):
    self.dataStack = dataStack
    self.tumorStack = tumorStack
    self.saveLocationStr = saveLocationStr
    self.measurer = measurer
    self.patient = patient
    self.date = date
    self.modality = modality
    self.T0 = T0

    self.start()

def run(self):
    self.saveImageStack(self.dataStack, self.tumorStack, self.saveLocationStr, self.measurer, self.patient, self.date, self.modality, self.T0)

def saveImageStack(self, dataStack, tumorStack, saveLocationStr, measurer, patient, date, modality, T0): #, dateStr, measurer,, saveLocationStr):
    """
    Input:
        dataStack:
            numpy array of the brain image data.
        tumorStack:
            numpy binary array of tumor data.
        modality:
            the modality of the image.
        T0:
            numpy binary array of T0, if you do not
            wish to show T0 (i.e. for flair or something) 
            leave as default None.
    Output:
        None
    Description:
        Saves the image stack of tumor and the review plots 
        to the output directory.
    """

    print('Saving image stack from within worker thread...')

    font = {'size' : 10} 

    matplotlib.rc('font', **font)

    np.seterr(all='ignore')
    warnings.simplefilter('ignore')

    for i in range(0, np.shape(dataStack)[2]):

        print('Saving slice ' + str(i + 1))

        #Save tumor stack
        im1 = Image.fromarray(tumorStack[:,:,i]*255)
        im1.save(saveLocationStr + 'Slice' + str(i+1) + '.tif')

        #Set up correct number of subplots for review plot.
        if T0 is not None:
            plt.subplot(141)
        else:
            plt.subplot(131)

        #Plot current brain slice in 1st position
        plt.imshow(dataStack[:,:,i], cmap=mpl.cm.bone)
        plt.axis('off')

        plt.title(patient + '\n' + date + '\n' + 'Slice ' + str(i+1) + ' of ' + str(int(np.shape(dataStack)[2])))

        #Select correct next subplot
        if T0 is not None:
            plt.subplot(142)
        else:
            plt.subplot(132)

        #Get a masked copy of the tumorStack 
        tempTumorStack = copy.deepcopy(tumorStack[:,:,i])
        tempTumorStack = np.ma.masked_where(tempTumorStack == 0, tempTumorStack)

        #Plot masked tumor stack over brain data
        plt.imshow(dataStack[:,:,i], cmap=mpl.cm.bone)
        plt.imshow(tempTumorStack, cmap=mpl.cm.jet_r, interpolation='nearest')
        plt.axis('off')

        plt.title(modality + ' Region')

        #Get the auto-zoomed region and plot it
        (x, y) = tumorStack[:,:,i].nonzero()
        if( int(np.shape(x)[0]) == 0 or int(np.shape(y)[0]) == 0):
            if T0 is not None:
                plt.subplot(143)
                plt.imshow(T0[:,:,i], cmap=mpl.cm.bone)
                plt.axis('off')

                plt.title('T0')

                #Plot autozoomed with perimiter over brain data
                plt.subplot(144)
                plt.imshow(np.zeros(np.shape(dataStack[:,:,i])), cmap=mpl.cm.bone)

                plt.title('Perimiter of \n' + modality + ' + T0 for SA')

                plt.axis('off')

            else:
                plt.subplot(133)
                plt.imshow(np.zeros(np.shape(dataStack[:,:,i])), cmap=mpl.cm.bone)

                plt.title('Perimiter of \n' + modality + ' for SA')

                plt.axis('off')

        else:
            minX = np.min(x)
            minY = np.min(y)
            maxX = np.max(x)
            maxY = np.max(y)

            zoomedXmin =  minX - (minX * .10)
            zoomedXmax = (maxX * .10) + maxX
            zoomedYmin =  minY - (minY * .10)
            zoomedYmax =  (maxY * .10) + maxY

            widthOf = zoomedXmax - zoomedXmin
            heigthOf = zoomedYmax - zoomedYmin

            #Get perimiter of tumor for autozoomed region
            #Can do n=8 if we want
            #tempTumorStack = bwperim(tempTumorStack,n=8)
            tempTumorStack = mahotas.labeled.borders(tempTumorStack)
            tempTumorStack = np.where(tempTumorStack == np.max(tempTumorStack), 1, np.nan)

            #Plot T0 then auto-zoomed if user wanted T0
            if T0 is not None:
                plt.subplot(143)
                plt.imshow(T0[:,:,i], cmap=mpl.cm.bone)
                plt.axis('off')

                plt.title('T0')

                #Plot autozoomed with perimiter over brain data
                plt.subplot(144)
                plt.imshow(dataStack[int(zoomedXmin):int(zoomedXmax), int(zoomedYmin):int(zoomedYmax), i ], cmap=mpl.cm.bone)
                plt.imshow(tempTumorStack[int(zoomedXmin):int(zoomedXmax), int(zoomedYmin):int(zoomedYmax) ], cmap=mpl.cm.jet_r)
                #plt.xlim(minX, maxX-minX)
                #plt.ylim(minY, maxY-minY)

                plt.title('Perimiter of \n' + modality + ' + T0 for SA')

                plt.axis('off')

            #Just plot autozoomed
            else:
                plt.subplot(133)
                plt.imshow(dataStack[int(zoomedXmin):int(zoomedXmax), int(zoomedYmin):int(zoomedYmax), i ], cmap=mpl.cm.bone)
                plt.imshow(tempTumorStack[int(zoomedXmin):int(zoomedXmax), int(zoomedYmin):int(zoomedYmax) ], cmap=mpl.cm.jet_r)

                plt.title('Perimiter of \n' + modality + ' for SA')

                plt.axis('off')

        #Finish setting up plot to specs, render, and save it
        plt.subplots_adjust(wspace=.5)
        plt.axis('off')
        plt.tick_params(bottom='off', top='off', left='off', right='off')
        plt.draw()
        plt.savefig(saveLocationStr + 'MRI_Comparison\\Slice' + str(i+1) + '.png', dpi=200)

编辑#2: 在启动线程的主窗口中添加方法的代码 -

def uploadAndSave(self):
    [db, unused] = initGUI.getDbDataBetter()
    self.db = db

    subtypeID = str(self.dbImage.image_subtype_id.toString())
    query = QtSql.QSqlQuery("SELECT subtype_name FROM image_subtypes where "
                        + "id = " + subtypeID, self.db)
    query.next()
    imageModality = str(query.value(0).toString())

    dateForPlots = str(self.dbImage.date_of_image.toString()).replace('T',' ')
    date = dateForPlots.replace('-','').replace(':','.')

    basePath = 'S:\Lab_KSwanson\MRI Project\Test measurements\\' + self.tumorBrain.patient + '\\' + self.measurer + '\\' + date + '\\'
    print('Saving images...')
    seriesDescription = str(self.dbImage.series_description.toString())
    saveLocationStr = brain_io.createOutputFilepath(basePath, seriesDescription, imageModality)
    if imageModality.upper().find('T1') < 0:
        T0pass = None
    else:
        T0pass = self.tumorBrain.T0


    operation_time = datetime.datetime.now().isoformat().replace('T',' ')[0:19]

    # create instance of saveImageStackWorker(QThread) thread class
    self.saveThread = brain_io.saveImageStackWorker()
    self.saveThread.populate(self.tumorBrain.pixData, self.tumorBrain.tumor, saveLocationStr, self.measurer, self.tumorBrain.patient, dateForPlots, imageModality, T0pass)

    # brain_io.saveImageStack(self.tumorBrain.pixData, self.tumorBrain.tumor, saveLocationStr, self.measurer, self.tumorBrain.patient, dateForPlots, imageModality, T0pass)
    self.tumorBrain.save(saveLocationStr + date + '.dat')

    [db, unused] = initGUI.getDbDataBetter()
    query = QtSql.QSqlQuery('SELECT file_path FROM measurements m WHERE m.user_id = ' + self.userIDStr + ' AND m.image_id = ' 
                    + str(self.dbImage.id.toString()) + ' AND m.status = "R"', db)

    #If there was a rejected measurement, this will return True
    remeasure = query.next()    

    print('Computing volume, surface area, etc...')
    T1 = algorithms.vtk_stats(self.tumorBrain.tumor, 
                                spacing=(self.tumorBrain.xSpacing, 
                                         self.tumorBrain.ySpacing, 
                                         np.mean(self.tumorBrain.SliceThickness)))

    T0 = algorithms.vtk_stats(self.tumorBrain.T0, 
                                      spacing=(self.tumorBrain.xSpacing, 
                                               self.tumorBrain.ySpacing, 
                                               np.mean(self.tumorBrain.SliceThickness)))


    mass = tvtk.MassProperties(input=T1.output)
    T0mass = tvtk.MassProperties(input=T0.output)
    #mySA = algorithms.calculateSurfaceArea(self.tumorBrain.tumor, 
    #                                       self.tumorBrain.xSpacing, 
    #                                       self.tumorBrain.ySpacing, 
    #                                       self.tumorBrain.SliceThickness)

    #mySAT0 = algorithms.calculateSurfaceArea(self.tumorBrain.T0, 
    #                                       self.tumorBrain.xSpacing, 
    #                                       self.tumorBrain.ySpacing, 
    #                                       self.tumorBrain.SliceThickness)
    #print('mysa = ' + str(mySA))

    #area = 0 
    #for i in range(0, int(self.tumorBrain.tumor.shape[2])):
    #    tumor_filt = self.tumorBrain.tumor[:,:,i]
    #    currThreshold = self.thresholdList[i]
    #    tumor_filt = np.where(tumor_filt > currThreshold, 1, 0)
    #    area = area + np.sum(np.sum(tumor_filt))

    #myVolumeT1 = np.sum(self.tumorBrain.xSpacing**2 * area * self.tumorBrain.SliceThickness)
    myVolumeT1 = mass.volume

    #T0sum = np.sum(np.sum(np.sum(self.tumorBrain.T0)))
    #myVolumeT0 = np.sum(self.tumorBrain.xSpacing**2 * T0sum * self.tumorBrain.SliceThickness)
    myVolumeT0 = T0mass.volume

    myVolume_T0_T1 = myVolumeT1 + myVolumeT0

    T0_radius = ((3.0*(myVolumeT0))/(4.0*math.pi))**(1.0/3.0)
    T0_T1_radius = ((3.0*(myVolume_T0_T1))/(4.0*math.pi))**(1.0/3.0)

    #print('volume vtk = ' + str(mass.volume))
    #print('my volume = ' + str(myVolume_T0_T1))
    #print('my radius = ' + str(T0_T1_radius))

    if mass.volume + T0mass.volume == 0 or mass.surface_area == 0:
        circularity = 0
    else:
        circularity = ((math.pi)**(1.0/3.0))*((6.0*(myVolume_T0_T1))**(2.0/3.0) / mass.surface_area)

    print('SA = ' + str(mass.surface_area))
    print('T0 SA = ' + str(T0mass.surface_area))
    print('Volume = ' + str(myVolume_T0_T1))
    print('T0 Volume = ' + str(myVolumeT0))
    print('Radius = ' + str(T0_T1_radius))
    print('T0 Radius = ' + str(T0_radius))
    print('Circularity = ' + str(circularity))

    # Ask to see rendering
    msgBox = QtGui.QMessageBox(QtGui.QMessageBox.Question, QtCore.QString('Render'), QtCore.QString('Show Tumor Rendering?'), QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)
    ret = msgBox.exec_()
    if ret == QtGui.QMessageBox.Yes:
        algorithms.render_surface(T1, T0)

    query = QtSql.QSqlQuery('select max(group_id) from ' + 
                            'measurement_audit_log where measurement_type_id = ' 
                            + "'1'", db)      

    query.next()
    group_id = str(query.value(0).toInt()[0] + 1)

    # Check for a measurement assignment.
    osUserName = os.environ.get("USERNAME").lower()
    query = QtSql.QSqlQuery('SELECT id from users where pathology_user_name = "' + osUserName + '"')
    query.next()
    user_id = str(query.value(0).toInt()[0])

    query = QtSql.QSqlQuery('select id from ' +
                            'measurement_assignments_audit ' +
                            'where image_id = ' + str(self.dbImage.id.toString()) + ' ' +
                            'and measurer_id = ' + user_id + ' ' +
                            'and status = "P"')
    assignment = query.next()
    date_of_completion = operation_time[0:10]
    if not assignment:
        # Create a new assignment
        newAssignmentQuery = ('insert into measurement_assignments_audit ' + 
            '(measurement_assignment_id, assigner_id, measurer_id, image_id, patient_id, date_of_assignment, ' +
            'comments, date_of_completion, priority, measurement_group_id, operation_user_id, operation_code, ' +
            'operation_time, status) values (0, ' + user_id + ', ' + user_id + ', ' + str(self.dbImage.id.toString()) + ', ' + 
            str(self.dbImage.patient_id.toString()) + ', "' + date_of_completion + '", ' + '"Self-assigned through brainsegment", "' + 
            date_of_completion + '", 2, ' + group_id + ', ' + user_id + ', "I", "' + operation_time + '", "P")')
        query = QtSql.QSqlQuery(newAssignmentQuery) 
    else:
        # Update the assignment
        updateAssignmentQuery = ('update measurement_assignments_audit set date_of_completion = "' + date_of_completion + '", ' + 
            'measurement_group_id = ' + group_id + ' where id = ' + str(query.value(0).toInt()[0]))
        query = QtSql.QSqlQuery(updateAssignmentQuery)

    brain_io.uploadMeasurement(self.dbImage, self.db, self.version, self.tumorBrain.patient, remeasure, 1, 1, T0_T1_radius, saveLocationStr + 'MRI_Comparison', operation_time, str(self.dbImage.id.toString()), group_id)
    brain_io.uploadMeasurement(self.dbImage, self.db, self.version, self.tumorBrain.patient, remeasure, 2, 2, myVolume_T0_T1, saveLocationStr + 'MRI_Comparison', operation_time, str(self.dbImage.id.toString()), group_id)
    brain_io.uploadMeasurement(self.dbImage, self.db, self.version, self.tumorBrain.patient, remeasure, 7, 3, mass.surface_area, saveLocationStr + 'MRI_Comparison', operation_time, str(self.dbImage.id.toString()),group_id)
    brain_io.uploadMeasurement(self.dbImage, self.db, self.version, self.tumorBrain.patient, remeasure, 11, 11, circularity, saveLocationStr + 'MRI_Comparison', operation_time, str(self.dbImage.id.toString()),group_id)

    if T0pass is not None:
        query = QtSql.QSqlQuery('SELECT image_file_path from images i where i.id = ' + str(self.dbImage.id.toString()), db)
        query.next()
        #print('SELECT i.image_file_path from images i where i.id = ' + str(self.dbImage.id.toString()))
        image_file_path = str(query.value(0).toString()).replace('\\','\\\\')

        query = QtSql.QSqlQuery('SELECT id from images i where i.image_file_path = "' + image_file_path + '" and i.image_subtype_id in (14, 15, 20, 21) ', db)
        #print('SELECT id from images i where i.image_file_path = "' + image_file_path + '" and i.image_subtype_id in (14, 15, 20, 21) ')
        query.next()
        T0idStr = str(query.value(0).toString())

        T0radius = ((3.0*T0mass.volume)/(4.0*math.pi))**(1.0/3.0)

        brain_io.uploadMeasurement(self.dbImage, self.db, self.version, self.tumorBrain.patient, remeasure, 1, 1, T0_radius, saveLocationStr + 'MRI_Comparison', operation_time, T0idStr, group_id)
        brain_io.uploadMeasurement(self.dbImage, self.db, self.version, self.tumorBrain.patient, remeasure, 2, 2, myVolumeT0, saveLocationStr + 'MRI_Comparison', operation_time, T0idStr, group_id)
        brain_io.uploadMeasurement(self.dbImage, self.db, self.version, self.tumorBrain.patient, remeasure, 7, 3, T0mass.surface_area, saveLocationStr + 'MRI_Comparison', operation_time, T0idStr, group_id)

2 个答案:

答案 0 :(得分:4)

请务必使用matplotlib.use('Agg')在后端生成图像 在pyplot之前调用matplotlib.use()有一些排序要求。

http://matplotlib.org/faq/howto_faq.html

在不显示窗口的情况下生成图像 最简单的方法是使用非交互式后端(请参阅什么是后端?),例如Agg(用于PNG),PDF,SVG或PS。在你的图形生成脚本中,只需在导入pylab或pyplot之前调用matplotlib.use()指令:

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
plt.plot([1,2,3])
plt.savefig('myfig')

答案 1 :(得分:1)

如果处理图片需要很长时间,则应使用multiprocessing.Pool

为此,您应该为每个图像创建一个包含数据的列表(或其他可迭代的)。你应该根据循环中包含的处理代码创建一个函数。

接下来,您使用池的map_async方法将该函数应用于所有图像。这将在不同的进程中执行,因此它不应该打扰GUI。作为奖励,它将使用您机器中的所有CPU核心来完成工作。