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.
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]
Edit the macro you recorded that performs blurring to include a a for loop that blurs using sigmas 1.5, 2, 2.5, 3, … up to 10.
At each iteration, find all peaks and try and store the locations of these peaks.
Use the getFileList function to perform this operation on all of the files in your stacks folder, i.e. do not use the batch user interface!
See the bottom of the Help section for Exercise 2.4 to get started with the for loop…
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.
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!
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));
}
}
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
This is a very straihghtforward assignment - this requires 3 things (in a single line):
~(...)
with Math.log((...))
Math.log
to a float
using
...(float)Math.log...
setf
instead of set
NB: This will produce strange results if used with an 8-bit image
Create a plugin to perform maximum filtering over a 3x3 region; that is replace each pixel with the maximal value in a 3x3 neighborhood.
For each pixel location (x,y) you will need to examine the neighboring pixel values.
Don’t forget to handle boundaries! For non-boundary pixels you will need to check 8 pixels for each location (x,y), whereas for edges (corners), this reduces to 5 (3)!
Lastly you will need the Math.max
function.
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.
** Note: ** This is a difficult problem and requires that you familiarize yourself with the imagej plugin API (Advanced Programming Interface).
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.
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
numpy
modulematplotlib
libraryOpenCV
, Scipy.ndimage
vtk
libraryJust 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
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
This is known as the Difference of Gaussians (DoG) filter, and is regarded as an efficient approximation to the Laplace of Gaussian (LoG) filter