Editing Macros

Writing macros

Recording macros is useful for starting to understand the imagej macro system, and for very simple tasks.

Much more flexibility and control is afforded by learning to edit and write macros. As an example, load the Macro we created in the last subsection,

Plugins > Macros... > Edit...

and select the file that was saved.

This opens an imagej editor window (the title will be the macro file name, usual extension, .ijm).

You should see the commands run("Duplicate...", "title=blurred"); etc that you recorded in the previous section.

Now lets edit the file, making a simple substitution. Instead of calling the Gaussian Blur with a hardcoded number for sigma, lets use a variable.

Replace

"sigma=5"

with

"sigma="+value

First of all, let’s examine what the original line did. Without digressing too much, the ImageJ Macro function run takes a variable number of arguments, separated by commas ( , ). The second line passed two string (anything between two double quotes ( “ )) arguments to run. The first argument was the name of the function, “Gaussian Blur…”, and the second the parameters to that function in string form, “sigma=5”.

Our replacement line means the following; instead of using the hardcoded string “sigma=5”, create a string by appending the variable value to the string “sigma=”

This means we are free to specify the number assigned to value elsewhere.

Run this macro on an open image - save the file (File > Save) and run the macro (Macros > Run).

You should end up with an error dialog entitled “Macro Error”.

Read through the contents of the window. The error message is letting you know that while trying to run the macro, the parser came across a variable, called value, which had not been defined.

Congratulations!

You have completed your first ImageJ debugging operation -proficiency at debugging skills is at the heart of good scripting/programming

Go ahead and define value. This can occur anywhere above where it is first used. In our case that means inserting a new first line.

tip

Good coding

It is useful to define “constant” variables at the start of your code so that you can quickly locate them if you need to check or change them

To define value in the imagej macro language we can simply write, for example,

value=5;

in a new first line.

If you run the macro now, you should find it performs exactly the same task as before.

note

ImageJ Macro scripting uses dynamic typing. This means that the type of a variable, meaning whether it’s an integer number, a character, etc, is inferred from the assignment statement. While allowing less coding (you don’t have to declare variable types), there is increased risk of adding bugs which may be difficult to track down, so do pay extra attention!

We haven’t gained anything yet, as we’re back where we started after recording the macro.

So let’s make use of a simple control flow construct, the for loop.

The for loop is a way of iterating over values and performing operations using those values.

The syntax is

for ( <initial statement > ; <condition to test before each iteration>
; <statement to perform after each iteration>){

    <code to perform each iteration>

}

NB: Everything in brackets ( < > ) should be replaced.

For example if we modify our original code to the following:

for (value=2; value<=10; value+=2){

    run("Duplicate...", "title=blurred");

    run("Gaussian Blur...", "sigma="+value);

}

we will have created a for loop that initializes value to 2, checks at each iteration that value is less than or equal to 10, and increments value by 2 at the end of each iteration.

Then for each iteration the statements enclosed within the braces are performed.

If you run this you will end up with multiple windows, with increasing amounts of blurring.

There many more programming constructs that can be used, such as while loops, do while loops, if/else conditions, and many more. For more information see http://rsbweb.nih.gov/ij/developer/macro/macros.html.

Python version

Python version of blurring in a loop

Notebook for download

Batch processing 2

The batch processing we learned how to access via the user interface can also be achieved by delving into the macro code.

Again, more control is available to those willing to code this functionality in the form of a loop.

For example, if we have a folder whose path is stored in the DIRECTORY variable, with images titled image001.tif, image002.tif, … etc, then the following would load each in series.

DIRECTORY = "C:\Some\Path\That\Has\Loads\Of\Data\";
allfiles = getFileList(DIRECTORY);
NUMFILES = allfiles.length;
value = 2;

for (filenumber=0; filenumber<NUMFILES; filenumber++){

open(allfiles[filenumber]);

run("Duplicate...", "title=blurred");

run("Gaussian Blur...", "sigma="+value);

}

Here we used a built-in imagej function, getFileList. As the name suggests, it scans a directory and returns a list (technically, an Array) of file names. We also made use of the fact that Java arrays have an attribute, length, which tells us how many elements the array contains.

Now we could also add more functionality that saves output from the processing, and uses the input filename in the output filename.

note

Unlike common everyday enumeration, array elements are accessed using zero-indexing in Java (and the ImageJ macro language) in the same way that we start at ground 0 when counting floors of a building! This means that to access the first element of an array called “A” we would use

A[0]

Exercise 2.6

Note The following section requires a fair amount of knowledge programming knowledge (either in Java or similar). If you have reached this section, and do not wish or are unable to proceed with plugin writting in Java, please see a demonstrator about working on an advanced macro project.

Extra: Advanced Macro Projects

If you have reached this far, you should be able to write a macro from scratch.

Please see a demonstrator about coming up with a macro that does something that’s useful to you!

Extra: Introduction to Plugin writing with Java

ImageJ is written in Java, which is a general purpose programming language designed to allow developers to write code once and run on any machine, as java programs are executed via the Java virtual machine.

ImageJ offers the ability go beyond the limitations of the macro language via writing your own plugins in Java. We will cover the basics of writing a plugin by giving the template of a simple plugin.

tip

As with Macro writing, an easy way to learn is by first modifying an existing plugin that has functionality that overlaps with what you want to do!

Lets start with a simple plugin to invert an image (from [http:g/rsbweb.nih.gov/ij/plugins/download/Image\_Inverter.java](http:g/rsbweb.nih.gov/ij/plugins/download/Image_Inverter.java) )

import ij.*;
import ij.plugin.filter.PlugInFilter;
import ij.process.*;
import java.awt.*;

/* This sample ImageJ plugin filter inverts images.

A few things to note:
 1) Filter plugins must implement the PlugInFilter interface.
 2) User plugins do not use the package statement;
 3) Plugins residing in the "plugins" folder, and with at
 least one underscore in their name, are automatically
 installed in the PlugIns menu.
 4) Plugins can be installed in other menus by 
 packaging them as JAR files.
 5) The class name ("Image_Inverter") and file name 
 ("Image_Inverter.java") must be the same.
 6) This filter works with selections, including non-rectangular
selections.
 7) It will be called repeatedly to process all the slices in a stack.
 8) It supports Undo for single images.
 9) "~" is the bitwise complement operator.
*/

public class Image_Inverter implements PlugInFilter {

 public int setup(String arg, ImagePlus imp) {
 if (IJ.versionLessThan("1.37j"))
 return DONE;
 else 
 return DOES_ALL+DOES_STACKS+SUPPORTS_MASKING;
 }

 public void run(ImageProcessor ip) {
 Rectangle r = ip.getRoi();
 for (int y=r.y; y<(r.y+r.height); y++)
 for (int x=r.x; x<(r.x+r.width); x++)
 ip.set(x, y, ~ip.get(x,y));
 }

}

Exercise 3.1

Modify the plugin above to return a log-scaled version of an image.

tip

The following page may help:

http://docs.oracle.com/javase/6/docs/api/java/lang/Math.html

Exercise 3.2

Create a plugin to perform maximum filtering over a 3x3 region; that is replace each pixel with the maximal value in a 3x3 neighborhood.

Exercise 3.3 - Advanced

Modify the macro from exercise 2.2 with a plugin that displays a dialog box to allow users to enter values for minimum and maximum scales, and show a live preview of peaks found.

Shameless plug: Using Python with numpy and scipy instead of ImageJ

Java is a relatively low-level language, meaning that more code is required to achieve any given task.

The following table summarizes this in terms of Statements and Lines ratios, which shows the the number of statements/lines of c that each statement/line of the compared language is equivalent to.

Language Statements ratio Lines ratio
C 1 1
C++ 2.5 1
Fortran 2 0.8
Java 2.5 1.5
Perl 6 6
Smalltalk 6 6.25
Python 6 6.5

More expressive languages exist, but are relatively new and less mature than those listed here.

As a very simple example, the java code to iterate over elements of a 2D array A and write the value to the console is

for (int i=0; i<size0; i++){
    for (int j=0; j<size1; j++){
        System.out.println(A[i][j]); 
    }
}

while the equivalent Python syntax is

for a in A.ravel():
    print a

The difference doesn’t seem a lot, but as a program grows, a factor of 2 or 3 in the amount of code writen starts to become important.

In addition, development time is generally much longer than the time taken to use the produced algorithm/software, thus making easier development more important than speed of final program.

Because of this I encourage those of you interested in advanced image processing and related tasks to also consider using Python (or a similarly powerful and expressive scripting language, e.g. Matlab) as your image processing platform.

While ImageJ is a great platform for simpler to intermediate image processing tasks, problems can arise due to the overhead of programming in Java when wanting to extend beyond the macros and existing plugins as mentioned in the previous section.

Equivalent to the Inverter plugin

import numpy
import scipy.misc

data = scipy.misc.imread(filename)
# NB: the data can be 3d etc.
data = ~data

note

The simple example above neglects ROI handling. This can be achieved in numpy using masked arrays. E.g.

data = numpy.ma.masked_where(data < 10, data)

Subsequent operations are performed on the elements of the array where the condition is met.

Python offers an alternative scripting environment with a high-level access to a larger range of functionality including

  • Better numerical and matrix operations via the numpy module
  • More sophisticated and publication quality graphs and figures via e.g. the matplotlib library
  • Interface to native (i.e. fast!) computer vision libraries such as OpenCV, Scipy.ndimage
  • Fast OpenGL based visualization using a high level interface via bindings to the vtk library

Just as you have been given an introduction to Macro writing in this course, the first step is to learn the basics of the Python programming language.

Check the Bioinformatics Hub events page for details of upcoming couses

The Software Carpentry website also contains good tutorials and other material for learning more about Python and good programming practices http://software-carpentry.org/4_0/python/


Footnotes

1

Scale-space analysis has rigorous mathematical foundations, see the numerous works by Tony Lindeberg, for example http://www.csc.kth.se/~tony/abstracts/Lin94-SI-abstract.html

2

This is known as the Difference of Gaussians (DoG) filter, and is regarded as an efficient approximation to the Laplace of Gaussian (LoG) filter