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
http://rsbweb.nih.gov/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.