2013 Project Week:Threaded SimpleITK Modules
- Yourimagehere.png
Image description
Key Investigators
- Brad Lowekamp, NLM
- Steve Pieper, Isomics
Project Description
For some filters and segmentation algorithms written using SimpleITK in slicer it would be nice to let them run in the background while letting slicer's main thread continue to process events. But the python byte code interpreter only allows a single path of python code to run at a time (see the discussion in the Preliminary Work section below). However since SimpleITK's implementation is in C++, it could release the GIL to allow the rest of the application to run while it processes. Care must be taken when processing progress events or modifying variables that are shared with the main thread.
Objective
- Support multi-threaded execution of SimpleITK filters.
- Try releasing the GIL when calling into SimpleITK
- Look at what's required for synchronization and progress reporting
Approach, Plan
- Review other threading implementations in slicer, vtk, and itk to look for best practices
- Processing tasks (for CLIs) in vtkSlicerApplicationLogic
- ITK MultiThreader
- VTK MultiThreader
- Python threading
Progress
- Able to patch SimpleITK to enable threading in all SimpleITK methods
- Compiled patch into Slice and confirmed able to run ITK filters in background
Preliminary Work
The example code below calls two different python functions from inside threads. One function calls the 'time.sleep' method, and one uses VTK to do some processing. As you can see from the output printed below, the VTK threads block, and a new thread is not started until the last one completes. On the other hand, the sleep based thread does return right away. The behavior is based on the CPython global interpreter lock. The topic was discussed in detail on the vtk developers list back in 2005.
import vtk import threading import Queue import time, datetime import os class ThreadClass(threading.Thread): def __init__(self, queue, work): threading.Thread.__init__(self) self.queue = queue self.work = work def run(self): now = datetime.datetime.now() self.queue.put( "start %s, time: %s" % (self.getName(), now) ) self.work() now = datetime.datetime.now() self.queue.put( "end %s, time: %s" % (self.getName(), now) ) def process(): e = vtk.vtkImageEllipsoidSource() e.SetWholeExtent(0,300,0,300,0,300) s = vtk.vtkImageGaussianSmooth() s.SetInputConnection(e.GetOutputPort()) s.Update() def sleep(): time.sleep(1) for work,message in ((process,'vtk'), (sleep,'sleep')): print ('threading test with %s:' % message) threads = 3 queue = Queue.Queue() for i in range(threads): t = ThreadClass(queue,work) t.start() print('started:') for i in range(2*threads): print(queue.get()) print('finished\n')
#8 Slicer-superbuild $ ./VTK-build/bin/vtkpython /Users/pieper/Dropbox/hacks/threadVTK.py threading test with vtk: started: start Thread-1, time: 2012-12-18 17:13:17.047282 end Thread-1, time: 2012-12-18 17:13:18.006989 start Thread-2, time: 2012-12-18 17:13:18.007318 end Thread-2, time: 2012-12-18 17:13:18.953446 start Thread-3, time: 2012-12-18 17:13:18.953762 end Thread-3, time: 2012-12-18 17:13:19.897673 finished threading test with sleep: started: start Thread-4, time: 2012-12-18 17:13:19.898124 start Thread-5, time: 2012-12-18 17:13:19.898346 start Thread-6, time: 2012-12-18 17:13:19.898506 end Thread-4, time: 2012-12-18 17:13:20.899137 end Thread-6, time: 2012-12-18 17:13:20.899378 end Thread-5, time: 2012-12-18 17:13:20.899469 finished
Update
Multi-threading proof of concept:
import SimpleITK as sitk import threading import Queue import time, datetime import os class ThreadClass(threading.Thread): def __init__(self, queue, work): threading.Thread.__init__(self) self.queue = queue self.work = work def run(self): now = datetime.datetime.now() self.work() self.queue.put( "start %s, time: %s" % (self.getName(), now) ) now = datetime.datetime.now() self.queue.put( "end %s, time: %s" % (self.getName(), now) ) def process(): img = sitk.GaussianSource(sitk.sitkFloat32, [256]*3) simg = sitk.SmoothingRecursiveGaussian(img) def sleep(): time.sleep(1) for work,message in ((process,'sitk'), (sleep,'sleep')): print ('threading test with %s:' % message) threads = 5 queue = Queue.Queue() for i in range(threads): t = ThreadClass(queue,work) t.start() print(queue.qsize()) while queue.empty(): print('empty') sleep() for i in range(2*threads): print(queue.get()) print('finished\n')
Results:
threading test with sitk: 0 empty empty empty empty empty empty start Thread-2, time: 2013-01-11 11:03:27.098504 end Thread-2, time: 2013-01-11 11:03:32.171759 start Thread-4, time: 2013-01-11 11:03:27.099969 end Thread-4, time: 2013-01-11 11:03:32.563888 start Thread-1, time: 2013-01-11 11:03:27.097554 end Thread-1, time: 2013-01-11 11:03:33.151524 start Thread-5, time: 2013-01-11 11:03:27.103264 end Thread-5, time: 2013-01-11 11:03:33.333674 start Thread-3, time: 2013-01-11 11:03:27.099090 end Thread-3, time: 2013-01-11 11:03:33.586790 finished threading test with sleep: 0 empty start Thread-6, time: 2013-01-11 11:03:33.587261 end Thread-6, time: 2013-01-11 11:03:34.589810 start Thread-7, time: 2013-01-11 11:03:33.587563 end Thread-7, time: 2013-01-11 11:03:34.590058 start Thread-8, time: 2013-01-11 11:03:33.587745 end Thread-8, time: 2013-01-11 11:03:34.590183 start Thread-9, time: 2013-01-11 11:03:33.587997 end Thread-9, time: 2013-01-11 11:03:34.590300 start Thread-10, time: 2013-01-11 11:03:33.588265 end Thread-10, time: 2013-01-11 11:03:34.590451 finished