SimpleITK Filter
s and other classes derived from ProcessObject
s have the ability for user code to be executed when certain events occour. This is known as the Command and Observer design patters to implement user callbacks. This allows for the monitoring and abortion of processes as they are being executed.
Consider the following image source which takes a few seconds to execute. It would be nice to quickly know how long your going to need to wait, to know if you can go get a cup of coffee.
In [ ]:
from __future__ import print_function
import matplotlib.pyplot as plt
%matplotlib inline
import SimpleITK as sitk
print(sitk.Version())
import sys
import os
import threading
from myshow import myshow
from myshow import myshow3d
In [ ]:
size=256 # if this is too fast increase the size
img = sitk.GaborSource(sitk.sitkFloat32, size=[size]*3, sigma=[size*.2]*3, mean=[size*0.5]*3, frequency=.1)
myshow3d(img,zslices=[size/2],dpi=40);
In [ ]:
myshow(img);
We need to add a command to display the progress reported by the ProcessObject::GetProgress
method during the sitkProgressEvent
. This involves three commponents:
Commands
We'll look at some examples after a brief explanation of these components.
The avaiable events to observed are defined in a namespace enumeration.
sitkAnyEvent | Occurs for all event types. |
sitkAbortEvent | Occurs after the process has been aborted, but before exiting the Execute method. |
sitkDeleteEvent | Occurs when the underlying itk::ProcessObject is deleted. |
sitkEndEvent | Occurs at then end of normal processing. |
sitkIterationEvent | Occurs with some algorithms that run for a fixed or undetermined number of iterations. |
sitkProgressEvent | Occurs when the progress changes in most process objects. |
sitkStartEvent | Occurs when then itk::ProcessObject is starting. |
sitkUserEvent | Other events may fall into this enumeration. |
The convention of pre-fixing enums with "sitk" is continued, although it's getting a little crowded.
C++ is more stongly typed than Python it allows for implicit conversion from an enum type to an int, but not from an int to an enum type. Care needs to be made to ensure the correct enum value is passed in Python.
To be able to interface with the ProcessObject
during execution, the object-oriented interface must be used to access the method of the ProcessObject. While any constant member function can be called during a command call-back there are two common methods:
ProcessObject::GetProgress()
ProcessObject::Abort()
The methods are only valid during the Command
while a process is being executed, or when the process is not in the Execute
method.
Additionally it should be noted that follow methods can not be called during a command or from another thread durring execution Execute
and RemoveAllCommands
. In general the ProcessObject
should not be modified during execution.
The command design pattern is used to allow user code to be executed when an event occurs. It is implemented in the Command
class. The Command
class provides an Execute
method to be overridden in derived classes.
There are three ways to define a command with SimpleITK in Python.
Command
class.PyCommand
class' SetCallbackPyCallable
method.lambda
function in ProcessOject::AddCommand
.
In [ ]:
help(sitk.Command)
In [ ]:
class MyCommand(sitk.Command):
def __init__(self):
# required
super(MyCommand,self).__init__()
def Execute(self):
print("MyCommand::Execute Called")
cmd = MyCommand()
cmd.Execute()
In [ ]:
help(sitk.PyCommand)
In [ ]:
cmd = sitk.PyCommand()
cmd.SetCallbackPyCallable( lambda: print("PyCommand Called") )
cmd.Execute()
Back to watching the progress of out Gabor image source. First lets create the filter as an object
In [ ]:
size=256
filter = sitk.GaborImageSource()
filter.SetOutputPixelType(sitk.sitkFloat32)
filter.SetSize([size]*3)
filter.SetSigma([size*.2]*3)
filter.SetMean([size*0.5]*3)
filter.SetFrequency(.1)
img = filter.Execute()
myshow3d(img,zslices=[size/2],dpi=40);
SimpleITK doesn't have a large heirachy of inheritance. It has been kept to a minimal, so there is no common Object
or LightObject
base class as ITK has. As most of the goals for the events have to do with observing processes, the "Subject" interface of the Observer patter or the "Invoker" part of the Command design pattern, has been added to a ProcessObject
base class for filters.
The ProcessObject
base class has the following methods of handling commands: AddCommand
, RemoveAllCommands
, and HasCommand
.
Adding these functionalities are not available in the procedural interface available for SimpleITK. They are only available through the Object Oriented interface, and break the method chaining interface.
In [ ]:
help(sitk.ProcessObject)
In [ ]:
class MyCommand(sitk.Command):
def __init__(self, msg):
# required
super(MyCommand,self).__init__()
self.msg = msg
def __del__(self):
print("MyCommand begin deleted: \"{0}\"".format(self.msg))
def Execute(self):
print(self.msg)
In [ ]:
cmd1 = MyCommand("Start")
cmd2 = MyCommand("End")
filter.RemoveAllCommands() # this line is here so we can easily re-execute this code block
filter.AddCommand(sitk.sitkStartEvent, cmd1)
filter.AddCommand(sitk.sitkEndEvent, cmd2)
filter.Execute()
A reference to the Command
object must be maintained, or else it will be removed from the ProcessObject
.
In [ ]:
filter.AddCommand(sitk.sitkStartEvent, MyCommand("stack scope"))
print("Before Execution")
filter.Execute()
In [ ]:
filter.RemoveAllCommands() # this line is here so we can easily re-execute this code block
filter.AddCommand(sitk.sitkStartEvent, lambda: print("Starting...",end=''))
filter.AddCommand(sitk.sitkStartEvent, lambda: sys.stdout.flush())
filter.AddCommand(sitk.sitkEndEvent, lambda: print("Done"))
filter.Execute()
In [ ]:
filter.RemoveAllCommands()
filter.AddCommand(sitk.sitkProgressEvent, lambda: print("\rProgress: {0:03.1f}%...".format(100*filter.GetProgress()),end=''))
filter.AddCommand(sitk.sitkProgressEvent, lambda: sys.stdout.flush())
filter.AddCommand(sitk.sitkEndEvent, lambda: print("Done"))
filter.Execute()
Utilization of commands and event frequently occours with advanced integration into graphical user iterfaces. Let us now export this advanced integration into IPython Notebooks.
IPython notebooks support displaying output as HTML, and execution of javascript on demand. Together this can produce animation.
In [ ]:
import uuid
from IPython.display import HTML, Javascript, display
divid = str(uuid.uuid4())
html_progress="""
<p style="margin:5px">FilterName:</p>
<div style="border: 1px solid black;padding:1px;margin:5px">
<div id="{0}" style="background-color:blue; width:0%%"> </div>
</div>
""".format(divid)
def command_js_progress(processObject):
p = processObject.GetProgress()
display(Javascript("$('div#%s').width('%i%%')" % (divid, int(p*100))))
In [ ]:
filter.RemoveAllCommands()
filter.AddCommand(sitk.sitkStartEvent, lambda: display(HTML(html_progress)))
filter.AddCommand(sitk.sitkProgressEvent, lambda: command_js_progress(filter))
filter.Execute()
In [ ]:
import uuid
from IPython.display import HTML, Javascript, display
g_Abort = False
divid = str(uuid.uuid4())
html_progress_abort="""
<div style="background-color:gainsboro; border:2px solid black;padding:15px">
<p style="margin:5px">FilterName:</p>
<div style="border: 1px solid black;padding:1px;margin:5px">
<div id="{0}" style="background-color:blue; width:0%%"> </div>
</div>
<button onclick="set_value()" style="margin:5px" >Abort</button>
</div>
""".format(divid)
javascript_abort = """
<script type="text/Javascript">
function set_value(){
var command = "g_Abort=True"
console.log("Executing Command: " + command);
var kernel = IPython.notebook.kernel;
kernel.execute(command);
}
</script>
"""
def command_js_progress_abort(processObject):
p = processObject.GetProgress()
display(Javascript("$('div#%s').width('%i%%')" % (divid, int(p*100))))
if g_Abort:
processObject.Abort()
def command_js_start_abort():
g_Abort=False
In [ ]:
g_Abort=False
filter.RemoveAllCommands()
filter.AddCommand(sitk.sitkStartEvent, command_js_start_abort )
filter.AddCommand(sitk.sitkStartEvent, lambda: display(HTML(html_progress_abort+javascript_abort)))
filter.AddCommand(sitk.sitkProgressEvent, lambda: command_js_progress_abort(filter))
A caveat with this approach is that the is that the IPython kernel must continue to execute while the filter is running. So we must place the filter in a thread.
In [ ]:
import threading
threading.Thread( target=lambda:filter.Execute() ).start()
While the lambda
command are convenient, the lack for having an object to hold data can stil be problematic. For example in the above code the uuid, is used to uniquely identify the HTML element. So if the filter is executed multiple times then the JavaScript update will be confused on what to update.
In [ ]:
#### The following shows a failure that you will want to avoid.
threading.Thread( target=lambda:filter.Execute() ).start()
In [ ]:
import uuid
from IPython.display import HTML, Javascript, display
class HTMLProgressWatcher:
def __init__(self, po):
self.processObject = po
self.abort = False
po.AddCommand(sitk.sitkStartEvent, lambda: self.cmdStartEvent() )
po.AddCommand(sitk.sitkProgressEvent, lambda: self.cmdProgressEvent() )
po.AddCommand(sitk.sitkEndEvent, lambda: self.cmdEndEvent() )
def cmdStartEvent(self):
global sitkIPythonProgress_UUID
self.abort=False
self.divid = str(uuid.uuid4())
try:
sitkIPythonProgress_UUID[self.divid] = self
except NameError:
sitkIPythonProgress_UUID = {self.divid: self}
html_progress_abort="""
<p style="margin:5px">{0}:</p>
<div style="border: 1px solid black;padding:1px;margin:5px">
<div id="{1}" style="background-color:blue; width:0%%"> </div>
</div>
""".format(self.processObject.GetName(), self.divid)
display(HTML(html_progress_abort+javascript_abort))
def cmdProgressEvent(self):
p = self.processObject.GetProgress()
display(Javascript("$('div#%s').width('%i%%')" % (self.divid, int(p*100))))
if self.abort:
self.processObject.Abort()
def cmdEndEvent(self):
global sitkIPythonProgress_UUID
del sitkIPythonProgress_UUID[self.divid]
del self.divid
In [ ]:
filter.RemoveAllCommands()
watcher = HTMLProgressWatcher(filter)
In [ ]:
filter.Execute()
In [ ]:
threading.Thread.start?
In [ ]: