Difference between revisions of "Jython Scripting"

(Running a watershed plugin on an image)
(Reading lines from text file)
Line 210: Line 210:
 
imp.updateAndDraw()
 
imp.updateAndDraw()
 
</source>
 
</source>
 +
 +
 +
== Reading data from a text file ==
 +
 +
A data file containing rows with 4 columns:
 +
 +
...
 +
399 23 30 10.12
 +
400 23 30 12.34
 +
...
 +
 +
... where the columns are X, Y, Z and value, for every pixel in the image.
 +
We assume we know the width and height of the image.
 +
From this sort of data, we create an image, read out all lines and parse the numbers, a
 +
 +
<source lang="python">
 +
width = 512
 +
height = 512
 +
stack = ImageStack(width, height)
 +
 +
file = open("/home/albert/Desktop/data.txt", "r")
 +
 +
try:
 +
  fp = FloatProcessor(width, height)
 +
  pix = fp.getPixels()
 +
  cz = 0
 +
  # Add as the first slice:
 +
  stack.addSlice(str(cz), fp)
 +
  # Iterate over all lines in the text file:
 +
  for line in file.readlines():
 +
    x, y, z, value = line.split(" ")
 +
    x = int(x)
 +
    y = int(y)
 +
    z = int(z)
 +
    value = float(value)
 +
    # Advance one slice if the Z changed:
 +
    if z != cz:
 +
      # Next slice
 +
      fp = FloatProcessor(width, height)
 +
      pix = fp.getPixels()
 +
      stack.addSlice(str(cz), fp)
 +
      cz += 1
 +
    # Assign the value:
 +
    pix[y * width + x] = value
 +
  # Prepare and show a new image: 
 +
  imp = ImagePlus("parsed", stack)
 +
  imp.show()
 +
# Ensure closing the file handle even if an error is thrown:
 +
finally:
 +
  file.close()
 +
</source>
 +
  
 
[[Category:Scripting]]
 
[[Category:Scripting]]

Revision as of 15:44, 9 September 2009

The Jython interpreter plugin

The interpreter provides a screen and a prompt. Type any jython code on the prompt to interact with ImageJ.

Launch it from plugins - Scripting - Jython Interpreter. See Scripting Help for all keybindings, and also Scripting comparisons.

Within the interpreter, all ImageJ, java.lang.* and TrakEM2 classes are automatically imported. So creating new images and manipulating them is very straighforward.


Language basics

  • Any text after a # is commented out.
  • There are no line terminators (such as ';' in other languages), neither curly braces to define code blocks.
  • Indentation defines code blocks.
  • Functions are defined with def, and classes with class.
  • Functions are objects, and thus storable in variables.
  • Jython (and python in general) accepts a mixture of procedural and object-oriented code.
  • Jython currently implements the Python language at its 2.5 version. All documentation for python 2.5 applies to Jython bundled with Fiji (with the remarks listed later).


Workflow for creating Jython scripts

The recommended setup is the following:

  • Edit a file in your favorite text editor, and save it with a an underscore in the name and a .py extension anywhere under ImageJ plugins folder.
  • Run Plugins - Scripting - Refresh Jython scripts only the very first time after newly creating the file under any folder or subfolder of ImageJ's plugins folder. A menu item will appear with its name, from which it can be run.
  • Keep editing (and saving) the file from your editor. Just select the menu item to execute it over and over. Or use the "Find..." command window to launch it easily (keybinding 'l').

The next time Fiji is run, automatic commands in macros/StartupMacros.txt will setup all your scripts in the Plugins menu.


Some limitations of jython

Though jython tries to be as close as possible as python, there are some differences you may experience during scripting.

  • Float "special numbers" such as NaN and Inf are not handled.

For instance,

 a = float('nan') 

will create the correct float number in python, but will throw an exception in jython.

Instead, to create a NaN in jython, use:

>>> a = Double.NaN
>>> print a
NaN 

To test if a number is NaN:

>>> if Double.isNaN(a):
        print "a is NaN!"
a is NaN! 
  • Some existing python modules can't be imported in jython.

This is for instance the case of the module numpy, which would have been really convenient for analysing data and results.

But see these java numerical libraries: http://math.nist.gov/javanumerics/#libraries , of which:

  • JaMa (Java Matrix Package)
  • Java3D (particularly its vecmath package provides general matrix and vector classes (GMatrix, GVector).

... are already included in Fiji.

Jython tutorials for ImageJ

Defining variables: obtaining the current image

imp = IJ.getImage()

Which is the same as:

imp = WindowManager.getCurrentImage()

Since calling the above is long and tedious, one can declare a variable that points to the above static methods:

c = WindowManager.getCurrentImage

Above note the lack of parentheses.

To execute the function, just use parentheses on it:

 imp = c()

The above gets the value of c, which is the method named getCurrentImage in class WindowManager, and executes it, storing its returned object in imp.


Manipulating pixels

Creating a grayscale ramp image

First create an image and obtain its pixels:

imp = ImagePlus("my new image", FloatProcessor(512, 512))
pix = imp.getProcessor().getPixels()

The length of an array:

n_pixels = len(pix)

Then loop to modify them:

# catch width
w = imp.getWidth()
 
# create a ramp gradient from left to right
for i in range(len(pix)):
   pix[i] = i % w
 
# adjust min and max, since we know them
imp.getProcessor().setMinAndMax(0, w-1)

... and show the new image:

imp.show()


Creating a random 8-bit image

First import necessary packages: Random, from standard java util library, and jarray, the Jython module for native java arrays:

from java.awt import Random
from jarray import zeros

Then create the array and fill it with random bytes:

width = 512
height = 512
 
pix = zeros(width * height, 'b')
Random().nextBytes(pix)

(See the jarray documentation where the 'b'-byte, 'd'-double, etc. are explained.)

Now make a new IndexColorModel (that's what ImageJ's ij.process.LUT class is) for 8-bit images:

channel = zeros(256, 'b')
for i in range(256):
    channel[i] = (i -128) 
cm = LUT(channel, channel, channel)

... and compose a ByteProcessor from the pixels, and assign it to an ImagePlus:

imp = ImagePlus("Random", ByteProcessor(width, height, pix, cm)
imp.show()

Creating a random image, the easy way

All the above can be summarized like the following:

from java.util import Random
imp = IJ.createImage("A Random Image", "8-bit", 512, 512, 1)
Random().nextBytes(imp.getProcessor().getPixels())
imp.show()

Running a watershed plugin on an image

# 1 - Obtain an image
imp = IJ.openImage("http://rsb.info.nih.gov/ij/images/blobs.gif")
ip = imp.getProcessor()

# 2 - Apply a threshold: only zeros and ones
# Set the desired threshold range: keep from 0 to 74
ip.setThreshold(147, 147, ImageProcessor.NO_LUT_UPDATE)
# Call the Thresholder to convert the image to a mask
IJ.run(imp, "Convert to Mask", "")

# 3 - Apply watershed
# Create and run new EDM object, which is an Euclidean Distance Map (EDM)
# and run the watershed on the ImageProcessor:
EDM().toWatershed(ip)

# 4 - Show the watersheded image:
imp.show()

The EDM plugin that contains the watershed could have been indirectly applied to the currently active image, which is not recommended:

IJ.run("Watershed")

If you had called show() on the image at any early stage, just update the screen with:

imp.updateAndDraw()


Reading data from a text file

A data file containing rows with 4 columns:

...
399 23 30 10.12
400 23 30 12.34
...

... where the columns are X, Y, Z and value, for every pixel in the image. We assume we know the width and height of the image. From this sort of data, we create an image, read out all lines and parse the numbers, a

width = 512
height = 512
stack = ImageStack(width, height)

file = open("/home/albert/Desktop/data.txt", "r")

try:
  fp = FloatProcessor(width, height)
  pix = fp.getPixels()
  cz = 0
  # Add as the first slice:
  stack.addSlice(str(cz), fp)
  # Iterate over all lines in the text file:
  for line in file.readlines():
    x, y, z, value = line.split(" ")
    x = int(x)
    y = int(y)
    z = int(z)
    value = float(value)
    # Advance one slice if the Z changed:
    if z != cz:
      # Next slice
      fp = FloatProcessor(width, height)
      pix = fp.getPixels()
      stack.addSlice(str(cz), fp)
      cz += 1
    # Assign the value:
    pix[y * width + x] = value
  # Prepare and show a new image:  
  imp = ImagePlus("parsed", stack)
  imp.show()
# Ensure closing the file handle even if an error is thrown:
finally:
  file.close()