Difference between revisions of "Introduction into Macro Programming"

(Using variables: Clarify some pitfalls)
(Describe conditionals)
Line 131: Line 131:
  
 
Typical uses for commented-out code are instructions that help with debugging, but are too verbose (or too slow) for regular execution of the macro.
 
Typical uses for commented-out code are instructions that help with debugging, but are too verbose (or too slow) for regular execution of the macro.
 +
 +
= Conditional code blocks =
 +
 +
Sometimes, you need to execute a certain part of the code if and only if a certain condition is met. Example:
 +
 +
// If the image is not binary, abort
 +
if (!is(“binary”)) {
 +
exit(“You need a binary image for this macro!”);
 +
}
 +
 +
There are several parts to a conditional block: the ''if'' keyword, the condition inside the parentheses, and the code block enclosed in curly braces.
 +
 +
In this case, the condition calls the function ''is'' to ask whether the current image is binary, and the exclamation mark negates the result, i.e. ''!is("binary")'' yields true if and only if the current image is <u>not</u> binary (as opposed to ''is("binary")'', which returns true in the opposite case).
 +
 +
If the code block consists of only one statement, the curly braces may be omitted, but it is a good practice to keep them (for example, nested conditional blocks are much easier to understand with curly braces than without).
 +
 +
Likewise, it is a good practice to ''indent'' the code inside the conditional block (i.e. to add white space in front of the lines inside the block). This makes reading the code much easier, too.
 +
 +
== else ==
 +
 +
You can optionally add an ''else'' clause, i.e. a code block that is executed when the condition is <b>not</b> met. Example:
 +
 +
if (is("binary")) {
 +
        write("The current image is binary");
 +
}
 +
else {
 +
        write("The current image is not binary");
 +
}

Revision as of 17:53, 22 April 2010

Why Macros?

Macros can be used to

  • automate repetitive tasks
  • document what you did
  • share common procedures
  • add tools to the toolbar
  • add keyboard shortcuts

Variables

The most important concept when starting to program macros are variables. A variable is a placeholder for a changing entity. It has a name and a value, which can be numeric or text (so-called strings).

Variables are needed whenever you want to execute the same code several times, but for different images, parameters, etc

Variables can also be used to store user input obtained through a dialog.

A variable can be assigned like this:

factor = 1024;

In this example, factor is the name of the variable, 1024 is the value assigned to the variable. The semicolon tells ImageJ that the assignment is done.

Example: assign text to a variable:

message = "Hello, World!";

In this case, the variable is named message, and the text Hello, World! is assigned to it; Text is specified inside double quotes.

Using variables

You can use variables in expressions: you can calculate with numeric variables, and you can concatenate text and text variables. Example:

x = 2;
y = 3;
result = x * x + y + y;

This assigns the variable x the value 2, the variable y the value 3, and then assigns the variable result the square of x plus the square of y.

This example shows how to concatenate a fixed text with the value of a variable:

name = "Bob";
msg = "Those days are over, " + name;

Note: a common pitfall is to include the name of a variable in a string. The following code demonstrates this:

title = "Macro";
write("The name: title"); // this is wrong
write*"The name: " + title); // this is right

In the first write call, a literal title will be printed instead of the value of the variable of that name.

It is especially important to keep this in mind when calling plugins:

r = 2; // the radius
run("Gaussian Blur...", "radius=r"); // this is wrong, r will not be evaluated
run("Gausian Blur...", "radius=" + r); // this is correct

Self-referencing assignments

When a variable is assigned, the right-hand side is evaluated first, and only then the assignment is performed. This allows you to double the value of a variable:

amount = amount * 2;

First, amount * 2 is evaluated. The result is then assigned back to the variable amount, effectively doubling it.

A very important operation is to increment a variable's value by one:

counter = counter + 1;

It is so important that there is a short form for it:

// This statement does the same as counter = counter + 1;
counter++;

Functions

Most of the time, you will call functions which implement the actions you want to execute. Functions have names, like variables, but they also have parameters that you can pass to the functions. ImageJ comes with many predefined functions that you can call to perform specific calculations or other operations.

This example writes Hello, World! to the Log window:

write("Hello, World!");

As before, a semicolon signifies the end of the statement. The name of the function is write, and the parameter list is enclosed in parentheses. In the case of write, there is only one parameter. If there are more parameters to be passed, they have to be separated by commas:

newImage("My pretty new image", "8-bit black", 640, 480, 1);

Like write, newImage is a builtin function of ImageJ. The order of the parameters is relevant, this is the way the function knows what each parameter means.

Defining functions

For recurring tasks, you can define your own functions:

function closeImageByTitle(title) {
        selectWindow(title);
        close();
}

Note that the title is just another variable, which is implicitly assigned when the function is called. In other words, this call will execute the code in above definition, with the variable title set to My pretty new image:

closeImageByTitle("My pretty new image");

Comments

When you read your code again in six months from now, you want to understand what your code does, and why. For this, you can add comments, i.e. text which is ignored by ImageJ when it executes the macro. Example:

// This variable contains the radius of the circle to be drawn
r = 15;

Everything after the two slashes up to the end of the line is a comment.

Multi-line comments

You can also have multi-line comments enclosed in /* ... */ blocks:

/*
 It turned out in practice that 0.5 is a good choice for alpha, because
 it leads to fewer artifacts than anything larger, and it is large enough
 to guarantee a quick convergence.
*/
alpha = 0.5;

Commented-out code

When reading macros written by other people, you will often find the concept of commented-out code. This is code that is pretended to be a comment so that it is not executed. Example:

a = 0.5;
// write("value of a: " + a);
run("Gaussian Blur...", "radius=" + a);

Typical uses for commented-out code are instructions that help with debugging, but are too verbose (or too slow) for regular execution of the macro.

Conditional code blocks

Sometimes, you need to execute a certain part of the code if and only if a certain condition is met. Example:

// If the image is not binary, abort
if (!is(“binary”)) {
	exit(“You need a binary image for this macro!”);
}

There are several parts to a conditional block: the if keyword, the condition inside the parentheses, and the code block enclosed in curly braces.

In this case, the condition calls the function is to ask whether the current image is binary, and the exclamation mark negates the result, i.e. !is("binary") yields true if and only if the current image is not binary (as opposed to is("binary"), which returns true in the opposite case).

If the code block consists of only one statement, the curly braces may be omitted, but it is a good practice to keep them (for example, nested conditional blocks are much easier to understand with curly braces than without).

Likewise, it is a good practice to indent the code inside the conditional block (i.e. to add white space in front of the lines inside the block). This makes reading the code much easier, too.

else

You can optionally add an else clause, i.e. a code block that is executed when the condition is not met. Example:

if (is("binary")) {
        write("The current image is binary");
}
else {
        write("The current image is not binary");
}