Filter Progress Reporting
Overview
SimpleITK has the ability to add commands or callbacks as observers of events that may occur during data processing. This feature can be used to add progress reporting to a console, to monitor the process of optimization, to abort a process, or to improve the integration of SimpleITK into Graphical User Interface event queues.
Events
Events are a simple enumerated type in SimpleITK, represented by the EventEnum type. More information about each event type can be found in the documentation for the enum. All SimpleITK filters, including the reading and writing ones, are derived from the ProcessObject class which has support for events. SimpleITK utilizes the native ITK event system but has simpler events and methods to add an observer or commands. The goal is to provide a simpler interface more suitable for scripting languages.
Commands
The command design pattern is used to allow user code to be executed when an event occurs. It is encapsulated in the Command class. The Command class provides a virtual Execute method to be overridden in derived classes. Additionally, SimpleITK provides internal reference tracking between the ProcessObject and the Command. This reference tracking allows an object to be created on the stack or dynamically allocated, without additional burden.
Command Directors for Wrapped Languages
SimpleITK uses SWIG’s director feature to enable wrapped languages to derive classes from the Command class. Thus a user may override the Command class’s Execute method for custom call-backs. The following languages support deriving classes from the Command class:
class MyCommand : Command {
private ProcessObject m_ProcessObject;
public MyCommand(ProcessObject po){
m_ProcessObject = po;
}
public override void Execute() {
Console.WriteLine("{0} Progress: {1:0.00}", m_ProcessObject.GetName(), m_ProcessObject.GetProgress() );
}
}
class MyCommand extends Command {
private ProcessObject m_ProcessObject;
public MyCommand(ProcessObject po) {
super();
m_ProcessObject=po;
}
public void execute() {
double progress = m_ProcessObject.getProgress();
System.out.format("%s Progress: %f\n", m_ProcessObject.getName(), progress);
}
}
class MyCommand(sitk.Command):
""" Sample command class that prints the progress of the process object. """
def __init__(self, po):
""" Constructor, note that the base class constructor is called here. """
# required
super().__init__()
self.processObject = po
def Execute(self):
""" Method that is called by the process object. """
print(
f"{self.processObject.GetName()}"
+ f" Progress: {self.processObject.GetProgress():1.2f}"
)
class MyCommand < Simpleitk::Command
def initialize(po)
# Explicit call to supoer class initlaizer is required to
# initialize the SWIG director class to enable overloaded methods
super()
@po = po
end
# The Command method to be executed on the event from the filter.
def execute
puts "%s Progress: %0.2f" % [@po.get_name, @po.get_progress]
end
end
Command Functions and Lambdas for Wrapped Languages
Not all languages are naturally object oriented, and it is often easier to simply define a callback inline with a lambda function. The following language supports inline function definitions for functions for the ProcessObject::AddCommand method:
writer.AddCommand(sitk::sitkStartEvent, [] { std::cout << "Writting..." << std::flush; });
writer.AddCommand(sitk::sitkEndEvent, [] { std::cout << "done" << std::endl; });
gaussian.AddCommand(sitk.sitkStartEvent, lambda: print("StartEvent"))
gaussian.AddCommand(sitk.sitkEndEvent, lambda: print("EndEvent"))
gaussian$AddCommand( 'sitkStartEvent', function(method) {cat("StartEvent\n")} )
gaussian$AddCommand( 'sitkEndEvent', function(method) {cat("EndEvent\n")} )
Code
using System;
using itk.simple;
namespace itk.simple.examples {
//! [csharp director command]
class MyCommand : Command {
private ProcessObject m_ProcessObject;
public MyCommand(ProcessObject po){
m_ProcessObject = po;
}
public override void Execute() {
Console.WriteLine("{0} Progress: {1:0.00}", m_ProcessObject.GetName(), m_ProcessObject.GetProgress() );
}
}
//! [csharp director command]
class FilterProgressReporting {
static void Main(string[] args) {
try {
if (args.Length < 3) {
Console.WriteLine("Usage: {0} <input> <variance> <output>", args[0]);
return;
}
// Read input image
ImageFileReader reader = new ImageFileReader();
reader.SetFileName(args[0]);
Image image = reader.Execute();
// Execute Gaussian smoothing filter
DiscreteGaussianImageFilter filter = new DiscreteGaussianImageFilter();
filter.SetVariance(Double.Parse(args[1]));
MyCommand cmd = new MyCommand(filter);
filter.AddCommand(EventEnum.sitkProgressEvent, cmd);
image = filter.Execute(image);
// Write output image
ImageFileWriter writer = new ImageFileWriter();
writer.SetFileName(args[2]);
writer.Execute(image);
} catch (Exception ex) {
Console.WriteLine(ex);
}
}
}
}
// This one header will include all SimpleITK filters and external
// objects.
#include <SimpleITK.h>
#include <iostream>
#include <stdlib.h>
#include <iomanip>
// create convenient namespace alias
namespace sitk = itk::simple;
// Create A Command Callback to be registered with the ProcessObject
// on the ProgressEvent
//
// Internally we maintain a reference to the ProcessObject passed
// during construction. Therefore, it would be an error to execute the
// Execute method after the ProcessObject is delete. But this class
// can be created on the stack, with out issue.
class ProgressUpdate : public sitk::Command
{
public:
ProgressUpdate(const sitk::ProcessObject & po)
: m_Process(po)
{}
void
Execute() override
{
// stash the stream state
std::ios state(NULL);
state.copyfmt(std::cout);
std::cout << std::fixed << std::setw(3) << std::setprecision(2);
// Print the Progress "Active Measurement"
std::cout << m_Process.GetName() << " Progress: " << m_Process.GetProgress() << std::endl;
std::cout.copyfmt(state);
}
private:
const sitk::ProcessObject & m_Process;
};
int
main(int argc, char * argv[])
{
if (argc < 4)
{
std::cerr << "Usage: " << argv[0] << " <input> <variance> <output>\n";
return 1;
}
// Read the image file
sitk::ImageFileReader reader;
reader.SetFileName(std::string(argv[1]));
sitk::Image image = reader.Execute();
// This filters perform a gaussian blurring with sigma in physical
// space. The output image will be of real type.
sitk::DiscreteGaussianImageFilter gaussian;
gaussian.SetVariance(atof(argv[2]));
// Construct our custom command on the stack
ProgressUpdate cmd(gaussian);
// register it with the filter for the ProgressEvent
gaussian.AddCommand(sitk::sitkProgressEvent, cmd);
sitk::Image blurredImage = gaussian.Execute(image);
// Covert the real output image back to the original pixel type, to
// make writing easier, as many file formats don't support real
// pixels.
sitk::CastImageFilter caster;
caster.SetOutputPixelType(image.GetPixelID());
sitk::Image outputImage = caster.Execute(blurredImage);
// write the image
sitk::ImageFileWriter writer;
writer.SetFileName(std::string(argv[3]));
//! [cpp lambda command]
writer.AddCommand(sitk::sitkStartEvent, [] { std::cout << "Writting..." << std::flush; });
writer.AddCommand(sitk::sitkEndEvent, [] { std::cout << "done" << std::endl; });
//! [cpp lambda command]
writer.Execute(outputImage);
return 0;
}
import org.itk.simple.*;
//! [java director command]
class MyCommand extends Command {
private ProcessObject m_ProcessObject;
public MyCommand(ProcessObject po) {
super();
m_ProcessObject=po;
}
public void execute() {
double progress = m_ProcessObject.getProgress();
System.out.format("%s Progress: %f\n", m_ProcessObject.getName(), progress);
}
}
//! [java director command]
class FilterProgressReporting {
public static void main(String argv[]) {
if ( argv.length < 3 ) {
System.out.format("Usage: java %s <input> <variance> <output>", "FilterProgressReporting" );
System.exit(-1);
}
org.itk.simple.ImageFileReader reader = new org.itk.simple.ImageFileReader();
reader.setFileName(argv[0]);
Image img = reader.execute();
DiscreteGaussianImageFilter filter = new DiscreteGaussianImageFilter();
filter.setVariance(Double.valueOf( argv[1] ).doubleValue());
MyCommand cmd = new MyCommand(filter);
filter.addCommand(EventEnum.sitkProgressEvent, cmd);
Image blurredImg = filter.execute(img);
CastImageFilter caster = new CastImageFilter();
caster.setOutputPixelType(img.getPixelID());
Image castImg = caster.execute(blurredImg);
ImageFileWriter writer = new ImageFileWriter();
writer.setFileName(argv[2]);
writer.execute(castImg);
}
}
#!/usr/bin/env python
import os
import sys
import SimpleITK as sitk
if len(sys.argv) < 4:
print("Usage: " + sys.argv[0] + " <input> <variance> <output>")
sys.exit(1)
##! [python director command]
class MyCommand(sitk.Command):
""" Sample command class that prints the progress of the process object. """
def __init__(self, po):
""" Constructor, note that the base class constructor is called here. """
# required
super().__init__()
self.processObject = po
def Execute(self):
""" Method that is called by the process object. """
print(
f"{self.processObject.GetName()}"
+ f" Progress: {self.processObject.GetProgress():1.2f}"
)
##! [python director command]
reader = sitk.ImageFileReader()
reader.SetFileName(sys.argv[1])
image = reader.Execute()
pixelID = image.GetPixelID()
gaussian = sitk.DiscreteGaussianImageFilter()
gaussian.SetVariance(float(sys.argv[2]))
##! [python lambda command]
gaussian.AddCommand(sitk.sitkStartEvent, lambda: print("StartEvent"))
gaussian.AddCommand(sitk.sitkEndEvent, lambda: print("EndEvent"))
##! [python lambda command]
cmd = MyCommand(gaussian)
gaussian.AddCommand(sitk.sitkProgressEvent, cmd)
image = gaussian.Execute(image)
caster = sitk.CastImageFilter()
caster.SetOutputPixelType(pixelID)
image = caster.Execute(image)
writer = sitk.ImageFileWriter()
writer.SetFileName(sys.argv[3])
writer.Execute(image)
if "SITK_NOSHOW" not in os.environ:
sitk.Show(image, "Simple Gaussian")
# Run with:
#
# Rscript --vanilla FilterProgressReporting.R input variance output
#
library(SimpleITK)
args <- commandArgs( TRUE )
if (length(args) < 3){
write("Usage arguments: <input> <variance> <output>", stderr())
quit(save="no", status=1)
}
reader <- ImageFileReader()
reader$SetFileName(args[[1]])
image = reader$Execute()
pixelID <- image$GetPixelID()
gaussian <- DiscreteGaussianImageFilter()
gaussian$SetVariance( as.numeric(args[2]) )
##! [R lambda command]
gaussian$AddCommand( 'sitkStartEvent', function(method) {cat("StartEvent\n")} )
gaussian$AddCommand( 'sitkEndEvent', function(method) {cat("EndEvent\n")} )
##! [R lambda command]
image = gaussian$Execute( image )
caster <- CastImageFilter()
caster$SetOutputPixelType( pixelID )
image = caster$Execute( image )
writer <- ImageFileWriter()
writer$SetFileName( args[[3]] )
writer$Execute( image )
require 'simpleitk'
if ARGV.length != 3 then
puts "Usage: SimpleGaussian <input> <sigma> <output>";
exit( 1 )
end
# Derive a class from SimpleITK Command class to be used to observe
# events and report progress.
## [ruby director command]
class MyCommand < Simpleitk::Command
def initialize(po)
# Explicit call to supoer class initlaizer is required to
# initialize the SWIG director class to enable overloaded methods
super()
@po = po
end
# The Command method to be executed on the event from the filter.
def execute
puts "%s Progress: %0.2f" % [@po.get_name, @po.get_progress]
end
end
## [ruby director command]
reader = Simpleitk::ImageFileReader.new
reader.set_file_name( ARGV[0] )
image = reader.execute
inputPixelType = image.get_pixel_idvalue
gaussian = Simpleitk::DiscreteGaussianImageFilter.new
gaussian.set_variance ARGV[1].to_f
# create a new MyCommand class, the references between the objects is
# automatically taked care of. The connection will automatically be
# removed when either object is deleted.
cmd = MyCommand.new gaussian
gaussian.add_command Simpleitk::SitkProgressEvent, cmd
image = gaussian.execute image
caster = Simpleitk::CastImageFilter.new
caster.set_output_pixel_type inputPixelType
image = caster.execute image
writer = Simpleitk::ImageFileWriter.new
writer.set_file_name ARGV[2]
writer.execute image