Difference between revisions of "TrakEM2 Scripting"

(Introduction to scripting TrakEM2)
(resetting affine transforms)
Line 149: Line 149:
  
 
If you change the affine transform of a Displayable directly (by calling <i>getAffineTransform()</i> and then manipulating it), keep in mind that you will most likely screw up the internal cached maps for fast location of the Displayable object. To solve that, be sure to all <i>updateBucket()</i> on the affected Displayable object.
 
If you change the affine transform of a Displayable directly (by calling <i>getAffineTransform()</i> and then manipulating it), keep in mind that you will most likely screw up the internal cached maps for fast location of the Displayable object. To solve that, be sure to all <i>updateBucket()</i> on the affected Displayable object.
 +
 +
= Manipulating Displayable objects =
 +
 +
=== Resetting the affine transform of all images in a Layer ===
 +
 +
Suppose you open the project and find that the images of a Layer have non-rigid affine transforms, and you'd like to remove the non-rigid part. A reasonable approach is to reset their affine transforms to identity, and then translate them to approximately where they used to be (based on their bounding box):
 +
 +
<source lang="python">
 +
layer = Display.getFront().getLayer()
 +
 +
# Get all selected images
 +
# patches = Display.getFront().getSelection().getSelected(Patch)
 +
 +
# Get all images in the current layer
 +
patches = layer.getDisplayables(Patch)
 +
 +
for patch in patches:
 +
  bounds = patch.getBoundingBox()
 +
  patch.getAffineTransform().setToIdentity()
 +
  patch.translate(bounds.x, bounds.y, False)
 +
 +
Display.repaint()
 +
</source>
 +
 +
Save the above into a file named "reset_affine_transforms.py" under plugins directory or subdirectory to run it directly from the menus, or copy-paste it into the Jython Interpreter.
 +
 +
See also: the different methods for manipulating the affine transform of a [http://t2.ini.uzh.ch/api/ini/trakem2/display/displayable.html Displayable object] like a Patch.
  
 
= Adding images =
 
= Adding images =

Revision as of 08:40, 18 February 2010

Examples in Jython.

Open the "Plugins - Scripting - Jython Interpreter" (see Scripting Help) and make sure there is a TrakEM2 project open, with a display open. Then type or paste the examples below.

Introduction to scripting TrakEM2

Some basics:

  • The canvas into which images are dragged and visualized is part of a Display object. The latter has methods to access its Selection, as well as the Layer and LayerSet that the Display is viewing.
  • The Layer contains 2D objects like Patch (each Patch wraps an image) and DLabel (floating text).
  • The LayerSet contains 3D objects like AreaList, Pipe, Polyline, Ball, Dissector, Treeline and Stack (the latter wraps an ij.ImagePlus that contains an ij.ImageStack).

Both Layer and LayerSet are in a way containers. The LayerSet contains as well a list of Layer. The Display merely views the data in a LayerSet, one Layer at a time.

See a TrakEM2 class diagram for a complete list.

See also the complete TrakEM2 API documentation

To run a script, follow isntructions as indicated in the Scripting Help.

Get the instance of a selected image

>>> p = Display.getFront().getActive()
>>> print p
090504_0314_ex0768.mrc z=0.0 #67398


Obtain the ImagePlus of a selected image

>>> p = Display.getFront().getActive()
>>> imp = p.getImagePlus()
>>> print imp.width, imp.height
2048 2048


Access the Layer and Selection of a Display

The 'front' is the last activated display window. If there's only one display window, then that is 'front'. To access the front display, we call static function getFront() in namespace Display:

>>> front = Display.getFront()
>>> layer = front.getLayer()
>>> layer_set = front.getLayerSet()
>>> sel = front.getSelection()
>>> print sel.getSelected().size()
10
>>> print sel.isEmpty()
0

In Jython, 1 is True and 0 is False

The most interesting data members of a Display, as seen above, are mainly the Layer and the Selection.

Lock all selected objects

for d in Display.getFront().getSelected():
  d.setLocked(True)

Obtain a collection of selected images

The Selection object of a Display can return a number of collections with any selected objects in it, for example of type Patch (those that wrap an image). All you need to do is to call getSelected with the name of the class to filter for:

for d in Display.getFront().getSelected(Patch):
  print d.title


Setting and getting member objects in jython

In Jython as in Python, member objects have automatically get</> and <i>set functions.

For example, altough a Displayable has a private String title member, this is valid python code for getting and setting the title of a Displayable like a Patch:

>>> p = Display.getFront().getActive()
>>> print p.title
090504_0314_ex0768.mrc

Above, a Patch takes as title the name of the file containing the ImagePlus, by default. Let's change the title to something else:

>>> p = Display.getFront().getActive()
>>> p.title = "A new name for this Patch"
>>> print p.title
A new name for this Patch


The properties of a Displayable: title, color, visibility, locked, alpha, affine transform, dimensions and bounds

Let's set a few values:

>>> p = Display.getFront().getActive()
>>> p.title = "Test image"
>>> p.alpha = 0.4
>>> p.visible = True
>>> p.locked = False
>>> from java.awt import Color
>>> p.color = Color.blue

Tell all displays to update the canvas, so we see the changes:

>>> Display.repaint()


Let's read a few values:

>>> p.getAffineTransform()
AffineTransform[[1.0, 0.0, 474.0], [0.0, 1.0, 567.0]]
>>> print p.getBoundingBox()
java.awt.Rectangle[x=474,y=567,width=2048,height=2048]

The affine transform cannot be set, because it's a final member. But itself the value may be edited via setAffineTransform:

>>> from java.awt.geom import AffineTransform
>>> aff = AffineTransform()
>>> aff.scale(2.0, 2.0)
>>> p.setAffineTransform(aff)

Be careful: java's AffineTranform does concatenations and not pre-concatenations (order matters in matrix multiplication).

In most occasions, what you want can be accomplished with preTransform, such as translating an image:

>>> from java.awt.geom import AffineTransform
>>> aff = AffineTransform()
>>> aff.translate(300, -400)
>>> p.preTransform(aff, True)

More convenient are the methods scale, translate, rotate and particularly preTransform, for the manipulation of a Displayable's affine transform (see AffineTransform) and that of its linked Displayables (any transform propagates to the linked ones).

If you change the affine transform of a Displayable directly (by calling getAffineTransform() and then manipulating it), keep in mind that you will most likely screw up the internal cached maps for fast location of the Displayable object. To solve that, be sure to all updateBucket() on the affected Displayable object.

Manipulating Displayable objects

Resetting the affine transform of all images in a Layer

Suppose you open the project and find that the images of a Layer have non-rigid affine transforms, and you'd like to remove the non-rigid part. A reasonable approach is to reset their affine transforms to identity, and then translate them to approximately where they used to be (based on their bounding box):

layer = Display.getFront().getLayer()

# Get all selected images
# patches = Display.getFront().getSelection().getSelected(Patch)

# Get all images in the current layer
patches = layer.getDisplayables(Patch)

for patch in patches:
  bounds = patch.getBoundingBox()
  patch.getAffineTransform().setToIdentity()
  patch.translate(bounds.x, bounds.y, False)

Display.repaint()

Save the above into a file named "reset_affine_transforms.py" under plugins directory or subdirectory to run it directly from the menus, or copy-paste it into the Jython Interpreter.

See also: the different methods for manipulating the affine transform of a Displayable object like a Patch.

Adding images

Adding a single image to a layer shown in an open display

# Obtain a pointer to the frontmost open display:
front = Display.getFront()
# Open an image
filepath = "/path/to/image.tif"
imp = IJ.openImage(filepath)
# Create a new Patch, which wraps an image
patch = Patch(front.project, imp.title, 0, 0, imp)
patch.project.loader.addedPatchFrom(filepath, patch)
# Add it to a layer
front.layer.add(patch)


Copying images between two open projects

The script checks that at least two displays are open, and that they belong to two different projects. Then offers a dialog to choose the direction of copying, and finally copies all images, or all visible or selected images, from one project to the other:

# Albert Cardona 20100201
# Script to copy all images, all visible images, or all selected images
# from a source layer to a target layer.
# To run the script, put it under Fiji plugins folder or subfolder and call "Plugins - Scripting - Update Fiji"
# and make sure you have at least two projects open, each with at least one display open.
# 
# Written for Natalya at Graham Knott's group, EPFL

from ini.trakem2.display import *
from ij.gui import GenericDialog
from ij import IJ
from array import array


def run():
	# Check precondition: at least some displays open
	displays = Display.getDisplays()
	if displays.isEmpty():
		IJ.showMessage("Could not find any TrakEM2 displays open!")
		return
	# Check precondition: at least two displays from two different projects 
	projects = {}
	for display in displays:
		projects[display.project] = display
	if len(projects) < 2:
		IJ.showMessage("You need at least two projects with at least one display open for each!")
		return
	# Show choices
	gd = GenericDialog("Copying images between projects")
	titles = array(String, [display.getFrame().getTitle() for display in displays])
	gd.addChoice("Source layer:", titles, titles[0])
	gd.addChoice("Target layer:", titles, titles[1])
	choices = ["All images", "All visible images", "All selected images"]
	gd.addChoice("Copy:", choices, choices[0])
	gd.showDialog()
	if gd.wasCanceled():
		return
	source = displays[gd.getNextChoiceIndex()]
	target = displays[gd.getNextChoiceIndex()]
	if source == target:
		IJ.showMessage("You must choose different source and target layers!")
		return
	copy_mode = gd.getNextChoiceIndex()
	patches = None
	if 0 == copy_mode:
		patches = source.getLayer().getDisplayables(Patch)
	elif 1 == copy_mode:
		patches = source.getLayer().getDisplayables(Patch, True)
	else:
		patches = source.getSelection().getSelected(Patch)
	if 0 == len(patches):
		IJ.showMessage("No images to copy with option: " + choices[copy_mode])
		return
	# Copy images
	for patch in patches:
		p = patch.clone(target.project, False)
		target.getLayer().add(p)
	target.getLayerSet().enlargeToFit(patches)
	IJ.showStatus("Done copying images between layers.")

run()

To create a script with the above code, copy paste it into a file with an underscore in its name and extension ".py". Then place it in Fiji's plugins folder or subfolder thereof. Finally, restart Fiji or just call "Plugins - Scripting - Refresh Jython Scripts".

Measure

Measure the minimal distance from each ball to a surface defined by a profile list

Suppose for example that, using a Ball object, you have clicked on each vesicle of a synaptic terminal. And that, using a profile list, you have traced the surface area of a synapse.

3D view of a synaptic surface and its vesicles
Synaptic vesicle measurements of the minimal distance from each vesicle to the synaptic surface

Using the following script, we generate a surface from the profile list, and then measure, for each synaptic vesicle, its minimal distance to the synaptic surface.

The results are finally listed in a results table, from which column-ordered data may be exported for further processing in a spreadsheet.

# Albert Cardona 20100201
# Select a Ball and a Profile, and list the minimal distances of each ball
# to the nearest vertex of the mesh created by the profile list to which
# the profile belongs.
# 
# As asked by Graham Knott and Natalya, from EPFL


from ini.trakem2.display import Display, Ball, Profile
from ini.trakem2.utils import M, Utils
from ij import IJ
from ij.measure import ResultsTable
from ij.gui import GenericDialog
from java.util import HashSet


def run():
	sel = Display.getSelected()
	# Check conditions: one Ball and one Profile only must be selected
	if sel is None or sel.isEmpty():
		IJ.log("Please select a Ball and a Profile!")
		return
	c = [ob.getClass() for ob in sel]
	if Ball in c and Profile in c and 2 == sel.size():
		pass
	else:
		IJ.log("Please select just one Ball and one Profile")
		return
	obs = {}
	for ob in sel:
		obs[ob.getClass()] = ob
	balls = obs[Ball].getWorldBalls()
	profile = obs[Profile]
	profiles = []
	# Gather triangles from profile mesh (into a HashSet to remove the many duplicate vertices)
	verts = HashSet(Profile.generateTriangles(profile.project.findProjectThing(profile).getParent(), 1))
	# Prepare a results table
	rt = ResultsTable()
	rt.setPrecision(2)
	rt.setHeading(0, "Index")
	rt.setHeading(1, "Min distance to surface")
	# Fill data rows
	unit = profile.layer.parent.calibration.unit
	i = 0
	count = len(balls)
	for i in range(count):
		rt.incrementCounter();
		rt.addLabel("units", unit)
		rt.addValue(0, i)
		b = balls[i]
		# For each ball, measure the minimal distance to any of the triangle vertices.
		rt.addValue(1, Math.sqrt(reduce(Math.min, [M.distanceSq(b[0], b[1], b[2], vert.x, vert.y, vert.z) for vert in verts])))
		Utils.showProgress(float(i)/count)
	rt.show("Distances from ball to profile list surface")
	# Reset progress bar
	Utils.showProgress(1)

run()

See also

TrakEM2 tutorials

Jython scripting

Jython scripts for TrakEM2

All the following are included in Fiji's plugins/Examples/TrakEM2_Example_Scripts/ folder: