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)
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')
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With