2013 Project Week:Threaded SimpleITK Modules

From NAMIC Wiki
Jump to: navigation, search
Home < 2013 Project Week:Threaded SimpleITK Modules

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

Progress

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