Scripting in Simpleware: Customizing and Automating Workflows

Posted on 6 March 2017 by Steve Cockram

 

We recently ran a webinar on Automating Workflows in Simpleware ScanIP. In the webinar we talked about scripting in ScanIP to achieve better automation of repetitive tasks, and showed how ScanIP can be augmented for specific workflows with small scripts. Expanding on the topics covered in the webinar, I would like to share some of the code demonstrated very briefly during the webinar.

Creating a Custom Filter

In the webinar, we looked at creating a custom filter, and wrapping it in a ScanIP Command:

from scanip_api import *
import csv
import itertools
import os.path
app = App.GetInstance()
doc = App.GetDocument()
class ThinObjectSmoother(Command):
def __init__(self, dilate, smooth, erode):
Command.__init__(self)
self.dilate = dilate
self.smooth = smooth
self.erode = erode
def GetName(self):
return "Special Smooth"
def OnNativeDelete(self):
doc.ReleaseCommand(self)
def Do(self):
doc.ApplyDilateFilter(Doc.TargetMask, self.dilate, 0)
doc.ApplyRecursiveGaussianFilter(Doc.TargetMask, True, Sigma(self.smooth))
doc.ApplyErodeFilter(Doc.TargetMask, self.erode, 0)
return True
# Just run once...
doc.SubmitCommand(ThinObjectSmoother(4, 4.5, 3))
view raw RunFilterOnce.py hosted with ❤ by GitHub

The great advantage of commands is that they fit nicely into the ScanIP undo stack, meaning for complex operations performed via scripting, a single undo (Ctrl+Z) will undo every operation performed. Creating atomic operations like this greatly simplifies user-operation further down the line. After creating the simple command, I added this to the interface via a UserAction:

from scanip_api import *
app = App.GetInstance()
class ThinObjectSmoother(Command):
def __init__(self, dilate, smooth, erode):
Command.__init__(self)
self.doc = App.GetDocument()
self.dilate = dilate
self.smooth = smooth
self.erode = erode
def GetName(self):
return "Special Smooth"
def OnNativeDelete(self):
self.doc.ReleaseCommand(self)
def Do(self):
App.GetDocument().ApplyDilateFilter(Doc.TargetMask, self.dilate, 0)
App.GetDocument().ApplyRecursiveGaussianFilter(Doc.TargetMask, True, Sigma(self.smooth))
App.GetDocument().ApplyErodeFilter(Doc.TargetMask, self.erode, 0)
return True
class ThinObjectSmootherUserAction(UserAction):
def __init__(self, tab, group, name):
UserAction.__init__(self, tab, group, name)
def OnActivated(self):
App.GetDocument().SubmitCommand(ThinObjectSmoother(4, 4.5, 3))
app.AddUserAction(ThinObjectSmootherUserAction(
"Image processing",
"Smoothing",
"Rib smoothing"
))

Using this pattern, it is incredibly simple to enhance the ScanIP interface to include any custom functionality you might require. The usefulness of this technique is much more evident when conducting repetitive operations: i.e. when you have many, very similar, samples of similar items.

When using ScanIP for one-off research projects, scripting can be leveraged to ensure that the parameters you are using are the best parameters to achieve the result that you are trying to achieve. The workflow for this would be:

  1. Establish what a "good result" is, and be able to measure it
  2. Establish what parameters to modify
  3. Write code to take parameters as inputs and measure the quality of the output
  4. Loop over parameters

Optimizing Values and Fine-tuning Parameters

In the webinar, we focused on optimizing the values given to the rib-smoothing filter. Another exciting application of this technique is fine-tuning meshing parameters, in a mesh-convergence study or similar. The code relevant to this is quite trivial:

# Run on lots of possibilities
morphologicals = [2, 3, 4, 5]
smooths = [2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5]
results = []
for m, s in itertools.product(morphologicals, smooths):
doc.SubmitCommand(ThinObjectSmoother(m, s, m - 1))
volume = get_mask_volume()
results.append([m, s, volume])
doc.Undo()
doc.GenerateFastPreview()
with open(os.path.expanduser(r"~\Desktop\FilterDataSmall.csv"), 'wb') as csv_file:
writer = csv.writer(csv_file)
writer.writerow(["Morphological", "Smooth", "Volume"])
writer.writerows(results)
view raw experiment.py hosted with ❤ by GitHub

In fact, the difficulty we encounter here is measuring the volume; as the quick statistics are considered proxies to the ScanIP statistics, we write a small method to get the volume of an active mask:

def get_mask_volume():
temp_fn = os.path.join(app.GetUserTempDirectory("stats"), "data.csv")
doc.GeneratePreview()
doc.SetModelStatisticsTemplate("JustVolume", True)
doc.UpdateModelStatisticsData()
doc.ExportModelStatisticsData(temp_fn)
with open(temp_fn, 'rb') as csv_file:
reader = csv.reader(csv_file)
next(reader)
next(reader)
volume = float(next(reader)[0])
return volume
view raw maskVolume.py hosted with ❤ by GitHub

Setting up a GUI

The final part of the webinar touched on the creation of a GUI within ScanIP:

The code is relatively simple and available for download. Hopefully it should provide a basic framework for writing GUI-enhanced filters within the ScanIP framework.

If you would like to learn more about scripting, or have any particular questions about your workflows, you can contact us at simpleware-support@synopsys.com.

For a list of all our upcoming and recorded technical webinars, visit our Webinar page.