NIH Image Macros - An Introduction
v. 1.0a

Michael J. Green

Department of Geological Sciences
University College London

michael.green@ucl.ac.uk




Contents

1. About this manual
2. Introduction to macros
3. Variables
4. Decimals, powers, and text output
5. Functions and procedures
6. For loops
7. If statements
8. Repeat and while loops
9. Arrays
10. Introduction to image measurement
11. Introduction to image processing
12. Where next?

The information in this manual is free. It may be copied, distributed and/or modified under the conditions set down in the Design Science License published by Michael Stutz at
http://dsl.org/copyleft/dsl.txt.




1. About this manual

1.1 Prerequisites

The reader is assumed to be familiar with the basic operations of NIH Image (loading, saving, and processing of images, etc). but not with the use of macros. You'll need a recent version of NIH Image (e.g. version 1.62, 1999), and the example macros that come with it.


1.2 Target audience

Anyone who wants to write macros for NIH Image but has no computer programming experience, or just anyone who uses NIH Image a lot and is looking for ways to use it more efficiently.


1.3 Aims

The aim of this manual is not just to teach the "basics" of macro writing, but also to give the reader confidence to go to higher-level manuals and learn more complex things. For someone with no prior experience of programming, working through this manual should take about two weeks. Since the NIH Image macro language is based on the language Pascal, then anyone with some experience of Pascal will get up to speed much faster. But bear in mind that there are some major differences between "proper" Pascal and the NIH Image macro language, and to write NIH Image macros it is useful to have a specific manual.

This manual gives a thorough coverage of the basics of macro writing and is therefore very limited in scope. But it is this "nuts and bolts" knowledge which makes writing more complex macros in the future much easier. No prior knowledge of computer programming is assumed, and it is written at a level which is deliberately "accessible to all" even at the price of appearing too simple for some. In this respect it is different from the other manuals available which cover NIH Image macros, all of which assume some knowledge of Pascal and are generally pitched at a level that is too hard for the beginner. Its basis is a large number of simple macros, all of which are fully tested.

This manual will not make you an expert in NIH Image macro programming, though it is a useful first step. It also won't teach you very much about image analysis.


1.4 Other useful documents

Two essential documents that you need can be downloaded from the NIH Image home page at http://rsb.info.nih.gov/nih-image/. These are the Online Manual, which gives a good general overview of everything NIH Image does (though like I say, the macros section - Appendix A - is pretty hard going for non-programmers) and the list of Macro Commands, which is available under "More Documentation". By all means download Mark Vivino's "Inside NIH Image" from there too, but if you can write macros with it as your only guide, then you don't need to be reading this.


1.5 What macros do

Macros let you automate NIH Image, so that you can repeat the same list of operations many times. This can be a huge time saver when you have a long list of images to process, and easily repays the time spent learning to write macros. Macros also give you virtually unlimited scope for extending what you can do with NIH Image, letting you develop your own tools for image measurement and image processing. In this sense, macros basically let you do lots of things with NIH Image that you didn't think it could do. The only limit is your ingenuity.


1.6 Manual structure in brief

After the introduction, we spend two chapters looking at variables, one looking at structured programming, and then another three working through the basic commands of the macro language (which are taken from Pascal). At this point, the reader should be able to write basic Pascal programs as well as basic macros. We then spend three chapters concentrating more on the specifics of image analysis in NIH Image. The final chapter covers where to go next, for more information.


1.7 Two important questions

These are so important that I'll put them in now. Say you are considering writing a macro. First: is it really needed? As in, is there already a command in NIH Image that does the same thing, that you've overlooked? For example, there's no point writing a macro to threshold an image into black and white - NIH Image can already do this. Second: has someone else already written this macro? Most people use NIH Image for the same kind of things, run into the same kind of problems, and write the same kind of macros to fix them. Check the macros you already have, and check other people you know who use NIH Image, and check the NIH Image mailing list (available at
http://list.nih.gov/archives/nih-image.html). Maybe someone else already solved your problem. (On the other hand, if it's a simple one, why not write the macro anyway as an exercise - keep your brain in gear?)



2. Introduction to macros

2.1 Creating a new macro

Load up NIH Image, then click "File", then "New", and you get a dialogue box; click the "Text Window" radio button in the top right hand corner (don't worry about the size or the title for now). This creates a new text window where you can enter your macro(s). Actually, you can put any text you like into an NIH Image text window - it's just like a simple word processor. Text files can be saved and loaded using the "File" menu. They appear on the desktop with an NIH-Image-specific "TEXT" icon.


2.2 Basic macro structure

All macros look like this:

macro 'Do Nothing';
begin
end;

Here, "Do Nothing" is the title of the macro, and the "begin ... end;" bit marks where the macro commands actually go. Note the semicolons - these are "statement terminators". We'll look at these in more detail later - for now, be aware that if they are missing, the macro will crash.


2.3 Comments

Comments - which are ignored by the computer - can be inserted into macros between curly brackets {}. These are essential in more complex macro programming, to help the programmer keep track of where he is at. They can be spread over more than one line, as long as there is a closing bracket there somewhere.

macro 'Just Comments';
begin
      {This is a comment}
end;

macro 'Just Comments 2';
begin
      {This is a comment
      it continues here...
      and here.}
end;


2.4 A simple macro

Now for a macro which actually does something. When it is run, it waits until the user presses the mouse button, then beeps. (Don't worry about the details of these commands for now).

macro 'Beep After Click';
begin
      repeat until button;
      Beep;
end;


2.5 Loading and running macros

Typing in any of the above macros and saving them results in a "TEXT" file, which can be double-clicked (or loaded using the "File" menu) to bring it back into NIH Image for more editing. But to actually run any macro, we have to "load" it in a different way. To edit a macro, you load it as above, as a text file. But to run a macro, you have to load it into the "Macro Runner", if you like - a different part of NIH Image. This is done using the "Special" menu.

A macro which is present in a text window can be loaded into the "Macro Runner" using "Special" then "Load Macros from Window". Alternatively, you can load macros straight from disk by closing any text windows then clicking "Special"; this time there will be an option "Load Macros..." which lets you load them into the "Macro Runner" from disk.

Macros which have been loaded into the "Macro Runner" appear at the bottom of the "Special" menu as a list of titles. Clicking on the title runs the macro. Remember, the title of a macro goes in single speech marks '...' after the "macro" command. There don't seem to be any restrictions on what you can call a macro, though it's best not to have titles that are too unwieldy.


2.6 Macro programs and macro files

We need to make this distinction clear. A single macro is a "macro program". More than one of these can be entered into a single text window, then the whole lot saved as a single "macro file". The "Macro Runner" uses macro files, not individual programs - so you can easily load up more than one program, but if you do want to have a group of macro programs all accessible together, they must be saved in the same macro file. The best way to understand this is to play with the different macro files supplied with NIH Image, in the "Macros" directory - open the files as text windows, and check out the various macro titles, then try loading them from the window into the "Macro Runner", and check out how the "Special" menu appears.


2.7 The 32k limit

No macro file can be larger than 32k. If it is, NIH Image will only let you load in the first 32k of it. This may sound like a problem, but you can actually fit an awful lot of program into 32k. It's unlikely that you'll ever produce a macro file which is larger than 32k, even with extensive notes and comments.


2.8 Hotkeys

Instead of having to click on the "Special" menu, then selecting the macro you want, macros can also be programmed to execute using a defined "Hotkey". This goes after the title, in square brackets []. For example:

macro 'Just Comments [J]';
begin
      {This is a comment}
end;

macro 'Beep After Click [B]';
begin
      repeat until button;
      Beep;
end;

Load in these two macros from a single text window, then check the "Special" menu - you'll see the hotkeys after the titles. Just pressing J or B will run them (not that J actually does anything, mind!).


2.9 Organising the "Special" menu

Macros appear on the "Special" menu list in the order they appear in the macro file. If you have a lot of macros in the same file, it can be useful to divide up the long list of titles with dividing lines. This is done using this one-line statement:

macro '(-';

This isn't really a macro at all: all it does is insert a line in the "Special" menu list.


2.10 Autoloading a macro file

You can nominate one macro file to be loaded automatically when NIH Image is loaded, such that the macros it contains are immediately available to be run. The file must be called "Image Macros" and must be in the same directory as NIH Image (i.e. the application with the microscope icon). This can be useful if you are using the same set of macros over and over again.


2.11 Figuring all this out

The best way to practice "macro handling" is to play with the macros that come supplied with NIH Image, in the "Macros" directory. Practice loading them into the text editor, and into the "Macro Runner", and practice running them using hotkeys. (Note that some require an image file also to be loaded, which they can operate on). This will also give you a good idea of (1) the different things macros can do, and (2) what the macro language looks like. "Fun and Games" is a good one to begin with.



3. Variables

3.1 Introduction to variables

Variables allow you to store and manipulate numbers and text strings. You have to understand how to handle variables before you can do anything else, really. Variables are assigned values using the "assignment operator", which is a colon followed by an equals sign (:=). Note that this is different from many other programming languages, where an "=" on its own does the job. This is not the case in Pascal, where the lone "=" has a specific meaning (see later).

So, we assign values to variables like this:

a:=5;

b:=10;

c:=250;

counter:=1000;

There doesn't seem to be any limit on the length of a variable name, though it's best not to let them get too long. Short and informative is the way to go.


3.2 Types of variables

The NIH Image macro language has four types of variables: integers, real numbers, booleans, and strings. Integers are whole numbers (no decimal places). Real numbers can have decimal places. Boolean variables are for use in boolean logic, and have only two values, true or false. Strings contain text characters.


3.3 Specifying the type of each variable

Now, it is a peculiarity of Pascal that it is a "strongly-typed" language, which means that before any macro starts doing anything, we need to specify for each variable, which type it is. This is done in a section of the macro after the "var" statement. So the basic structure of a macro is now:

macro 'Title [T]';
var
      {variable types specified here}
begin
      {actual macro commands}
end;

So, to take the four example variables above: they are all integers, so we would specify them as follows:

var
      a: integer;
      b: integer;
      c: integer;
      counter: integer;

Let's say we also want to use "height" and "width", which are real numbers, "testflag", which is a boolean variable, and "username", which is a text string. We would specify these as follows:

      height: real;
      width: real;
      testflag: boolean;
      username: string;


3.4 Shorthand for specifying variable types

Instead of typing

var
      a: integer;
      b: integer;
      c: integer;

we can get away with

var
      a,b,c: integer;

to save space. I generally prefer the longhand version, since it is clearer, unless space is getting very short.


3.5 The PutMessage command

Before we can do anything with variables, we need some means of checking what is going on, and the effects of our manipulations. The simplest way to get output to the screen in NIH Image is to use the PutMessage command, which brings up a dialogue box on the screen with our chosen message in it, and an "OK" box to click. For example:

macro 'Say Hello [H]';
begin
      PutMessage('Hello.');
end;

Whatever is in the single speech marks '...' appears in the dialogue box.


3.6 Displaying the values of variables

PutMessage can also output the values of variables, which are separated from any '...' output by a comma. So:

macro 'Variable Output [V]';
var
      a: integer;
begin
      a:=5;
      PutMessage('The value of a is ',a);
end;

If a variable is given a type, but not assigned a value, it just takes the value zero. To demonstrate this:

macro 'Zero Value Check [N]';
var
      a: integer;
begin
      PutMessage('The value of a is:',a);
end;

But always remember that you can't use a variable in the main program without having specified what type it is in the "var" section.


3.7 Mathematical operations

Integer and real variables can be added, subtracted, multiplied and divided by using the +, -, * and / symbols respectively. The following examples all use integers, for simplicity.

macro 'Add Values [A]';
var
      a: integer;
      b: integer;
begin
      a:=10;
      b:=a+4;
      PutMessage('The value of b is:',b);
end;

macro 'Subtract Values [S]';
var
      a: integer;
      b: integer;
begin
      a:=10;
      b:=a-4;
      PutMessage('The value of b is:',b);
end;

macro 'Multiply Values [M]';
var
      a: integer;
      b: integer;
begin
      a:=10;
      b:=a*6;
      PutMessage('The value of b is:',b);
end;

macro 'Divide Values [D]';
var
      a: integer;
      b: integer;
begin
      a:=10;
      b:=a/2;
      PutMessage('The value of b is:',b);
end;


3.8 Div and mod commands

The div and mod commands are used during division of integers. Div ignores the remainder of any division, such that "9 div 4" equals two. Mod only gives the remainder, such that "9 mod 4" equals one. So together, these two commands give us the whole picture, i.e. 9 divided by 4 is two remainder one. Note again that they can only work with integers.

macro 'Div Test [D]';
var
      a: integer;
      b: integer;
      c: integer;
begin
      a:=9;
      b:=4;
      c:=a div b;
      PutMessage('The answer is: ',c);
end;

macro 'Mod Test [M]';
var
      a: integer;
      b: integer;
      c: integer;
begin
      a:=9;
      b:=4;
      c:=a mod b;
      PutMessage('The answer is: ',c);
end;


3.9 Real numbers and integers

Integers must be whole numbers with no decimal places. This means that 3, 4, and 5 are all integers, but 3.0, 4.00 and 5.0000 are not - they are reals. Now, there is no reason why you can't add a real to an integer, or divide an integer by a real, etc, but the result will always be another real. Both integers and real numbers can be negative. There are limits on how big numbers can get in NIH Image (although they are vast: billions of billions). Similarly, real numbers have a limited number of decimal places which can be accurately stored.


3.10 Booleans and strings

Boolean variables can only have two values: true or false. You can literally type "true" or "false" into the macro:

macro 'Boolean Assign';
var
      a: boolean;
begin
      a:=true;
      PutMessage('The value of a is:',a);
end;

However, the value is stored as a number: 1 for true and 0 for false. So the macro above prints 1 as the value of a (or 0 if "a:=false" is used). The values 1 and 0 can be directly assigned instead - so although a boolean variable must be either one value or another, there are two ways of expressing this dichotomy.

String variables contain text (or numbers, spaces, and other characters) and can be up to 255 characters long. They are inputted between single speech marks '...'.

macro 'String Assign';
var
      a: string;
begin
      a:='NIH Image';
      PutMessage('The string is: ',a);
end;


3.11 Take care with different types of variable

Now, in proper Pascal, you can't assign an inappropriate value to any type of variable without getting an error message. NIH Image is not as strict: you can assign 3.45 to an integer, or 2 to a boolean, without complaint - until you try to do something with them, in which case you may find the integer has been rounded off, and the boolean "nonsense value" will cause a crash. It is best to "police yourself", and make sure that all your variables are specified as certain types in the var section of the macro, and that they are only ever assigned appropriate values. This'll make your code much easier to follow, and less likely to contain bugs.

macro 'Well Assigned';
var
      a: integer;
      b: real;
      c: boolean;
      d: string;
begin
      a:=15;
      b:=3.145;
      c:=true;
      d:='London';
end;


3.12 Future work with variables

We'll look at reals, integers, and (especially) booleans in much more detail later on (strings aren't so important in image analysis). Don't worry if you have unanswered questions at this stage. For extra practice, load up some of the macros that come with NIH Image into the text editor, decipher the list of variables in the "var" sections, then try and track the variables through each macro and see what happens to them. "LUT Macros" has plenty of variables, for example.



4. Decimals, powers, and text output

4.1 Decimal places

This is a minor topic, but quite awkward to understand so it's worth covering in detail. Obviously, here we're only dealing with real variables (though as I've said before, NIH Image doesn't actually complain if you try and add decimal places to integers or booleans!).

To begin, any real of the format x.0, x.00, x.000, etc, is just printed as x. To demonstrate:

macro 'Real Variable [R]';
var
      a: real;
begin
      a:=7.00000;
      PutMessage('The variable a is:',a);
end;

But, as soon as the decimal places are anything other than zeroes, the display switches to a standard "four decimal places mode". So, 1.2345 is just displayed as 1.2345. 1.2 is "padded" with zeroes, so it is displayed as 1.2000. Any real with more than four decimal places is rounded off, so 1.234567 is displayed as 1.2346. You can check these by modifying the macro above. Note that this rounding doesn't affect the actual value of the variable, just the value that is displayed. So, it would be useful to know how to expand upon this standard "four decimal places mode".


4.2 How to change them

This is quite involved, so bear with me. The first step is to decide how many decimal places you want to display. Let's say you have a value x of 1.2345678, which has seven decimal places overall. To print it with the standard four decimal places, you would use

PutMessage(x);

To get all seven decimal places, you would use:

PutMessage(x:1:7);

And in general, to get 'd' decimal places, you would use:

PutMessage(x:1:d);

The obvious question is now: what does the 1 control in this format? For a variable x, in the format

PutMessage(x:f:d);

d refers to the number of decimal places and f refers to the field width. This is the number of character spaces in which the number is printed. It works like this: "1" requires 1 space; "2.4" requires 3 spaces (one for the decimal point); "10.03" 5 spaces, "1029.46573" 10 spaces, and so on. This is simple enough, but how come the examples above all have a field width of one? The answer is that the decimal places value, d, overrides the field width value f. If the value of d that we specify requires more decimal places than the field width we specify, then NIH Image just uses the minimum field width necessary to print all those decimal places. So, the simple answer is, forget about field widths and always use

PutMessage(x:1:d);

so that you can control the number of decimal places. For the interested, the reason why field width is important is because Pascal uses right-aligned numbers (so that they print correctly in columns). So, if you want to put a space in front of variable x, with the value "1.23", for example: you need 4 spaces for the number (remember, the decimal point needs one too), and another for the space, so you need a field width of 5. So:

PutMessage('The number is:',x:5:2);

will print a space between the colon and the number. But you can just as easily put the space between the speech marks and not worry about having to calculate a field width, which is much simpler:

PutMessage('The number is: ',x:1:2);

will have the same effect.


4.3 Mangling of decimal places

I have noticed that NIH Image isn't very good at displaying lots of decimal places. Above about 7 or 8 decimal places, the output tends to get a bit mangled, with extra random numbers appearing. For example, here's an attempt to display 15 decimal places. Try it and see whether it works.

macro 'Many Decimals [M]';
var
      x: integer;
begin
      x:=1.23456789012345;
      NewTextWindow('Text Output');
      WriteLn(x:1:15);
end;


4.4 Why there isn't a power operation

Switching topics completely: now let's concentrate on calculating powers (using only integers, for simplicity). We immediately hit an obstacle: there isn't a command in the macro language to calculate them! This is a result of the minimalist philosophy behind Pascal. Powers can be calculated using two simpler commands, so there's no need for a specific power command.

First, let me introduce two new functions. These are ln(x), which generates the natural logarithm of x, and exp(x), which is e to the power x. We can assign the outputs of these functions to new variables easily, like this:

x:=ln(y);

a:=exp(b);

Thinking back to A-level maths, you will recall that a to the power b is equal to e to the power of (b times the natural log of a).


4.5 The power-calculating macro

So, we have to combine these two functions to get our power-calculating macro. Here it is:

macro 'Power [P]';
      {Purpose: calculate a to the power b}
var
      a: integer;
      b: integer;
      result: real;
begin
      a:=3;
      b:=6;
      result:=exp(b*ln(a));
      PutMessage('The result is:',result:1:0);
end;

Note how we use brackets on the crucial line of calculation, to avoid ambiguity in calculation. This is a good practice to follow generally. NIH Image is clever enough to do multiplication before addition, and so on, but even so: use brackets wherever possible to reduce ambiguity and make code clearer. Note also the use of comments to clarify that we are calculating a to the power b and not the other way round. Why do we need "result:1:0"? Surely the answer should be an integer? Well, because we are using the exponential-and-logarithm method, the intermediate values in the calculation have to be real, so the answer must be real. So if we don't have the result:1:0, we get 729.0000 printed. (Note that we can still specify "answer" as an integer if we like - NIH Image doesn't complain, but it will still print 729.0000 unless we specify no decimal places!).

This macro only deals with positive integers. Negative numbers will cause a crash, since you can't calculate the natural logarithm of a negative number. It is OK to set b to zero, but not a: trying to calculate ln(0) gives an error also.


4.6 Using GetNumber

In order to use different values in the calculation above, we have to edit the macro code itself. By using the GetNumber command, we can enable the user to type in values for a and b when the macro is run.

macro 'Power With Input [I]';
var
      a: integer;
      b: integer;
      result: real;
begin
      a:=GetNumber('Enter The Number (An Integer):',5,0);
      b:=GetNumber('Enter The Power (An Integer):',2,0);
      result:=exp(b*ln(a));
      PutMessage('The Result Is: ',result:1:0);
end;

See how GetNumber works? There are three sections in the brackets, separated by commas. The first, inside the speech marks '...', is the text which appears in the GetNumber dialogue box. The second is a suggested value, which comes ready-selected in the input box. The third number is the number of decimal places for the suggested value - zero, in our case, since we our default values (which are selected if the user just clicks OK) are 5 and 2, resulting in an answer of 25. In a sense, GetNumber is a function just like ln(x) - both return single numerical values which are assigned to other variables.


4.7 Back to text output

Switching topics once more, we return to text output. So far, we've only covered PutMessage as a means of text output. A useful complement to this is ShowMessage, which prints text in the Info window.

macro 'Info Window Hello';
begin
      ShowMessage('Hello.');
end;

We can alter any of the macros we've written so far with text output, to have that output in the Info window instead of in a dialogue box which needs its OK button clicking. The commands are equivalent - ShowMessage can incorporate variables as well.

The rule is that you can only print one message at a time in the Info window. Any new message will overwrite the previous one. To see this, run the following macro:

macro 'Waiting [W]';
begin
      ShowMessage('Start');
      wait(5);
      ShowMessage('Waiting for 5 seconds');
      wait(5);
      ShowMessage('Waiting for 10 seconds');
end;

The command wait(x) just makes the computer wait for x seconds - we'll use this again later on. So, after 5 and 10 seconds, the Info window tells you how long the computer has been waiting.


4.8 Multi-line statements in the Info window

To get more text into the Info window, we can incorporate "carriage returns" into our ShowMessage commands - equivalent to pressing enter. The backslash character \ represents a carriage return. It must go at the front of each new line, and is not printed even though it goes within the speech marks. For example:

macro 'Two Lines [T]';
begin
      ShowMessage('First line','\Second Line');
end;

See how "Second Line" appears on a new line without the "\"? We can actually use "\" on its own to create a blank line:

macro 'Miss A Line [M]';
begin
      ShowMessage('First line','\','\Second line');
end;

But the one thing we can't do is split these statements into more than one ShowMessage command. This is the restricting rule - each new ShowMessage command wipes out everything that was before it in the Info window. So to print anything of substance in the Info window, you need a really long ShowMessage statement - it isn't much use, really. To get proper control over text output, we need to look at generating text windows.


4.9 Generating a text window for text output

Remember when we first created a macro? The first thing you do is to create a new text window off the "File" menu. We can do this from within a macro, using the NewTextWindow command (obviously enough!):

NewTextWindow('Window Title');

is the syntax. This creates a text window exactly the same as the ones you type macros into. Once the macro is finished, and the text window remains, it can be saved as an NIH Image "TEXT" file, reloaded, edited, and so on.


4.10 Writing in a text window

This section is based on the premise that we only have one window open when the macro is running - the text window we have generated using NewTextWindow. If we have more than one text window, or an image window as well, then things get more complicated. But we'll deal with that later.

We write in text windows using the Write and WriteLn commands. As long as only one text window has been launched, there is no confusion for the computer over where to write. Basically, "Write('....');" just prints what is in the '....' - without a carriage return. "WriteLn('....');" prints the string and does include a carriage return, i.e. whatever is next printed starts on a new line. So "WriteLn;" on its own is the equivalent of pressing enter.

The following macro prints "First Statement" using "Write", then uses "WriteLn" to press enter, then uses another WriteLn command to miss a line, then prints-"Second Statement"-and-presses-enter all-in-one by using WriteLn with a statement. It all becomes clear once you have used them a few times.

macro 'New Text Window';
begin
      NewTextWindow('Macro Text Output');
      Write('First Statement');
      WriteLn;
      WriteLn;
      WriteLn('Second Statement');
end;

"Macro Text Output" is the name of the new text window; if it is clicked closed, after the macro has finished, then the program will prompt a save of the TEXT file with the same name.

In general, it's much easier to use text windows for text output, except when a clickable dialogue box is needed to get the user's attention. WriteLn is almost always used as well, rather than Write, because of its automatic carriage return. From now on, we'll use test windows for text output, except in our minor "test macros".

Note that we can also print string variables using Write and WriteLn:

macro 'Print Name';
var
      name: string
begin
      name:='Michael';
      NewTextWindow('Macro Text Output');
      WriteLn(name);
end;


4.11 The 32k limit (again)

As we know, macro files (which are written in text windows) can't be larger than 32k. In fact, this restriction applies to all NIH Image text windows - so that the text output from a macro into a new text window can't be larger than 32k, either. This means that you can't print out large amounts of data into a text window. But there are other ways of dealing with large amounts of results data, of which more later.




5. Functions and procedures

5.1 Global variables

So far, every time we write a macro that uses variables, we've had to include a "var" section at the start of that macro, where we assign a type to each variable. The variables in separate macros are totally separate - even if they have the same names. Each time we run a new macro, the computer "forgets" about any variables that might have been used in previous macros.

Because we can have more than one macro program in a macro file - and almost always will do, once things get more complicated - then it is very useful to have "global variables" as well as standard "macro variables". Global variables are accessible to all the macros in a file. They have types assigned in a "var" section at the very top of the file, before any of the macros. Here's a simple example.

var
      answer: real;

macro 'Add Numbers [A]';
var
      a: real;
      b: real;
begin
      a:=GetNumber('Enter the first number',2,2);
      b:=GetNumber('Enter the second number',3,2);
      answer:=a+b;
end;

macro 'Show Answer [S]';
begin
      ShowMessage('The added result is: ',answer:1:2);
end;

The variable "answer" is global - we assign it a value in "Add Numbers", then display it in "Show Answer". Simple enough. In fact, this is the only way to pass a value between two macros like this - if we "initialised" the variable "answer" in both macros, the second one wouldn't work because the value wouldn't be passed across. Note that all we are doing at the top is specifying the TYPE of the global variable; we aren't actually assigning it a value until the macros themselves are run.


5.2 A simple macro file with a global variable

Here's a longer example, though still very simple:

var
      number: integer;

macro 'Input Number [I]';
begin
      number:=GetNumber('Enter an integer',7,0);
end

macro '(-';

macro 'Multiply By 2 [A]';
var
      answer2: integer;
begin
      answer2:=number*2;
      ShowMessage('Integer*2 = ',answer2:1:0);
end;

macro 'Multiply By 4 [B]';
var
      answer4: integer;
begin
      answer4:=number*4;
      ShowMessage('Integer*4 = ',answer4:1:0);
end;

macro 'Multiply By 8 [C]';
var
      answer8: integer;
begin
      answer8:=number*8;
      ShowMessage('Integer*8 = ',answer8:1:0);
end;

See how the "macro file" concept is starting to work? A set of related macros, that fill up the "Special" menu with different functions, organised by dividing lines and instantly accessible by hotkeys.


5.3 Introduction to functions

We've already met two functions in Pascal: exp(x) and ln(x). What exactly is a function? Well, the rule is that a function can only ever produce a single value. Whatever you put in, you only ever get a single number out. So the basic syntax for a function just uses the assignment operator ':=' ... like, a:=exp(x) or b:=ln(y). In general, we use Result:=FunctionName(Input).

Now, exp(x) and ln(x) are "given" functions - the code that actually does the calculation is already in NIH Image, so we don't have to worry about it. But what if want to define our own functions? We need three things: the name of the function, the input variable name(s) and type(s), and the output variable type. Remember that we can have lots of input variables to a function if we like, but only ever one output variable, since a function can only ever return a single value. Don't we need to know the name of the output variable? No, because this is denoted by the function name - they are the same thing, if you see what I mean.


5.4 Function variables

Let's say the function is going to be called "square", and will calculate the square of an integer. So, we have one output variable (an integer). We say that the function "returns" an integer. We define it like this:

function square: integer;

The syntax is very similar to assigning a variable type. But we also have an input variable (another integer - let's call it "input"), so we need to include this in the definition:

function square(input: integer;): integer;

In general, it looks like:

function title(input variable names and types;): output variable type;

If we have more than one input variable, we could use something like:

function FunctionName(a: integer; b: integer; c: integer;): integer;

though remember we can abbreviate this to:

function FunctionName(a,b,c: integer;): integer;


5.5 The square function

Getting back to the square function, here is the very simple code:

function square(input: integer;): integer;
begin
      square:=input*input;
end;

There's no "var" section, since we already know about the variable types from the first line. All we have to do is define how we get from the input variable to the function output, using the assignment operator :=.

Now we can write a quick macro to use this function:

macro 'Get Square [S]';
var
      a: integer;
begin
      a:=GetNumber('Enter the integer to be squared:',5,0);
      a:=square(a);
      PutMessage('The result is ',a:1:0);
end;

Note that this macro goes after the function it uses. Functions and procedures must always be placed BEFORE the macros that use them in the macro file. So the whole macro file is:

function square(input: integer;): integer;
begin
      square:=input*input;
end;

macro 'Get Square [S]';
var
      a: integer;
begin
      a:=GetNumber('Enter the integer to be squared:',5,0);
      a:=square(a);
      PutMessage('The result is ',a:1:0);
end;


5.6 More on functions

Actually, the "square" example is a bit pointless, since there is a function that calculates squares in Pascal already - sqr(x)! (It provided a good, easy example though). But as I have said, it's essential to find out whether you really need bother to program something before you start - if there's a simpler way, then use it.

Some other basic functions for NIH Image macros are sqrt(x) which returns the square root, abs(x) which returns the absolute value, i.e. knocks off any decimal places, round(x) which rounds to the nearest integer, random - which returns a random number between 0 and 1 (note: it doesn't need an input value) and several others. Browse the list of NIH Image Macro Commands to find more.

For example, sin(x) and cos(x) are self-explanatory - though there is no tan(x). Why not? Same reason as there is no power command: tan(x) equals sin(x) over cos(x). So here's an opportunity to program a genuinely useful function.


5.7 The tan function

We have one input value (an angle, in radians) and one output value (the tangent). Both obviously need to be reals. Here's the code, without further ado:

function tan(input: real;): real;
begin
      tan:=sin(input)/cos(input);
end;

macro 'Find Tangent [F]';
var
      angle: real;
      result: real;
begin
      angle:=GetNumber('Enter the angle (in radians)',3.142,3);
      result:=tan(angle);
      PutMessage('The tangent is ',result);
end;

Note that the result is printed with the default four decimal places.


5.8 Extending the macro language

Once we've placed the tan function in our macro file, we can use it in all subsequent macro programs in that file. In effect, we've added a new command to the language. Computer language is very powerful in this way - we can build small modules that do simple things, then call upon them from more complex programs, then use these programs as building blocks for even greater things. One way to write very powerful macros is to split them up into simple, smaller units - a modular approach to programming. Functions and procedures are useful for this.


5.9 Introduction to procedures

Functions can only return a single value. Procedures can do virtually anything. They are much more powerful than functions. You can abstract a section of code from a program, put it in a procedure, and then just call the procedure whenever you want that code to be run. This allows for much more efficient programming. Only a very simple example of a procedure will be given here: here's the code. See if you can figure out how it fits together.

procedure calculation;
begin
      number:=number+increment;
end;

macro 'Add Two [A]';
var
      number: integer;
      increment: integer;
begin
      increment:=2;
      number:=GetNumber('Enter an integer',5,0);
      calculation;
      ShowMessage('The result is ',number:1:0);
end;

This is an "unnecessary" procedure, in that we could have left the line

number:=number+increment;

within the program itself, and done without the procedure. But things can obviously get more complicated: we'll encounter procedures again in a "proper" macro in chapter 10. Note that as with functions, procedures must come before the macros that call them in the macro file.

In general, if a macro program starts to get too long (say, greater than a page full of code), then consider breaking it up into procedures - it'll be much easier to follow and debug. For further practice, check out the example macros supplied with NIH Image, and find out how they use global variables, functions, and procedures. An excellent example is the "Demo Macro", which is almost entirely organised into procedures.



6. For loops

6.1 Introduction

For loops let you repeat an operation a certain number of times. The "operation" can be a load of different things together, if you like. But the golden rule is that you have to know how many times you're going to repeat them, before you start. A for loop has to be "fixed" in this way.


6.2 Single statement for loops

The basic for loop structure looks like this:

for counter:=1 to 10 do
      command;

We have the variable "counter" (or you can call it what you like), which must be an integer. The command is then repeated 10 times. We could run counter from 1 to 5, to repeat the command 5 times, or indeed from 5 to 10, or whichever values we like. But it can only go up by 1 with each cycle. It's a variable just like any other - so it has to have its integer type specified in the "var" section.

Important: note that there is only one semicolon in the above statement - at the end. There isn't one after the "do".

As an example, here's a macro which prints the numbers from 1 to 20 in a text window, using a for loop.

macro 'For Loop [F]';
var
      counter: integer;
begin
      NewTextWindow('Macro Output');
      for counter:=1 to 20 do
      WriteLn(counter);
end;

See how it works? The WriteLn command after the for statement is executed, then the counter variable is incremented, then the command is repeated, and so on, until the counter reaches 20, when the command is executed for the last time.


6.3 Multiple statement for loops

There's an obvious limitation here - we can only put one command after the for statement. How about multiple commands? Now, we have to introduce a "begin ... end" structure into the for loop. The basic structure is modified to:

for counter:=1 to 10 do begin
      command 1;
      command 2;
      command 3;
end;

See how all the commands go between the begin and the end? Now we can have multiple commands, which need to be terminated by semicolons. But ignore the commands for a minute, and just look at the for statement structure:

for counter:=1 to 10 do begin ... end;

See how there is only one semicolon again? The for loop is a single command, in a sense, so it is terminated by a single semicolon at the end. Each action within the for loop is a separate command, and also needs to be terminated. Hopefully you can understand where all the semicolons belong!

Here's an example, extended from the example above:

macro 'Longer For Loop [L]';
var
      counter: integer;
begin
      NewTextWindow('Macro Output');
      for counter:=1 to 20 do begin
            WriteLn(counter);
            wait(1);
            WriteLn('And the loop starts again...');
      end;
end;


6.4 Indenting - a note

See how the above example has two "ends" at the end? Each of these "ends" belongs to a "begin". Go straight up from the last end, and you'll hit the begin that starts the macro. Go straight up from the penultimate end, and you'll hit the for statement and its begin. This indenting of the code is starting to become useful to help us follow the structure of the code. I always indent in 5 spaces for each "chunk" of code ... after the first "begin", then within any for loop, and also for other types of commands which we'll meet later. The key is not to follow my example exactly, but to be consistent in how you indent, which makes your code much easier for you (and others) to understand.


6.5 For loops with procedures

You may already have thought of a cunning way to expand the simple for loop described in section 6.2. Here it is:

procedure loopstuff;
begin
      command 1;
      command 2;
      command 3;
end;

for x:=a to b do
      loopstuff;

Take the multiple commands, put them in a procedure, then run the procedure as a single command from within the macro for loop. Cunning. In general, I prefer to use the "begin ... end" structure with all for loops, for clarity. But even so, it can be useful to "farm out" long lists of instructions to procedures in this way, to keep the main body of code manageable if you want to do a huge list of different things within a single for loop.


6.6 Honest for loops

For loops have to be "honest". As I said above, you can only go up in integers, one at a time, and you can't modify the counter variable from within the for loop. This is a golden rule of programming. In effect, if you say you're going to count from 1 to 10, then you have to ... you can't then decide to stop at 8. For loops have to be honest. Never try and modify the counter variable from within the for loop: doing this makes the initial for statement a "lie", which makes the code very tricky to follow.

To go up by a different increment each time, use another variable and calculate its value from the counter. Here's an example, which counts from 2 to 40 in steps of 2:

macro 'Stepped For Loop [S]';
var
      counter: integer;
      stepvalue: integer;
begin
      NewTextWindow('Macro Output');
      for counter:=1 to 20 do begin
            stepvalue:=counter*2;
            WriteLn(stepvalue);
      end;
end;


6.7 Nesting of for loops

For loops can be "nested" - one can run within another. This lets you cover a "two-dimensional" concept, e.g. all the coordinates of an image. But as above, you can only increment one at a time. Here's an example - see if you can predict what it does:

macro 'Nested For Loop [N]';
var
      counter1: integer;
      counter2: integer;
begin
      NewTextWindow('Macro Output');
      for counter1:=1 to 60 do begin
            WriteLn(counter1);
            for counter2:=1 to 20 do begin
                  WriteLn(counter2);
            end;
      end;
end;

We need two counter variables. For each increment of counter1 between 1 and 60, counter2 is run between 1 and 20. So the above macro prints out the numbers between 1 and 20 sixty times! Hmmm, useful... but seriously, as I said above, nested for loops are useful when dealing with images, among other things. Note how the text window scrolls as it fills up - and how you can edit its contents when the macro is finished.

Note also the three "ends" at the end - things are getting complicated now, and careful indenting is essential. Otherwise we are never sure which end belongs to which begin! To make things clearer still, we could add blank lines to the above macro (which are ignored by the computer) and some comments inside curly brackets {}. Clear, well-organised code is the way to go.


6.8 Counter variables

The counter is just an integer variable: nothing special. So there's no reason why we can't use any of these:

for x:=1 to limit do

for x:=(a+b) to (c-d) do

for x:=(a+(b*c)) to (abs(exp(q))) do

and so on. Note the use of brackets.


6.9 Breaking free

We've been fretting all this time about keeping our for loops honest, but what happens when you've just started running a for loop up to 10,000 and realise something is wrong? How do you escape? Here we cover how to break free from a macro once it is running. We can build an "escape hatch" into the code itself, but we need to cover more ground to do this. For now, here are instructions on how to halt a macro from the outside.

Macros don't multi-task - so if a macro is running, the computer is frozen. To escape from a macro hold down control, the apple key, and the full stop/period (.). This'll stop it running. Note that the computer won't notice that you are pressing this "escape combination" until it has finished its immediate task - what this means is that in some more complex macros, you might have to hold down the escape combination for a few seconds. Don't worry though, it always works, unless the Mac itself has crashed (which never happens, right?).


6.10 On semicolons

For a final word, I want briefly to discuss the semicolon symbol ; in Pascal. It has a fairly confusing role as a statement separator/terminator, in that it doesn't go after everything, and indeed can cause major problems if it is put after every line (e.g. after a for loop). Bear in mind that carriage returns ("new lines") are ignored in Pascal - hence, the need for a terminator symbol. A semicolon on its own forms an "empty statement" - it is accepted by the computer, but nothing actually happens. So this macro is legal, and does nothing:

macro 'Empty Statements [E]';
begin
      ;
      ;
      ;
      ;
end;

I have read that you never need a semicolon immediately before an end statement (i.e. on the end of the preceding line - Pascal treats carriage returns as spaces remember, basically ignoring them). This seems to be true - but I find it more logical to put them in anyway. I'm not sure that there's a general rule about what semicolons actually do. You just have to learn where to put them. Develop your own style, that works, and be consistent.




7. If statements

7.1 Introduction to if statements

If statements deal with either-or situations - they choose between two alternatives (or just "alternatives", in the strict definition of the word). For choosing amongst more than two things, we use a "case" statement in Pascal - not available in NIH Image, so we have to construct an equivalent from multiple if statements - but let's not worry about this for now. The either-or nature of if statements means that we use BOOLEAN variables to decide what to do. Recall that these can only take the values 1 or 0 (or, equivalently, true or false). Before we consider if statements further, we need to look at boolean variables in more detail.


7.2 Introduction to boolean variables

Boolean variables are assigned values using the assignment operator ":=". For example, "x:=1" and "y:=0" (or "x:=true" and "y:=false" - think of the two pairs as totally interchangeable, which, to the computer, they are). We can also assign boolean values in an indirect way: this is a little harder to understand. For example, we can say:

x:=1;
y:=x;

which makes both x and y equal to 1. But this remains simple only as long as all the variables are boolean. What if we want to say "if x (an integer) is greater than 5, then y (a boolean) is true"? Read carefully now. This looks like an if statement in the making, i.e. we should use the if command. BUT there is another way of doing it - more compact but less intuitive. Either is perfectly acceptable - all we are looking at here is two ways of getting to the same place. We will cover the short, non-intuitive way first.


7.3 Boolean expressions

Consider these six "relational operators". Each results in a boolean result, i.e. either true or false, no "middle ground". They are given in Pascal symbols rather than in proper maths symbols, e.g. for "is not equal to", mathematics uses an equals sign with a line through it, a symbol which isn't available on a computer keyboard.

= equal to
< less than
> greater than

<= less than or equal to
>= greater than or equal to
<> not equal to

So, all the following are boolean expressions. This is despite the fact that all the variables involved are integers. (We could draw up similar examples using real numbers too). This can be difficult to get your head round, but is actually quite obvious.

x=5, y<78, y<=9, z>5, r<>4

and so on. In the first example, although x is an integer, it either is or isn't equal to 5 - so the output is true or false, i.e. boolean. And the same for all the rest.


7.4 Assigning values to boolean variables

Now, how do we assign the outcomes of these expressions to boolean variables? We use the ":=", as before. Note, at this point, we have (almost without noticing it) covered the reason why Pascal uses ":=" and not just "=" as an assignment operator; the = sign on its own is used in boolean comparisons. It's a plus point for Pascal that the equals sign doesn't have a confusing dual role like it does in some other languages, though some might consider it too picky. Anyway, the way we assign variables is:

1. Put the boolean expression in brackets: (x=5).

2. Assign the boolean variable this value: a:=(x=5).

This looks confusing because of the two equals signs - which is why I covered this operator first. It becomes more obvious when the other relational operators are used, e.g. a:=(y>5) and b:=(z<>2). In fact, the brackets are not strictly necessary ... but I find a:=x=5 far too confusing! Always use brackets, is a sensible rule. Strictly, it is good practice only to compare integers with integers, reals with reals, and booleans with booleans (the latter may seem odd, but if a=true and b=true then there is no reason why (a=b) should not be a boolean operator).

One more boolean operator is odd(x) which is true if x is odd and false is x is even (x has to be an integer, of course). We can use this in the same way: a:=(odd(x)) for example, where a is boolean and x is an integer.

So, we can now evaluate a boolean expression and get the result into a boolean variable, all in one short expression of the form a:=(x) where a is the boolean variable and x is the boolean expression. But beware! This area is continually confusing. For example, the expression "a=true" is either true or false; it is NOT an assignment of a value. So x:=(a=true) is true if and only if a really is true. If a is false, then x is also false. Similarly, x:=(a=false) gives x the value true if a really is false; if a is actually true, then x gets the value false. Always look out for the difference between ":=" and "="!


7.5 A first if statement

Now, how about the other way of getting to this place, via if statements? Let's take the example a:=(x=5) again. What this really says is "if x equals 5 then a is true, otherwise a is false". We could actually write this as an if statement, as shown in the following macro.

macro 'Does X Equal 5? [D]';
var
      x: integer;
      a: boolean;
begin
      x:=GetNumber('Enter an integer',5,0);
      if (x=5)
      then
            a:=true
      else
            a:=false;
      PutMessage('The value of a is ',a:1:0);
end;

Note the "if .. then .. else .." structure. The core of the matter is the following section:

if (x=5)
      then
            a:=true
      else
            a:=false;

which is in fact totally equivalent to the more pithy (but less obvious!) statement "a:=(x=5);". Look closely at the code above and you will notice that in "semicolon" terms it is actually a single statement, i.e. it is only terminated by a semicolon once (Pascal basically ignores carriage returns, remember). From this point onward, I will only be using the "shorthand" way to assign boolean variables, since it takes up much less space than the "if statement" way. This will free up the if statements to do their true job, which is to make important decisions, i.e. what do we do IF a variable is true or false? The proper job of if statements is to act on boolean expressions, not just to evaluate them. Take a break here, and don't worry about how the detail of if statements works - we haven't finished with this yet. We will come back to them from a slightly different angle.


7.6 The basic if structure

Consider this structure:

if x
      then
           ...
      else
           ... ;

Obviously, x is a boolean variable; what is it really saying is "if x is true then do this, otherwise do that" - where the second option is equivalent to x being false. Wouldn't it make more sense to actually encode "if x=true then"? The answer is no, because the "=true" is actually redundant. Since x is an assigned boolean variable, it must be either true or false already, so there is no point asking "if true=true" or "if true=false" - we are just generating a redundant operation which will slow down the computer. It may appear strange at first, especially with expressions like "if flag then" which don't follow a very language-like syntax, but it makes perfect sense. So our basic structure is:

if (boolean expression)
      then
            (command if true)
      else
            (command if false) ;

Note that only one of the two options is ever run - the other is ignored. This is the whole point of a boolean statement.


7.7 More complex if statements

Consider the following macro:

macro 'If [I]';
var
      input: boolean;
begin
      input:=GetNumber('Enter either zero or one:',1,0);
      NewTextWindow('Macro Output');
      if input
      then begin
            WriteLn('You entered one, i.e. true.');
      end
      else begin
            WriteLn('You entered zero, i.e. false.');
      end;
end;

A number of points need to be carefully discussed. First, we have bypassed any boolean calculations: the boolean variable input just has a 1 or a 0 assigned to it by the user. The macro then prints out the appropriate value, not by directly printing "input" but by inferring it from the "if input" statement. Second, here we finally see a situation where you must give the value 1 or 0 to a boolean variable. The program will let you input anything you like as the value of input, but it will crash at the if statement (giving an error message saying "boolean value expected") unless a 1 or 0 is used. Note that this would not be the case if the value of input was just printed out without the computer having to "think" about it.

Third, we have "jumped" to an exact parallel with the for loop structure: we have added a "begin" and "end" after both the "then" and "else" commands, which allows multiple statements to be used. In detail, the simple structure was:

if (boolean operator)
      then
            (single command if true)
      else
            (single command if false);

The complex structure is:

if (boolean operator)
then begin
      (command if true);
      (command if true);
      (command if true);
end
else begin
      (command if false);
      (command if false);
      (command if false);
end;

Look carefully at the "ends" above. There are two - the first is the "then end", if you like, and the second the "else end". Only the latter has a semicolon after it - a semicolon after the then end will cause problems. Despite all the intermediate commands, it is still basically structured like a single command:

if .. then begin .. end else begin .. end;

so it only requires a single semicolon. As with the for loop, any complex operations require the "begin .. end" to be added, and from now on we will always use them, to make the program clearer (so-called "defensive programming - aiming to make it harder for bugs to be encoded in the first place). This "begin .. end" approach will be used for loops too. And as for loops, we can also use procedures, instead of multiple statements between the begin and end, if desired. Note also the role of indentation, which is ignored by the computer, but makes the structure of the loops clearer to the human eye.


7.8 Omitting the else part

The "else" section is optional. An "if .. then .." statement without the else will only do anything if its boolean expression is true; otherwise, it does nothing. The "else" option is equivalent to an empty statement - a semicolon on its own. An example is below.

macro 'No Else [N]';
var
      testvalue: boolean;
begin
      testvalue:=1;
      if testvalue then begin
            PutMessage('Test Value is one.');
      end;
      PutMessage('This is always printed.');
end;

See how the second dialogue box is always printed? The semicolon after the first "end" command signals the end of the if statement to the computer, telling it that there is no else statement this time, so that if testvalue really is zero then it can go straight on to the next command after the if structure. Look at the comparison of "with else" and "without else" if statements below, and note the position of the semicolon.

if (boolean operator)
then begin
      (command 1 if true);
      (command 2 if true);
      (command 3 if true);
end
else begin
      (command 1 if false);
      (command 2 if false);
      (command 3 if false);
end;

compared with:

if (boolean operator)
then begin
      (command 1 if true);
      (command 2 if true);
      (command 3 if true);
end;

There is only one semicolon in the "main structure" in both cases, but if an "else" part is needed, then it must go after the second "end", WITHOUT a semicolon after the first "end". If there is no "else" statement, the semicolon goes after the first "end". I hope this is clear. It is simpler to omit the else statement unless it is specifically needed.


7.9 Nested ifs and logic puzzles

If statements can be "nested" just like for loops, when more than one decision is needed. Look at the following example and try to work out under which conditions each message will actually get displayed.

macro 'Nested If [N]';
var
      bool1: boolean;
      bool2: boolean;
begin
      bool1:=1;
      bool2:=1;
      if bool1 then begin
            if bool2 then begin
                  PutMessage('Message 1');
            end;
            PutMessage('Message 2');
      end;
      PutMessage('Message 3');
end;

Message 1 will only get displayed if both bool1 and bool2 are true - both if statements must be "passed correct" before it is reached. In this case, the bool1 if statement will also be true, resulting in Message 2 getting printed as well. Message 3 comes after both if structures are complete and is therefore always printed regardless of the truth of bool1 and bool2. Think about the other possibilities. If only bool1 is true, then Message 2 and Message 3 will be printed, but not Message 1. If bool2 is true but not bool1, then the first if statement is not passed and only Message 3 will appear.

This is quite a difficult macro to follow just by looking at the code - we have to carefully match up each "if .. then begin" with its appropriate "end;", then we can allocate the other commands to the various if loops. Each "chunk" of code is inserted into the middle of another chunk, rather than them following each other in logical order. The computer finds this easier to follow than the human brain! Indentation and comments are very useful in following nested if loops. Consider the following:

if a then begin
      if b then begin
            if c then begin
                  command 1;
                  command 2;
                  command 3;
            end; {end of if c structure}
            command 4;
            command 5;
            command 6;
      end; {end of if b structure}
      command 7;
      command 8;
      command 9;
end; {end of if a structure}

The strong indenting and comments make it easier to see that if ONLY a is true, and not b or c, then the computer jumps straight to commands 7, 8 and 9 (for example). Note that for every if structure, we use a begin and an end, even if (as in one of the earlier macros) we are only using single commands and could get away without - begin and end markers make understanding the code much easier. They also remove any ambiguity if there is a "dangling else" statement.


7.10 The dangling else problem

Here's another version of the "Message X" puzzle that we met above:

macro 'Dangling Else [D]';
var
      a: boolean;
      b: boolean;
begin
      a:=0;
      b:=1;
      if a then
            if b then
                  PutMessage('Message 1')
            else
                  PutMessage('Message 2');
      PutMessage('Message 3');
end;

Again, the challenge is to explain which messages will appear under the different truth conditions. As before, Message 3 is outside the nested statements and is therefore always printed. Message 1 clearly requires both a and b to be true. But what about Message 2 - who does the "dangling else" belong to? The answer is the "if b" statement, purely because it is the most recent if statement, and this is the rule Pascal uses - it simply goes back through the code until it finds the most recent if, and assumes the dangling else belongs to this. So, Message 2 is only printed if a is true and b is false. But by using begin and end with every if statement, we can avoid this ambiguity. The modified macro becomes:

macro 'New Dangling Else [D]';
var
      a: boolean;
      b: boolean;
begin
      a:=1;
      b:=0;
      if a then begin
            if b then begin
                  PutMessage('Message 1');
            end
            else begin
                  PutMessage('Message 2');
            end; {end of if b statement}
      end; {end of if a statement}
      PutMessage('Message 3');
end;

Now everything is (hopefully) clear. Think very clearly now about how we would change the above macro so that the "else" statement above was part of the first if statement - the "if a" statement. We need to carefully rearrange our begins, ends, and semicolons:

macro 'New Dangling Else 2 [Q]';
var
      a: boolean;
      b: boolean;
begin
      a:=1;
      b:=0;
      if a then begin
            if b then begin
                  PutMessage('Message 1');
            end; {end of if b statement}
      end
      else begin
            PutMessage('Message 2');
      end; {end of if a statement}
      PutMessage('Message 3');
end;

This may seem a bit incomprehensible: the main thing to remember is never to attempt editing or altering any program like this "on the fly" - code must always be constructed from the basic structural building blocks of ifs, then add elses, then add "begin .. end" pairs and a single semicolon for each if statement, then add the desired commands in the right places with a semicolon after each. It does look complicated, but it is totally logical!


7.11 The and and not operators

We have seen how if statements use a kind of shortened logic, "if x" really meaning "if x is true" in written language. We can use three boolean operators to create more complex boolean expressions for our if statements to evaluate. These operators are and, or, and not. In each case, we have a choice about where to use them - either in the if statement itself:

if (x and y) then ...
if (x or y) then ...
if (not x) then ...

or we can incorporate them into the boolean variables' assignments beforehand:

a:=(x and y)
b:=(x or y)
c:=(not x)

Hopefully you will find them obvious enough to use. The main thing to remember is always to use brackets to keep everything clear; this isn't necessary for single-variable expressions like the above, but as soon as we are using expressions like

a:=((x<>6) and (y>4))

then the brackets become essential. And, or and not can be used with all kinds of boolean expressions like these. The end result, as always, is just a single boolean variable: a true or false. I'm sure you know the definitions already, but just to summarise: "a and b" is true only when both a and b are true; "a or b" is true if either a, or b, or both a and b, are true; "not a" is true if a is false and false if a is true.


7.12 A demonstration of "and" and "or"

The following macros give simple demonstrations of the "and" and "or" operators:

macro 'And Test [A]';
var
      input1: boolean;
      input2: boolean;
      testvalue: boolean;
begin
      input1:=GetNumber('Enter input 1:',1,0);
      input2:=GetNumber('Enter input 2:',1,0);
      NewTextWindow('Macro Output');
      testvalue:=(input1 and input2);
      if testvalue
      then begin
            WriteLn('Both inputs were true.');
      end
      else begin
            WriteLn('One or more inputs were false.');
      end;
end;

macro 'Or Test [O]';
var
      input1: boolean;
      input2: boolean;
      testvalue: boolean;
begin
      input1:=GetNumber('Enter input 1:',1,0);
      input2:=GetNumber('Enter input 2:',1,0);
      NewTextWindow('Macro Output');
      testvalue:=(input1 or input2);
      if testvalue
      then begin
            WriteLn('At least one input was true.');
      end
      else begin
            WriteLn('Both inputs were false.');
      end;
end;


7.13 Always use brackets

Now, what happens when we combine these operators? For example, what does "not a and b" mean? There is an order of priority within Pascal, such that "not" takes precedence over "and" ... so that the "not" in "not a and b" only applies to a, leaving b unchanged. This can be clarified using brackets: not a and b equals (not a) and b. So how exactly does this order of priority work? This is not something we need to know because from now on we will ALWAYS use brackets in such expressions. User-entered brackets override Pascal's order of priority, thus giving us complete control: we can specify (not a) and b, or not(a and b), as we wish. The key is to avoid ambiguity, so that we, and everyone else who reads our program, knows what we are doing.


7.14 There's no EOR

Note that there is no equivalent to EOR or XOR ("exclusive or") in Pascal. This may be needed for a situation where we want to know if either a or b, but not both a and b, are true. We just have to write it out the long way, as "(a and not b) or (b and not a)". The same is true of its opposite, "equivalence", i.e. both boolean values the same - both true, or both false. We must write this out as "(a and b) or ((not a) and (not b))". However, all this is getting a bit academic anyway. Our main use will simply be of and, or, and not in basic decision-making.


7.15 nPics

Now is an appropriate time to introduce the variable nPics, which tells us how many images are currently open. The situation is this: we have a macro which can only run on one image at a time. How do we check that one, and only one, image is open?

macro 'nPics if [N]';
begin
      if (nPics=0) then begin
            PutMessage('This macro requires one image to be open.');
      end;
      if (nPics>1) then begin
            PutMessage('This macro requires only one open image at a time.');
      end;
end;

Note that although nPics is an integer variable (I presume!), it does not have to be initiated, so this macro does not require a "var" section. Rather than one, general, error message, this macro implements two which are slightly differently worded depending on the situation. They are controlled by if statements without elses - if nPics=1, then all the commands are just ignored. I hope this is obvious enough.


7.16 Let's recap

That was a very long chapter. If you got to here and are still not sure, best to go and work through it again more slowly. Remember, you have to understand boolean expressions before you can understand if statements.




8. Repeat and while loops

8.1 Introduction to conditional loops

Here we cover conditional loops - "repeat" and "while" loops. These combine the features of for loops and if statements. As we know, for loops have to be executed a preordained number of times; if statements bring choice into the program but can only execute an option once. Conditional loops allow us to make decisions within the program about how many times a loop should be repeated, so they allow programs to be more versatile.


8.2 Basic structure of the repeat loop

The repeat loop has the following structure:

repeat
      action 1;
      action 2;
      action 3;
until x;

where actions 1-3 are the loop commands, which are to be repeated, and x is a boolean expression as with if statements - it is either true or false (and this boolean expression can incorporate all the logical complexity we covered with if statements, if required). When the repeat statement is encountered, the computer executes the actions, then checks to see whether the "exit condition" x is met, i.e. is true. If it is, then the computer moves on to the rest of the program. If the exit condition is not met, i.e. false, then the actions are repeated and the check redone. This repeats until the exit condition is met - as many loops as is necessary! The first important thing to remember is that the actions will always be executed once, even if the exit condition is true before the repeat loop is even encountered. Only their repeats are dependent on the value of the exit condition. Note also that the fundamental structure, as with the if statement, is based on a single line terminated by a single semicolon:

repeat ... until x;

8.3 An example of a repeat loop

Below is an example repeat loop in a macro.

macro 'Repeat [R]';
var
      input: integer;
begin
      input:=GetNumber('Enter an integer less than 20:',5,0);
      NewTextWindow('Macro Output');
      repeat
            WriteLn(input);
            input:=input+1;
      until (input=20);
      WriteLn('Exit condition met.');
end;

This inputs and prints an integer, then prints the integers greater than this, up to a maximum value of ... 19. Note this: 19, not 20 - the exit condition is that input is equal to 20, so an outgoing value of 20 exits the loop without going back to the printing routine at the start. If we were to change

WriteLn(input);
input:=input+1;

to

input:=input+1;
WriteLn(input);

then the printed output would go up to 20, but the initial value would not be printed. Make sure you understand this. Care is needed in situations like this where the order of commands is crucial.


8.4 Infinite loops

There is an obvious weakness in this program. If we enter an integer greater than 20, or 20 itself (since it will be incremented to 21 before the exit condition is tested, which will result in a "false" result), then the exit condition will never be met, and the variable "input" will keep on increasing in value indefinitely! This "infinite loop" possibility must be considered whenever repeat loops are used - the general rule is that something within the actions must in some way modify the exit condition so that it is eventually made true.

We could pre-empt this problem in the macro above in two ways. First, we could add a check on the value of input when it is first entered - an if statement which gives an error message if the value is 20 or greater. Or, more elegantly, we could change the exit condition on the repeat statement to be (input>=20) rather than (input=20); this would also prevent an infinite upward sequence. However, the weakness was left in the program to illustrate how to handle infinite loops when they "slip through" the program design. So what happens when we enter a value of 20 or more into the macro?

The macro begins printing its infinite sequence, but in fact stops after about 15 seconds (on an iMac), having reached a value of about 660, because the output in the text window exceeds the 32k limit on text files. Alternatively, we can interrupt the infinite loop earlier by pressing CTRL-APPLE-. and holding them down for sufficiently long so that a "break" in the program is reached.


8.5 Waiting for user input using repeat loops

As a final point, we can also use repeat loops to wait for the user to press a certain key. For example:

macro 'Beep After Click [B]';
begin
      repeat until button;
      Beep;
      PutMessage('You have just pressed the mouse button.');
end;

You may recall that we met a version of this macro in the second chapter. We can also wait for one of the "shift" keys to be pressed:

macro 'Beep After Shift [B]';
begin
      repeat until KeyDown('shift');
      Beep;
      PutMessage('You have just pressed "shift".');
end;

We can also detect the "option" and "control" keys using the KeyDown command above (which is a boolean function). Bear in mind that the rest of the macro is "frozen" until the exit condition is met. The mouse click control routine can be extremely useful in interactive macros.


8.6 Introduction to while loops

The "while" loop is very similar to the "repeat loop" - again, it is conditional. Here, however, there is an "entry condition" rather than an "exit condition". This means that the "flow" through the loop is different. When the while statement is first encountered, the entry condition (a boolean expression) is evaluated, and if it is false, the whole while loop is ignored. If it is true, then the while loop is executed, then the computer re-evaluates the entry condition. The looping continues until the entry condition is calculated as false.


8.7 Structure of while loops

So immediately we can see the key difference between repeat loops and while loops - it is possible that a while loop will not be executed at all, whilst a repeat loop will always be executed at least once. The basic structure of a while loop is as follows:

while x do begin
      action 1;
      action 2;
      action 3;
end;

where actions 1-3 are the commands within the loop which are repeated, and x is a boolean expression: the entry condition. If x is false at the first evaluation, the whole while loop is ignored. If it is true, the actions are repeated until it finally turns out false. Note once again the "single line, single semicolon" structure:

while .. do begin .. end;

and note also the possibility once more of an infinite loop if the actions do not in some way affect the entry condition. The same "rule" as mentioned for repeat loops applies to while loops also, in order to stop infinite repetition: the actions within the loop must somehow modify the entry condition.


8.8 An example of a while loop

Here is an example of a while loop in a macro.

macro 'While [W]';
var
      input: integer;
begin
      input:=GetNumber('Enter a number less than 10',5,0);
      NewTextWindow('Macro Output');
      while (input<=10) do begin
            WriteLn('Entry condition met...');
            WriteLn(input);
            input:=input+1;
            WriteLn('Ending while loop...');
      end; {end of while loop}
      WriteLn('Entry condition failed...');
      WriteLn('End of macro.');
end;

Note the comment indicating that the "end" in the middle of the macro is linked to the while statement - not really needed here, but useful to have in more complex code.


8.9 Comparison of repeat and while loops

While statements are particularly useful in detecting "crazy" values like -9999 that may be used in data to mark missing values or the ends of defined intervals, for example, via loops like:

while (x<>-9999) do begin ... end;

Of course, a repeat loop could also be used, of the form:

repeat ... until x=-9999;

Repeat and while often overlap in this way, and there are no general guidelines for choosing which to use. The two questions we need to ask are: first, does the loop always need to be executed at least once, or can we get away with it sometimes never being executed? And second, would it be easier to have an exit condition, which causes the loop to stop when it is true, or an entry condition, which causes the loop to repeat when it is true? In many situations either can be used just as well. But consider this example:

repeat ... until ((not a) or (not b))

versus

while (a and b) do ...

The two versions are logically equivalent, but the "while" version is obviously a lot easier for a human to follow. In general, it is simpler to use a "while" loop if you find yourself writing a "repeat until not"-type loop. But as I said above, often either will do. Why not work out both while and repeat versions and see which looks best?


8.10 A word on multiple boolean expressions

Finally, note that using "or" as part of an exit or entry condition can cause problems of ambiguity, since no record is preserved of which part(s) of the or statement were actually true, i.e. what the precise reason was for the loop being exited or entered. If you type "repeat ... until (a>0 or b>0)" then when the loop stops, you won't know which variable(s) are indeed positive, just that one or more of them is. Another if statement is needed straight away afterwards, to give us the complete picture: was it a, b, or both?

Another more general note along this line is that every statement in a multiple boolean expression is always evaluated, which can result in some programs becoming inefficient. It's better to split them up as much as possible, which also makes the program easier to follow. Instead of using "if a and b and c or d... etc" then use multiple nested if statements, such that if a is false, then the computer doesn't have to worry about b, c, d, and so on, because it can jump through to the end of the if structure. This applies to repeat and while loops as well - better to nest them than to let things get complex and unreadable.


8.11 A language summary

We've now covered the basic language elements: for, if, repeat, and while. Combining these allows a huge diversity of macros to be written. Practice writing simple macros that use them, and have another look at the supplied macros. If you are keen, you could also look at the source code of NIH Image, which is available from

ftp://rsbweb.nih.gov/pub/nih-image/source/

and other Pascal programs, to find more programming inspiration. The rest of this manual is very specific to NIH Image and much less so to general Pascal.



9. Arrays

9.1 Introduction to arrays in Pascal

Arrays are "structured variables": they can contain more than one value, and each value is stored in a specific location. Arrays are "random access", which means that we can access any of the component values - we don't need to cycle through them in a specific order, for example. We needn't worry about how the computer stores array values and keeps track of them. All we need to know is the notation for accessing the values. In Pascal, if we have an array called ArrayName with 10 values, then the first value is

ArrayName[1]

and the 10th value is

ArrayName[10]

Basically, we use square brackets [] to enclose the appropriate "subscript" (the address in the array) of the desired array "element". To go through the terminology again: an array is a variable, made up of elements, each of which can be accessed using the appropriate subscript in square brackets. Now, that's as far as we need to go with arrays in Pascal. Not far at all! This is because arrays in NIH Image are very specialised, and their handling doesn't involve "standard" Pascal array commands. So from now on we will be concentrating specifically on NIH Image arrays.


9.2 The NIH Image user arrays: rUser1 and rUser2

We cannot define our own arrays in NIH Image. We have to use the limited selection of arrays that are already defined by the program. All of these predefined arrays are used by the program in storing data, during the normal (non-macro-based) operation of NIH Image, except for two which are specifically reserved for macro users. These are named rUser1 and rUser2, and we will be concentrating on these, at first. They are both "linear" arrays, with elements accessed via a single subscript. So, we can write values to these arrays using ":=" like this:

rUser1[3]:=5
rUser2[1]:=3.142
rUser1[x]:=4.2
rUser2[i+5]:=x

and we can read values from them in the same way:

x:=rUser1[1]
y:=rUser2[5]
z:=rUser1[i]

and so on. It appears that all the values are stored as real numbers, i.e. the type of the array variable is "real". Essentially, rUser1 and rUser2 are just two lists of numbers which we can modify at our will. Displaying their contents is a little more involved - read on.


9.3 How the non-user arrays work: rCount

The arrays in NIH Image are there to store results from measuring images, so array output is displayed in the "Results" window. The arrays record different aspects of the single region of interest (ROI) which is being measured. (See chapter 11 for coverage of ROIs in more detail). We need to really understand this, before we can know how array subscripts work.

The ordinary ("non-user") arrays are there to contain results like "area of ROI", "mean greyvalue of ROI", "maximum greyvalue in ROI", "minimum greyvalue in ROI", "length of ROI", and so on. When the user clicks on "Analyze" then "Measure", having already selected a ROI, then each of these arrays has a result assigned to it. Visualise the results from the first ROI measurement: the area goes into Area[1], the mean into Mean[1], the minimum greyvalue into Minimum[1], and so on (the array names aren't exactly right, but it doesn't matter). Then, say a different ROI is selected and measured. A whole new spread of results is generated, and these go into the element [2] ... so the mean goes into Mean[2], then area into Area[2], and so on. This assignment is all done remotely, by the program itself. So now we have the key question - how does NIH Image know where to put the results, in order to avoid overwriting previous measurement data? Results data are placed into the next available element of each array - this subscript will be the same across all the results arrays, since a complete set of results is always generated for each "Measure" command. The subscript is controlled by a counter variable called rCount.


9.4 How rCount works

The variable rCount is increased by one, automatically, following each "Measure" operation. So, picture a user making measurements on NIH Image, not using macros, just the standard program's features. rCount is initially 1. The first ROI is selected and the "Measure" command issued. A whole set of results is generated and placed into Area[rCount], Mean[rCount] and so on, which is equal to Area[1], Mean[1], etc. At the end of this operation, rCount is automatically incremented, giving it a value of 2. Say the user then does another measurement, on a new ROI. The results set generated go into Mean[rCount], Area[rCount], and so on, again, but this time rCount is 2, so the next element in each array is used. And so on and so on, until all the measurements are done.


9.5 Displaying the contents of the rUser arrays

The rCount variable is applied to rUser1 and rUser2 as well - but only for display. What this means is that from within a macro, we can access any elements of rUser1 and rUser2 that we like, changing them as we please - but as soon as we want to DISPLAY the contents of rUser1 and rUser2, we need to consider what rCount is doing. Let's use a simple macro to show this.

The first thing we need to do is to tell the computer we will be using only rUser1 out of all the arrays. We do this using

SetOptions('User1');

Note that we use 'User1', not 'rUser1' - potentially confusing! When assigning or reading values to or from rUser arrays, we must always have the "r" at the start. We can attach a label to this array, which will appear at the top of the Results window output:

SetUser1Label('Results');

Now we can assign a value to the first element in the array:

rUser1[1]:=5;

And we can display the Results window (which is there just to output arrays - it could alternatively be referred to as the Array window, I suppose) using

ShowResults;

However, if we put all these commands together, the macro doesn't work - the Results window comes up blank. This is because the ShowResults command depends on rCount, which has not been assigned a value. ShowResults displays all results from the arrays defined using SetOptions('..'), UP TO and including the elements numbered rCount. So to display our value, we need to set rCount equal to 1, using the SetCounter command:

SetCounter(1);

Note that we don't use rCount:=1; we have to use this special command instead. Our complete macro is now:

macro 'Array Test [A]';
begin
      SetOptions('User1');
      SetUser1Label('Results');
      rUser1[1]:=5;
      SetCounter(1);
      ShowResults;
end;

This produces the output:

      Results

1.     5.00

in the Results window. Basically, the output is a list of numbers, with the label of the rUser1 array at the top, and the subscript numbers at the left. The two decimal places seems to be a fixed number.


9.6 Using both user arrays

We can make this clearer by bringing in rUser2 as well. Look at the following macro.

macro 'Two Array Test [A]';
var
      counter: integer;
begin
      SetOptions('User1User2');
      SetUser1Label('Left');
      SetUser2Label('Right');
      rUser1[1]:=5;
      rUser1[2]:=6.35;
      rUser1[3]:=4.28;
      rUser1[4]:=7;
      rUser1[5]:=0.89;
      for counter:=1 to 5 do begin
            rUser2[counter]:=counter+4;
      end;
      SetCounter(5);
      ShowResults;
end;

Here we select both rUser1 and rUser2 for use, using the string 'User1User2'; note that there is no space between them. We then label the columns 'Left' and 'Right', then assign values to the array elements directly (for rUser1) and indirectly, via a for loop (for rUser2). Before we display the results, we have to issue the command

SetCounter(5)

so that the Results window will display the first five elements of both arrays. If we didn't do this, the values would still be there, unchanged, but they wouldn't show up! Note that the variable "counter" in the above macro is not connected to the "SetCounter" command in any way. The output of "Two Array Test" is shown below.

      Left     Right

1.     5.00     5.00
2.     6.35     6.00
3.     4.28     7.00
4.     7.00     8.00
5.     0.89     9.00

It's not hard to see how this table would fit onto an Excel spreadsheet.


9.7 Using rCount to put results into arrays

We don't need the rCount variable if we are just ASSIGNING values to array addresses. We could use any integer variable. But is is easiest to use rCount as a counter, because in order to display the results properly, we need to keep rCount updated anyway, each time we add a new measurement. Now: how can we assign values to the rUser arrays when we don't know in advance how many values are going to be assigned? We can't use a for loop - that would require us to know how many values were going to be needed. A repeat (or while) loop is the answer.
We have to increment rCount after each assignment, then repeat, to avoid overwriting previous values, until we have all that we need. A rough routine for this sort of measurement would be structured like this:

1. rCount starts at value x
2. Generate the result values and output them to rUser1 and 2
3. Show results
4. Increase rCount to x+1
5. Repeat if necessary

To redo one measurement, we can just reduce rCount by one and repeat: the previous value will be overwritten. In this sense, rCount is like a cursor on a written document. And if we want to clear all the data, we can reset rCount to one - its initial value - ready to start recording again from the beginning. We do this using the "ResetCounter" command, which is equivalent to SetCounter(1). How high can rCount go? It can range from 1 to 256, so rUser1 and rUser2 can both contain 256 values at maximum. So, this potentially gives us the capability to store and edit a large number of image measurements (or result values from somewhere else) in the rUser arrays. We'll look at a macro that does this in the next chapter.


9.8 Saving the array data and loading it into Excel

Next we need to know how to export the contents of the user arrays. The easiest way should be to select the Results window, then copy and paste the columns into Excel. Unfortunately, this doesn't work! Instead, we have to save the data from within the macro. We use Export('File Name') to bring up a "Save Data" dialogue box, in which the suggested file name will be 'File Name' (although it can be changed by the macro user). The user will have to specify a destination for the save. Now, there are a number of options concerning which data to save. The contents of rUser1 and rUser2 are classed as "Measurements", so this is the correct radio button (at the bottom of the dialogue box) to press. Trying other options, e.g. "raw data", will result in an error. Now, we cannot force the "Measurements" button to be pressed, but we can make sure that it is at least pressed when the box first appears, using the SetExport command. So this is our (necessarily imperfect) save routine:

SetExport('Measurements');
Export('Arrays');

All the user needs to do, then, is to click "Save", and an NIH Image "TEXT" file will be created with the results in. This is space-delimited data, and it can be loaded into Excel using the Text Import Wizard.


9.9 The other arrays

So, now we know how to set up, manipulate, and save the rUser1 and rUser2 arrays. We can store 512 results values in these, and easily output them in columns - much better than trying to cope with 512 separate variables. But we must be aware of what the counter is doing, as soon as we start doing "automatic" measurements, in order to avoid overwriting any data.

The next step is to look at the other arrays offered by NIH Image. These all have some role to play in the ordinary operation of the program, but we can bend them to the will of our macros, providing we know what we are doing. I won't go into more detail here: details of the other arrays are available in the main NIH Image manual in Appendix A. The thing to watch is that these arrays are automatically overwritten when measurements are made. So you really have to know what you are doing, if you plan to store other numbers in them.


9.10 The LineBuffer array

This is where NIH Image stores the data from the pixels in an image. So it's a pretty big array! Any image processing work that we do will generally involve the LineBuffer array. We'll cover it in more detail in chapter 11. For now, just remember that the basic principles of array elements and addresses apply to it just as they do to rUser1 and rUser2.



10. Introduction to image measurement

10.1 Image measurement and image processing

I make a (simplistic) division between two kinds of basic things you can do with NIH Image. The first is image measurement, where you basically leave the image unchanged, and just describe what it contains: areas, lines, points, etc. The second is image processing, which involves getting at the image pixels themselves and manipulating their values. This includes sharpening the contrast, edge detection, thresholding, noise removal, etc. This chapter of the manual is a brief introduction to image measurement; the next chapter is on image processing.


10.2 Creating a new image file

To create a new image file, click "File", then "New", then click the image radio button and enter a name. The new image will have the default size of 552 by 436 pixels unless changed. This is analogous to a text file - it can be saved, edited, reloaded, etc.


10.3 The MakeNewWindow command

We can also create a new image within a macro, using the MakeNewWindow command:

macro 'Make New Window [M]';
begin
      MakeNewWindow('New Image Window');
end;

and we can change the size of the image that we want by using SetNewSize(x,y) where x is the width and y is the height, in pixels. So this macro creates a 100 by 200 window:

macro 'Make Small Window [S]';
begin
      SetNewSize(100,200);
      MakeNewWindow('New Image Window');
end;

The SetNewSize command appears to be "global", in that once the above macro has been run, the default new window size changes to 100 by 200, and all new windows are set to this size until another SetNewSize command is used.


10.4 Image coordinates

NIH Image uses Cartesian coordinates to "map" an image, with the origin in the TOP LEFT HAND CORNER. This is not the same as the X and Y numbers which are updated in the Info window! These, more familiarly, are based on an origin in the bottom left hand corner. But for all our image processing macros, we will be working from the top left. We can illustrate the coordinate arrangement using MoveTo(x,y) which moves the "current location" to (x,y), and LineTo(x,y) which draws a line from the current location to (x,y). This is most clearly explained by reference to the following macro, which draws in the diagonals of a square image window.

macro 'Draw Cross [D]';
begin
      SetNewSize(400,400);
      MakeNewWindow('New Image Window');
      MoveTo(0,0);
      LineTo(400,400);
      MoveTo(400,0);
      LineTo(0,400);
end;

The four corners of the square image have the following coordinates:


(0,0)..............(400,0)
.                        .
.                        .
.                        .
.                        .
.                        .
.                        .
.                        .
.                        .
(0,400)..........(400,400)


10.5 The random lines macro

You might think that coordinates would have to be integers - there is no "half a pixel", right? True, but NIH Image does in fact allow coordinates with decimals - it just rounds them off to integers. This allows a primitive macro to be written that draws random lines on a new square image. The command "random", which needs nothing following it, generates a random real number between 0 and 1; if we multiply our x and y coordinates by random coefficients (which we can do "in situ" in the LineTo and MoveTo commands, as with other commands), then we get a whole load of random lines - very artistic.

macro 'Random Lines [L]';
var
      counter: integer;
begin
      SetNewSize(500,500);
      MakeNewWindow('Random Lines');
      for counter:=1 to 100 do begin
            MoveTo(random*500,random*500);
            LineTo(random*500,random*500);
      end;
end;


10.6 The main project: a point-measuring macro

The rest of this chapter will give a "walk-through" commentary on how to write a macro to do a simple, but useful, task. The user moves the mouse pointer over an image, and clicks on a feature, and the location of this feature is recorded in the rUser arrays (in terms of x and y pixel coordinates). Up to 256 points can be located in this way. We also want to build in an undo feature, and a save feature.


10.7 SetCursor and GetMouse

First we need to set the cursor to a cross, to allow accurate positioning; we do this using:

SetCursor('cross');

and it will stay in this shape until we change it back to an arrow (by using 'arrow' instead of 'cross'; 'watch' or 'finger' are the other options, for interest's sake). Then we wait until the mouse button is pressed, then use

GetMouse(MouseX,MouseY);

which assigns the x and y coordinates of the mouse pointer, at the time of the click, to the integer variables MouseX and MouseY (which we have to type-assign beforehand, of course). Now, as usual, these coordinates are calculated from the top left hand corner of the image. But - this is important - they are not limited to the area of the image. A click above the top of the image will just give a negative y coordinate, and a click to the left of the left hand side of the image will give a negative x coordinate. Clicking in the top left hand corner of the monitor screen will result in both coordinates being negative. But there will not be an error message. The GetMouse command covers the whole screen - it only complains if there is not an active image window open. This window doesn't have to be on "top" of the desktop, and nor does the click have to be within the image boundaries. So we have to be careful of nonsense results.


10.8 Reading a single mouse position

We then choose rUser1 and rUser2 as our results arrays, and label them for the X and Y coordinates respectively:

SetOptions('User1User2');
SetUser1Label('Mouse X');
SetUser2Label('Mouse Y');

Then we can assign the coordinate values, set the display counter appropriately, and display them:

rUser1[1]:=MouseX;
rUser2[1]:=MouseY;
SetCounter(1);
ShowResults;

So, the whole macro so far, which reads the coordinates of a single mouse click into the rUser arrays and displays the results, is:

macro 'One Mouse Click [M]';
var
      MouseX: integer;
      MouseY: integer;
begin
      SetCursor('cross');
      repeat until button;
      GetMouse(MouseX,MouseY);
      SetOptions('User1User2');
      SetUser1Label('Mouse X');
      SetUser2Label('Mouse Y');
      rUser1[1]:=MouseX;
      rUser2[1]:=MouseY;
      SetCounter(1);
      ShowResults;
end;

This may not seem like much, but it's an important first step in our project. It's always a good idea to get a simple routine like this working first, then move on to a more complex program.


10.9 A repeat loop for multiple mouse clicks

The next step is to be able to record the coordinates of multiple mouse clicks, by using the higher addresses in the rUser arrays (and keeping the same labels: rUser1 is X, and rUser2 is Y). Now, we know that the maximum size of the rUser arrays is 256, so we need a counter that goes up to 256 then stops. We could use a for loop for this, but later on we'll be dealing with open-ended numbers of clicks, so right from the start we will use a "repeat ... until" structure.

macro 'Measure Positions v.1 [M]';
var
      MouseX: integer;
      MouseY: integer;
      counter: integer;
begin
      counter:=1;
      SetOptions('User1User2');
      SetUser1Label('Mouse X');
      SetUser2Label('Mouse Y');
      ShowResults;
      repeat
            SetCursor('cross');
            repeat until button;
            GetMouse(MouseX,MouseY);
            wait(0.5);
            rUser1[counter]:=MouseX;
            rUser2[counter]:=MouseY;
            SetCounter(counter);
            UpdateResults;
            counter:=counter+1;
      until counter=257;
end;

So, the variable counter starts off as 1, and is incremented with each pass through the repeat loop, until the exit condition (counter=257) is true. Why 257? Look carefully at the location of the counter:=counter+1 statement ... right at the end of the repeat loop. Clearly, the final pass through the loop will be when counter=256, then when it gets incremented right at the end, the exit condition will become true. So the program will measure up to the 256th click, then stop. I think it is clear why we only get 256 clicks recorded even though the exit condition uses counter=257. (In fact, it's unlikely that many people would want to measure that many points on an image - but it's best to make the program robust in this way).


10.10 Some features to note

Note also that the SetCursor command must be inside the repeat loop - otherwise the cursor spends most of its time as a watch. This is because of the wait(0.5) command after GetMouse - wait half a second. It is the wait which causes the (automatic) change of cursor to a watch ... so why do we need to wait in the first place? This is because NIH Image is so quick (in this simple program, anyway!) that a single click means that it rushes through the repeat loop and back to the "repeat until button" command before your finger has released the mouse button from the first click! In fact, on my iMac, each single click is long enough for the computer to whizz round the repeat loop about 8 times! So, without the wait command, each click registers as 8 clicks in the same location. This is a very strange bug - it's hard to link the symptoms with the cause. But it is quite common - it's useful to deliberately slow down the computer sometimes just so you can see what it is doing.

So, we use "counter" to address both arrays, and in the SetCounter command for display. Now read through the whole macro, making sure you understand why each command is there. Note the results display commands. After setting the column labels, we use ShowResults to bring up the Results window (even though it is blank at this stage). Then after each click we use UpdateResults, which adds only the last measurement to the Results window. This is better than just using "ShowResults" each time, since it scrolls the window if it gets too small - ShowResults does not (although the data is still "there" - this is a display issue only).


10.11 Forcing clicks to be within the image area

This is a fairly versatile program, but we still have a problem with "impossible" locations. If you're above or left of the image area, then one of the coordinates will be negative, which is obvious enough, but if you're below it or to the right, you need to know exactly how big the image is, in order to know when the click is within its area. We can do this using

GetPicSize(width,height);

then checking that MouseX and MouseY are not negative, and are not greater than the width or height respectively. If they are, then we can put out an error "beep", then allow the user to click again for that particular measurement. Sorting out this problem lengthens the program so much that things start to get complicated. So, I've removed part of the code into a procedure. Look closely at the macro below - it does the same as the one above, but forces all the registered clicks to be within the image area. Those that aren't, have to be redone.

At each click, the OutOfBounds boolean condition is evaluated (it's in the procedure). Then, if it is true, the while loop forces another run through the measuring procedure, without updating the counter - so the previous coordinates are replaced. The macro just beeps and the top screen bar flashes to indicate that the click "won't go".


10.12 An exit option

We have another major issue to solve. Say the user wants to stop before measurement 256? The only way he can do so, so far, is to hold down control-apple-. and press the mouse button at the same time - quite a test of dexterity! We can use the KeyDown boolean function to detect whether one of the shift keys is depressed immediately after the repeat until button command. If this boolean condition is true, then we can build in an "exit" command which will stop the macro. The simple exit operation is now: hold down shift, then click the mouse - the macro will finish, without recording that last click as a measurement.


10.13 Here's the actual macro (so far)

procedure GetClick;
begin
      SetCursor('cross');
      repeat until button;
      if KeyDown('shift') then begin
            exit;
      end; {if}
      GetMouse(MouseX,MouseY);
      wait(0.5);
      OutOfBounds:=(MouseX<0) or (MouseX>width) or (MouseY<0) or (MouseY>height);
end;


macro 'Measure Positions v.2 [M]';
var
      MouseX: integer;
      MouseY: integer;
      counter: integer;
      width: integer;
      height: integer;
      OutOfBounds: boolean;
begin
      GetPicSize(width,height);
      counter:=1;
      SetOptions('User1User2');
      SetUser1Label('Mouse X');
      SetUser2Label('Mouse Y');
      ShowResults;
      repeat
            GetClick;
            while OutOfBounds do begin
                  Beep;
                  GetClick;
            end; {while}
            rUser1[counter]:=MouseX;
            rUser2[counter]:=MouseY;
            SetCounter(counter);
            UpdateResults;
            counter:=counter+1;
      until counter=256;
end;


10.14 Macros to save and clear the data

Two other small macros can be added to make the project more versatile. The "Save Data" macro is very simple:

macro 'Save Data [S]';
begin
      SetExport('Measurements');
      Export('Image Data');
end;

This states that the data to be saved are in the rUser arrays, and makes the suggested file name "Image Data". The "Save" dialogue box appears, with the "Measurements" radio button clicked and the file name already in place; all the user has to do is click the "Save" button, and the data is saved (in delimited format, which can be loaded into Excel) in the current directory. The directory can be changed by the user if desired.

The "Clear All Data" macro is also very simple: we use ResetCounter, which sets rCount to 0, so that the ShowResults command actually just displays a blank window.

macro 'Clear All Data [C]';
begin
      ResetCounter;
      ShowResults;
end;

This macro is really only for cosmetic purposes: the main macro is written in such a way that it automatically renews the results arrays when it is rerun. However, before the first click is made on the second run, the results from the first run will still be displayed in the Results window, which can be offputting. It is only when the first click is registered that the Results window is wiped and the first new coordinates appear. So it is useful to have this short macro available to stop things getting confused.


10.15 An "undo" option

Much more important is an "undo" option. It is more user-friendly to build this option into the main macro itself rather than have a separate macro. Here is the whole thing, with the new undo option included.

procedure GetClick;
begin
      SetCursor('cross');
      repeat until button;
      if KeyDown('shift') then begin
            exit;
      end;
      UndoFlag:=(KeyDown('option'));
      GetMouse(MouseX,MouseY);
      wait(0.5);
      OutOfBounds:=(MouseX<0) or (MouseX>width) or (MouseY<0) or (MouseY>height);
end;


macro 'Measure Positions v.3 [M]';
var
      MouseX: integer;
      MouseY: integer;
      counter: integer;
      width: integer;
      height: integer;
      OutOfBounds: boolean;
      UndoFlag: boolean;
begin
      GetPicSize(width,height);
      counter:=1;
      SetOptions('User1User2');
      SetUser1Label('Mouse X');
      SetUser2Label('Mouse Y');
      ShowResults;
      repeat
            GetClick;
            while OutOfBounds do begin
                  Beep;
                  GetClick;
            end; {while}
            if UndoFlag then begin
                  counter:=counter-2;
                  SetCounter(counter);
                  UpdateResults;
                  counter:=counter+1;
            end
            else begin
                  rUser1[counter]:=MouseX;
                  rUser2[counter]:=MouseY;
                  SetCounter(counter);
                  UpdateResults;
                  counter:=counter+1;
            end; {if}
      until counter=256;
end;

We have a new boolean variable, UndoFlag, which has the value:

UndoFlag:=(KeyDown('option'));

which means that UndoFlag is true if the option key (which is labelled "alt" on the iMac) is held down when the mouse is clicked. This is a good key to use for Undo, since it is conventionally held down with another key to do the opposite of what that key normally does, e.g. zoom out instead of zoom in, in both NIH Image and Photoshop. We then use an if command to choose either the standard "add this click to the results arrays" if UndoFlag is false, or to execute a new piece of code if UndoFlag is true. The latter is:

counter:=counter-2;
SetCounter(counter);
UpdateResults;
counter:=counter+1;

This may not be obvious at first. Why do we go back two places, then show the results, then go forward one place? The answer is: for display purposes only! We could just deduct one from the counter, then re-enter the main program. This would make the next click's coordinates overwrite the previous ones. However, we really want to "wipe" the previous pair of coordinates from the Results window, to show that the Undo operation is working.

To do this, we need to set the counter to one less than the last pair, then use "UpdateResults", so that the results will only be displayed up until the penultimate pair. The last pair of coordinates are still there - they are just not being displayed! Then, after the UpdateResults command, we can increase the counter by 1, so that it is ready to overwrite the last (and now hidden) pair of coordinates once more. I hope this is clear! If not, it is simple to verify that it works.


10.16 Future work

Think about what could make this macro even better. It could be made more robust: if the user tries to "undo" beyond the start of the rUser arrays (if he immediately uses the undo option, for example), the macro crashes. A command could easily be added to detect this and prevent it happening.

One obvious idea for future development is to add a marker of some sort to the image, to show where the clicks actually are. Of course, this will affect the image pixels. One solution is to program in a transparent "overlay" which can mark the user's measurements without affecting the pixels. This has been implemented in "Object Image" by Norbert Vischer. It's an extended version of NIH Image, available from:

http://simon.bio.uva.nl/object-image.html

More details are given in the last chapter. Alternatively, if you only need a simple image-measuring macro of some kind, or a very specialised one, why not investigate writing your own?



11. Introduction to image processing

11.1 The digital image concept

Digital images (such as those processed in NIH Image) are made of discrete pixels. The image processing in this chapter is basically a set of ideas for operating on the pixels that make up an image. For simplicity, we deal only with greyscale ("black and white") images, since these only have a single value for each pixel (unlike colour images). We'll learn how to access these values and modify them, thus enabling us to do the basics of image processing.


11.2 Regions of interest (ROIs)

An ROI is selected (in non-macro mode) using the mouse - ROIs can be rectangular, linear, oval, or more complex shapes. Within a macro, an ROI can be defined by specifying its coordinates and size, using the MakeROI command:

macro 'Make ROI [M]';
begin
      MakeROI(0,0,100,100);
end;

The above macro selects a rectangular ROI with its top left hand corner at (0,0) - which is the top left hand corner of the image, remember - and pixel dimensions of 100 x 100: a square. For other types of ROI, see the NIH Image Manual. If no ROI is explicitly selected, the whole image is assumed to be the ROI for the purposes of the commands in the next section.


11.3 Simple commands

Before getting into more complex things, there are several simple commands that can change pixel values - AddConstant, ChangeValues, EnhanceContrast, Fill, Invert, etc. These all operate on the current ROI, and are exactly equivalent to selecting the appropriate commands from NIH Image in non-macro mode. So, to invert an ROI, we can use:

macro 'Invert [I]';
begin
      Invert;
end;

This will invert the whole image if no ROI is selected, or just the ROI if one is selected (try running it immediately after "Make ROI", above). But there's no point in doing this for one image - we might as well just use the menu command "Invert". The only justification for using a macro like this is if we need to do the same thing to a large number of files: such batch processing is covered later in this chapter.


11.4 GetPixel and PutPixel

GetPixel allows us to access pixel values, one by one, and PutPixel to specify the values we want certain pixels to have. Simply enough, GetPixel(x,y) returns the value of the pixel at coordinates (x,y), and PutPixel(x,y,z) assigns the value z to the pixel at (x,y).

Note, however, that this is not generally a sensible way to do image analysis. Pixel-by-pixel tinkering is very slow, and only worth doing if you a small number of pixels are involved. To cover a whole image, pixel by pixel, it's much faster to work in rows.


11.5 GetRow, PutRow, and the LineBuffer array

There can be hundreds of thousands of pixels in an image. Storing this information needs seriously big arrays. Image pixel greyvalues can be accessed and modified via an array called the LineBuffer, which reads one line of the image in at a time. This buffer can get very long - there is no point trying direct addressing here, like LineBuffer[6]:=34 ... we have to sort out a for loop that will cover the whole array. And to cover the whole image, we need to feed all of it through the LineBuffer array, line by line. This operation is conceptually simple, though it requires a lot of computer processing power.

We can access each pixel of an image using the GetRow command. This feeds a row of pixel values into the LineBuffer array. The length of the row, and its starting coordinates (from the top left, as always) are defined in the GetRow command:

GetRow(x,y,length)

is the correct syntax, where (x,y) are the coordinates of the left-hand end of the line of pixels, and length is its length - which must equal the total width of the image, if we want to access the whole image a row at a time. Once this command has been executed, then LineBuffer[1] contains the greyvalue of the first pixel, LineBuffer[2] the second, and so on up to LineBuffer[length], which represents the final pixel.

We can, of course, alter the elements of LineBuffer to whatever values we like, cycling through them all using a for loop. Then we can output these new values to the image, via the "mirror image" of GetRow, which is PutRow. This has the same syntax:

PutRow(x,y,length)

places "length" pixels from the LineBuffer array into a row in the current image, starting at location (x,y). As we will be processing the whole image, without wanting to displace anything, we will always keep x, y, and length the same for each "pair" of GetRow and PutRow commands.

One more thing we need to know is the dimensions of the image, i.e. how long is each row going to be, assuming we want to cover the whole image, and how many rows are there, i.e. what is the vertical height of the image? We can obtain the image dimensions, in pixels, using

GetPicSize(width,height)

where width and height are in pixels. Of course, there is potential for confusion if more than one image is open - we must be sure we are measuring the dimensions of the right one. The simplest way to do this is to build an error-detecting command into the macro that ensures that only one image is open, i.e. that nPics=1. Alternatively, we can activate the window we want using a suitable identifier (see later).


11.6 Changing pixel values - two examples

So, it's now fairly simple to write a macro to change every pixel in an image. The example below changes every pixel to a greyvalue of 25 - a pale grey. Not much use, but it shows how the basic "pixel-altering" structure works, via a row-by-row approach. Note the nested for loops which run along each row, and down the "column" of rows that makes up the image.

macro 'Make All Pixels 25 [P]';
var
      width: integer;
      height: integer;
      counter: integer;
      counter2: integer;
begin
      GetPicSize(width,height);
      for counter:=0 to height do begin
            GetRow(0,counter,width);
            for counter2:=1 to width do begin
                  LineBuffer[counter2]:=25;
            end;
            PutRow(0,counter,width);
      end;
end;

It is possible to do this type of thing using columns, instead: there are equivalent GetColumn and PutColumn commands. For now we'll just use rows.

We can easily modify the above structure to change pixels however we like. For example, we can generate a "snowstorm" by changing each pixel to a random value between 0 and 255, which we do using the "random" command:

macro 'Snowstorm [S]';
var
      width: integer;
      height: integer;
      counter: integer;
      counter2: integer;
begin
      GetPicSize(width,height);
      for counter:=0 to height do begin
            GetRow(0,counter,width);
            for counter2:=1 to width do begin
                  LineBuffer[counter2]:=(random*255);
            end;
            PutRow(0,counter,width);
      end;
end;

Note that LineBuffer is only "designed" to hold 8-bit values, i.e. on a scale of 0 to 255. Values outside this range will cause a crash. So, if you are doing calculations which could result in a value outside the range (e.g. adding multiple pixel values together, then dividing to get an average), then you must use intermediate variables, placing only the final averaged value back into the LineBuffer.


11.7 Macros in action

Image processing in this way is very slow. If you plan to do a lot of it, then you'll save time in the long run by learning to program "proper" Pascal add-ons to NIH Image, rather than just macros. For now, we may find ourselves stuck with macros that take 10 minutes per image, or more. The best thing to do here is to read a good book, and only look up when the macro is finished and a new image can be got started. Think about ways to design a 'user-friendly' macro. For example, make it beep when it is finished, then you won't need to watch the screen.

Another observation is useful in this case. There are two ways to run a macro: via a "hot key", or using the mouse to select the macro name from the Special menu. The latter is slower, but has a (probably serendipitous) advantage that the "Special" title stays highlighted until the macro has finished (upon which it reverts to normal). This is not the case with the "hot key" method. This means that the "menu selection" option gives a visual indicator of whether or not the macro has finished, which is useful when you are doing something else, and need to know this at a glance.

If you have a lot of images to process, it's worth looking at the batch processing method (see later in this chapter).


11.8 To repeat two earlier points

Remember that you don't need to write a macro if (1) NIH Image can already do the job you need, or (2) someone else already wrote a macro that can do it. Let's take an example.

I wanted to average some images. I was fairly sure that NIH Image could do this without a macro - and it can. It is possible to load a whole set of images into NIH Image, then to convert them into a "stack". Then, the "Average" command (under the "Stack" menu) produces an arithmetic average of the images in the stack - exactly what I wanted.

Unfortunately it only works with images up to 2048 pixels wide - and mine were 2592 pixels wide. I was also limited by memory - even giving NIH Image 128MB only allowed ten separate images to be loaded to the stack, and loading in all 47 of my images would have required c. 450MB ... and I only had 196MB of RAM in total! So in this case, I had to write a macro to get round the limits of NIH Image - and it was slow, and inefficient. But even so, it only took me a few days to do the whole operation.

I won't bother with the whole code for the averaging macro here. The core is very simple - row by row, use GetRow to get the LineBuffer values from each image and add them to the overall average, then create the average image using PutRow. But there are two more useful points to cover.


11.9 Selecting among multiple image windows

How can we switch between different image windows when we have more than one image open at the same time? The easiest way to do this is using the SelectWindow command:

macro 'Select Window [S]';
begin
      SelectWindow('Test Image');
end;

This macro will select the window called "Test Image" - if there isn't a window called this, then a 'Window Not Found' error is returned, so we must be sure that the window is actually there! Selecting a window brings it to the top of the desktop and makes it the "Active Window" - you can tell the active window because it has horizontal lines either side of the title in the title bar. Many macro operations (e.g. the Write command) operate on the active window, so if we have more than one window open, we need to make sure that the active window is the right one to operate on.

If we don't know the image's exact name, we use the "PIDNumber" function, which is more versatile, since it uses the ORDER OF LOADING to assign each image window a different, permanent number. Since PIDNumber is a function, it returns a numerical value (a bit like nPics), so we must assign its value to a variable:

macro 'PIDNumber';
var
      PicID: integer;
begin
      PicID:=PIDNumber;
      PutMessage(PicID);
end;

If we run the above macro with no image open, we just get a PicID value of zero. With one image, we get -1. The way the assignment works is that the first image to be loaded gets -1, the second -2, the third -3, and so on. These numbers are permanent, and they are not reset by other images being closed or re-opened. Consider carefully how the assignment works. Let's say we open image A first, then image B, then image C. Our PIDNumbers are -1, -2, and -3. We then close A and B, so that we only have C open. Its PIDNumber stays at -3. We then reopen image B, then reopen image A. Image B now has the PIDNumber -4, and image A gets -5. And so on - you can't change the PIDNumber of an image without closing it and reloading it. I hope this is clear.

Now, the function PIDNumber only returns one value at a time - the PIDNumber of the active window, the window at the top of the desktop with the lines on its title bar. There can only ever be one active window. A newly-loaded image will always be the active window.

So, how do we move between windows within a macro, once we know which PIDNumber we want? There are two options. The SelectPic(x) command selects the window with PIDNumber x and brings it to the top of the screen. The ChoosePic(x) command is identical, but doesn't move the selected image to the top - technically, it "selects" the window without "activating" it. The latter is more useful in macros where different images need to be accessed, since we don't want the screen forever flicking back and forth between the different windows being activated.

The golden rule is always to capture the PIDNumber of an image when you KNOW that it is the active image (e.g. when it has just been loaded up), and then to put this value into a variable with a meaningful name (like 'ThirdImagePID'), then you can switch to it later, whenever you like, safe in the knowledge that the PIDNumber never changes.


11.10 Batch processing

This is one of the most time-saving capabilities of macros. Any operation, no matter how simple, is worth coding a macro for if you have to do it to 1,000 images, because you can batch-process the whole lot in one go without having to load them in individually. In general, if you have so many images that it's worthwhile writing a batch-processing routine for them, then it won't be feasible to type in all the filenames - instead, you'll need to generate all the filenames required. The easiest way to do this is to have "progressively numbered" filenames, which can be generated using a for loop. This is the crux of batch-processing: generating the correct list of filenames. The rest is simple: we use "open('filename')" to open each image in turn, then use a procedure to process it, then save it with "save" and close the window with "close".

For example, let's say we have five images, with filenames "Image1" through "Image5". We can generate these file names by having a for loop running from one to five and adding this number to the string "Image". Look at the following macro:

macro 'Load Five Images [L]';
var
      counter: integer;
begin
      for counter:=1 to 5 do begin
            open('Image',counter:1:0);
      end;
end;

See how we use "counter" to generate the numbers? Note that we have to specify "counter:1:0" as the suffix in the "open" command, to remove any spaces before the number. Running the macro does bring up a dialogue box, for the first file, but when "Open" is clicked, the remainder of the files are also loaded without any more dialogue boxes. This will only work as long as all the files are in the same directory; if they are not, then the full path needs to be specified in the macro. This would be something like 'MacintoshHD:Images:New:Image1' instead of just the filename.

To load in and process all five images, we just add the commands described above. The macro below uses "Invert" as an example process:

macro 'Invert Five Images [I]';
var
      counter: integer;
begin
      for counter:=1 to 5 do begin
            open('Image',counter:1:0);
            Invert;
            save;
            close;
      end;
end;

Once the initial "Open" is clicked in the dialogue box, this macro loads each image in turn, inverts it, saves the change, and closes it. Simple really.



12. Where next?

12.1 Doing more things with macros

Study the NIH Image Manual and "Inside NIH Image", and experiment with the list of macro commands. This will greatly increase the range of things that you can do with macros. Look once more at the supplied macros, too. There is also some specific literature available for different applications of NIH Image, e.g. confocal microscopy, gel analysis, etc. See the NIH Image home page at

http://rsb.info.nih.gov/nih-image/

Information of all kinds is also available on the NIH Image mailing list. Details are available at

http://rsb.info.nih.gov/nih-image/list.html

The list archives are stored at

http://list.nih.gov/archives/nih-image.html


12.2 Better programming

Learning the basics of good programming strategy is useful for writing complex macros. For example, splitting a long macro into manageable procedures and functions, and testing these thoroughly before putting the whole thing together. The overall structure of a macro can also be tested, even before the component parts are finished, by writing a "framework" macro which shows how it all fits together. For the best results, seek to combine the "bottom up" and "top down" approaches. Don't be reluctant to try out different commands just to see what will work - you can't harm the computer. And remember, good programmers modify what they already have, and don't write what they don't need to. Read some books on general computer programming.


12.3 Modified versions of NIH Image

The open source code of NIH Image promotes the develop of "evolved" versions with extra capabilities. One I use often is "Object Image", which provides a transparent overlay for marking measurements on (among many other features). It is available from

http://simon.bio.uva.nl/object-image.html

There are many others which can be downloaded from several sites, including

ftp://ftp.gwdg.de/pub/macintosh/nih-image/

http://www.ccp14.ac.uk/ccp/ccp14/ftp-mirror/nih-image/pub/nih-image/

See if one of these has the capabilities you need. Also available are lots of user-programmed macros.


12.4 Modifying NIH Image itself

Modifying the Pascal code that comprises NIH Image itself is a way to write much faster and more powerful macros. These obviously need to be written in proper Pascal, not the miniature version that we've been discussing. The NIH Image source code is available from the NIH Image home page. Some details of working in Pascal, within NIH Image, are in "Inside NIH Image", also available from the NIH Image home page.

To learn Pascal, try "Oh! Pascal!" by Doug Cooper - the most accessible book I've found - or any equivalent (one is called "Learn Pascal in Three Days", if you're really short of time (!)). One Pascal compiler for the Mac is THINK Pascal, available from

http://www.lysator.liu.se/~ingemar/tp45d4/think.html

And bear in mind that the reason for all the development activity is the "open source" nature of NIH Image. Read more about open source programs at the Open Source Initiative at
www.opensource.org.


12.5 More on image analysis

A good book on general image analysis is "The Image Processing Handbook" by John C. Russ. There are many others. Image analysis (and by extension, NIH Image) has a vast range of potential applications. The only limit is your ingenuity.


Return to Top