Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does my GUI go unresponsive even though I'm outsourcing to a worker thread?

I have a PyQt4 GUI in which I need to save a stack of Numpy arrays as *.tif images. This seems to take a significant amount of time and causes my GUI to go unresponsive for up to several minutes, depending on the number of images in the stack.

The bulk of the processing happens in this loop over the images:

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)

I figured I would outsource the expensive work to a PyQt4 worker QThread, so following the example at http://diotavelli.net/PyQtWiki/Threading,_Signals_and_Slots I created a worker class that inherits from QtCore.QThread, called all the above code inside the run method of this class, and created and start()ed an instance of this class where the *.tif stack saving needs to occur in the main window.

The GUI still goes unresponsive as it did previously, but the thread seems to be successfully executing the code. I am concerned that perhaps some package used in the code above is not properly releasing the GIL (as referenced here http://www.riverbankcomputing.com/pipermail/pyqt/2011-August/030470.html). Is there a way to manually ensure this is working?

I have commented out and replaced the *.tif stack saving code with a time.sleep delay, and the GUI still goes unresponsive, so perhaps I am implementing QThread wrong?

Am I missing something else? Have I provided enough information?

edit:

I create the thread in the main window here:

# 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)

And here is the entire worker thread class I defined, where the function saveImageStack(args) is the same *.tif saving code I provided above:

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)

edit #2: Adding the code for the method in the main window that starts the thread -

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)
like image 981
dotybear Avatar asked Jul 18 '12 21:07

dotybear


1 Answers

Be sure to generate your images in the backend with matplotlib.use('Agg') There is some ordering requirement to call the matplotlib.use() prior to the pyplot.

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

Generate images without having a window appear The easiest way to do this is use a non-interactive backend (see What is a backend?) such as Agg (for PNGs), PDF, SVG or PS. In your figure-generating script, just call the matplotlib.use() directive before importing pylab or pyplot:

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
plt.plot([1,2,3])
plt.savefig('myfig')
like image 118
zerocog Avatar answered Oct 02 '22 00:10

zerocog