The information here is from an initial implementation of adding call-back of command observer handling to SimpleITK. The current experimental topic can be found here: https://github.com/blowekamp/SimpleITK/tree/STRAW-SIMPLEITK-441_CallbackPrototype
The goal was to add support for ITK event support through SimpleITK, so that users of SimpleITK can receive call backs for execution on events such as start, progress, end, iteration as well as better control the execution with support abort control and other events.
The first thing is to simplely use an enumerated type for the differnt event types in SimpleITK.
C++ Implementation:
enum EventEnum { sitkAnyEvent = 0, sitkAbortEvent = 1, sitkDeleteEvent = 2, sitkEndEvent = 3, sitkIterationEvent = 4, sitkProgressEvent = 5, sitkStartEvent = 6, sitkUserEvent = 7 };
C++ is more stongly typed than many target languages it allows for implicit conversion from an enum type to an int, but not from an int to an enum type. While on many target languages the enum elemenents are one-to-one with integers.
The convention of pre-fixing enums with "sitk" is continued, although it's getting a little crowded. (Recently the KernelType
enumeration for morhology was copied from each filter to the namespace.)
from __future__ import print_function
import SimpleITK as sitk
print(sitk.Version())
import sys
import os
import threading
from myshow import myshow
from myshow import myshow3d
--------------------------------------------------------------------------- ImportError Traceback (most recent call last) <ipython-input-1-fe3d6ff67427> in <module>() 8 import threading 9 ---> 10 from myshow import myshow 11 from myshow import myshow3d /Users/blowekamp/src/SimpleITK-Notebooks/myshow.py in <module>() 1 import SimpleITK as sitk ----> 2 import matplotlib.pyplot as plt 3 4 def myshow(img, title=None, margin=0.05, dpi=80 ): 5 nda = sitk.GetArrayFromImage(img) ImportError: No module named matplotlib.pyplot
SimpleITK Version: 0.6.0rc1-gc9d89 Compiled: Jan 6 2013 02:27:53
# hit tab-tab to reveal suggested completions
sitk.sitk
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-2-c6b6f0779591> in <module>() 1 # hit tab-tab to reveal suggested completions ----> 2 sitk.sitk AttributeError: 'module' object has no attribute 'sitk'
The considerations for the command type include the ability to easily create a custom callback for the target language, be stack allocatable, as well as have the exposed interface be free from pointers.
The resulting C++ user interface is about as simple as can be:
class Command: protected NonCopyable { public: Command(); virtual ~Command(void); virtual void Execute(void); };
An interesting choice that was made here was not to include the object which emited the event nor additional user data as an argument to the Execute
method. Modern languages have features such as lambda function and closures enabling the ability to bind argument to existing methods to create new procedures free from exposed parameters.
The complication of the class is enabling the ability to be stack allocatable, which the details are handled seemlessly internally.
A custom derived Python class has a method call SetCommandCallable
for easy use.
help(sitk.PyCommand)
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-5-d6bd8e75135b> in <module>() ----> 1 help(sitk.PyCommand) AttributeError: 'module' object has no attribute 'PyCommand'
cmd = sitk.PyCommand()
cmd.SetCommandCallable( lambda: print("PyCommand Called") )
cmd.Execute()
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-4-a286551e351d> in <module>() ----> 1 cmd = sitk.PyCommand() 2 cmd.SetCommandCallable( lambda: print("PyCommand Called") ) 3 cmd.Execute() AttributeError: 'module' object has no attribute 'PyCommand'
We are going to need an image shortly, so create a Gabor kernel using keyword arguments.
size=128
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);
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 most of the goals for the event 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.
Recent developments in the ProcessObject
have included adding global and instance specific options for Debug and Warning messages as well as the number of threads. Additionally, an Abort
method has been added to assist with execution management of filters.
The member methods added for the event interface is as follows:
int AddCommand( itk::simple::EventEnum event, itk::simple::Command &cmd); void RemoveCommand( int cmdID ); void RemoveAllCommands(); bool HasCommand( itk::simple::EventEnum event ) const;
Adding these functionalities does not go with the procedural interface style available in SimpleITK. They are only available through the Object Oriented interface, and break the method chaining interface.
help(sitk.ProcessObject)
Help on class ProcessObject in module SimpleITK: class ProcessObject(__builtin__.object) | Proxy of C++ itk::simple::ProcessObject class | | Methods defined here: | | Abort(self) | Abort(ProcessObject self) | | AddCommand(self, *args) | AddCommand(ProcessObject self, itk::simple::EventEnum event, Command cmd) -> int | AddCommand(ProcessObject self, itk::simple::EventEnum e, PyObject * obj) -> int | | DebugOff(self) | DebugOff(ProcessObject self) | | DebugOn(self) | DebugOn(ProcessObject self) | | GetDebug(self) | GetDebug(ProcessObject self) -> bool | | GetName(self) | GetName(ProcessObject self) -> std::string | | GetNumberOfThreads(self) | GetNumberOfThreads(ProcessObject self) -> unsigned int | | GetProgress(self) | GetProgress(ProcessObject self) -> float | | HasCommand(self, *args, **kwargs) | HasCommand(ProcessObject self, itk::simple::EventEnum event) -> bool | | RemoveAllCommands(self) | RemoveAllCommands(ProcessObject self) | | SetDebug(self, *args, **kwargs) | SetDebug(ProcessObject self, bool debugFlag) | | SetNumberOfThreads(self, *args, **kwargs) | SetNumberOfThreads(ProcessObject self, unsigned int n) | | __del__ lambda self | | __getattr__ lambda self, name | | __init__(self, *args, **kwargs) | | __repr__ = _swig_repr(self) | | __setattr__ lambda self, name, value | | __str__(self) | __str__(ProcessObject self) -> std::string | | ---------------------------------------------------------------------- | Static methods defined here: | | GetGlobalDefaultDebug() | GetGlobalDefaultDebug() -> bool | | GetGlobalDefaultNumberOfThreads() | GetGlobalDefaultNumberOfThreads() -> unsigned int | | GetGlobalWarningDisplay() | GetGlobalWarningDisplay() -> bool | | GlobalDefaultDebugOff() | GlobalDefaultDebugOff() | | GlobalDefaultDebugOn() | GlobalDefaultDebugOn() | | GlobalWarningDisplayOff() | GlobalWarningDisplayOff() | | GlobalWarningDisplayOn() | GlobalWarningDisplayOn() | | SetGlobalDefaultDebug(*args, **kwargs) | SetGlobalDefaultDebug(bool debugFlag) | | SetGlobalDefaultNumberOfThreads(*args, **kwargs) | SetGlobalDefaultNumberOfThreads(unsigned int n) | | SetGlobalWarningDisplay(*args, **kwargs) | SetGlobalWarningDisplay(bool flag) | | ---------------------------------------------------------------------- | Data descriptors defined here: | | __dict__ | dictionary for instance variables (if defined) | | __weakref__ | list of weak references to the object (if defined) | | ---------------------------------------------------------------------- | Data and other attributes defined here: | | __swig_destroy__ = <built-in function delete_ProcessObject> | delete_ProcessObject(ProcessObject self) | | __swig_getmethods__ = {'GetGlobalDefaultDebug': <function <lambda>>, '... | | __swig_setmethods__ = {}
The complication is that both the ProcessObject
and the Command
base class can be stack allocated and internally the ProcessObject
maintains a pointer to the Command
. A new book keeping mechinism was created. The Command
keeps a list ProcessObjects
which reference, and then ProcessObject
keeps the list of Commands
. When either is deleted the others are notified to create keep reference valid. This is tricky and error prone.
gFilter=sitk.DiscreteGaussianImageFilter()
gFilter.SetVariance(100)
gFilter.AddCommand(sitk.sitkStartEvent, cmd)
0
gFilter.Execute(img)
PyCommand Called
<SimpleITK.Image; proxy of <Swig Object of type 'std::vector< itk::simple::Image >::value_type *' at 0x11b374b40> >
In Python the AddCommand
has been extended to accept PyCommand objects and creating a PyCommand
. This is really useful.
gFilter.RemoveAllCommands()
gFilter.AddCommand(sitk.sitkStartEvent, lambda: print("Starting...",end=''))
gFilter.AddCommand(sitk.sitkStartEvent, lambda: sys.stdout.flush())
gFilter.AddCommand(sitk.sitkEndEvent, lambda: print("Done"))
gFilter.Execute(img)
Starting...Done
<SimpleITK.Image; proxy of <Swig Object of type 'std::vector< itk::simple::Image >::value_type *' at 0x11b374c90> >
The commands are not too useful unless you can query the ITK object through the SimpleITK interface. Interacting with ITK objects has been totally encapsulated in side the SimpleITK iterface. This is a new feature!
A couple status variables and methods can be expose in the SimpleITK ProcessObject
through the polymorphic interface of the same ITK class. This is the approach taked for the GetProgress
and Abort
methods.
gFilter.RemoveAllCommands()
gFilter.AddCommand(sitk.sitkProgressEvent, lambda: print("\rProgress: {0:03.1f}%...".format(100*gFilter.GetProgress()),end=''))
gFilter.AddCommand(sitk.sitkProgressEvent, lambda: sys.stdout.flush())
gFilter.AddCommand(sitk.sitkEndEvent, lambda: print("Done"))
gFilter.Execute(img)
Progress: 100.0%...Done
<SimpleITK.Image; proxy of <Swig Object of type 'std::vector< itk::simple::Image >::value_type *' at 0x11b374450> >
For lack of a better name (open to suggestions), SimpleITK has "measurements" which are additional results available after execution of of filter. This has include things like number of iterations, convergence tolerances, and other computed statistics. They have been static in nature.
A new concept is being developed called "active measurements". This is values, and objects available during callbacks/commands as the filer is executing. The "Progress" accessible through the ProcessObject
would be defined as an "active measurement".
With the goal of reasonable efficiency we didn't want to copy these active measuement values each iteration without regaurd to if they were even used. To drill down into the ITK object the std::(tr1)::function
pointers are use in conjunction with bind
and function composition to create a callable with ITK object context and conversion.
For Example:
unsigned int ImageRegistrationMethod::GetOptimizerIteration() const { if (bool(this->m_pfGetOptimizerIteration)) { return this->m_pfGetOptimizerIteration(); } return this->m_Iteration; }
Where the function
is defined:
std::tr1::function<std::vector<double>()> m_pfGetOptimizerPosition;
and can be initialized with a variety of methods such as:
this->m_pfGetOptimizerPosition = std::tr1::bind(&_GetOptimizerPosition,optimizer);
IPython notebooks support displaying output as HTML, and execution of javascript on demand. Together this can produce annimation.
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))))
gFilter.RemoveAllCommands()
gFilter.AddCommand(sitk.sitkStartEvent, lambda: display(HTML(html_progress)))
gFilter.AddCommand(sitk.sitkProgressEvent, lambda: command_js_progress(gFilter))
gFilter.Execute(img)
FilterName:
<SimpleITK.Image; proxy of <Swig Object of type 'std::vector< itk::simple::Image >::value_type *' at 0x11b366f90> >
It's possible to get button in HTML to execute python code...
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
g_Abort=False
gFilter.RemoveAllCommands()
gFilter.AddCommand(sitk.sitkStartEvent, command_js_start_abort )
gFilter.AddCommand(sitk.sitkStartEvent, lambda: display(HTML(html_progress_abort+javascript_abort)))
gFilter.AddCommand(sitk.sitkProgressEvent, lambda: command_js_progress_abort(gFilter))
0
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.
import threading
threading.Thread( target=lambda:gFilter.Execute(img) ).start()
FilterName:
threading.Thread( target=lambda:gFilter.Execute(img) ).start()
FilterName: