IDL Programming Techniques 2nd Edition
IDL Programming Techniques 2nd Edition
Programming
Techniaues I
Second Edition
--
David W;Fanlzing, Ph.D.
IDL Programming Techniques
Second Edition
David W. Fanning, Ph.D.
Copyright O 2000 Fanning Software Consulting. All lights reserved. No part of the contents of this book
may be reproduced or transmitted in any form or by any means without the written permission of the
publisher.
Printing History
October 2000: First Printing
November 2000: Added information on double buffering of graphics displays.
April 2003: Minor updates to image processing section.
ISBN 0-9662383-2-X
ISBN 0-9662383-2-X
April 2003
+ Discovering the Possibilitie
Table of Contents
iii
Help with IDL Commands 8
Creating Command Journals 8
Creating Variables 9
Variable Attributes Change Dynamically 10
Be Careful With Integer Variables 11
Working with Vectors and Arrays 13
Creating Vectors 13
Using Array Subscripts 13
Creating Arrays 14
Accessing Elements in Arrays 14
Extracting Vectors and Subarrays 15
Working with IDL Graphics Windows 15
Creating Graphics Windows 15
Determining the Current Graphics Window 16
Making a Graphics Window the Current Graphics Window 16
Deleting Graphics Windows 17
Positioning and Sizing Graphics Windows 17
Bringing a Graphics Window Forward on the Display 17
Putting a Title on a Graphics Window 18
Erasing a Graphics Window 18
An Alternative to TVRD 73
Basic Image Processing in IDL 74
Histogram Equalization 74
Smoothing Images 75
Removing Noise From Images 77
Enhancing the Edges of Images 77
Frequency Domain Filtering of Images 78
Building Image Filters 78
viii
Adding Attributes to Scientific Data Sets and HDF Files 170
Gathering Information about Scientific Data Sets 172
Gathering SDS Attribute Information 173
Adding Color Palettes to HDF Files 173
Examples of Reading and Writing HDF Files 174
xiv
CW-FSlider Event Stl-ucture 393
CW-Orient Event Structure 394
CW-PDMenu Event Structure 394
CW-RGBSlider Event Structure 394
CW-Zoom Event Stl-ucture 394
FSC-InputField Event Structure 394
Widget Program Event Sttuctures 394
XColors Event Structure 394
ReadImage Event Structure 395
Other Widget Event Stsuctures 395
Keyboard Focus Events 395
Kill Widget Request Events 395
Widget Timer Events 395
Widget Tracking Events 395
Index ...................
IIIlI~lLllmmIlnnllmu~Blmulmmmllm~lmmmllBllllnlamlllmlmllmlnlllllllllmmBmlllmll 435
Preface
Preface to the First Edition
Writing a book must surely be one of the most difficult, solitary, and lonely things I
have ever done. And yet, paradoxically, it cannot be done at all without the help and
support of a great many people. Foremost among these is my wife, Carol. It is not pos-
sible to write a book, make a living, and pay adequate attention to hearth and home, all
at the same time. Carol has made it possible-in more ways than you can imagine-to
have this obsession realized. I am deeply grateful to her and to our three sons for the
time I borrowed from their lives. Meny Christmas! I am finally home.
Many people have read earlier versions of this book and have offered their comments
and suggestions. I wish to thank all of them for their help. I am particularly indebted to
Dick Jackson of the National Research Council of Canada who volunteered a great
many hours of his own time to read the entire manuscript from start to finish and offer
extensive valuable suggestions. This is a much better book as a result of his efforts. I
am also thankful to many fine folks at Research Systems and on the IDL newsgroup
who have patiently answered many of my questions about IDL and have been support-
ive of my work. The errors that remain in the book are entirely my own.
I owe a special debt of gratitude to David Stern, the founder of Research Systems and
the original creator of IDL. I spent five of the best years of my professional life work-
ing for David. I am deeply grateful for the freedom David gave me to pursue my
overriding interest, which was to teach people how to use IDL. Working with IDL is
the best job I have ever had.
I don't think this book would have ever happened without a thin volume of stories by
Barry Lopez, called River NotesDesert Notes. I don't know why this book speaks to
me in the way it does, but I know I owe much of my professional life and development
to its messages. Stories tell us who we are and connect us to the world we live in. I
often tell his wonderful story, Directions, in my IDL courses. It is a powerful symbol
to me of what I am about both personally and professionally. Thank you, Mr. Lopez,
for guiding and nourishing me with the mystery of your stories.
Finally, I wish to thank the hundreds of people who have attended my IDL program-
ming courses over the past six years. I learned almost everything I know about IDL
from you. Thanks for making those workshops warm, safe places where we could all
make mistakes and learn from one another. This book is written especially for you.
Fort Collins, Colorado
Christmas Eve, 1997
xvii
Preface
xviii
Getting Started
Chapter Overview
The purpose of this chapter is to explain why I wrote this book, what you can expect to
get out of reading it, and to give you information that will make it easier to work
through the IDL programming examples you find here. Specifically, you will learn:
* How this book is organized
* How to use this book
How to download and organize the files that come with this book
How to use variables, keywords, and commands in IDL
How to create and work with vectors and arrays in IDL
How to work with graphics windows in IDL
book is an overview of the essential elements of IDL for people who don't like to read
manuals and who learn best by example. It is a compendiun~of IDL programming tips
and techniques that can only be learned by experience. Essentially, this is the book I
wish I had when I was learning to use IDL!
Realize that if you change the current color table on a machine with a 24-bit display
and you are working at the IDL command line, you will almost certainly need to re-
type the graphic display command to see the new colors take effect. This is normal
and is a direct effect of how 24-bit color works. You will see how to deal with this
more effectively later in the book.
You can determine the depth of your color display by typing these two commands:
IDL> Device, Get-Visual-Depth=thisDepth & Print, thisDepth
Commands that you should type at the IDL command line are preceded by the IDL
command prompt, IDL>, like this:
IDL> Surface, data
Other IDL commands will be typed in editor windows. You can use the editor of your
choice or the editor that is supplied with IDL. It is up to you.
Capitalization
I use a particular style of capitalization for IDL commands in this book. This style is
completely arbitrary. IDL is case insensitive, except for commands that interact with
the operating system (e.g., the file names of commands that open files will be case
sensitive on UNIX machines) and when it is performing string comparisons. The
capitalization is meant to help you remember command and keyword names and to
give you a visual clue as to the function of words on the command line.
I capitalize the first letter of all IDL commands and keywords. In addition, any letter
that may serve as a mnemonic is also capitalized. For example:
Surface, data, CharSize=2.0, Color=180
XLoadCT
Widget-Control, tlb, Set-Walue=info, /NO-Copy
I do not capitalize the first letter of variable names, although I may capitalize
subsequent letters in variable names that may be compound words. For example:
data = FIndGen(l1)
buttonvalue = thisvalue
ptrToData = Ptr-New ( )
I completely capitalize IDL reserved words. For example:
REPEAT test UNTIL
FOR j=0,10 DO BEGIN
ENDWHILE
You may use any capitalization you like when you type the commands at the IDL
command line or into a text editor.
Comments
Anything to the right of a semi-colon on an IDL command is treated as a comment by
IDL and is ignored by the IDL interpreter. In general, I try to write comments on their
own line in an IDL program, usually set off by a blank space before and after it and
indented three spaces from the line it is commenting on. For example:
; This is the loop part of the program.
FOR j=0,10 DO BEGIN
data = j*2
count = count + j
ENDFOR
Occasionally you will see a comment on the end of a command line. I do this
especially when I am documenting the fields of an IDL structure variable. For
example:
info = {r:r, $ ; The red color vector
g:gt $ ; The green color vector
b:b ) ; The blue color vector
Getting Stwted
If you are using anonymous ftp, the files can be found via an Internet browser at:
Copy all program files in text or ASCII mode. If you like-and your computer can
uncompress a zip file archive-you can copy all the program and text files at once by
copying the coyotefiles.zip file. This is a zip file archive of the program files you need.
The data files will be collected from their various locations and copied into your
cunent directory. There is a list of the data files that will be used in the book, along
with their data types and sizes, in "Appendix B: Data File Descriptions" on page 397.
Positional Parameters
The three variables peak, lorz, and 1at in the command above are also called positiorznl
parameters. In this particular instance, these positional parameters are irzput variables
(i.e, they are bringing data into the command), but you cannot tell this by looking at
them. They could just as easily be output variables. (Or they could be both input and
output variables, for that matter.) The command line syntax is exactly the same. You
will only be able to tell by context and by reading the published documentation for the
command or program.
Working with ZDL Contntands
A positional parameter has a defined sequence or order to the right of the command
name. (Note that key~~ordparcimeters, discussed below, do not affect positional
parameter order.) In this case, the variable peak must be to the right of the command
Contocn. and to the left of the variable 1011,for example. The variable 1011must be to
the right of peak and to the left of Iat, and so on. You cannot leave out, say, the second
positional paranleter and specify the first and third.
For example, these two commands are incorrectly formatted and will cause errors.
The first because the order of the positional parameters is changed, and the second
because the second positional parameter is not present.
Contour, lon, peak, lat, XStyle=l, YStyle=l, / ~ o l l o w ,$
Levels=vals, C-Labels= [l,O,l,0, O I I I l01
r
Contour, peak, , lat, XStyle=l, YStyle=l, / ~ o l l o w ,$
Levels=vals, C ~ L a b e l s = [ l r O r l , O , O , 1 , 1 , 0 ]
Positional parameters are often required parameters to the command, but they do not
have to be. For example, in the cosrect command above peak is a required parameter
to the Contoul. command, but lorz and lat are optional positional parameters. Again,
you will know this by reading the published documentation for the command.
Keyword Parameters
XStyle, YStyle, Follo~v,Levels, and C-Labels are keyword pamneters. Unlike
positional parameters, keyword parameters can come in any order to the right of the
command name. They can even come in the midst of positional parameters without
affecting the relative positions of those parameters. In other words, keyword
parameters are not counted like positional parameters. This is a valid construction of
the Contour command above:
Contour, peak, Levels=vals, lon, X ~ t y l e = l ,~ ~ t y l e = l$ ,
/ ~ o l l o w ,Levels=vals, lat, ~ - ~ a b e l s = [ l , O , l , O , O ~ 1 ~ 1 , 0 1
By convention keyword parameters are optional parameters. Like positional
parameters they can be input parameters or output parameters to the command. You
will know by context and by reading the documentation for the command.
Notice the way in which the keywords are used in the command above. Keywords can
be set to a particular value (e.g., xstyle=l), to a variable (e.g., ~evels=vals),to a
vector of values (e.g., C-~abels=[I,O,l,O,o , l ,1,0]), and even set with a slash
character (e.g., /~ollow).
Consider the latter syntax more closely. Some keywords have a binary quality. That is,
they are either onloff, yeslno, tmelfalse, 110, etc. You often find these keywords being
"set" or "turned on" by the syntax /Keyword. The syntax /Keyword is identical to
(means the same as) the syntax Keywoid=l. No more and no less.
In fact, the Coiztour command above could have been wt-itten like this:
Contour, peak, Levels=vals, lon, /xstyle, / ~ ~ t y l e$,
/ ~ o l l o w ,Levels=vals, lat, C-Labels=[l,O,l,O,O,l,1,0]
This command means the same thing as the command above. The reason the
command was not written like this, is that it might falsely imply that the XStyle and
YStyle keywords have a binary quality (i.e., they are either on or off), which they
don't. They can be set to other values besides 0 and 1.
IDL> a = [ 3 , 5, 7 , 3 , 6, 91
IDL> Help, a
IDL> Plot, a
When you want to close the journal file, just type the Jozirltal command again, all by
itself at the IDL command line, like this:
IDL> Journal
The journal file is a simple ASCII text file that you can edit, if you like, with any text
edito:; including the editor built into IDL Development Environment. When you want
to replay the commands in the journal file, use the @ sign as the first character on the
IDL command line. For example, to replay the commands in the book-commartds.pro
file above, type this:
Be sure to give each journal file you create a unique name. You cannot append to jour-
nal files, so if you open a second file with the same name as the first many operating
systems will simply overwrite the first journal file without warning.
If you would like to have a unique journal file name each time you wanted a journal
file, you could write an IDL program like this:
PRO Journal-Unique
Journal, String ( l journal-l , in-Date (SysTime( ) ) , .pro , $
Format=' (A, 14, 512.2, A) l)
END
Then, instead of typing Jour~zal,you can type Journal-Unique to open a journal file
with a unique name. This file has already been written for you and is one of the files
you downloaded to use with this book.
Creating Variables
You will be creating many variables in this book. It will help if you know a little about
them before you get started. Variable names must start with a letter. They can include
other letters, digits, underscore characters, and dollar signs. A variable name may
have up to 255 characters. A convention used in this book is to make the initial letter
in variable names lowercase. Here are some valid variable names:
pt rToDat a
image2
this-image
ashandle
Variables have two important attributes: a data type, and an orgarzizntiorzal structure.
Data type refers to the kind of data it is. There are 14 basic data types in IDL (as of
IDL 5.3). In Table l you see each basic type, the size of each variable type in bytes,
the way a variable of that type can be created, and the name of the IDL function that
can convert a variable to that data type. In addition to a data type, a variable has an
organizational structure. Valid organizational structures are scalars (i.e., single
values), vectors (really a one-dimensional array), arrnys (of up to eight dimensions),
and IDL structures (a type of organization that can contain variables of various data
types and organizational structures in separate compartments calledfields).
As you will see, IDL is a program that excels at working with vector or array data, so
there are a number of built-in IDL commands for creating vectors and arrays of the
different data types. In particular, there are functions for creating arrays of the proper
data type in which each element is initialized to zero, and there are functions for
creating arrays of the proper data type in which each element is initialized to its own
Getting Started
index in the array. You see a list of these functions in Table 2. For example, to create a
100 by 100 byte anay of zeros, you can type this:
IDL> array = BytArr (100,100)
To create a vector of 100 floating point values ranging in value from 0 to 99, you can
type this:
IDL> vector = FIndGen(100)
You will see many ways to use these IDL functions in this book.
Table 1: The 14 basic data types iiz ZDL. Also showit is the size of t12e variable,
tlze way a variable of that type caiz be created, and tlze IDL fuizctiolz that
caiz be used to cast or coerce a variable to that data type.
value. This is because IDL "promotes" variables to the data type that preserves the
most accuracy in mathematical calculations. When nun^ is redefined (on the left hand
side of the equal sign) it is promoted to a float to maintain the accuracy of the floating
point calculation on the right hand side of the equal sign.
Consider this example:
result = 4 * X
In this case, it is impossible to know what data type and organizational structure the
variable result will have because you know nothing about the variable x.In fact, reszrlt
will depend almost completely on the data type and organizational structure of
variable x.If x is a floating-point vector of 10 elements, result will also be a floating-
point vector of 10 elements. If it is a long-integer array of size 100 by 200, result will
be the same. Note that if x has a date type of byte, that result will always have a data
type of integer. (Organizational structure doesn't matter, in this case.) This is a result
of the multiplication by an integer value.
.. Remember that the expression on the right-hand side of the equal sign is always eval-
uated before a data type and organizational structure can be assigned to the variable on
the left-hand side of the equal sign. IDL will promote variables to the data type that
maintains the most precision in evaluating the expression
It might take you a long time to figure out why your aspect ratio is always O! The
correct way to write this code is to force one of the integer values to be a float, like
this:
aspect = Float ( !D.X-Size) / !D.Y-Size
Now your aspect variable has the floating value you expect.
The other common way to get into trouble with integer variables is to not realize that
integers in IDL are what are often called short integers in other programming
languages. In other words, an integer in IDL is only two bytes long. Integers in most
other programming languages are four bytes long (called a long integer in IDL).
A two-byte signed integer can only have positive values up to 32,767. Values greater
than this "overflow" and are usually represented in IDL as negative numbers.
You can have trouble with short integers in two ways. First, you don't take account of
short integers in loops. For example, suppose you wanted to read a data file and you
didn't know how many lines there were in it. You might write a piece of code like this:
count = 0
WHILE NOT EOF (lun) DO BEGIN
Getting Started
Table 2: IDL functions that will create vectors and arrays of multiple dimen-
sions, either initialized to 0 or initialized to their own index number.
If you had more than 32,768 lines of data, this code would fail. The reason is that the
variable couizt is initialized as an integer. It should be initialized as a long integer:
count = OL
WHILE NOT EOF (lun) DO BEGIN
READF , lun , temp
data(count) = temp
count = count + 1L
ENDWHILE
Now you can read as many lines as you like.
Another common place to find this error is in the counter for For loops. It is a good
idea to always write your For loop command something like this:
FOR j=OL,num-l DO ...
The second way you might get into trouble with short integers is when you try to read
data that was produced by a program written in some other programming language (or
vice versa). If you are going to read integer data produced by a C or Fortran program,
you almost always want to be sure you are reading into lolzg integers in IDL.
Similarly, you will want to write long integer data to files that will be read by a C or
Fortran program as integers.
Working with Vectors and Arrnys
Note that a new compiler option in IDL 5.3 can force IDL integers to be four-byte
integers rather than two-byte integers by default. Normally this compiler option com-
mand is placed at the beginning of IDL procedures and functions. It causes the com-
piler to force all integer valiables in that procedure or function to be four-byte long
integers.
Compile-Opt DEFINT3 2
Creating Vectors
You can create a vector (a vector is just a one-dimensional array) or an assay at the
IDL command line by enclosing the vector values in square brackets, like this:
IDL> v e c t o r = [l, 2 , 31
This is an integer vector because the data values are integer values.
You can get information from IDL about the data type and organizational structure of
variables by using the Help command, like this:
IDL> Help, v e c t o r
VECTOR INT = Array [3]
If you wanted to add a fourth element to this vector, this is easily done in IDL. Just
type this, for example:
IDL> v e c t o r = [ v e c t o r , 41
IDL> P r i n t , v e c t o r
Notice that vector subscripts start at 0 and not at 1. Notice also that vector subscripts
use parentheses to distinguish themselves. This makes it difficult sometimes to
distinguish a call to a function command from a subscsipted array. To help with this
problem, square bracket subscript notation was introduced in IDL 5.0. In other words,
if you are running IDL 5.0 or higher you can type this:
IDL> P r i n t , v e c t o r [ O :21
Beginning with IDL 5.3, you can force square bracket notation by setting a compiler
option. Normally this compiler option command is placed at the beginning of IDL
Getting Started
procedures and functions. It causes the compiler to enforce square bracket subscript
notation in that procedure or function.
Compile-Opt STRICTARR
This book uses square bracket subscripting to avoid any confusion with function calls.
If you are using an IDL 4.x version of IDL, you will have to substitute parentheses for
square brackets in the code to get the commands to work.
To use array subscripting to put another element between the second and third
elements of the vector, you can do this:
IDL> v e c t o r = [ v e c t o r [O : l ] , 5 , v e c t o r [2 : 31 1
IDL> P r i n t , v e c t o r
Vectors can also be created by using the array creation routines discussed above. For
example, to create a 6-element floating-point vector with values ranging from 0 to 50,
you can type this:
IDL> v e c t o r = FIndGen(6) * 10
IDL> P r i n t , v e c t o r
Creating Arrays
Arrays can also be created from the IDL command line. For example, we can create a
3 column and 2 row array like this:
IDL> a r r a y = [ [ l , 2 , 31 , [ 4 , 5 , 61 ]
IDL> P r i n t , a r r a y
Your output in your IDL output window will look like this:
Notice that this is identical to first creating a vector and then reformatting that vector
into a 3 column by 2 row array with the Refonn command, like this:
IDL> v e c t o r = IndGen ( 6 ) + 1
IDL> a r r a y = Reform(vector, 3 , 2 )
IDL> P r i n t , a r r a y
What this tells you is that vectors and arrays are stored in IDL in row order. This
becomes important when you are writing IDL programs because you will often want
to take advantage of the way data is stored in IDL.
simply an arbitrary choice. There is no particular reason to choose one way or the
other.
You can access the same element in this array using one-dimensional subscripts.
Knowing that array elements are stored in row order, you want the fourth element in
the array. You can access it like this:
IDL> Print, array [3]
The fact that you can access multi-dimensional arrays with one-dimensional
subscripts is a powerful tool in many D L programs.
You can also subscript arrays with vectors. For example, if you want to print the first,
second, fourth, and sixth element of the array, you can type this:
IDL> indices = [O, 1, 3, 51
IDL> Print, array [indices]
IDL> Window
Notice that the title bar of this window has a 0 in it. This is this window's graplzics
wirzdow irzdex rl~fl?~bei-.
Each graphics window has an unique graphics window index
number associated with it when the window is created. The Window command without
any positional parameters always creates a window with window index number 0. We
say this is "Window 0".You can have at least 128 graphics windows open at any one
time in an IDL session. You can assign a graphics window index number for windows
0 through 3 1. IDL will assign graphics window index numbers for windows 32
through 127 by creating windows with the Free keyword (discussed below). For
example, if you want to create a window with graphics window index number 10, you
can type this:
IDL> Window, 10
If a window with the same graphics window index number already exists on the
display, a Wirzdow command like this will first destroy the old one and then create a
new one with this index number.
If you prefer (this is always a good idea when you are creating windows in IDL
programs), you can open a window with a graphics window index number that is
"free" or unused. The Free keyword is used for that purpose, like this:
IDL> Window, /Free
A window that is created with a Free keyword will have a graphics window index
number greater than 3 1. The Free keyword is the only way to create a normal graphics
window with an index number greater than 3 1.
Note that on PCs and Macintosh computers, you can use the ALT-TAB or OlTION-
TAB keys, respectively, to cycle through and select windows that are open but not cur-
rently visible on the display and make them the window with the window focus.
Chapter Overview
The bread and butter of scientific analysis is the ability to see your data as simple line
plots, contour plots and surface plots. In this chapter, you will learn how easy it is to
display your data this way. You will also learn to use system variables and keywords
to position and annotate simple graphical displays.
Specifically, you will learn:
Q
How to display data as a line plot with the Plot command
* How to display data as a surface with the Sulfice and Sl~ade-Su~fcommands
How to display data as a contour plot with the Corztour command
Q
How to position simple graphical displays in the display window
* How to use common keywords to annotate and customize your graphical displays
are written. Object graphics comnlands are not really meant to be typed at the IDL
command line. Rather, they are included in IDL programs, especially widget
programs (programs with graphical user interfaces). Object graphics commands are
discussed later in this book.
You see that cuive is a floating point vector (or one-dimensional array) of 101
elements.
To plot the vector, type:
IDL> Plot, curve
IDL will try to plot as nice a plot as it possibly can with as little information as it has.
In this case, the X or horizontal axis is labeled from 0 to 100, corresponding to the
number of elements in the vector, and the Y or vertical axis is labeled with data
coordinates (i.e., this is the dependent data axis).
But most of the time a line plot displays one data set (the independent data) plotted
against a second data set (the dependent data). For example, the curve above may
represent a signal that was collected over some period of time. You might want to plot
the value of the signal at some moment of time. In that case, you need a vector the
same length as the curve vector (so you can have a one-to-one correspondence) and
scaled into the units of time for the experiment. For example, you can create a time
vector and plot it against the curve vector, like this:
IDL> time = FIndGen(lO1)* (6.0/100)
IDL> Plot, time, curve
The FIlzdGerz command creates a vector of 101 elements going from 0 to 100. The
multiplication factor scales each element so that the final result is a vector of 101
elements going from 0 to 6. Your graphical output should look similar to that in
Figure 1.
Creating Line Plots
Figure I: A plot of the independent data (time) versus the dependent data (curve).
Notice that there are no titles on the axes associated with this plot. Placing titles on
graphics plots is easy. Just use the XTitle and YTitle keywords. For example, to label
the curve plot, you can type this:
IDL> Plot, time, curve, XTitle=ITime Axis1, $
YTitle=ISignal Strength1
You can even put a title on the entire plot by using the Title keyword, like this:
JDL> Plot, time, curve, XTitle='Time Axis1, $
YTitle=lSignal Strength1, Title=lExperiment 35M1
Your output will look similar to the illustration in Figure 2. Note that the display
shows white lines on a black background, while the illustration shows black lines on a
white background. These illustrations are encapsulated Postscript files produced by
IDL. It is common for the drawing and background colors to be reversed in PostScript
files. (See "Problem: PostScript Devices Use Background and Plotting Colors
Differently" on page 189 for more information.)
Experiment 35M
30
5
m
E 20
G
-
gm 10
iTj
0
0 2 4 6
Time Axis
Figure 2: A simple line plot with axes labels and a plot title.
Si~npleGraphical Displays
Notice that the plot title is slightly larger than the axes labels. In fact, it is 1.25 times
as large. You can change the size of all the plot annotations with the Charsize
keyword. For example, if your eyes are like mine, you might want to make the axis
characters about 50% larger, like this:
IDL> Plot, time, curve, XTitle=ITime Axis', $
YTitle='Signal Strength1, Title=l~xperiment3 5 M 1 , $
CharSize=1.5
If you want the character size on all your graphic displays to be larger than normal,
you can set the ChnrSize field on the plotting system variable, like this:
IDL> ! P.CharSize = 1.5
Now all subsequent graphics plots will have larger characters, unless specifically
overruled by the ChnrSize keyword on a graphics output command.
You can even change the character size of each individual axis by using the
[XYZIChnrSize keyword. For example, if you want the Y axis annotation to be twice
the size of the X axis annotation, you can type this:
IDL> Plot, time, curve, XTitle='Time Axis', XCharSize=l.O, $
YTitle=lSignal Strength', YCharSize=2.0
Note that the [XYZIChnrSize keywords use the current character size as the base from
which they calculate their own sizes. The current character size is always stored in the
system variable !l?CharSize. This means that if you set the XCharSize keyword to 2
when the !RCharSize system variable is also set to 2, then the characters will be four
times their normal size.
Table 3: The line style carz be clta~zgedby assignirzg these index nz~~nbers
to the
Linestyle keyword.
The thickness of lines used on line plots can also be changed. For example, if you
wanted the plot displayed with dashed lines that were three times thicker than normal
you can type this:
IDL> Plot, time, curve, LineStyle=2, Thick=3
Figure 3: A line plot with the data plotted as symbols instead of as a line.
To create larger symbols, add the SyrlzSize keyword like this. Here the symbol is twice
the "normal" size. A value of 4 would make the symbol four times its normal size, and
SO on.
4 Diamond
5 Tiiangle
6 Square
7 X
8 User defined (with the UserSyn? procedure).
Table 4: Tlzese are the index izuntbers you can ztse witlt tlze PSym keyword to
produce different plotting symbols on your plots. Note that using a
negative number for the plotting symbol will coizrtect the symbols with
lines.
color triples that describe the charcoal, yellow, and green colors.) For example, load a
charcoal, yellow and green color into color indices 1,2, and 3 like this:
IDL> TVLCT, [70, 255, 01, [70, 255, 2551 I [70, 0, 01 I 1
To draw the plot in yellow on a charcoal background, type:
IDL> Plot, time, curve, Color=2, Background=l
If nothing appears in your display window when you type the command above, it is
likely because you are running IDL on a 24-bit color display and you have color
decomposition turned on. You will learn more about color decomposition later, but for
now, be sure color decomposition is off. Type this to produce the proper colors:
IDL> Device, Decomposed=O
IDL> Plot, time, curve, Color=2, Background=l
If you want just the line to be a different color, you must first plot the data with the
NoDntn keyword turned on, then overplot the line with the OPlot command
(discussed below). For example, to have a yellow plot on a charcoal background, with
the data in green, type:
IDL> Plot, time, curve, Color=2, Background=l , / ~ o ~ a t a
IDL> OPlot , time, curve, Color=3
Figure 5: A plot with the zero poirzt of the Y axis located ort the top of the plot.
If your chosen axis range doesn't fall within IDL's sense of what an aesthetically
pleasing axis range should be, IDL may ignore the asked-for range. For example, t ~ y
this command:
IDL> Plot, time, curve, XRange=[2.45, 5.641
The X axis range goes from 2 to 6, which is not exactly what you asked IDL to do. To
make sure you always get the axis range you ask for, set the XStyle keyword to 1, like
this:
IDL> Plot, time, curve, XRange=[2.45, 5.641, XStyle=l
You will learn more about the [XYZIStyle keywords in the next section.
Table 5: A table of the key~vordvalues for the [XYZIStyle keywords that set
properties for the axis. Note that values cart be added to specih more
than one axis property.
Czrstoinizing Graphics Plots
You can turn an axis off completely. For example, to show the plot with a single Y
axis, you can type:
IDL> Plot, time, curve, XStyle=4, YStyle=8
Your output should look similar to the illustration in Figure 6.
Figlcre 6: A plot with the X axis srcppressed aitd Y box axes turned off.
You could show the same plot with Y axes and Y grid lines:
IDL> Plot, time, curve, XStyle=4, YTickLen=l, YGridStyle=l
The [XYZIStyle keyword can be used to set more than one axis property at a time. This
is done by adding the appropriate values together. For example, you see from Table 5
that the value to force the exact axis range is 1, and the value to suppress the drawing
of box axes is 8. To both make an exact X axis range and to suppress box axes, these
values are added together, like this:
IDL> Plot, time, curve, XStyle=8+1, XRange= [ 2 , 5 ]
To create a full grid on a line plot is accomplished (strangely) with the TickLerz
keyword, like this:
IDL> Plot, time, curve, TickLen=l
Outward facing tick marks are created with a negative value to the [XYZlTickLe~z
keywords. For example, to create all outward facing tick marks, type:
IDL> Plot, time, curve, T i c k ~ e n = - 0 . 0 3
To create outward facing tick marks on individual axes, use the [XYZITickLerz
keywords. For example, to have outward facing tick marks on just the X axis, type:
IDL> Plot, time, curve, XTickLen=-0.03
You can also select the number of major and minor tick marks on an axis with the
[XYZITicks and [XYZIMinor keywords. For example to create the plot with only two
major tick intervals, each with 10 minor tick marks, on the X axis, type:
IDL> Plot, time, curve, XTicks=2, XMinor=lO, XStyle=l
Sintple Graplzicnl Displays
Figure 7: An unlimited number of data sets can be plotted on the same line plot.
Figure 8: A line plot with two Y axes. The second axis is positioned with the Axis
command. Be sure to save the data scaling with the Save keyword.
The initial Plot command establishes the data scaling (in the !X.S and ! X S scaling
parameters) for all the subsequent plots. In other words, it is the values in the !X.S and
!Y;S system variable that tells IDL how to take a point in data space and place it on the
display in device coordinate space. Make sure the initial plot has axis ranges sufficient
to encompass all subsequent plots, or the data will be clipped. Use the XRnrzge and
YRarzge keywords in the first Plot command to create a data range large enough. To
distinguish different data sets, you can use different line styles, different colors, differ-
Creatirtg Sztrface Plots
ent plot symbols, etc. The OPlot command accepts many of the same keywords the
Plot command accepts.
IDL> TvLCT, [255, 255, 01, [O, 255, 2551, [O, 0, 01, 1
IDL> Plot, curve, / ~ o ~ a t a
IDL> OPlot, curve, Color=l
IDL> OPlot, curve/2.0, Color=2
IDL> OPlot , curve/5.0, Color=3
Figure 10: A surface plot with meatzirtgful values associated with its axes.
The parameters Ion and lat in the command above are monotonically increasing and
regular. They describe the locations that connect the surface grid lines. But the grid
does not have to be regular. Consider what happens if you make the longitudinal data
points irregularly spaced. For example, you can simulate randomly spaced
longitudinal points by typing this:
IDL> seed = -lL
IDL> newlon = RandomU(seed, 41) * 41
IDL> newlon = newlon[Sort (newlon)] * (24./40) + 24
IDL> Surface, peak, newlon, lat, ~ T i t l e = ' L o n g i t u d e ~$,
YTitle='Latitudel, ~ ~ i t l e = ~ E l e v a t i oCharsize=1.5
n~,
You see now that the longitudinal X values are not regularly spaced. Although it may
look like the data is being re-sampled, it is not. You are simply drawing the surface
Cztstorniziitg Sztrface Plots
grid lines at the locations specified by the longitude and latitude data points. Your
output should be similar to the illustration in Figure l l .
Figzcre 11: The same surface plot, bzrt with alz irregularly spaced X vector.
Figure 12: Surface plots can be rotated with tlze Ax arzd Az key~vords.
are much more useful to them, especially when surface rotations are desired. Partly
this is because direct graphics surface rotations have an artificial limitation that
requires the Z axis to be pointing in a vertical direction in the display window. No
such limitation exits for object graphics surfaces.
To see what some of the advantages are, run the program FSC-Sutface, which is
among the program files you downloaded to use with this book. Press and drag the
cursor in the graphics window to rotate the surface plot. Controls in the menu bar give
you the opportunity to set many properties of the surface plot, including shading
parameters, lights, and colors. The FSC-Sulface program looks similar to the
illustration in Figure 13.
Figz~re13: Tlze FSC-Surface program. Click aizd drag in the grapltics ~viitdowto
rotate tlzis surface plot. Tlze program is written usiizg the object graplzics
class library. Properties of tlze surface caiz be chaizged via optioizs iiz the
meizzc bar.
It is also possible to draw the mesh lines of the surface in different colors representing
a different data parameter. For example, you can think of draping a second data set
over the first, coloring the mesh of the first data set with the information contained in
the second.
To see how this would work, open a data set named Snow Pack and display it as a
surface with these commands. Notice that the Srzow Pack data set is the same size as
the peak data set, a 41-by-41 floating point array.
IDL> snow = LoadData (3)
IDL> Help, snow
IDL> Surface, snow
Now, you will drape the data in the variable srzow over the top of the data in the
variable peak, using the values of srlow to color the mesh of peak. First, load some
colors in the color table with the LoadCT command. The actual shading is done with
the Slzades keyword, like this:
IDL> LoadCT, 5
IDL> Surface, peak, Shades=BytScl(snow, Top=!D.Table-Size-l)
Notice you had to scale the snow data set (with the BytScl command) into either the
number of colors you are using in this IDL session or the size of the color table. If you
failed to scale the data, you might see a set of axes and no surface display at all. This is
because the data must be scaled into the range 0 to 255 to be used as the shading
parameters for the surface.
Si~ttpleGraphical Displays
Sometimes you might like to display the surface colored according to elevation. This
is easily done just by using the data itself as the shading paranleter.
IDL> Surface, peak, Shades=BytScl(peak, Top=!D.Table-Size-l)
Figure 14: The surface plot with a skirt oiz the surface.
You can obtain a kind of "stacked line plot" appearance by drawing only horizontal
lines. For example, type:
IDL> Surface, peak, /~orizontal
If you like, you can display just the lower or just the upper surface and not both (which
is the default) using keywords. For example, type the following two commands:
IDL> Surface, peak, /upper-only
IDL> Surface, peak, /~ower-Only
Sometimes you might want to draw just the surface itself with no axes:
IDL> Surface, peak, XStyle=4, YStyle=4, ZStyle=4
Figure 15: A shaded surface plot of the elevatioiz data usiizg a Gourazcd light sozcrce
shading algoritlzm.
Figure 16: The peak elevation data shaded with tl2e sizow data set.
If you want to shade the surface according to its elevation values, simply byte scale
the data set itself, type this:
IDL> Shade-Surf, peak, Shades=BytScl(peak, Top=!D.Table-Size)
Draping another data set over a surface is a way to add an extra dimension to your
data. For example, by draping a data set on a three-dimensional surface, you are
visually representing four-dimensional information. If you were to animate these two
data sets over time you would be visually representing five-dimensional information.
(To learn about data animation see "Animating Data in IDL" on page 102.)
Sometimes you just want to drape the original surface on top of the shaded surface.
This is easy to do simply by combining the Shade-Suifand Su$ace commands. For
example, type:
IDL> Shade Surf, peak
IDL> surf ace, peak, / ~ o ~ r a s e
Figure 17: A basic corttozcr plot of the data. Notice that tlze X artd Y axis labels rep-
resent the ~zzcmberof elemertts irz tlze data array.
37
Sinzple Gi.aplzical Displays
In earlier versions of IDL, the Corztour command used what was called the cell
drnvvilzg r.tzetlzod of calculating and drawing the contours of the data. In this method
the contour plot was drawn from the bottom of the contour plot to the top. This
method was efficient, but it didn't allow options like contour labeling. The cell
follo~ililzglnetlzod was used to draw each contour line entirely around the contour plot.
This took more time, but allowed more control over the contour line. For example, it
could be interrupted to allow the placement of a contour label. The cell following
method could be selected with the Follow keyword:
IDL> Contour, peak, lon, lat, XStyle=l, ~ S t y l e = l ,/ ~ o l l o w
Starting in IDL 5 the contour command always uses the cell following method to draw
contours, so the Follow keyword is obsolete. But it is still used for its beneficial side-
effect of labeling every other contour line automatically.
Figure 19: A corztourplot with the izzlmber of contour levels set to 12. Note that ev-
ery other contour level is labeled. This is a side-effect of zcsiizg the Follow
keyword.
Unfortunately, although the IDL documentation claims that IDL will select a specified
number of "equally spaced" contour intervals, this may not happen. If you look
closely at the contour plot you just made, you will notice that fewer than 12 levels are
calculated by IDL. Apparently, the NLevels keyword value is used as a "suggestion"
by the contour-selecting algorithm in IDL.
Thus, most IDL programmers choose to calculate the contour levels themselves. For
example, you can specify exactly where the contours should be drawn and pass them
Modifying n Coi~torii.
Plot
to the Contoui-command with the Levels keyword, rather than with the NLevels
keyword, like this:
IDL> vals = [200, 300, 600, 750, 800, 900, 1200, 15001
IDL> Contour, peak, lon, lat, XStyle=l, YStyle=l, /Follow, $
Levels=vals
To choose 12 equally spaced contour intervals, you can write code something like
this:
IDL> nlevels = 12
IDL> step = (Max(peak) - ~ i n ( p e a k)) / nlevels
IDL> vals = Indgen(nleve1s) * step + Min(peak)
IDL> Contour, peak, lon, lat, XStyle=l, YStyle=l, /Follow, $
Levels=vals
If you like, you can specify exactly which contour line should be labeled with the
C-Labels keyword. This keyword is a vector with as many elements as there are
contour lines. (If the number of elements does not match the number of contour lines,
the elements do not get re-cycled like they sometimes do with other contour
keywords.) If the value of the element is 1 (or more precisely, if it is positive), the
contour label is drawn; if the value of the element is 0, the contour label is not drawn.
If there is no value for a particular contour line, the line is not labeled. For example, to
label the first, third, sixth and seventh contour line, type:
IDL> Contour, peak, lon, lat, XStyle=l, YStyle=l, /Follow, $
Levels=vals, C-Labels= [l,0, l, 0,O ,1 , 1 , 01
To label all the contour lines, you could use the Replicate command to replicate the
value 1 as many times as you need. For example, type this:
IDL> Contour, peak, lon, lat, XStyle=l, YStyle=l, /Follow, $
Levels=vals, C-Labels=Replicate(l, nlevels)
30
a,
m
.S
C.' 20
(6
-I
10
0 I . . . . . . . . . . . . . . . . . . , . d.'?m,,
b , . . , . .:
0 10 20 30 40
Longitude
Figzcre 20: Corttoui.lines cart be labeled with text yozc provide yourself.
Table 3 for a list of possible line style values.) For example, to make the contour lines
dashed, type:
IDL> Contour, peak, lon, lat, XStyle=l, YStyle=l, $
/ ~ o l l o w ,C_LineStyle=2
If you wanted every third line to be dashed, you could specify a vector of line style
indices with the C-Linestyle keyword. If there are more contour lines than indices, the
indices will be recycled or used over. Type:
IDL> Contour, peak, lon, lat, XStyle=l, YStyle=l, / ~ o l l o w ,$
NLevels=9, C_LineStyle=[0,0,2]
Your output should look similar to the illustration in Figure 21.
Figure 21: Yozc can rnodifi many aspects of a contourplot. Here every third contour
lirte is drawit in a dashed line style.
The thickness of the contour lines can also be changed. For example, to make all
contour lines double thick, type:
IDL> Contour, peak, Ion, lat, XStyle=l, YStyle=l, $
Modifying n Contozcr Plot
You could make every other line thick by specifying a vector of line thicknesses, like
this:
IDL> Contour, peak, lon, lat, XStyle=l, YStyle=l, $
NLevels=12, C-~hick=[1,2], /Follow
You can modify the contour plot so that you can easily see the downhill direction. For
example, type:
IDL> Contour, peak, lon, lat, XStyle=l, YStyle=l, $
/ ~ o l l o w ,NLevels=12, ownhi hill
Your output should be similar to the illustration in Figure 22.
the Tek-Color command to create and load drawing colors for the contour lines, like
this:
IDL> Tek Color
IDL> TVLCT, [70, 2 5 5 1 , [70, 2 5 5 1 , [70, 0 1 , 1
IDL> Contour, peak, lon, lat, XStyle=l, YStyle-l, $
NLevels=lO, Color=2, Background=l, $
C-Colors=IndGen (10)+2, / ~ o l l o w
It is also easy to use the C-Colors keyword to make every third contour line blue,
while the rest are gseen. For example, type:
IDL> Contour, peak, lon, lat, ~ ~ t y l e = lYStyle=l,
, $
NLevels=12, Color=2, ~ackground=l,$
C Colors= [3, 3, 41 , / ~ o l l o w
Figure 23: The coiztourplot showing the "hole" at the lowest corztour level.
The reason for the hole is that IDL fills the space between the first and second contour
lines with the first fill coloc It would seem to make more sense to fill the space
between the 0th (or background) and first contour with the first fill color. But to get
IDL to do that you have to specify your own contour intervals and pass them to the
Contour command with the Levels keyword. This is usually done with code like this:
IDL> step = (Max(peak) - Min(peak) ) / 12.0
IDL> clevels = IndGen (12)*step + Min (peak)
Now you get the contour fill colors correct:
IDL> Contour, peak, lon, lat, XStyle=l, YStyle=l, /Fill, $
Levels=clevels, /Follow, C-Colors=Indgen(l2)+1, $
Background=!P.Color, Color=!P.Background
In general, it is a good idea to always define your own contour levels when you are
working with filled contour plots. Moreover, if you are displaying your filled contour
plots along with a color bar, creating your own contour levels is the only way to make
sure that your contour levels and the color bar levels are identical.
Figure 24: The same contour plot, bzct with the levels calculated aizd specified di-
rectly. The hole is no~v
filled and the correct nzcinber of levels is apparertt
iiz the plot.
Sometimes you will want to fill a contour plot that has missing data or contours that
extend off the edge of the contour plot. These are called opelz colztours. IDL can
sometimes have difficulty with open contours. The best way to fill contours of this
type, is to use the Cell-Fill keyword rather than the Fill keyword. This causes the
Contour command to use a cell filling algorithm, which is not as efficient as the
algorithm used by the Fill keyword, but which gives better results under these
circumstances.
IDL> Contour, peak, lon, lat, XStyle=l, YStyle=l, $
Levels=clevels, C-Colors=1ndgen(l2)+1, /cell-Fill
Siirtple Grapltical Displays
The Cell-Fill keyword should also be used if you are putting your filled contour plots
on map projections. Otherwise, the contour colors will sometimes be incorrect.
The cell filling algorithm sometinles damages the contour plot axes. You can repair
them by drawing the contour plot over again without the data. Like this:
IDL> Contour, peak, lon, lat, XStyle=l, YStyle=l, $
Levels=clevels, / N o ~ a t a ,/ N o ~ r a s e ,/F'ollow
Sometimes you want to see the contour lines on top of the color-filled contours. This
is easily accomplished in IDL with the Overplot keyword to the Contoul-command.
For example, type:
IDL> Contour, peak, lon, lat, XStyle=l, YStyle=l, $
Levels=clevels, ill, C Colors=IndGen(l2)+1
IDL> Contour, peak, lon, lat, ~ ~ ~ ~ l YStyle=l,
e = l , / ~ o l l o w ,$
Levels=clevels, /Overplot
Your output should look similar to the illustration in Figure 25.
Figztre 25: A contourplot with the contozcr lines overplotted oiz top of the filled con-
tours.
Don't confuse the Overplot keyword with the NoErase keyword. They are similar, but
definitely not the same. In a contour plot, the Overplot keyword draws the contour
lines only, rzot the contour axes. The NoEmse keyword draws the entire contour plot
without first erasing what is already on the display.
Graphic Margin
I I
Figzcre 26: The graphic positiorz is tlze area of tlze display erzclosed by the axes of the
graphic. The graphic region is sirttilar, but also corztairzs space for the
graplzic's titles and other artrzotatiorz. The graphic margin is jzcst tlze op-
posite of the graphic positiorz. The graphic margin is specified iiz char-
acter z~rzits, whereas the graphic position alzd graphic region are
specified irz norinalized coorditzate zcnits.
The entire graphic region may be set by the !P.Regiorz system variable, or regions on
individual axes can be set using the Regioiz field to the !X, !k: and !Z system variables.
The graphic margins may be set by the [XYZIMnrgirz keywords to the Plot, Surfice,
Corztoui; or other IDL graphics command or by the Mnrgi1.1field in the !X, !I: and !Z
system variables.
By default, IDL sets the graphic margin when it tries to put a graphic into the display
window. But, as you will see, that is not always the best choice. It is sometimes better
to use the graphic position to position your graphics displays, especially if you are
combining graphics output commands in one display window.
Sin~pleGraphical Displays
Unlike many other system variables, whose values are set back to the default values
by setting the system variable equal to 0, the margin system variables must be set
explicitly to their default values. If you didn't type the two commands above, be sure
to do it now.
All subsequent graphics output will be positioned similarly. To reset the ! R Position
system variable so that subsequent graphic output fills the window normally, type:
If you wanted to position just one graphic display, you could specify a graphic
position with the Position keyword to the graphics command. Suppose you wanted a
contour plot to just fill up the left-hand side of a display window. You might type this:
IDL> Contour, peak, position= [O.l, 0.1, 0.5, 0.91
Note that the Positiolz keyword can be used to put multiple graphics plots into the
same display window. Just be sure to use the NoEl-ase keyword on the second and all
subsequent graphics commands. This will prevent the display from being erased first,
which is the default behavior for all graphics output commands except TV and TVScl.
For example, to put a line plot above a contour plot, you can type:
IDL> Plot, time, curve, ~osition=[O.l,0.55, 0.95, 0.951
IDL> Contour, peak, ~ o s i t i o n = [ ~ . l0.1,
, 0.95, 0.451, / ~ o ~ r a s e
!P.Multi[cl] This element specifies whether the graphics plots are going
be displayed by filling up the rows first (!P.Mzrlti[4]=0) or by
filling up the columns first (!PMulti[4]=1).
For example, suppose you want to set !Z?M~lltito display four graphics plots on the
display in two columns and two rows, and you would like the graphics plots to fill up
the columns first. You would type:
Now as you display each of the four graphics plots, each graphic fits into about a
quarter of the display window. Type:
IDL> Window, XSize=500, YSize=500
IDL> P l o t , t i m e , c u r v e , LineStyle=O
IDL> Contour, peak, l o n , l a t , X S t y l e = l , Y S t y l e = l , NLevels=lO
IDL> S u r f a c e , peak, l o n , l a t
IDL> Shade-Surf, peak, l o n , l a t
Your output should look similar to the output in Figure 27.
Figzcre 27: You caiz plot multiple graphics plots iiz a siitgle display wiitdow.
room on the display for titles and other types of annotation. You can leave room with
multiple plots by using the "outside margin" fields of the !X, !t: and !Z system
variables. The outside margins only apply when the !l?Mzrlti system variable is being
used. They are calculated in character units, just as the normal graphic mara'
ulns are
calculated.
For example, suppose you wanted to have an overall title for the four-graphic plot
display you just created. You might leave room for the title and create it like this:
IDL> !P.Multi = 10, 2, 2, 0, l]
IDL> ! Y.OMargin = [2, 41
IDL> Plot, time, curve, LineStyle=O
IDL> Contour, peak, lon, lat, XStyle=l, YStyle=l, NLevels=lO
IDL> Surface, peak, lon, lat
IDL> Shade-Surf , peak, lon, lat
IDL> XYOutS, 0.5, 0.9, /Normal, 'Four Graphics Plots1, $
Alignment=0.5, Charsize=2.5
Your output should look like the illustration in Figure 28.
Figure 28: A four by four multiplot with space left at the top of the plot for a title by
using the !Y.OMargiiz keyword.
Siirzple Grapltical Displays
Figzcre 29: You can use !P.Multi to position asymmetric arraitgements of plots irt
the display window.
Note that the TV command does not work with !Z?Multi like the Plot or Contour com-
mands do. However, the TVInzage program, which is a replacement for the TV com-
Addiizg Text to G1*aplticalDisplays
mand and is among the programs you downloaded to use with this book, does honor
the !P.Mzrlti system variable. Try these commands:
IDL> image = LoadData ( 7 )
IDL> !P.Multi=[O, 2 , 21
U)L> FOR j = 0 , 3 DO TVImage , image
Be sure you reset !l?Mzrlti to display a single graphics plot on the page. Like many
system variables, !P.Mzrlti can be reset to its default values by setting it equal to zero,
like this:
Table 6: Tlzefont "Jlavor" cart be selected by setting the !R Foitt system variable
or the Foizt keyword to the appropriate value. Vectorfonts are the
defaultfont type for direct graphics commaitds. They have the
advaittage of being platform irtdeperzde~tt.
By default fonts in direct graphics routines are set to the vector or software font style.
Vector fonts are described by vector coordinates. As a result, they are platform
independent and can be rotated in 3D space easily. However, many people find vector
fonts too "thin" for quality hardcopy output and prefer more substantial fonts for this
purpose (i.e., true-type fonts or Postscript hardware printer fonts). Vector fonts can be
selected permanently by setting the !P.Forzt system variable to -1 or by setting the
Forzt keyword on a graphical output command, like this:
IDL> Plot, time, curve, Font=-l, XTitle=lTime', $
YTitle='Signall, Title=IExperiment 35F3a1
True-type fonts are also called outline fonts. The font is described by a set of outlines
that are then filled by creating a set of polygons. IDL comes with four true-type font
families: Times, Helvetica, Courier, and Symbol, but you can supplement these with
any other true-type font family you have available. True-type fonts take longer to
render because the font must be scaled and the polygons created and filled, and many
people find them a bit unattractive at small point sizes on normal low-resolution
displays. But they have the great advantage of being rotatable and nice looking on
hardcopy output devices. True-type fonts are the default font for the object graphics
system in IDL.
To render a plot with the default me-type Helvetica font face, set the Font keyword to
1, like this:
Siinple Graplzical Displays
For example, you can put the title on the line plot using rzounnlized coordinates, like
this. When you are writing IDL programs it is often a good idea to specify things like
titles and other annotation with normalized coordinates. This makes it easy to place
graphics not only in the display window, but in Postscript and other hardcopy output
files as well.
IDL> Plot, time, curve, Position= [0.15, 0.15, 0.95, 0.851
IDL> XYOutS, 0.2, 0.92, 'Results: Experiment 3 5 F 3 a 1 , $
Size=2.0, /Normal
revert back to the default Simplex Roman is write another string with explicit Simplex
Roman characters. For example:
IDL> XYOutS, 0.5, 0.5, !3Junk1,/Normal, Charsize=-l
Notice the use of the ClznrSize keyword in the code above. When this keyword has a
value of - 1, the text string is suppressed and not written to the display.
Figure 30: Be careful when yozc select a Hershey font, oryou might end zcg with plot
titles that look like Greek to yozcr users.
Aligning Text
You can position text with respect to the location specified in the call to XYOutS with
the Aligrzrnei~tkeyword. A value of 0 will left justify the string (this is the default
case); a value of l will right justify the string; and a value of 0.5 will center the string
with respect to the location specified by the X and Y values. For example:
IDL> Window, XSize=300, YSize=250
IDL> XYOutS, 150, 55, 'Research1,Alignment=O.O, $
/Device, CharSize=2.0
IDL> XYOutS, 150, 110, 'Research', Alignment=0.5, $
/Device, CharSize=2.0
IDL> XYOutS, 150, 170, 'Research1,Alignment=l.O, $
/Device, CharSize=2.0
IDL> Plots, [0.5, 0.51, [1.0, 0.01, / ~ o r m a l
Erasing Text
Text that is written with XYOutS can sometimes be "erased" by writing the same text
over in the background color. The Color keyword in conjunction with the
!P.Bnckgrouizd system variable is used for this purpose. Note that this only works
perfectly if the text was written on nothing but the background! There are often other,
better methods to erase annotations. (See "Erasing Annotation From the Display" on
page 114, for example.) To see how to erase annotation with the background color,
type this:
IDL> Window, XSize=300, YSize=250
IDL> XYOutS, 150, 110, 'Researchv,Alignment=0.50, $
/Device, CharSize=2.0
IDL> XYOutS, 150, 110, 'Research1,Alignment=0.50, $
/Device, CharSize=2.0, Color=!P.Background
Adding Text to Grapltical Displays
Orienting Text
The text specified with the XYOutS command can be oriented with respect to the
horizontal with the Orieiztation keyword, which specifies the number of degrees the
text baseline is rotated away from the horizontal baseline. For example, type:
IDL> Window, XSize=300, YSize=250
IDL> XYOutS, 150, 110, 'Research', Alignment=0.50, $
/ ~ e v i c e ,CharSize=2.0, Orientation=45
IDL> XYOutS, 150, 180, 'Research1,Alignment=0.50, $
/ ~ e v i c e ,CharSize=2.0, Orientation=-45
Positioning Text
IDL also provides a number of ways to position and manipulate text in graphical
displays. For example, it is possible to create subscripts and superscripts in a plot title.
Text positioning is accomplished by means of embedded positioning commands, as
shown in Table 8, below.
Figure 31: Tlzisplot is annotated witlz a dashed line drawiz across its center witlz the
PlotS commaitd.
The PlotS procedure can also be used to place marker symbols at various locations.
For example, here is a way to label every fifth point in the curve with a diamond
symbol.
IDL> TvLCT, [70, 255, 01, [70, 255, 2501, [70, 0, 01, 1
IDL> Plot, time, curve, Background=l, Color=2
IDL> index = IndGen ( 20) * 5
IDL> Plots,time [index], curve [index], PSym=4,$
Color=3, SymSize=2
The PlotS command can also be used to draw a box around important information in a
plot. By combining the PlotS command with other graphics commands, such as
XYOutS, you can effectively annotate your graphic displays. For example, like this:
IDL> TvLCT, [70, 255, 01, [70, 255, 2551, [70, 0, 01, 1
IDL> Device, Decomposed=O
IDL> Plot, time, curve, Background=l, Color=2
IDL> box-X-coords = [O.4, 0.4, 0.6, 0.6, 0.4l
IDL> box-y-coords = [0.4, 0.6, 0.6, 0.4, 0.41
IDL> Plots, box-X-coords, box-y-coords, Color=3, /Normal
IDL> XYOutS, 0.5, 0.3, 'Critical Zone1,Color=3, Size=2, $
Alignment = 0.5, /Normal
Adding Color to Your Gi.aphical Displays
You can easily use the XYOzitS and PlotS commands to create legends for your graph-
ics displays.
Open a window and plot the XY locations, so you can see how the data is distributed
in a random pattern. Type:
IDL> Window, XSize=400, YSize=350
IDL> Plot, X, y, Psym=4, Position=[0.15, 0.15, 0.75, 0.951, $
XTitle='X ~ o c a t i o n s ' ,YTitle='Y Locations'
You are going to display the Z data associated with each XY point pair as a circle of a
different color. To do this, you will need to load a color table and scale the Z data into
the range of colors you have available to you. Type:
Siitlple Graplzical Displays
IDL> LoadCT, 2
IDL> zcolors = BytScl(z, Top=!D.Table-Size-l)
The Circle progranl you are using in this example has a couple of weaknesses. Its
major deficiency is that it doesn't always produce circles! If you specify the
coordinates of the circle in the data coordinate system, the circle may show up on the
display as an ellipse, depending upon the aspect ratio of the plot and other factors. (To
obtain an excellent circle program, download the program TVCii-clefrom the NASA
Goddard Astrophysics IDL Library. You can find the library with a web browser at
this World Wide Web address: http://idlastro.gsfc.i~asa.go~~fionzepage.ht~~zl.)
To avoid this deficiency in the Circle program, convert the data coordinates to device
coordinates with the Coizvert-Coord command. Type:
IDL> coords = Convert-Coord(x, y, /Data, /To-~evice)
IDL> X = coords(O,*)
IDL> y = coords(l,*)
Now, you are finally ready to use the Polyfll command to draw the colored circles
that represent the data Z values. Type:
IDL> For j=O, 29 DO Polyfill, Circle(x(j), y(j), 10), $
/Fill, Color=zcolors(j), / ~ e v i c e
As an added touch, it would be nice to have a color bar that can tell you something
about the Z values and what the color means. You can add a color bar to the plot with
the Colorbar program that came with this book. Type:
IDL> Colorbar, position = [0.85, 0.15, 0.90, 0.951, $
Range= [Min(z),Max (z)1 , /vertical, $
Format=I(I5)l, Title='Z Values1
Your output should look similar to the illustration in Figure 32.
3186
V)
-a3,
(d 1595
>
N
5
0.0 0.2 0.4 0.6 0.8 1.O
X Locations
Figure 32: The color of the circles represents a tlzird dimensiorz irt tlzis 2Dplot.
Working with mage Data
Chapter Overview
IDL got its start as a language for handling and processing images. It is no surprise
that many scientists and engineers the world over still use it for that reason. This
chapter lays the groundwork for working with images. Among the topics you will
learn about are these:
* How to read and display image data
* The difference between 8-bit and 24-bit images
* How to scale image data
* How to position an image in the display window
* How to change image sizes
* How to read images from the display device
* How to perform basic image processing tasks
* How to construct simple image filters
Displaying Images
You can use either of two IDL commands to display your image: TV or TVScl. These
commands are identical in almost every way, including the keywords that can be used
with them. They differ in only one respect: TVScl scales the image data into byte
values that correspond to the number of colors you are using in your IDL session. (On
24-bit displays, the image data is scaled into the range 0 to 255.) For example, if you
are using 220 colors in your IDL session, TVScl scales the image data into byte values
between 0 and 219 before the image is displayed.
The TV command, on the other hand, takes the image data at face value and simply
transfers it to the display as byte data. Image data values are truncated to byte values if
necessary. If the image data is not scaled into the range of 0 to 255, there is an
excellent chance the image will be displayed incorrectly.
Notice that unlike the Plot, Suiface, and Colztour commands, the TV and TVScl com-
mands do not erase the display before displaying the image. Most of the time this is
not a problem, but sometimes it is. If you want a clear display window to view your
image data, you can erase whatever is currently displayed in the window with a simple
Erase command, like this:
IDL> Erase
Here is an example to illustrate the point. The image data set you just read into IDL
has already been scaled into the range of 0 to 255. You can see this is so by typing:
IDL> Print, Max (image), Min (image)
But, if you are working on an 8-bit display, chances are very good that you are not
using all 256 colors that are available on your display device. To see how many colors
you are using, type:
IDL> Print, !D.Table-Size
The number of colors used in an IDL session on an 8-bit display (here represented by
the size of the color table) is usually in the range of 210-240 colors, but it can be
considerably less. On a 24-bit display, you will have access to 16.7 million colors, but
your color table size will still be just 256. You will learn later how IDL selects the
number of colors it uses.
If you are sunning IDL on an 8-bit display, open a window, load the gray-scale color
table, and display the image with the TV command, like this:
IDL> Window, 0, XSize=192, YSize=l92
IDL> LoadCT, 0
IDL> TV, image
Your output should look like the illustration in Figure 33.
Since you used the TV command, the data was transferred to the display without any
scaling. Although it is not apparent yet, all the pixels with image values greater than
the number of colors in the IDL session are set to the same color value. That is, pixels
with values greater than !D. Table-Size-l are being displayed with the same color
pixel. (In this case, you are seeing "colors" as shades of gray.)
You might be able to see the difference if you display the image with the TVScl
command. (You won't see any difference at all on a 24-bit display, since there you
have all 256 possible byte values available to you.) Open another window and move it
near the first. Use the TVScl command to display the image, like this:
IDL> Window, 1, ~ ~ i z e = l 9 2YSize=192
,
IDL> TVScl, image
Figure 33: Art image of David Sterrt, fozcrtder of Research Systems artd creator of
IDL. The otlter image irt the people.dat data set is Ali Bahrami, the first
employee of Research Systems. Both David arzd Ali are still irtvolved irt
the developmertt of IDL.
You may be able to see a difference in shading between the two images. Since this
image data has a maximum value of only 238, the difference might be subtle. If you
can't see the difference, try scaling the data between 0 and 255 first, like this:
IDL> WSet, 0
IDL> image = BytScl (image)
IDL> TV, image
IDL> WSet, 1
IDL> TVScl, image
If you still can't see the difference, try loading a color table. The Red Temperature
color table might work. Type:
IDL> LoadCT, 3
Now, to see what TVScl does, scale the data and display it with the TV command:
IDL> Window, 2, XSize=192, YSize=192
IDL> scaled = BytScl(image, Top=!D.Table-Size-l)
IDL> TV, scaled
What you see in window 2 should be identical to what you see in window I. This is
what we mean when we say TVScl byte scales the data into the number of colors used
in the IDL session.
Note that if the image in the display window is not displayed with a red-temperature
color scale you may be running IDL on a 16- or 24-bit color display. If this is the case,
be sure to turn color decomposition off for these exercises. (You will learn more about
color decomposition in "Working with Colors in IDL" on page 8 1.) Type these com-
mands:
IDL> Device, Decomposed=O
IDL> TV, scaled
If you are on a 16-bit or 24-bit display, you will need to re-issue each graphics
command from now on after changing the color table in order to see the new colors
take effect. On a 16-bit or 24-bit display, the colors in the color table are not directly
indexed or linked to the colors on the display. Rather, the color table is used as a way
for the image to find the color it should use for the pixel. But that pixel color is
expressed directly.
Working witlt Iinage Data
In general, if you don't know whether your data is scaled or not, you probably want to
use the TVScl command, since this will give your image the maximum possible
contrast in pixel values. But if color is important to you (and it almost always is), then
you probably never want to use the TVScl command. Instead, you will want to scale
your image data yourself, and use the TV command to display it.
You do this by applying the keywords Top, Min, and M m to the BytScl command. For
example, suppose you always want to display your data in 100 different shades of gray
or colors. And suppose that the minimum data value you expect in any data set is 15,
whereas the maximum valid data value is 245. Then the BytScl command is used like
this:
In this example, any value in the data set that has a value less than 15 will be set to the
value of 15 before the data is scaled. Similarly, any value in the data set with a value
greater than 245 will be set to 245 before the data is scaled.
Once your data is scaled, it is displayed with the TV command, like this:
IDL> TV, scaledImage
Now, if you always scale your data sets in the same way (and you always have at least
100 shades of gray or colors in your IDL session), your data set from last week can be
compared directly with this week's data set. A particular color red will always indicate
a specific data range or pressure.
At this point, you may have a number of graphics windows on the display. Here is a
trick to get rid of all the open windows in a single command. Type:
IDL> WHILE !D.Window N E -1 DO WDelete, !D.Window
To display the image data iinage in the same window in what appears to be two
different color tables, you must scale the image data into these two portions of the
color space. First, use the BytScl command to scale the image data into the first
portion of the color table. Make a new image, iinagel, like this:
W L > image1 = BytScl (image, Top=half - l)
Working with Iitzage Data
Now, make a second image, inzage2, by scaling the image data into the second portion
of the color table, like this:
IDL> image2 = BytScl (image, Top=half -l) + Byte (half)
You want the image on the left to be displayed with a gray-scale color table (table
number 0). To do so, you must load those gray-scale colors in the portion of the color
table occupied by the first image's data values. Type:
IDL> LoadCT, 0, NColors=half, Bottom=O
You can interactively choose any color table you like for the image on the right if you
use the XLoadCT command to load those colors in just the second portion of the color
table. Like this:
IDL> XLoadCT, NColors=half, Bottom=half
Finally, put the two scaled images side-by-side into the same window, like this. Notice
that you are using the TV command. Do you understand why?
IDL> Window, XSize=192*2, YSize=192
IDL> Device, Decomposed=O
IDL> TV, image1
IDL> TV, image2, 192, 0
To restore a single normal color table before continuing with the examples in this
chaptel; type:
IDL> LoadCT, 0
The output should look similar to that in Figure 34 and in color if you are on a 24-bit
display. It should appear in gray-scale on an 8-bit display. If that isn't what you
expected, please read on.
Figure 34: The rose data set as it is srcpposed to look. If your output doesn't look like
a bearctificl rose, try turning decomposed color orz and display it agairt.
If your output doesn't look like a beautiful rose, the problem may be that you are
running IDL on a PC or Macintosh computer, with 24-bit color turned on, and with
color decomposition turned off. (UNIX computers will display a 24-bit image in the
correct colors, no matter what the color decomposition setting, but this is not true for
other computers.) Set the color decomposition keyword and re-display the image:
IDL> Device, Decomposed=l
IDL> TV, rose, True=l
Unfortunately, 24-bit images always appear in gray-scale on 8-bit display devices. To
see the image in its actual colors on such devices, you have to create a 2D image and
the red, green, and blue color tables to go with the image from the 24-bit or 3D image
data set. This is done in IDL with the Color-Quan command. If you are on an 8-bit
display device, type these commands:
IDL> image2d = Color-Quan (rose, 1, r, g, b)
IDL> TVLCT, r, g , b
IDL> TV, image2d
You will now see the image in color, although the color reproduction is never as good
as the 24-bit image displayed on a 24-bit device.
on. This is done automatically on most workstations which are in 24-bit color, but is
not done automatically in Windows or Macintosh 24-bit color. To be absolutely sure
of seeing the 24-bit image in the correct image colors, you should type these
commands on a 24-bit display:
IDL> Device, Decomposed=l
IDL> TV, rose, True=l
Note that the TVItnnge program you downloaded to use with this book auton~atically
sets the correct color decomposition mode and the correct True keyword, depending
upon whether it is a 24-bit or 8-bit image you are displaying. It will also automatically
apply the Colo~Quaizfunction if a 24-bit image is to be displayed on an 8-bit device.
This is true even when the 8-bit device is the Postscript device.
IDL> TVImage, rose
Save the file as disp1ny.pl.o and compile the program code like this:
IDL> .Compile display
If the program has errors and doesn't compile for some reason, fix the errors and
recompile.
Now, to see the image immediately update when the color table is changed, type this:
IDL> XColors, N ~ t i f y P r o = ~ D i s p l a ,y 'Image=world
Close the currently open XColovs program.
You can use the WID keyword to select another window for the display. For example,
you can type this:
IDL> Window, 5
IDL> image5 = LoadData (5)
IDL> TV, image5
IDL> XColors, N ~ t i f y P r o = ~ D i s p l a yTitle=IWindow
~, 5 ColorsT, $
Image=image5, WID=5
IDL> Window, 4
IDL> image4 = LoadData (4)
IDL> TV, image4
IDL> XColors, N o t i f y P r ~ = ~ D i s p l a yTitle='Window
~, 4 ColorsT, $
Image=image4, WID=4
Notice that your two XColovs programs can exist on the display at the same time and
that they display into the proper window. This is not possible with the RSI-supplied
XLoadCT, which must limit itself to a single copy in order to protect its color tables
with are in a common block. (You can have as many XCo1ol.s programs as you like, as
long as each has a different title.)
The equivalent capability for XLoadCT is located in the keywords UpDateCallback
and UpdateCbDatn. The display program must be written so that it can accept one,
and only one, keyword parameter, which must be named Data. For example, you
could write the display procedure like this:
PRO XDisplay, ~ a t a = i m a g e
TV, image
END
Then call the XLoadCT program like this:
IDL> Window, 0
IDL> image = LoadData (7)
IDL> TV, image
IDL> XLoadCT, UpDateCallback='XDisplayl, UpdateCbData=image
If you wanted to pass the window index number into the XDisplay program, you
would have to make the Data variable into a structure. For example, like this:
PRO XDisplay, Data=struct
WSet, struct.wid
TV, struct.image
END
But this would mean you would always have to pass a window index number:
$
IDL> XLoadCT, Up~ateCallback=~XDisplay',
~ p d a t e ~ b ~ a t a = { i m a g e : i m a gwid:!D.~indow)
e,
To my mind, this makes the XLoadCT method a lot less flexible than the XColovs
method for passing information back and forth. I find XLoadCT even less flexible in
widget programs.
Workiirg with Intage Data
Figure 35: Images resized with the Rebiiz commarzd must be integer multiples of the
origirzal image size.
By default, Rebirz uses bilinear interpolation when magnifying an image, and nearest
neighbor averaging when reducing an image. Nearest neighbor sampling can be used
in both directions if the Snrnple keyword is set. Bilinear interpolation is more accurate,
but requires more computer time.
Working with brtnges
The positions start at the top-left of the display and continue to the bottom-right. For
example, in a 384 by 384 window, there are four positions for a 192 by 192 image,
starting in the upper-left corner of the window. Try typing the following commands:
IDL> Window, XSize=384, YSize=384
IDL> TVScl, image, 0
IDL> TVScl, image, 1
IDL> TVScl, image, 2
IDL> TVScl, image, 3
It is also possible to position an image in a window by specifying the pixel location of
the lower-left hand corner of the image explicitly. This is done by specifying two
additional parameters to the TV or TvScl commands after the name of the image data.
For example, to locate the 192 by 192 image named image in the middle of the
window you just created, type:
IDL> Erase, Color=!D.Table-Size-l
IDL> TVScl, image, 96, 96
In this case, you placed the lower-left corner of the image at pixel location (96,96).
Positioning an image in this way is important when you want to leave room for such
additional graphic elements as color bars or other annotations.
For example, type these commands to display a color bar on the left-hand side and the
image on the right-hand side of a window. Your display window should look similar to
the illustration in Figure 36.
Figure 36: The image with a color bar next to it showing the color gradations.
Figzcre 37: Usirzg the TVZmage commarzd rzot only allows you to positiorz images iiz
a device-iizdeperzdent way, it also makes it easy to zrse other graphics
comrnarzds.
An Alternative to TVRD
A color decomposition independent alternative to TVRD, named TVREAD, is
available among the programs you downloaded to use with this book. It can be used in
exactly the same way TVRD is used, but you don't have to be aware of the state of the
Working lvitlz Zrnage Data
Decolnposecl keyword to the Device command. Nor do you have to worry about
setting the True keyword in TVREAD, as you do in TVRD. The return image will be an
8-bit image if the screen dump is done on an 8-bit display, and a 24-bit image if the
screen dump is done on a 24-bit display.
Another advantage of TVREAD is that it can automatically send the screen dump to a
BMP, GIF, JPEG, PICT, PNG, or TIFF file. You don't have to wol-sy about the depth of
your display device, or the state of color decomposition. For example, to display two
images one above the other in a window and create a JPEG file of result, type these
commands:
IDL> LoadCT, 4
IDL> Window, XSize=350, YSize=500
IDL> !P.Multi = [O, 1, 21
IDL> TVImage, LoadData ( 5 )
IDL> TVImage , LoadData (7)
IDL> image = TVREAD (/JPEG)
IDL> !P.Multi = 0
Histogram Equalization
If you look at the distlibution of pixel values in an image, you will often find that the
distribution tends to cluster in a narrow range of values. In effect, the image has a very
narrow dynamic color range. If you spread the pixel distribution out, so that each
subrange of values had approximately the same number of pixels with those values,
the infornation content of the image can sometimes be improved. This process of
spreading the pixel distribution over the entire dynamic color range is known as
histogram equalization.
For example, open the data set CT Stall Thoracic Cavity with the LoadData
command. This image is a CT scan with a narrow dynamic color range.
IDL> scan = LoadData (5)
To see a histogram plot of the pixel value distribution of the variable scmz, type these
commands. Your graphics window will look similar to the illustration in Figure 38.
Figure 38: A normal image with a narro~vpixel distribution. Here most of the pixels
have values behveen 50 and 100.
Basic Zi~tageProcessirtg in ZDL
IDL> LoadCT, 0
IDL> Window, 0, ~ ~ i z e = 6 0 0YSize=250
,
IDL> TV, scan
IDL> Plot, ~istogram(scan),/ ~ o E r a s e ,Max_Value=5000, $
position=[0.5, 0.15, 0.95, 0.951
You see that most of the pixels have values that fall between 50 and 100. To spread the
pixel distribution out over the whole color range, so that there are approximately equal
number of pixels with each possible color value, use the Hist-Equal command, like
this:
IDL> equalized = Hist-Equal (scan)
To see the new pixel distribution histogram and the histogram-equalized image, type:
IDL> Window, 1, XSize=600, YSize=250
IDL> TV, equalized
IDL> Plot, ~istogram(equa1ized) , Max-Value=5000, $
Position= [O.5, 0.15, 0.95, 0.951 , / ~ o E r a s e
The histogram equalized image should look similar to the illustration in Figure 39.
Figure 39: A histogram-equalized image. The pixel distributiorz has been spread out
over the entire color dyitamic range.
Smoothing Images
Images can be smoothed by averaging each pixel value with the values of its
surrounding neighbors. This is known as mean or boxcar smoothing. Mean smoothing
is accomplished in IDL with the Sn1ootl.1function, which performs an equally
weighted smoothing using a square neighborhood of a given odd width. For example,
if the neighborhood is 3-by-3, then each pixel is replaced by the mean value of it and
its eight surrounding pixels.
To see an unsmoothed image and the image smoothed with a 5-by-5 boxcar average,
type:
IDL> Window, 0, XSize=192*3, YSize=192
IDL> TV, image, 0, 0
IDL> smoothed = Smooth (image, 5, /Edge-Truncate)
IDL> TV, smoothed, 192, 0
Notice the Edge-Truncate keyword used with the Smooth command. This keyword
replicates pixels near the edge of the image so that a smoothing occurs over the entire
image. If the keyword is not used, pixels near the edge of the image are simply
replicated and not smoothed.
Working with Ii~tngeData
Your display window should look similar to the illustration in Figure 40.
Figure 40: The origirtal irnage or2 the left, the srnoothed irnage irz the middle, arzd the
urzsharp masked intage orz the right.
Using the Snzootlz command, you are giving equal weight to the neighboring pixels to
determine the mean value. This sometimes results in unwanted blurring of the image.
Another approach is to use a process called com~olutioizto do the smoothing. In this
technique, a square kernel is convolved with the image. For example, the Sr~zootlz
command, in its 3-by-3 case, uses a kernel like this:
1 1 1
1 1 1
1 1 1
You will get less blurring in your image if you give more weight to the central pixel
and less to its neighbors. For example, you might want to create a kernel like this:
To convolve the image with this kernel, use the Convol command, like this:
IDL> kernel = [[1,2,11, [2,8,2], [1,2,1]1
IDL> TV, image, 0, 0
IDL> TV, Smooth(image, 3, /~dge-Truncate), 192, 0
IDL> TV, Convol (image, kernel, Total (kernel), $
/Edge-Truncate), 2*192, 0
You can, of course, create kernels of any size. Here is a typical Gaussian distribution
5-by-5 kernel:
Figure 41: The original irnage on the left, the noisy image in the center, and the
noisy image smoothed with a mediarz filter on the right.
Figure 42: Three different ways to ertharzce the edges of images. Oiz the left, the So-
be2 method. The Roberts method is shown iiz the certtec Coizvolvirtg tlze
image with a Laplaciarz kernel is sho~vitoiz tlze right.
In general, low frequency terms represent the general shape of the image and high
frequency terms add fine detail to the image. Viewing the frequency domain image is
not usually instructive, but it is sometimes useful to observe the power spectlzolz of the
frequency domain image.
The power spectrum is a plot of the magnitude of the various components of the
frequency domain image. Different frequencies are represented at different distances
from the origin (usually represented as the center of the image) and different
directions from the origin represent different orientations of features in the original
image. The power at each location shows how much of that frequency and orientation
is present in the image. The power spectrum is particularly useful for isolating
periodic structure or noise in the image. The power spectrum magnitude is usually
represented on a log scale, since power can vary dramatically from one frequency to
the next.
To calculate and display the power spectrum of this convection image next to the
original image, type:
IDL> power = ~hift(~log(Abs(freq~ornainImage)),
124, 124)
IDL> TV, power, 248, 248
The symmetry in the power spectrum indicates that this image contains a great deal of
periodic structure at increasing frequencies. (Your output should look similar to the
illustration in Figure 43. The purpose of this exercise is to filter out the higher
frequencies in the image.
The next step is to apply a frequency filter to the transformed image. A Butterworth
low-pass filter is used to filter out the high frequency components of the image. These
Workiiig with Zntnge Datn
high frequencies give detail to the image, so the end result will be to perform an image
smoothing operation. Construct the low-pass frequency filter by typing this:
IDL> filter = 1.0 / (1.OD + ( ~ i s (248)/15.0)
t "2)
Notice that the cutoff frequency width is 15 pixels. This will be sufficient to eliminate
about half the higher frequencies you see in the power spectrum in Figure 43.
Figure 43: Art illustration of frequeitcy domain filtering. The zcrzfiltered image next
to its power spectrum iiz the top half of thefigure. Thefiltered image next
to its power spectrum irt the bottom half of the figure. About half the
high-frequency cornportents have been removed from the filtered image.
To apply the frequency filter, transform the image from the frequency domain back
into the spacial domain, and finally display the filtered image, type:
IDL> filtered = ~ ~ ~ ( f r e q D o m a i n 1 r n a*g e
filter, 1)
IDL> TV, filtered, 0, 0
To prove you did in fact filter out the higher frequency components of the image,
display the power spectrum of the filtered image next to the filtered image. Type:
IDL> filteredFreqImg = FFT(filtered, -1)
IDL> power = Shift (Alog (Abs (filteredFreqImg)) , 124, 124)
IDL> TV, power, 248, 0
Your output will look similar to the illustration in Figure 43.
Graphical Display Techniques
Chapter Overview
After you have learned how to display a line, surface, and contour plot, you are on
your way to displaying data in imaginative and innovative ways. This chapter
describes a number of specific visualization techniques that will enhance your data
displays. No attempt is made to describe every possible technique in IDL. Rather, this
chapter introduces a few of the more common ones. The purpose of this chapter is to
give you tools and ideas for creating your own unique data displays.
Specifically, you will learn:
How IDL works with colors
* How to ask for color in a device independent way
How to create and save color tables in IDL
* How to modify axis annotation to your specifications
* How to set up a 3D coordinate system in IDL direct graphics
How to combine graphical displays
How to work with bad or missing data in IDL
How to animate graphical displays
How to grid XYZ data for graphical display
How to provide cursor interaction with your graphical display
* How to erase annotation from your graphical display
How to draw a "rubberband" box on your graphical display
How to use the Z graphics buffer for graphical display tricks
m-
Pixel Value
used as ltlrlex
-
Color Table Color Palette
Q56 colors)
Figure 44: The Indexed Color Model for 8-bit pixel values. The pixel valzle is used
as art index into the color table. The values found in the red, green, and
blue columns of the color table determine the specific color triple asso-
ciated with or indexed to that pixel value.
turned off. Otherwise, they specify colors directly as a color triple.) But the Indexed
Color Model also ties or links the indexed color to a specific location in the color
table, whereas the RGB Colos Model specifies the color directly. Colors that are
linked to a particular color table location are called dy~zanziccolor displays. While
colors that are displayed directly are often called static color displays. For the most
part (there are exceptions), 8-bit displays are dynamic displays and 24-bit displays are
static displays.
The most important difference between a dynamic and static color display is that if
you have a dynamic display and you change the numbers loaded at a particular
location in the color table, pixels that are indexed to that location change colors
immediately. Whereas with a static display, pixel colors are specified directly and
more or less peimanently. They are unaffected by subsequent changes in the color
table values. (This is not always m e . See the discussion below concerning the
DirectColor visual class.)
While this may seem strange, it actually has great value. What it means is that systems
that use the RGB Color Model can display all 16.7 million colors simultaneously,
whereas systems that use the Indexed Color Model can only display 256 simultaneous
colors out of the palette of 16.7 million colors.
You can tell what type of color model you are using by using new keywords to the
Device command that were first introduced in IDL 5.1. These keywords are
Get-Visual-Depth and Get-Visual-Nanze. These are both output keywords which
return a value to a named IDL variable, like this:
IDL> ~ e v i c e , et-visual-Name=thisName, $
Get-Visual-Depth=thisDepth
IDL> Print, thisName, thisDepth
Truecolor 24
this well. The most common problem is that Directcolor visuals often give you
private color maps, which require you to make the graphics window the current
window to load the correct colors in the window. When this is done, other windows
can disappear. This is known as the "color flashing problem" and is a result of the way
the X Window manager handles color tables. Recent advances in both hardware and
software has eliminated many of these problems, but they are still encountered
frequently. As a rule, I like to use either 8-bit Pseudocolor or 24-bit Truecolor
visuals, since I can count on these working properly across many platforms.
The visual class used in an IDL session is normally assigned by default when IDL
starts up, either from information in the .XDefnults file if IDL is running on a UNIX
machine or from IDL's normal rules for assigning the visual class. (The rules require
that IDL inquire of the hardware what visual classes are supported and assigns the
"highest' visual class and depth available.) But this default assignment can be
overruled by specifying the visual class and depth from within IDL. (PC and
Macintosh versions of IDL make their assignment from the graphics card installed on
the machine and its current configuration. This cannot be changed from within IDL.)
This assignment must be made before any graphics windows have been opened and
will apply for the rest of the IDL session.
Here are typical visual class assignment statements on a UNIX machine:
IDL> Device, Pseudo_Color=8
IDL> Device, True_Color=24
eight bits as the blue index. For example, on a 24-bit display with color decomposition
on, IDL tlies to decompose the number 180 in the command above into red, green, and
blue indices. Since the number 180 sets only bits in the red portion of a decomposed
number, this plot will be displayed with a red color, no matter what color is actually
loaded at that index in the color table. You see this idea of a decomposed index
illustrated in Figure 45.
128 128 0l 2 8
Pixel Value
Decomposed into
Three Indices 253 253 253 253
254 254
255 255
254
255
@
255
Color Table Color Palette
(16 million colors)
Figure 45: The RGB Color Model uses a 24-bitpixel v a h e to iltdepettdently speciJL
the RGB compoizents of a color. All 16.7 millioiz colors are available si-
multarzeously in the color palette if the color vectors coiztaiiz valzcesfront
0 to 255.
When a gray-scale color table is loaded (as illustrated in Figure 45) all 16.7 million
colors are immediately accessible to IDL. So, for example, if I wanted to draw a plot
in the color yellow on this 24-bit system, I would want to pick a 24-bit integer number
that had the eight lowest bits set (full red), the eight middle bits set (full green), and
none of the highest bits set (no blue). As it happens, this number for the color yellow
is represented as the long integer 65535. To draw the plot above on a 24-bit color
display, you would type:
IDL> Plot, data, Color=65535L
Since most of us are not fluent manipulating the bits of a 24-bit number, the number is
sometimes expressed in hexadecimal notation, where two digits (0-F) are enough to
set eight bits at a time (i.e., 256 or 2"8 possible values can be set by two hexadecimal
digits). For example, using hexadecimal notation, the way to express full red and
green, but no blue is like this:
IDL> Plot, data, Color= OOFFFF1xL
To draw a yellow (255, 255,O) plot on a charcoal (70,70,70) background, with a
green (0, 255,O) title, using hexadecimal notation, you type this:
Graphical Display Techniqztes
decon~positionone way or the other (e.g., you might be displaying either an 8-bit
image, in which case you want color decomposition to be turned off, or a 24-bit
image, in which case you want color decomposition turned on) you must set it before
you issue the graphics display command:
Device, Decomposed=O
TV, image8bit
Device, Decomposed=l
TV, image24bit, True=l
A new Get-Deconzposed keyword was introduced for the Device command in IDL 5.2
which can tell you the current "decomposed" state.
IDL> Device, Get~Decomposed=usingDecomposed
IDL> Print, usingDecomposed
Note that if you are running IDL on a PC or Macintosh platfornl with a 24-bit display,
and you want to display a 24-bit image in the correct image colors, you must either
have a gray-scale color table loaded or you must set the Deconzposed keyword equal
to l. If you have Deconfposed set equal to 0, then the 24-bit image values are routed
through the color table vectors. This rzever results in what you want, unless a gray-
scale color table is loaded. So a good rule of thumb is to always set color decomposi-
tion on before you display a 24-bit image. Note that the TVInzage program you down-
loaded with this book automatically sets the correct Decolnposed value depending
upon whether the image is 8-bit (Deconzposed=O) or 24-bit (Deco~?zposed=l).
GetColor works like this. If it is called with a single parameter that is the "name" of a
color it recognizes, it returns either a three-element vector that is that color's triple or
a 24-bit value that can be decomposed into that color, depending upon the current
decomposed state of the device at the time the program is called. If you have color
decomposition turned off (or if you are on an 8-bit display device) then you can load
the color triple at a color table index and use it appropriately, like this:
IDL> Device, Decomposed=O
Graphical Display Techiziques
One of the huge advantages of a 24-bit display is that you can have literally dozens of
images on the display at once, each displayed with a different color table. (Remember
that loading color tables on a 24-bit display only really makes sense if color decomp-
osition is turned off. And remember that it is turned or? by default when IDL starts up.)
But what if you now loaded a different color table? Would you want the colors in
those dozens of images to change? Probably not, since each image uses its own set of
colors, which are specified directly.
Thus, if you have an image displayed on a 24-bit monitor, and you change the color
table with a color table changing tool like XColors or XLoadCT, the new colors will
not take effect until you re-display the image, and translate the image pixels once
again through the color table into specific direct colors. To see how you might write
code to change color tables on a 24-bit display and have your graphics re-displayed
automatically, see "Automatic Updating of Graphic Displays When Color Tables are
Loaded" on page 66.
value of one is a linear ramp function from one value to the next. Gamma values of
less than one or greater than one result in exponential ramp functions of different
shapes and steepness.)
The XPnlette command allows you to modify and create your own color tables by
setting end point colors with sliders, and then interpolating the intervening values.
Individual colors may also be modified in this program. Note that you may specify
colors in XPcrlette in color systems other than the RGB color system. In the end,
however, no matter what color system you use to specify the colors, it is a red, green,
and blue color vector that is loaded into the color tables.
Note that if you run XPnlette on a 24-bit display you should be sure that you have
turned color decomposition off, since this program is an old 8-bit program that has not
been updated to work automatically on 24-bit devices.
It is quite easy to make your own color tables as well. Here is a simple little program
(with no error checking!) named Make-CT that can create a color table of an arbitrary
number of colors between two color end points. Open a text editor and type:
FUNCTION MAKE-CT, begcolor, endcolor, ncolors
scaleFactor = FindGen(nco1ors) / (ncolors - 1)
colors = BytArr (ncolors, 3)
FOR j= O r 2 DO colors [ * , j ] = begcolor [j] + (endcolor [j] $
- begcolor [j]) * scaleFactor
RETURN, colors
END
Compile this program by typing this:
IDL> .Compile make-ct
Open the World Elevatiorz Data image and display it in a window, like this:
IDL> image = LoadData (7)
IDL> Window, XSize=360, YSize=360
IDL> LoadCT, 0
IDL> TVScl, image
Suppose you want a color table that goes from yellow (255,255,O) to blue (O,O, 255).
You can create it with the Make-CT program and load it like this:
IDL> yellow = [255, 255, 01
IDL> blue = [O, 0, 2551
IDL> TVLCT, Make CT(yellow, blue, !D.Table-Size)
IDL> Device, ~ e c % ~ o s e d = ~
IDL> TVScl, image
Suppose you want to display this image in 150 colors that go from yellow to green to
blue. You might do this:
IDL> scaledImage = ~ y t S c l
(image, Top=149)
IDL> green = [O, 255, 01
IDL> TVLCT, Make CT (yellow, green, 75)
IDL> TVLCT, maker^^(green, blue, 75)) 75
IDL> TV, scaledImage
Note that a lot of obfuscation of data can take place in the way you choose your colors.
Be careful. You might want to have a look at the paper by Bernice E. Rogowitz and
Lloyd A. Treinish entitled "How Not to Lie with Visualization" in Computers Irz
Physics, 10(3):268, 1996. If you are really interested in color and the display of data,
read The Visual Display of Quarztitative Ii~omzntiorzand Erzvisiorzirzg Irzforrnatiolz by
Edward Tufte. These books just might change the way you write IDL programs!
Workirzg lvitlt Colors in ZDL
You could save the vectors now if you wanted to, but most color table vectors are 256
elements in length. It would be easy to make these vectors that length. Type:
Now, you can write these vectors into a file if you like, but it is easier to use IDL's
Save command to save them in an IDL save file. Type:
, r, g, b
IDL> Save, File=~mycolors.savl
Next, just to prove to yourself that you did save the vectors, load another color table
and delete these three variables. Type:
IDL> LoadCT, 0
IDL> DelVar, r, g, b
IDL> Help, r, g, b
Now, when you are ready to use the vectors, you restore them with the Restore
command. Notice that they come back in the same variable name in which you saved
them. If you have a variable with the same name defined in the same IDL session (as
you do here), the variable will be overwritten. This means you will probably want to
give these variables well-thought out names. Type:
IDL> Restore, l mycolors . savl
IDL> Help, r, g, b
To use these variables you will have to resize them into the number of colors in your
IDL session. The commands will look like this:
IDL> r = Congrid(r, !D.Table Size)
IDL> g = Congrid (g, !D.~ a b l e r ~ i z e )
IDL> b = Congrid (b, !D.Table-Size)
IDL> TVLCT, r, g, b
Rather than type these commands every time you want to load one of your saved color
tables, it would be easier to write a little IDL program that did it automatically. If you
always save your RGB vectors with the variable names r, g, and b, you can write a
program name CT-bad like this. (There is no error checking in this little program!)
Open a text editor and type:
PRO CT-Load, filename
IF N-Paramso EQ 0 THEN filename = 'mycolors.savl
Restore, filename
r = Congrid(r, !D.Table-Size)
Grapltical Display Teclzniqlres
g = Congrid(g, !D.~able-size)
b = Congrid(b, !D.~able-size)
TVLCT, r, g, b
END
Save the file as ct-londpr-o and compile it like this:
Now, whenever you want to load this color table, all you have to do is type this:
IDL> CT-Load
Figure 46: The izzcnzber of major tick i~ttewalsis chaitged with the XTicks keyword.
To write the labels as floating point values with two digits to the right of the decimal
place, type:
IDL> Plot, curve, XTickFormat=I (F6.2)l
It is possible to use specific strings as tick labels, too. This is accomplished with the
TickNnrne keyword, which can have up to 30 string elements. For example, you can
label the plot by the days of the week, like this:
IDL> labels = [lMON1,lTUE1,lWED1,lTHU1,lFRI','SAT1]
IDL> Plot, curve, XTickName=labels
Your output looks like the illustration in Figure 47
The axis annotation can be suppressed by setting the axis tick format to ( A l ) ,like this:
IDL> Plot, curve, XTickFormat= (Al)
1 , , , , , . . , , , , , , . .
30
20
10
0
1 Jan 91 24 Nov 91 16 0ct 92 8 Sep 93 1 Aug 94 24 Jun 95
As you can see, these labels are quite long and there is a danger that they will crowd
together. You may want to display the dates in another way. For example, you might
want to rotate them by 45 degrees with respect to the axis. Unfortunately, the tick
formatting function you just wrote will not help in this case. You will have to resort to
a more brute force method, placing the labels with the X Y O L ~command.
~S
You can, however, still use the Date program to format the strings for you. To make
this work, you will have to draw the plot with the X axis labels suppressed and you
will need a vector with the proper tick values. You can suppress the axis labeling by
setting the tick format for the axis to ( A l ) . You can get the tick label values in vector
form by using the XTick-Get keyword. For example, the plot will be drawn like this
(the Positiorz keyword is used to leave room for the axis labels):
IDL> Plot, dates, curve, XTickFormat=' (Al)l , XStyle=l, $
XTicks=numTicks, XTick-Get=tickValues, $
Position=[O.l, 0.2, 0.85, 0.951
Then the labels will be attached with the XYOutS command. You can use the
![XY].Window system variables to find the end-points of the X and Y axes in
normalized coordinates. This information is critical for positioning the labels
correctly. Your code will look like this:
IDL> ypos = Replicate ( !Y.Window [O] - 0.04, numticks+l)
IDL> xpos = !X.Window [0] + ( !X.Window [l]) - !X.Window [0]) * $
FIndGen (numTicks+l)/ n u m ~ i c k s
IDL> FOR j= O r numTicks DO XYOutS, xpos [j] , ypos [j] , $
Date(0, j, tickValues[j]), Alignment=O.O, $
Orientation=-45, /Normal
Your output will look like the illustration in Figure 49.
30
20
10
0
7 7
U g' 84' ;;7
O9 %.c 0 W q% V
'
9
Q7 Q
, 9 93 9
' Q$
Figztre 49: Rotated axis labels are created tvitlz tlze XYOzttS command.
One way to handle this kind of data is to assign it the value NaN. The NaN value is a
particular bit pattern, different on each machine architecture. The bit pattern for the
machine IDL is running on is stored in the system variable !Values in the field F-NaN.
To see this, open the Elevatiorz Data data set with the LoadData command, like this:
IDL> data = LoadData (2)
IDL> ! P.CharSize = 1.0
This data set is a 4 1 by 41 floating point array. But suppose the data was not complete.
Suppose while you were collecting the data the collecting instrument temporarily shut
down while scanning three lines in the middle of the 2D data set. You want to assign
the NaN value to these three scan lines. You can type this:
IDL> badData = data
IDL> badData[*, 30:32] = !Values.F-NaN
Now, when you display the surface, IDL does not connect those values that are
represented by the NaN bit pattern. Type:
IDL> Surface, badData
Your output will look similar to the illustration in Figure 50.
There is another way to handle missing or bad data besides setting it to the NaN bit
pattern. This is with the Mill-Value and Max-Value keywords that are available for
most IDL graphics output commands. By setting these keywords, any data value that
is less than the minimum value or greater than the maximum value is ignored (not
drawn) by the graphics output command. For example, in the elevation data set you
used above, you can draw just those contours within a specific range. Here are some
commands that show you how this works. Here contours lines with values less than or
equal to 400 and greater than or equal to 1000 are not drawn in the plot on the right:
IDL> Window, XSize=500, YSize=375
IDL> !P.Multi = 10, 2, 1, 0, l]
IDL> values = FIndGen(10)*150 + 100
IDL> label = Replicate (l,10)
IDL> Contour, data, Levels=values , /Follow, C-Labels=label
IDL> Contour, data, Levels=values, / ~ o l l o w ,C-Labels=label, $
Min-Value=400, Max-Value=1000
IDL> !P.Multi = 0
Setting Up a 3 0 Coordiitate System iiz ZDL
Figz~re50: Missing data is represented iit this szcrface plot by the NUN vabe.
Your output in the right-hand side of your graphics window will look similar to the
illustration in Figure 5 1.
Figure 51: Coiztour lines can be suppressed by the Miiz-Value aizd Max-Vahe key-
words to the Contour cornmartd.
two ways: (l) use the Surface comnland with the Save keyword if you want to have
axes in your 3 0 space, or (2) use the Scale3 command if you just want a 3D space, but
you are not concerned about axes.
To give this plot more of a 3D appearance, you might want to connect each point by a
line to the XY plane. In fact, you might want to color the line according to the Z value,
to give the user even more information. You might type this:
IDL> zcolors = ~ y t S c l ( z ,Top=99) + 1B
IDL> LoadCT, 22, NColors=100, Bottom=l
IDL> FOR j=0,40 DO plots, [x[jl, x[jIl [ ~ [ j l ,~ [ j l ,l $
[z [ j ] , 0] , ~olor=zcolors[j] , / T ~ D
Your output will look similar to the illustration in Figure 52.
A graphical display with colors may be considered incomplete without a color bar to
indicate what the colors mean. You can add a colorbar to this display with the
Colorbar program you received with this book. Type:
Settirtg Up a 3 0 Coo~.dirtateSystem iii IDL
I I
Figzcre 52: A scattei-plot iiz tlze 3 0 space set zcp with tlze Surface cornmuitd.
Figure 53: A surface displayed with the axes tlzrouglz the origin. The 3 0 space was
created witlz the Scale3 command.
Notice that the Y axis in this plot does not appear to extend to the edges of the plot.
This is an optical illusion caused by the rotation of the plot. What can you do to
convince yourself that this is really an illusion?
Before you move to the next section, be sure to restore your system variables to their
original values. Type:
IDL> Restore, 'system.sav'
It is also easy to combine surface and contour plots. Open the 41 by 41 Elevatior? Data
data set with the LaadData command like this:
IDL> peak = LoadData(2)
You might want to be able to see, for example, a shaded surface rendition of this data
at the same time that you look at a contour plot of the data. This is easily accomplished
in IDL by using the Slzade-Sulfcommand to establish a 3D coordinate system, which
is then used to position the contour plot. Your code might look like this:
IDL> Window, XSize=400, YSize=400
IDL> TVLct, [70,01, [70,2551, [70,01, 0
IDL> LoadCT, 5, NColors=!D.Table-Size-2, Bottom=2
IDL> !P.Charsize = 1.5
IDL> Shade-Surf, peak, /Save, Background=O, Color=l, $
Shades=BytScl(peak, Top=!D.Table-Size-2) + 2B
You might want to add another Z axis on the right-hand side of this plot, like this:
IDL> Axis, ZAxis=O, 40, 0, 0, /T3D, Color=l
Finally, you are ready to add the contour plot. Be sure to use the NoErase keyword to
avoid erasing whatever is already on the display. The ZValue keyword positions the
contour plots along the Z axis. The value given to the ZValue keyword will be in
Gvnpkical Display Techniques
normalized units. A value of 1.0 will put the contour plot on the top of the 3D
coordinate space that has been created.
IDL> Contour, peak, NLevels=12, Color=l, ZValue=l.O, /T3D, $
/NoErase, /Follow
Note that sometimes when graphics plots are located along the edges of a 3D coordi-
nate space that some lines can get clipped. This is usually the result of round-off eiror
in the calculation that places them in the 3D space. If some of the contour lines in your
contour plot appear incomplete, add the NoClip keyword to the contour command,
like this:
IDL> Contour, peak, NLevels=12, Color=l, ZValue=l.O, / T ~ D ,$
/NoErase, / ~ o l l o w ,/ ~ o ~ l i p
Your output should look similar to the illustration in Figure 55.
Other combinations of graphics output commands are also possible. You are limited
only by your imagination.
Figure 56: The XZizterAnimate animatioiz tool, with the MRI Head data set loaded.
tables while the animation is mnning. Note that if you click the End Animntiorz button,
you will have to type all three animation commands over again to get the animation
working. You will have to stop the animation before you can use the slider to go to a
particular animation frame.
Normally, animations run as fast as your machine allows them to run. (A widget timer
event is responsible for getting the next animation frame in the sequence.) The
animation speed button adds a delay to this timer event. If you want to start your
animation out at a slower speed initially, you can give the final XIlztel-A~ziinnte
command a speed parameter. This number will be between 0 and 100. For example, to
start the animation off at 50% of its fastest speed, you might type this:
IDL> XInterAnimate, Set= [80,100,57], /Showload
IDL> FOR j=O, 56 DO $
XInteranimate, Frame=j, Image=head[*,*,j]
IDL> XInteranimate, 50
This is being done with the Wiizdow keyword. Type this code in a new editor window
as you see it here.
XInterAnimate, Set=[256,320,57], /Showload
yellow = G e t C ~ l o r ( ~ y e l l o w '!D.Table-Size-2)
,
LoadCT, 3, NColors=!D.Table-Size-2
FOR j=O, 56 DO BEGIN
TVImage, BytScl(head[*,*,j], Top=!D.Table-Size-3)
XYOutS, 0.1, 0.1, / ~ o r m a l ,StrTrim(j,2), Color=yellow
XInteranimate, Frame=j, Window=!D.Window
ENDFOR
XInteranimate, 50
END
Save the program as headniziinnte.pro. To run this program (which is called a main-
level IDL program), type this:
IDL> . Run headanimat e
You can put nizy IDL graphics commands inside the loop. This gives you considerable
flexibility with the kinds of data you can animate. You're output should look similar
to the illustration in Figure 57.
Figzcre 57: The arzimatiolz pixmaps are loaded by fillilzg the wilzdow with graphics
and coping the colzteizts of the wirzdow to tlze pix~nap.
Notice the Write MPEG button on the XZrzterAizilnnte interface. Indeed, you can use
the button to create MPEG movies of your data. But be careful. The button suggests
that it is easier to do than it really is, at least in IDL version 5.3. In fact, you should be
careful that your animation window is some multiple of 16 (as in the example above)
or you might obtain strange results. And not all MPEG viewers can play the MPEG
movies created by IDL. Be sure you have a recent version of the MPEG viewer that
supports the "latest" MPEG standard.
Graphical Display Teclzrtiqltes
like this, where the variable angles is an output variable and will contain the set of
Delaunay triangles returned from the calculation:
IDL> Triangulate, lon, lat, angles
Note that you can return the co171,exI?zrll of these points with the Triangulate conl-
mand, too. Simply add a fourth variable argument to the command above and the
points on the perimeter of this set of points will be returned to you. The convex hull is
useful in many arithmetic operations.
You can visualize this set of tiiangles (there are 70 triangles in this data set), by typing
this command:
IDL> FOR j=0,69 DO BEGIN t = [angles[*,j], angles[O,jll & $
plots, lon [tl , lat [tl , Color=3 & ENDFOR
Your output will look like the illustration in Figure 59.
Figure 59: The set of Delaunay triangles returned by the Trtangulate comntartd.
To grid the data, take the set of triangles that came back from Triarzgulnte and pass it
to the TriGrid procedure. You can set up the grid endpoints or bounds, and the grid
spacing. You can also specify the value for data that lies outside the triangles with the
Missii7g keyword. Moreover, you can get the new latitude and longitude vectors that
go with the gsidded data from the output keywords XGrid and YGrid. For example,
you can type this:
IDL> latMax = 50.0
IDL> latMin = 20.0
IDL> lonMax = - 7 0 .0
IDL> lonMin = -130.0
IDL> mapBounds = [lonMin, latMin, lonMax, latMax]
IDL> mapspacing = [0.5, 0.251
IDL> gridData = Trigrid(lon, lat, value, angles, $
mapspacing, mapBounds, Missing=Min(value) , $
XGrid=gridlon, YGrid=gridlat)
You can now use the gridded data in those IDL commands that require it.
IDL> Contour, gridData, gridlon, gridlat, /Follow, $
NLevels=lO, XStyle=l, YStyle=l, Background=l, Color=2
Your output will look like the illustration in Figure 60.
Grnpltical Display Tecltrziqires
Figure 60: The results of the TriGrid commaizd displayed as a contozcr plot.
Note that you can smooth the surface by using the Quilztic or Smootl? keywords (they
are synonymous), like this:
IDL> gridData = Trigrid(lon, lat, value, angles, $
mapspacing, mapBounds, Missing=Min(value), $
XGrid=gridlon, YGrid=gridlat, /Quintic)
IDL> Contour, gridData, gridlon, gridlat , / ~ o l l o w ,$
NLevels=lO, XStyle=l, YStyle=l, Background=l, Color=2
You can also get better results sometimes by using the Extrapolate keyword to allow
the extrapolation of values outside the triangle boundary. For example, you can type
this:
IDL> Triangulate, lon, lat, angles, hull
IDL> gridData = Trigrid(lon, lat, value, angles, $
mapspacing, mapBounds, Missing=Min(value), $
Extrapolate=hull)
IDL> Contour, gridData, gridlon, gridlat, / ~ o l l o w ,$
NLevels=lO, XStyle=l, YStyle=l, Background=l, Color=2
Your output should look similar to the illustration in Figure 6 1.
Figure 61: The results of TriGrid ~vitltthe Extrapolatioit and Smootlz keywords set.
Figure 62: Spherically gridded data displayed as a corztorcrplot ort top of a ntappro-
jectiort.
FValue=value, /Degrees
IDL> gridData = Trigrid(value, Sphere=angles, $
map~pacing,mapBounds, Missing=Min(value), $
/Extrapolate, /Degrees)
IDL> Map-Set, /orthographic, /Grid, /Continents, / ~ a b e l ,$
s so tropic, 35, -100, Color=l
IDL> Contour, gridData, gridlon, gridlat, / ~ o l l o w ,$
NLevels=lO, XStyle=l, YStyle=l, Color=2, /overplot
Your output should look similar to the illustration in Figure 62.
sor is clicked do+vn.It is not. The default behavior is to wait for a click then act as if a
no wait were in effect. In a loop this difference can be critical.
Drawing a Box
You might want to select a portion of the display and draw a box around it. Here are
some commands to select the diagonal corners of a box with the Cursor command,
draw the box (be sure to draw the box so that it includes a portion of the actual data),
and zoom the plot into the box coordinates. First, draw the plot:
IDL> P l o t , c u r v e
Next, use the cursor to select one corner of the box you want to draw. You will want to
be sure to click the cursor in the current graphics window. To make sure you know
which one that is and that it is not hidden, type:
IDL> WShow
Now type the first Cursor command. Click somewhere inside the plot axes:
IDL> Cursor, x l , y l , o own ; S e l e c t one corner of box.
Now type the second Cursor command. Click somewhere inside the plot axes:
IDL> Cursor, x 2 , y2, /Down ; S e l e c t diagonal c o r n e r of box.
The coordinates returned from the Cursor commands above are in data coordinate
space. Draw the box like this:
IDL> P l o t S , [ x l ,x1, x2, x 2 , X 1 1 , [ y l ,y 2 , y2 ,y l Y ~ I Color=~ellow
Your output will look something like the illustration in Figure 63, although the actual
box on your plot will depend on where you clicked in the window.
Graphical Display Teclziziqries
Figure 63: A line plot with a box dratviz arozuzd the portioit of the data. The box co-
ordiizates were selected with the Cursor commaizd aizd the box drawn
with the PlotS comnzaizd.
To zoom into this portion of the plot, you will have to be sure the box coordinates are
ordered properly. This is necessary because you may have first selected the lower-
right corner of the box and then the upper-left, in which case xl will be greater than
x2.You can imagine other scenarios as well. To account for all of them, type:
IDL> xmin = Min ( [xl,x2] , Max=xmax)
IDL> ymin = Min ( [yl,y2] , Max=ymax)
Finally, you are ready to zoom into the portion of the data enclosed by the box. In
addition to setting the data ranges properly, you also have to set the [XYIStyle
keywords. Do you know why? If you don't, try the command below without these two
keywords. What happens?
IDL> Plot, curve, ~ ~ a n g [xmin,
e= xmax] , YRange= [ymin,p a x ] , $
XStyle=l, YStyle=l
IDL> S = Size(image)
IDL> Cursor, col, row, /Device ; Click in the window!
IDL> PlotS, [col, col] , [O, S [ 2 ] 1 , /Device, Color=yellow
IDL> PlotS, [0, S [l]] , [row, row1 , /~evice,Color=yellow
Notice how easy it is to access the data in the image in that particular column and row.
For example, you can easily plot the column and row data profiles for the image, like
this:
IDL> Window, 1, XSize=500, YSize=300
IDL> !P.Multi = [O, 2, l]
IDL> Plot, image[*, row], Title=IRow Profile1
IDL> Plot, image [col, *] , Title=' Column Profile '
IDL> !P.Multi = 0
IDL> WSet, 0
Your output should look similar to the illustration in Figure 64.
200
150
150
100
100
50
50
0 0
0 100 200 300 400 0 100 200 300 400
Figure 64: The columrz and row profiles of the irnage in the colulnlz and row select-
ed with the Cursor commarzd.
Save the file as 1oopl.pr.o. (This file is among the files you downloaded to use with
this book.) To compile and run this little main-level program, type:
Move your cursor into the image window and start clicking with the left mouse button.
You will see the image pixel values printed out in your log window until you use some
button other than the left in the image window.
What happens if you use other keywords besides DOMII?
with the Czrrsor command?
Experiment a little and find out.
You will notice that the line of the box is not yellow, as you might have expected, but
is instead a sort of multicolored hue, although it shows up reasonably well. The
underlying pixels have been "flipped" in this graphics function,
To erase the box, all you have to do is flip the underlying pixel values back to their
original values. This is easily done by issuing the PlotS command again, like this:
You can issue the above command over and over, making the box appear and
disappear at will. Before you go on, be sure you set your graphics function back to
SOURCE mode, like this:
IDL> Device, Set-Graphics-~unction=3
You can easily take advantage of graphics functions in your IDL programs. For
example, open the loopl.pro main-level program you wrote earlier and modify it to
look like this. Here you are going to draw a large cross-hair at each image location as
you click in the image window. Save this program as loop2.pl-o. Type:
yellow = GetC0lor(~yellow~,!D.Tab~e-Size-2)
LoadCT, 3, NColors=!D.Table-Size-2
TvLCT, 255, 255, 0, topcolor
TVImage, BytScl(image', Top=!D.Table-Size-3)
!Mouse.Button = 1
; Go into XOR mode.
Device, Set_Graphics_Function=6
; Get initial cursor location. Draw cross-hair
Cursor, col, row, /Device, /Down
PlotS, [col,col], [O,3601 , /Device, Color=yellow
PlotS, [O,3601 , [row,row] , /Device, Color=yellow
print, ' Pixel Value: , image (col, row)
l
; LOOP.
REPEAT BEGIN
; Get new cursor location.
Cursor, colnew, rownew, /Down, /Device
; Erase old cross-hair.
PlotS, [col,col], [0,360], /~evice,Color=yellow
PlotS, [O,3601 , [row,row] , /Device, Color=yellow
Print, 'Pixel Value: l , image(colnew, rownew)
; Draw new cross-hair.
Plots, [colnew,colnew] , [O,3601 , /~evice,Color=yellow
PlotS, [0,360], [rownew,rownew], /Device, Color=yellow
; Update coordinates.
col = colnew
row = rownew
ENDREP UNTIL !Mouse.Button NE 1
;Erase the final cross-hair.
PlotS, [col,col], [O,3601 , /Device, Color=yellow
PlotS, [O,3601, [row,row] , /~evice,Color=yellow
Graphical Display Techrziqztes
Place your cursor in the image window and click several times with your left mouse
button. You should see a cross-hair at each cursor location. To exit the program, click
the right or middle mouse button.
Figure 65: The device copy technique iizvolves copying a rectarzgzclarportion of the
source wirzdo~vinto a locatiorz in the destiizatio~zwiizdo~v.In practice erz-
tire windows may be copied or the source and destiizatio~zwindow can be
the same wiizdow.
The actual copying is done with the Device command and the Copy keyword (hence,
the name of the technique). The general form of the command is this:
Device , Copy= [ s x , Sy, Col, row, dx, dy, sourceWindowID]
In this command, the elements of the Copy keyword are:
The device coordinates of the lower-left corner of the rectan-
gle in the source window. (The source window is the window
the rectangle is being copied from.)
col The number of columns to copy in the source window. This is
the width of the rectangle.
Erasing Alznotatioiz Froin tlze Display
row The number of rows to copy in the source window. This is the
height of the rectangle.
dx, dy The device coordinates of the lower-left corner of the rectan-
gle in the destiizatioiz window. (The destii~ntioi~
window is
the window the rectangle is being copied to. The destirmtion
window is always the current graphics window.)
sourceWindowID This is the window index number of the source window. The
rectangle is copied from this window into the current graph-
ics window (which is identified by the !D. Wii7h1vsystem
variable). The source window can be the current graphics
window, but it is more often a window other than the current
graphics window. It is often a pixmap window.
To see how this works, create a pixmap window and display the image in it. Pixmap
windows are created with the Wilzdow command and the Pixinap keyword, like this:
IDL> Window, 1, / ~ i x m a p ,XSize=360, YSize=360
IDL> TVImage, BytScl(image, Top=!D.Table-Size-2)
Notice that you had no visual clue that anything happened when you typed these
commands. This is because the pixmap window exists only in video RAM, not on the
display. To be sure there is something in this window, open a third, regular window
and try to copy the contents of the pixmap window into it. If your third window looks
like your image window, you have typed the commands correctly. Type:
IDL> Window, 2, XSize=360, YSize=360
IDL> Device, Copy= [O, 0, 360, 360, 0, 0, l]
Notice that you copied the entire contents of the pixmap window into this new
window. This is similar to just re-displaying the image in the new window, except that
it is several orders of magnitude faster. It is not unusual to copy the entire contents of
a pixmap window into a display window, even if you just have to "repair" a portion of
the display window.
Delete the last two windows you created (including the pixmap window), like this:
IDL> WDelete, 1, 2
It is important to remember to delete pixmap windows when you are finished with
them. They do take up memory that you may want to use for something else. Some
window managers allocate a fixed amount of memory for pixmap windows. Others
use virtual memory if your pixmap window exceeds the capacity of the video RAM.
X-terminals have notoriously little memory for pixmap windows.
To see how the device copy technique works in practice, modify the main-level
program loop2.pro you wrote earlier. You may want to copy that program to another
file and name it loop3.pr.o. Make the modifications shown below.
yellow = G e t C ~ l o r ( ~ y e l l o w !D.Table-Size-2)
~,
LoadCT, 3, NColors=!D.Table-Size-2
TVImage, BytScl(image, Top=!D.Table-Size-3)
!Mouse.Button = 1
; Create a pixmap window and display image in it.
Window, 1, /Pixmap, XSize=360, YSize=360
TVImage, BytScl(image, Top=!D.Table-Size-3)
;Make the display window the current graphics window
WSet, 0
: Get initial cursor location. Draw cross-hair.
Graphical Display Teclzrziqltes
Place your cursor in the image window and click several times with your left mouse
button. To exit the program, click the right or middle mouse button. Notice that the
cross-hairs are drawn in a yellow color.
Delete the pixmap window before you move on to the next exercise. Type:
IDL> WDelete, 1
WDelete, 1
END
To lun this program, type:
IDL> .Run scroll
The program scrolls once. To run it again, type:
Can you modify the program to make it keep scrolling until you stop it?
Z-Graphics Buffer
Figzcre 66: The 2-graphics buffer can be thought of as a 3 0 box that keeps track of
depth infornzation. Rays hit the objects in the 2-graphic buffer and their
pixel valzces are projected back onto the projection plane.
The idea is that once you load your objects into the 3D box, you take a "snap-shot" or
picture of the projection plane. This is the 2D projection of the 3D objects in the box.
Objects that are behind other objects will not be shown. (This behavior can be
modified by the Transparent keyword to some IDL graphics commands, as you will
see.) The snap-shot is, in effect, a screen dump of the projection plane taken with the
TVRD command.
Grnphics Displny Tricks in the 2-Graphics Bziffer
The Copy keyword copies the current color table into the Z-buffer. Be sure to save the
name of your current graphics display device, so you can get back to it easily. Type:
IDL> thisDevice = !D.Name
IDL> Set-Plot, Z , /Copy
Figure 67: The Z-graphics buffer can be used to combine 3 0 objects with hidden
surface removal performed automatically.
Graphics Displny Tricks ilz the 2-Graphics Buffer
Next, you want to constluct the individual polygons that describe these three image
slices or planes. In this case, each polygon will be a simple rectangle with four points
(the corners of the rectangle). Each point in the rectangle will be descsibed by an
(x,y,z) triple. Another way to say this is that each plane will be a 3 by 4 array. You can
type this:
IDL> xplane = [ [xpt, 0, 01, [xpt, 0, zs] , [xpt, ys I zsl , $
[xpt, ys, 01 l
IDL> yplane = [ 10, ypt, 01, [O, ypt, zsl , [xs, ypt, zsl, $
[XS, YPt, 01 l
IDL> zplane = [ [ 0, 0, zptl, [xs, 0, zptl , [xs, YSI zptl, $
[O, ys, zptl l
The next step is to get the image data that will correspond to each image plane. This is
done easily by using array subscripts in IDL. Type:
IDL> ximage = head [xpt, *, *l
IDL> yimage = head[*, ypt, *l
IDL> zimage = head[*, * , zpt]
Notice that these images are all 3D images (one dimension is a l).What you want are
2D images associated with each image plane, so you must reformat these 3D images
into 2D images with the Refor111command, as shown below. In this case, the Refor111
command reformats the image into an 80 by 100 by l image. When the final
dimension in an array is 1, IDL discards it. The result here is an 80 by 100 image.
IDL> ximage = Re£ orm (ximage)
IDL> yimage = Re£ orm ( yimage)
IDL> zimage = Re£ orm ( zimage )
To display these images properly, you want to make sure they are scaled properly into
the number of colors on your display. It is important to scale them in relation to the
entire data set. Scale the data and load colors like this:
IDL> minData = Min (head, Max=maxData)
IDL> topcolor = !D.Table-Size-2
IDL> LoadCT, 5, NColors=!D.Table-Size-l
IDL> TVLCT, 255, 255, 255, topColor+l
IDL> Device, Decomposed=O
IDL> ximage = BytScl(ximage, Top=topColor, Max=maxData, $
Min=minData)
IDL> yimage = BytScl(yimage, Top=topColor, Max=maxData, $
Min=minData)
IDL> zimage = BytScl(zimage, Top=topColor, Max=maxData, $
Min=minData)
Next, you are ready to set up the Z-graphics buffer. The Erase command will erase
whatever may have been left in the buffer previously. In this case, you are erasing
with a white color, to give more definition to your display. Type:
IDL> thisDevice = !D.Name
IDL> Set-Plot, ' Z t
IDL> Device, Set-Colors=topColor, set-Resolution=[400,400]
IDL> Erase, Color=topColor + 1
Set up the 3D coordinate space with the Scale3 command. Here the axes will be
labelled with the size of each dimension. Type:
You are finally ready to render the slices in the Z-graphics buffer. You will use the
Polyfll command for this purpose. The Pnrterrz keyword will be set to the image slice
you wish to display. The Ir~znge-Coo& keyword contains a list of the image
Graphics Display Tricks in the 2-Graphics Buffer
Figzcre 68: Arz example of warpirzg irnage data into plartes irz the 2-graphics buffer.
file tl-ansparent.pro.You can find a copy of this journal file anlong the files you
downloaded to use with this book.)
IDL> Journal, transparent
IDL> Set-Plot, ' 2 '
IDL> Erase, Color=topColor + 1
DL> Polyfill, xplane, / T ~ D ,~attern=ximage,/image-~nterp, $
Image-Coord=[ [0,01, 10, zsl, [ys, zsl, [YS, 01 1 , $
Transparent=25
IDL> Polyfill, yplane, /T3D, Pattern=yimage, /Image-Inter~,$
Image-Coord=[ [0,01, [O, ZS], [xs, zs], [xs, 01 1 , $
Transparent=25
IDL> Polyfill, zplane, / T ~ D ,Pattern=zimage, /Image-Interp, $
Image-Coord=[ [0,01, [xs, 01, [xs, ysl, 10, ys] 1 , $
Transparent=25
IDL> picture = TVRD()
IDL> Set-Plot , this~evice
IDL> Window, /Free, ~Size=400,YSize=400
IDL> Erase, Color=topColor + l.
IDL> TV, picture
IDL> Journal
If you make a mistake and the final output image doesn't look correct, correct the
mistake in the journal file and re-n~nthe journal file by typing this:
Figure 69: The isosurface arzd data slice cornbirzed irz the Z-graphics buffer.
Reading and Writing Data in IDL
Chapter Overview
The purpose of this chapter is to introduce you to the general input and output routines
in IDL. The basic rule in IDL is this: "If you have data, you can read it into IDL."
There is no IDL format, no special data massaging you have to do to make your data
ready to be brought into IDL. This makes IDL one of the most powerful and flexible
scientific visualization and analysis programs available.
Specifically, you will learn:
How to open a file for reading and writing
How to locate files and assign logical unit numbers
How to obtain machine independent file names
How to read and write ASCII or formatted data
How to read and write unformatted or binary data
How to work with large data files
How to read and write files in popular file formats like GIF and JPEG
More often you see Operz commands written in a more general way. For example, you
might see IDL code that looks like this:
OpenR, lun, filename
In this case, the variable l~irlholds a valid logical unit number and the variable
filerlnnze holds a machine specific file name, which will be attached to the logical unit
number.
Note that thefilerznnze variable name is machine specific. This means that it must be
expressed with the local machine syntax if it contains directory specifications and it
Inay be case sensitive on those machines (e.g., UNIX machines) in which file names
are case sensitive.
Finding Files
Another useful command is the FirzdFile command. This command returns a string
array containing the names of all files matching a given file specification. This is
useful in automating file opening tasks from within IDL programs or for building a list
of files from a directory without knowing how many files will be located there at any
one time. For example, if you wanted to print the size (in bytes) of all the data files in
the current directo~y,you might write IDL code like this:
files = FindFile('*.datl, Count=numfiles)
IF numfiles EQ 0 THEN Message, 'No data files here!'
FOR j=O,numfiles-l DO BEGIN
OpenR, lun, files(j), et-~un
fileInfo = FStat (lun)
Print, fileInfo.size
Free-Lun, lun
ENDFOR
One important point to note about the FirzdFile command is that the file specification
(e.g., '*.dat' above) is given in relative path names, not absolute path names. The
command also returns relative path names and not absolute path names. This is differ-
ent from the Pickfile dialog, which always returns an absolute path name.
- -- -- -- -
Logical U~ritNumbers Purposes
1-99 These numbers can be used directly in Open commands
Table 9: The 128 logical zcizit fzzcinbers are separated into two groups. One group
yozc ztse directly. Tlte otlzer yozc inanage ~vitlzthe Get-Lzcit and
Free-Luiz commaizds.
opened fewer than 28 files all at once). If you open files from procedures and
functions, it is an excellent idea to always use the G e t L u n comr7zai1d,since you can
never guarantee that a particular logical unit number will be available if you select it
directly.
24.0000
33.6000 77.2000
5
a r r a y vector scalar
Notice that IDL has put white space between each element of the array variables and
that it has started each new variable on its own line. IDL is using an 80 column width
by default. If you want a different column width, you can set it with the Width
keyword to the Opei?W command.
What you see is that the entire first line is read into the word variable.
Test data f i l e .
(If you wanted to separate the first line into individual words, you could use IDL's
string processing routines to do so.) This ability to read to the end of a line can be a
nice feature of IDL. To see why, first rewind the file pointer to point to the beginning
of the file. You can use the Poirzt-Lurz command for this purpose. Type:
IDL> P o i n t - L u n , lun, 0
Now we can read the first two lines of the data file into a header variable. Type this:
IDL> h e a d e r = S t r A r r ( 2 )
IDL> R e a d F , l u n , header
IDL> P r i n t , header
These commands read the first two lines of the file and position the file pointer at the
start of the array data. You see printed out both header lines on the same output line,
like this:
Test data f i l e . C r e a t e d : Wed May 2 1 1 4 : 3 6 : 1 3 1 9 9 7
Reading arid Writing Fonitatted Data
In this case, IDL reads eight separate data values from the file. When it got to the end
of the first row of data, it went to the second row (rule 4), because more data remained
to be read. You read into the middle of the second row. Now mle 5 comes into play. If
you read more data now, you will start on the third data line, because the rest of the
second line will be ignored. Try it. Type this:
IDL> vector3 = F l t A r r ( 3 )
IDL> ReadF, l u n , vector3
IDL> P r i n t , vector3
You see this:
The variable vector3 contains the values 12.0, 13.0, and 14.0. And now the file pointer
is located on the fourth data line in the file (rule 5 again).
6. Make every effort to convert data into the data type expected by the variable.
To see what this means, read the fourth and fifth data lines into a string array, so that
the file pointer is positioned at the sixth data line (the line that starts with the value
33.6000). Type this:
IDL> dummy = St r A r r ( 2 )
IDL> ReadF, l u n , dummy
Now, suppose you want to read two integer values. IDL makes every effort to convert
the data (floating point values in this case) into integers. Type this:
IDL> i n t s = I n t A r r ( 2 )
IDL> ReadF, l u n , i n t s
IDL> P r i n t , i n t s
You see this:
Notice that the floating point values have simply been truncated. There is no attempt
to round values to the nearest integer in this conversion process.
Reading and Writing Data in IDL
Complex data must have a real part and an imaginary part separated by a comma
and enclosed in parentheses. If only a single value is provided, it is considered the
real value and the imaginary value is set to 0. For example, you can read complex
data from the keyboard by typing this:
IDL> value = ComplexArr (2)
IDL> Read, value
: (314)
: (4.4,25.5)
IDL> Print, value
Be sure to close the test.clc1t file before you leave this section. Type this:
IDL> Free-Lun, lun
Sometimes string processing is easier if you turn strings into byte arrays first. Then
you can process the byte arrays, and convert back to strings at the end. This may be a
good approach here, although other methods are possible. To convert the string to a
byte array, type this:
IDL> thisArray = B y t e (thisstring)
IDL> Help, thisArray
You see this is a 19-elementbyte array. You need to know the ASCII character for a
blank space. Use IDL to tell you by typing this:
Reading arzd Writing Formatted Data
IDL> thisTemp = F l t A r r ( 4 1 )
Open the colunzn.rlnt file for reading and read the header line, like this:
IDL> OpenR, l u n , 'column. d a t , / G e t -Lun
IDL> ReadF, l u n , h e a d e r
Now, since you put the data into the file using a loop, you might t ~ to
y read the data
out of the file using a loop, too. In other words, you might try to type this:
IDL> FOR j = 0 , 4 0 DO ReadF, l u n , t h i s ~ a [tj ] , t h i s ~ o n [ j,] $
thisTemp [ j ]
Unfortunately, this does not work. Although no error is generated by the command
above, no data is written into the variables either. (If you print the values of the
variables you will see they are all zeros.)
The reason this doesn't work is that there is a hard and fast rule in IDL that states that
you calzrzot read into n subscripted variable. The reason for this is that IDL passes
subscripted variables into IDL procedures like RendF by ~jnlueand not by reference.
Data that is passed by value cannot be changed inside of the called routine, since the
routine has a copy of the data and not a pointer to the data itself. Changing this
behavior would require a major re-write of IDL and is not likely to happen.
There are two ways to solve this problem. The first involves reading the data into
temporary variables in a loop. This solution is best implemented by using a text editor
to enter the commands into a file, since it is difficult to write multiple line loops at the
IDL command line. Type this Poilzt-Lurz command to position the file pointer back at
the start of the data file:
IDL> Point-Lun, lun, 0
Type the following commands in a text file named 1oopread.pr-o. The file is among
those you downloaded to use with this book. Type:
templ = 0 . 0
temp2 = 0 . 0
temp3 = 0 . 0
ReadF, l u n , h e a d e r
FOR j = 0 , 4 0 DO BEGIN
ReadF, l u n , t e m p l , temp2, temp3
t h i s L a t [ j ] = templ
t h i s L o n [ j l = temp2
thisTemp [ j ] = temp3
ENDFOR
END
provided by IDL. To see how this works, rewind the data file again by typing this
command:
IDL> Point-Lun, lun, 0
Next, read the data all at once into a 3 by 41 floating point array, like this:
IDL> header = "
IDL> array = FltArr(3, 41)
IDL> ReadF, lun, header, array
Parse the vectors out of this large array using array subscripts, like this:
IDL> thisLat = array [ 0 , * 1
IDL> thisLon = array [l,* l
IDL> thisTemp = array [2,* l
IDL> Free-Lun, lun
Notice, however, that these new vectors are column vectors (i.e., they are 1 by 41 two-
dimensional arrays). Type this:
IDL> Help, thislat, thisLon, thisTemp
To make these column vectors into the more familiar row vectors, the Reform
command is used to reform the 1 by 4 1 arrays into 4 1 by 1 arrays. When the last
dimension of a multi-dimensional array is one, IDL drops that dimension. Type this:
IDL> thisLat = Reform(thisLat)
IDL> thisLon = Ref orm (thisLon)
IDL> thisTemp = Reform(thisTemp)
IDL> Help, thislat, thisLon, thisTemp
respectively, by typing in the Name text widget at the upper right of the form. All three
fields are floating point type.
When you are finished naming and typing each column of data, click the Fi~lisl?button
on the form. The result is an IDL structure variable that describes the data in the file
and can be used as input into the Reacl_ASCII command. Type:
IDL> Help, fileTemplate, /Structure
To read the data in the file, use the Read-ASCII command like this:
IDL> data = Read-ASCII('column.datl, Template=fileTemplate)
The data is read immediately and the result is an IDL structure variable containing
three fields, labeled latitude, lorzgitzide, and tenlperature. Type:
IDL> Help, data, /Structure
If you want to pull the vectors out of the structure, you can type this:
The ASCII-Tenzplare and Read-ASCII commands can be used with either free
formatted data files or with the explicitly formatted data files described below.
This command is especially useful when numerical information is needed from a file
header. For example, suppose the first line in an ASCII data file indicates the number
of columns and rows in the data file, followed by the date the data was collected, like
this:
10 24500 12 June 1 9 9 6
This header could be read from the file and a properly sized data array created for
reading the data, like this:
firstLine =
ReadF, lun, firstLine
columns = 0
rows = o
date = l
Reads, firstline, columns, rows, date
dataArray = FltArr (columns, rows)
contain information about the data inside the file. This information, called n~etadataor
attributes, can be used to read the data properly.)
About the only thing you can learn about an unformatted data file without prior
knowledge is how big it is. That is, how many bytes it contains. For example, the
FStat (File Status) command can tell you the size of this file in bytes, like this:
IDL> f i l e I n f o = F S t a t ( l u n )
IDL> P r i n t , f i l e I n f o . s i z e
65536
There are 65,536 bytes in this file, but this gives you no clue as to how the data is
organized structurally. For example, the bytes could be arranged as a 128 by 5 12 two-
dimensional array or as a 64 by 64 by 16 three-dimensional array. There is no way to
know. Desperate programmers sometimes try various combinations of arrays sizes and
then display the data. If it doesn't "look" right, they tly another size, and so on.
In this case, the data should be arranged as a 256 by 256 byte array, so the image
variable can be set up like this:
IDL> i m a g e = ~ y t A r (r2 5 6 , 2 5 6 )
Now, read the data from the file and close the file, like this:
IDL> R e a d U , l u n , i m a g e
IDL> F r e e - L u n , lun
To display the data, type this:
IDL> W i n d o w , X S i z e = 2 5 6 , Y S i z e = 2 5 6
IDL> D e v i c e , D e c o m p o s e d = O
IDL> T V S c l , i m a g e
To get around this significant limitation, most headers in unformatted data files have a
defined size (usually some multiple of 256). Suppose, for example, that you decide
that all image files will have a 5 12-byte header. You could use the Replicate and String
commands in IDL to create a string filled with blank characters that was exactly 5 12
bytes long like this:
IDL> header = String (Replicate(32B, 512) )
The byte value 32 is the ASCII value of a blank character. To insert the file
information string into this longer header string to create a header of the proper size,
you can use the StrPut (Put a String inside of another string) command in IDL, like
this:
IDL> StrPut, header, f i l e ~ n f o ,0
Finally, the header and the data can be written into a new unfolmatted data file, like
this:
IDL> OpenW, lun, 'process.datl
IDL> WriteU, lun, header, edge
IDL> Free-Lun, lun
When strings are written to an unformatted file, just exactly the number of bytes that
comprise the string are written into the file.
The type of repeating unit in the data file does not have to be a simple 2D image array
as it is here. It can be a complicated structure. For example, each repeating unit might
consist of a 128 byte header, two 100-element floating point vectors and a 100 by 100
integer array. If this was the case, an associated variable might be created for the file
like this:
OpenR, 10, 'example.datl
info = BytArr(l28)
xvector = FltArr (100)
yvector = FltArr (100)
data = IntArr(100, 100)
struct = {header:info, x:xvector, y:yvector, image:data)
repeatingunit = Assoc(l0, struct)
Since the variable that is mapped to this data file is a structure variable, you must
make a temporary copy of the structure before it can be de-referenced. For example, to
display the image portion of the third repeating unit in the file, you would type:
The connection between an associated variable and a file is closed in the usual way
with the Free-Lur? or Close commands, like this:
Free-Lun, lun
Close, 10
Table 10: ZDL can read artd write many popular data file formats, often by meatts
of library routines written iiz the IDL la~tguageor by dynamic lirtk
modules (DLM) that are added to ZDL at run-time. The C D 4 netCD8
and HDFfile formats are known collectively as scierttific data formats
and have their own IDL interface and library of routi~tes.See "Readi~tg
and Writing HDF Data" on page 161for more irtformatio~zabout HDF
file formats.
The query commands all work in the same way. They are functions that return a value
of 0 or l to indicate if they were successful (indicated by a I) reading the metadata of
an image file. If they successfully read a file, an IDL structure variable containing
information about the file is returned to the user as an output variable. Users can
access the fields of this structure to obtain information about the file.
For example, to query the JPEG file, rose.dat, in the IDL exnmples/data subdirectory
and return information about the file in the variablefileirzfo, type this:
IDL> filename = Filepath(Irose.j p g l , $
SubDir= [ examples , data l)
IDL> ok = Query-JPEG (filename, f ileinfo)
Readiirg and Writing Files with Popular File Forrttats
20 5
0 b 1
0 100 200 300
Column Number
Figure 70: The ColumtzAvg program that will be saved as a GZF, JPEG, and TIFF
file.
display device supports) of your display device. To see the depth of your current
display device, type this:
Device, Get-Visual-Depth=thisDepth & Print, thisDepth
If the depth of your display device is eight, you will obtain the image and color table
vectors in one way, if the display device depth is greater than eight, you will obtain
them another way. If you are writing platform-independent IDL code, you will have to
check the depth of the display device and execute the appropriate sequence of
commands depending on the depth of the device.
To obtain the 2D byte array, all you need is to get a screen dump of the current
graphics window. That is done with the TVRD command, like this:
IDL> image2d = TVRD ( )
Note that if you already have a 2D byte array, there is no need to copy the graphics
window. We copy it here because we want the GIF file to contain the entire graphic
display, not just the image data.
If the Display Depth is Greater than Eight
If the display depth is greater than eight, then you have just a bit more trouble ahead of
you in creating a GIF file. Because, remember, when you take a screen dump on a 16-
bit or 24-bit display you obtain a 24-bit image with the color information built into the
image itself. (See "Obtaining Screen Dumps on 24-Bit Displays" on page 73 for
additional information.) In other words, the TVRD command should be used like this:
IDL> Device, Decomposed=l
IDL> image24 = TVRD (True=l)
What you have to do is go from a 24-bit image with colors built into the image itself to
a 2D image and the appropriate color table. You see an illustration of the problem in
Figure 71.
image24 image2d R G B
I I
Figure 71: To create a GIFfile on a 24-bit display, you have to take a 24-bit image
screen dump and extract a 2 0 inzage array and the appropriate color vec-
tors. This is done with the Color-Quan command.
This is done with the Colo~Quarzcommand in IDL. With Colo~Qunrzyou can select
either of two different algorithms for extracting the color information from the 24-bit
image: a statistical method that attempts to find the N best colors that represent the
original colors in the image, or a method that divides the color space into a cube and
uses a dithering method to select colors. I've found the statistical method (which I
illustrate here) the best when there are many colors in the graphic display, and the
color cube method (which is selected by means of values to the Cube keyword) is
better if you have just a few colors in your graphic or your graphic is rendered in
shades of gray. Be sure to read the Color-Qumz documentation if you are not happy
with the result. And please understand that the results of Color-Qunrz are seldom as
good as the original 24-bit display. The command looks like this:
The number 1 as the second parameter indicates the pixel interleaving in the 24-bit
image. It should be the same value as you used for the True keyword in the TVRD
Reading and Writing Files with Popular File Fori~tats
command above. The variables i; g, and b are output variables that now contain the
color table vectors.
In fact the color at index 6 has no relationship at all to the colors at 5 and 7; it is just a
co1,or.
So GIF files that come with color tables cannot be resized with bilinear interpolation.
They must be resized by replicating the pixel values that are already in the image. For
example, this image should be resized by using the NoIrzterpolation keyword to TVIrn-
age, like this:
IDL> TVImage, thisImage, /~oInterpolation
But now you have the task of creating a 24-bit image out of this 2D image array and
the color table vectors. The problem is shown in Figure 72, below.
-
image2d R G B image24
Figzcre 72: Witlz JPEGfiles yozc ofterz have the opposite sitziatiorz from a GZFJile.
Here you have to coizstruct a 24-bit image frorzz a 2 0 image arzd tlze color
table vectors.
You construct the 24-bit image by substituting the color table value into the
appropriate plane of the 24-bit image. This is easily accomplished in IDL by
subscripting the color table vector by the 2D image values. The code will look like
this:
IDL> S = Size (image2d, / ~ i m e n s i o n s )
IDL> image24 = BytArr (3, S [Ol , S [l] )
IDL> TVLCT, r , g, b, e et
IDL> image24 [ 0 , * , *l = r [image2d]
IDL> image24 [ 1,* , *l = g [image2d]
IDL> image24 [ 2 , * , *l = b [image2d]
build your own objects) in a later chapter. But for now, just type the following
command to create a DICOM object:
IDL> thisobject = O b j - N ~ W ( ~ I D L £ £ D ~ Cfilename)
O~~,
To see the metadata of the file (or what is sometimes called the data dictionary), you
can dump the file elements with the Dzn1npElemerzts method. The command looks like
this:
This particular file has 93 different data elements, and I don't want to show them all,
but the first 25 elements in the file are shown in Figure 73.
0 : (0002,0000) : UL : META Group Length : 4 : 176
1 : (0002,0001) : OB : META File Meta Version : 2 : 0 1
2 : (0002,0002) : U1 : META Media Stored SOP Class UID : 26 : 1.2.840.10008.5
3 : (0002,0003) : U1 : META Media Stored SOP Instance UID : 4 4 : 1.2.840.113619.2
4 : (0002,0010) : U1 : META Transfer Syntax UID : 18 : 1.2.840.10008.1.2
5 : (0002,0012) : U1 : META Implementation Class UID : 18 : 1.2.840.113619.6.5
6 : (0002,0013) : SH : META Implementation Version Name : 6 : 1-2-5
7 : (0002,0016) : AE : META Source Application Entity Title : 6 : sdc21
8 : (0008,0000) : UL : ID Group Length : 4 : 390
9 : (0008,0001) : UL : ID Length to End (RET) : 4 : 132456
10 : (0008,0008) : CS : ID Image Type : 16 : ORIGINAL\PRIMARY
11 : (0008,0016) : U1 : ID SOP Class UID : 26 : 1.2.840.10008.5.1.4.1.1.4
12 : (0008,0018) : U1 : ID SOP Instance UID : 44 : 1.2.840.113619.2.1.2.139348932
13 : (0008,0020) : DA : ID Study Date : 8 : 19890203
14 : (0008,0021) : DA : ID Series Date : 8 : 19890203
15 : (0008,0023) : DA : ID Image Date : 8 : 19890203
16 : (0008,0030) : TM : ID Study Time : 6 : 092618
17 : (0008,0031) : TM : ID Series Time : 6 : 095819
18 : (0008,0033) : TM : ID Image Time : 6 : 095846
19 : (0008,0050) : SH : ID Accession Number : 0 :
20 : (0008,0060) : CS : ID Modality : 2 : MR
21 : (0008,0070) : L0 : ID Manufacturer : 18 : GE MEDICAL SYSTEMS
22 : (0008,0080) : L0 : ID Institution Name : 28 : THOMAS JEFF UNIVHOSPITAL MRI
23 : (0008,0090) : PN : ID Referring Physician's Name : 4 : HUME
24 : (0008,1010) : SH : ID Station Name : 8 : FOR.IC0
25 : (0008,1030) : L0 : ID Study Description : 4 : KNEE
Figure 73: Thefirst 25 data elemertts irt the MR-Krzee.dcm data file, obtairzedfi.om
duntpirtg tlte elements to tlte commaizd log wirzdo~v.
The first column of numbers is the Reference Number. The second column of
numbers, which are enclosed by parentheses are the Group and Element references.
These are expressed in hexadecimal notation. The third column of letters is the Value
Represeiztatiorz. And what follows on each line is the Description of the data element.
The easiest way to obtain one of these data elements is to use its G ~ o u pand Elernerlt
number with the Getvalue method of the object. The return value of this function will
be a pointer array, with each pointer pointing to a data element having that particular
Group and Eleinelzt number. For example, to obtain the modality of this image, you
can look at data element 20, which has a Group number of 0008 and an Elelnerzt
number of 0060, like this:
IDL> modality = thisObject-~GetValue(100081x,
'0060'~)
IDL> Help, modality
Reading and Writing Files with Popzllnr File Fonnats
Chapter Overview
HDF stands for Hierarchical Data Fornzat. This is one of the three data formats that
are called Scientific Data Formats in IDL. (The others are CDF and netCDF.) The
HDF file format is a multi-object file format for sharing scientific data in a distributed
environment. The format was developed at the National Center for Supercomputing
Applications. The purpose of the HDF format is to provide a machine-independent,
efficient data storage format for the various types of data and metadata (information
that describes the data) commonly used by scientists. The HDF format has been
chosen as the data file format for the NASA EOS (Earth Observing System) program
because of the ease of retrieving, visualizing, analyzing and managing data that is
stored in this format. Many data products, especially those coming from satellite
systems, are now stored in the HDF format. IDL supports version 4.lr3 of the HDF
library as of IDL 5.3.1. HDF-EOS support was also added in the IDL 5.2 version.
If you want to learn more about the HDF format, you can contact the National Center
for Supercomputing Applications directly. Use your favorite World Wide Web
browser to select the HDF home page at this URL:
The HDF Frequently Asked Questions (FAQ) file can be found at this World Wide
Web address:
You will find a lot of good HDF information at this site, including HDF user guides
and other HDF documentation.
In this chapter, you will learn:
How the HDF file format is implemented in IDL
How to read and write scientific data sets (SDS) in the HDF format
How to store metadata with your scientific data sets
How to store color table palettes with your scientific data sets
Reading aizd Writing HDF Data
Data Descriptor
4
Bytes
Figure 74: Each data object in HDF consists of a data descriptor, shown here, arzd
a data element, which is the data itseg
The tag field identifies the type of data stored in the data element. There are over 200
tags defined by the NCSA for general use. You can get a complete list of tags from
their World Wide Web site. You can see a short list of tags you might encounter in
Table 11, below.
The reference number in the data descriptor distinguishes between different data
elements with the same tag. For example, all scientific data sets will have the same
tag, but the combination of tag number and reference number will uniquely determine
a specific SDS in a file. The HDF routines will keep track of reference numbers for
you as you write HDF data objects to a file.
HDF Applicatioit Prograntnting Interface
--
20 1 8-Bit Palette
70 1 SD Dimension Record
702 SD Data
703 SD Scales
704 SD Labels
705 SD Units
706 SD Formats
708 SD Coordinates
73 1 SD Calibration Data
1963 Vdata
1965 Vgoup
Table 11: Commorz HDF tag numbers and their associated rneaizings.
The offset field points to the location of the data element in the file in bytes. The
length field identifies the size of the data element in bytes.
In general, you don't need to know much about the data descriptor except that it
exists. You will find yourself accessing the tag and reference number fields in various
ways. It will help if you know what these numbers refer to and how they are used.
There are five primary data objects allowed in an HDF file. They are mstel- images
(both 8-bit and 24-bit), colorpnlettes, scierztijk data (which are multi-dimensional
arrays and, in fact, could be 8-bit and 24-bit images), nizrzotntiorzs, and Vdntn (which
are tables of data). You see the five primary HDF data objects illustrated in Figure 75,
below. A sixth data object, the Vgroup, does not contain data, but is used to group the
other five primary data objects within an HDF file. Conceptually, a Vgr-oup is like an
IDL structure variable.
An HDF file consists of an HDF file header and any number of these data objects,
each with its data descriptor and data element. Data objects are individually accessible
in the file, even if they are included in larger sets or groups of data. In fact, a single
data object (e.g., a palette) may be included in several data sets or groupings.
VData
Annotations (tables of data)
Figure 75: The five primary HDF data objects. A sixth object, the Vgroup, does not
hold data, but serves to group aizy of these five primary objects within aiz
HDF data file.
constitute what is called an rrpplicntiorz yrogrrrnznzirzg irzterfice or API for HDF files.
All the IDL routines in the HDF API start with the prefix HDF-. You can generally
determine which data object the IDL routine applies to by the letters that follow this
initial prefix. You see an example of this by viewing the IDL prefixes and the
corresponding HDF objects the IDL routines apply to in Table 12, below.
Notice that there are two different APIs for working with scientific data. The older
API uses the prefix HDF-DFSD-. This model was the original scientific data set
(SDS) model developed by NCSA and it allowed access to a single HDF file at any
one time. The new API uses the prefix HDF-SD- and supports a more powerful and
general SDS model that allows access to multiple HDF files simultaneously.It also
incorporates the netCDF data model developed by the Unidata Program Center. The
HDF-DFSD- API was available in older versions of IDL mostly for backward
compatibility with older HDF files. It has now been dropped altogether in IDL 5.3.
The new HDF-SD- API should be used to create new HDF files.
Similarly, annotations should no longer be used in conjunction with SDS objects.
Metadata that was once stored as annotations in the old SDS model is now more
conveniently stored as attributes in the new SDS model. You will learn to use the new
SDS API in this chapter.
Working ~vitlzHDF Files
In this command the variablefileizni?ze is a string with the name of the file. It is often
obtained in a machine-independent way by using an IDL command like
DialogPickfile, like this:
filename = ~ialog-Pickfile(/~rite)
fileID = HDF-Open(filename, /Create, /write)
The keyword Create opens a new file instead of one that already exists. The keyword
Wi-ite puts the file in write access mode.
You may want to be sure that the file is an HDF file before you try to open it. All HDF
files have a "magic cookie" or special number written as the first four bytes of the file.
The HDF magic cookie is the hexadecimal number Oe031301. You can use the
HDF-IsHDF command to read this magic cookie and tell you whether the file is an
HDF file. The function returns a 1 if the file is an HDF file, and a 0 if not. You can use
it like this:
filename = Pickfile(/Write)
IF HDF-IsHDF(fi1ename) THEN $
fileID = HDF-Open(filename, /Write, / ~ e a d )
Notice that the Write and Rend keywords are both set in this open command. In this
access mode, you can both read data from the file and write additional information to
it as well. (An alternative keyword is RdWr, which has the same effect.)
Reading a i d Writing HDF Data
Note that the number of tags in an HDF file is not very useful. In the first place, the
number of tags will probably not be the same as the number of data objects you wrote
to the file because there are a number of data descriptors written for each data object.
These associated data descriptors pertain to attributes of data objects that may or not
be filled in or written explicitly by the user. It is much more useful to know how many
objects with a specific tag number are in the file.
Suppose you are interested in a particular type of data object, perhaps the number of
SDS objects in the file. You can see from Table 11 on page 163 that if you wanted to
know how many SDS objects were in the HDF file, you could look specifically for tag
number 702. You do this with the Tag keyword, like this:
fileID = HDF-Open(fi1ename)
numberOfSDSObjects = HDF-Number(fileID, Tag=702)
SDS dimensions specify the size and shape of a multidimensional array. The number
of dimensions of the SDS array is known as the m ~ ofk the SDS. If you assign the
same dimension name to two dimensions, the SD interface treats both dimensions as
the same data object and any changes made to one will be reflected in the other. The
size of a dimension will always be a positive integer.
Table 13: The ZDL applicatioit prograinrnirtg interface (APZ)for HDF scientific
data sets. The Type columit iderztifies which routirtes are ZDL
procedures as opposed to ZDL functions.
In addition to these attributes, data sets may have these other predefined attributes:
Calibration This attribute stores the scale and offset values used to cali-
brate the data in the SDS.
Coordinate System This is the coordinate system associated with a particular data
set (e.g, "Device" or "Normal")
Fill Value This is a value used to fill areas between non-contiguous
writes to SDS arrays.
Range A two-element vector that describes the minimum and maxi-
mum value of the data set.
Workilrg with Scientific Data Set HDF Files
To open an SDS file for reading and writing, you can type:
sdFileID = HDF-SD-Start ( filename , /RdWr)
user-defined infolmation can be stored with the HDF file itself as attributes by using
the HDF-SD-AttrSet command. Your code might look something like this:
dimension objects associated with each SDS. The dimension objects are identified by
dimension index numbers that begin with 0 and increase by one for each dimension of
the SDS (as defined above with the HDF-SD-Create command).
You can get the dimension object IDs for the 12 hour data set by typing this:
Now you can store the 1012 and lat vectors in the dimension objects with the
HDF-SD-DilnSet command. At the same time, you will set some of the predefined
attributes for the dimension objects. Type this:
HDF-SD-DimSet, latl2dimID, $
LABEL='Latitude1, $
NAME=ILatitude A x i s t , $
SCALE=lat, $
UNIT= Degrees
You can do something similar for the 24 hour data set, but this time you will take
advantage of the fact that dimensions with the same name, even if they are assigned to
a different SDS, will point to the same dimension object. Type this:
lon24dimID = HDF-SD-DimGetID(sdsID24, 0)
lat24dimID = HDF-SD DimGetID(sdsID24, 1)
HDF-SD -DimSet, l o n 2 4 d i m 1 ~ ,NAME= Longitude Axis
HDF-SD-DimSet, lat24dimID, NAME='Latitude Axis1
If you want the name of the first attribute in the file, you do it directly without first
getting a data set ID. You will use the HDF-SD-AttrIrzfo command, like this:
sdFileID = HDF-SD-Start(filename, /~ead)
HDF-SD-AttrInfo, sdFileID, 0, Name=thisAttrName
Print, thisAttrName
Suppose you have an HDF file, but you don't h o w anything about the number of data
sets or attributes that are in the file. You could open the file and print the names of all
the data sets in the file, by writing code like this:
sdFileID = HDF-SD-Start(filename, / ~ e a d )
HDF SD-FileInfo, sdFileID, datasets, attributes
FOR j=O, datasets-l DO BEGIN
thisSDS = HDF-SD-Select(sdFileID, j)
HDF-SD-GetInfo, thisSDS, Name=thisSDSName
Print, 'Dataset No. l, Str~rim(j,2), l : l , thisSDSName
ENDFOR
Having established all the data set names, you can now print out all the attribute
names, with code like this:
FOR j=O, attributes-l DO BEGIN
HDF-SD-AttrInfo, s d F i l e ~ ~j,
, Name=thisAttr
PRINT, '~ttributeNo. l , + StrTrim(j, 2), l: ' , thisAttr
ENDFOR
It is a good idea to load color tables and obtain the red, green, and blue vectors that
compose those tables from within the Z-graphics buffer. The advantage of doing this
is that you are assured of having 256 colors, no matter how many colors you are using
in your IDL session. The code to produce the 3 by 256 assay that is going to be stored
as a color palette in the HDF file looks something like this (color table 5 is loaded in
this example):
thisDevice = !D.NAME
SET-PLOT, ' Z '
LOADCT, 5, /SILENT
TVLCT, r, g, b, /GET
palette = BYTARR (3,256)
palette (0,* ) = r
palette(1, * ) = g
palette (2,* ) = b
SET-PLOT, thisDevice
When you are ready to store the palette in the HDF file, type this:
HDF-DFP -ADDPAL, filename, palette
When you want to retrieve the color palette from the file, use this command:
HDF-DFP GETPAL, filename, thispalette
To use the color palette in IDL, it must be resized to the number of colors in your IDL
session. The 3 by 256 array must also be transposed into a 256 by 3 assay in order to
accommodate the TVLCT command. Your code might look something like this:
TVLCT, Transpose(Congrid(thisPalette, 3, !D.Table-Size-l))
Chapter Overview
In using IDL, there is perhaps no topic more difficult or as misunderstood as the
question of how to reproduce what you see on your display screen in hardcopy form.
And yet, this is the one requirement that almost all of us who pursue scientific
endeavors must satisfy, for there are few other completely satisfactoly ways to share
our results anlong colleagues.
This chapter will concentrate on PostScript output, since Postscript is almost
universally accepted as the output medium of choice and most programmers working
with IDL will have access to a PostScript printer. Nearly all of what is said about
PostScript also applies to other output devices such as HP plotters or PCL printers.
Specifically, you will learn in this chapter:
* How to select a hardcopy output device
* How to configure a hardcopy output device
* How to send graphical output directly to a printer
* How to send graphical output to a file
* How to create graphical output for the hardcopy output device
How PostScript output differs from the output on your display
* How to position plots and images on the PostScript page
How to produce graphical output that can be included in other documents
* How to write graphical programs that are easily converted to hardcopy output
* How to use color in PostScript output
where optior?is any one of the options listed below. Notice that optior?is always a
string, so it is usually enclosed in quotes. Unlike many other strings in IDL, optior~is
also case insensitive.
CGM The output is written to a file in CGM, or Computer Graphics
Metafile, format. CGM is a device independent file format
used to exchange graphic information. CGM files can be
encoded in one of three methods: (1) text, (2) binary, and (3)
NCAR binary.
HP The output is written to a file in Hewlett-Packard Graphics
Language (HP-GL) format, suitable for output on a variety of
HP-GL pen plotters.
PCL The output is written to a file in Hewlett-Pacltard Printer
Control Language (PCL) format, suitable for output on a
variety of HP laser and inkjet printers.
PRINTER The output is sent directly to the default printer in whatever
form is appropriate for the printer,
PS The output is written to a file in Postscript format.
Z The output is written into the Z-graphics buffer.
After printing, you should set the output device back to your type of graphics display
device with the Set-Plot command. Here are the usual display devices.
WIN A personal computer running the Microsoft Windows or NT
operating system.
MAC A computer running the MacOS operating system.
X A computer running an X Window windowing system.
Only one device can be your currerzt graphics device. You can determine which device
is current by examining the !D.Nanze system variable, like this:
IDL> Print, !D.Name
Note that the name of the device is case insensitive when you set it, but it is rzot case
insensitive when you use the name in your code. The graphics device name that is
stored in !D.Name is always stored in uppercase characters. This is vitally important
in string comparison statements like this one:
IDL> I F !D.Name EQ 'PS' THEN Print, 'Using Postscript . . . I
configure the device. Depending upon which device you have set as your current
device, you might see information about how many colors are available on that
device, what graphics function IDL is using, what hardware font is currently selected,
and so on.
Notice that this information display is different for each hardcopy output option. For
example, type these commands to see how the Postscript output device is configured
by default:
IDL> thisDevice = !D.Name
IDL> Set-Plot, ' P S 1
IDL> Help, / ~ e v i c e
IDL> Set-Plot , thisDevice
Here is the result of the Help command, issued on a Windows NT machine:
Available graphics-devices: CGM HP NULL PCL PRINTER PS WIN Z
Current graphics device: PS
File: <none>
Mode: Portrait, Non-Encapsulated, EPSI Preview Disabled,
Color Disabled
Offset (X,Y): (1.905,12.7) cm., (0.75,5) in.
Size (X,Y): (17.78,12.7) cm., (7,5) in.
Scale Factor: 1
Font Size: 12
Font Encoding: Adobestandard
Font: Helvetica
# bits per image pixel: 4
( ! 3 ) Helvetica ( ! 4) Helvetica-Bold
(!5) Helvetica-Narrow ( ! 6 ) Helvetica-Narrow-Bold
(!7) Times-Roman ( ! 8) Times-BoldItalic
( ! 9) Symbol ( ! 10) ZapfDingbats
( ! 11) Courier (!12) Courier-Oblique
( ! 13) Palatine-Roman (!14) Palatine-Italic
( ! 15) Palatino-Bold ( ! 16) Palatino-BoldItalic
(!17) AvantGarde-Book (!18) NewCenturySchlbk-Roman
(!19) NewCentury-Bold ( ! 2 0 ) Undefined-User-Font
If these commands were issued to the PostScript device instead of to the display
device, you would have a file that contained two pages of output. To send these
commands to a PostScript file, you might type commands like this:
IDL> thisDevice = !D.Name
IDL> Set-Plot, PS'
IDL> Device, XSize=3, YSize=3, /Inches
IDL> Plot, LoadData(1) , Position= [O.l, 0.1, 0.9, 0.81
IDL> XYOutS, 0.5, 0.9, 'Simple Plot', Align = 0.5, /Normal
IDL> Surf ace, Dist (41)
IDL> Device, /Close File
IDL> Set-Plot, thisDevice
Notice that you can use keywords like Positiorl and Nomzal to place graphical display
elements in either the display window or a PostScript window. This is a good way to
create output that looks the same whether it is on the display or in a PostScript file.
You will read more about this in a moment.
A trick you can use to force the PostScript file to advance a page is to use the Erase
command. This is handy, for example, if you want to place several images in a Post-
Script file. Normally, the display window is not erased prior to a TV or TVScl com-
mand, so multiple TV commands will simply stack each image on top of the previous
one. The Erase command allows you to put the images into the same file on different
pages, like this:
IDL> thisDevice = !D.Name
IDL> Set-Plot, 'PS'
IDL> TV, LoadData (5)
IDL> Erase
IDL> TV, LoadData(7)
IDL> Device, /Close-File
IDL> Set-Plot, thisDevice
On personal computers, it is a bit harder to print PostSciipt files created with the PS
device from within IDL. In fact, most people don't bother, since there are numerous
utilities for printing PostScript files available for these machines.
GlzostScript is also available for computers running the UNIX and MacOS operating
systems.
Another interesting program is a free program named PrirztFile. This little utility
program may be set up to utilize drag-and-drop functionality or to send the Postscript
file to a local or networked printer. It will even print encapsulated PostScript files. You
can find this program at this URL:
True-Color Images
The Postscript device can also display a 24-bit color or true-color image. A true-color
image is a 3D may, with one dimension set to 3. For example, an 172-by-ntrue-color
image can be pixel interleaved (3, m, n), row interleaved (m, 3, n), or image
interleaved (m, n, 3).
The Postscript device is equivalent to an 8-bit display device. This means you have
the same issues to consider when you display a 24-bit image as you do for any other 8-
bit device. (See "Displaying 24-Bit Images" on page 64 for additional information.)
For example, here is how a pixel interleaved, true-color image might be sent to a
PostScript device as a color image:
IDL> rose = LoadData (16)
IDL> thisDevice = !D.Name
IDL> Set-Plot, 'PS'
IDL> Device, Color=l, Bits_Per_Pixel=8
IDL> image2d = Color-Quan (rose, l, r, g, b)
IDL> TVLCT, r, g, b
IDL> TV, image2d
IDL> Device, /Close-File
IDL> Set-Plot, thisDevice
Creating Hardcopy Grapltics Outplct
Note that the TVI~nngecommand that you downloaded to use with this book automat-
ically knows if image output is to the PostScript device and sets the image up appro-
priately for viewing. All that is required if you are using TVIlnage are these
commands:
IDL> rose = LoadData (16)
IDL> thisDevice = !D.Name
IDL> Set-Plot, 'PS1
IDL> Device, Color=l, Bits_Per_Pixel=8
IDL> TVImage, rose
IDL> Device, /Close-File
IDL> Set-Plot , thisDevice
Before you move on to the rest of this chapter, be sure your current graphics output
device is your display device. If you are unsure, type the following command, which
will give you details of how your current graphics device is configured:
IDL> Help, / ~ e v i c e
If your display device is not the current graphics device, make it so by typing the
command below. Use the display designation that is appropriate for your machine.
IDL> Set-Plot, ' X 1 ; or 'Win1 or 'Mac1
IDL uses its nonnal iules to position the graphic into the window. In this case, IDL
calculates the size of a character in device units and uses this to determine default
margins for the plot. The plot will go into the window based on those margins and
will, in general, be positioned to fill up the graphics window.
But will what you see on your display be the same as what you see in your Postscript
output? Probably not, although it should be similar. The reason it is not exactly the
same is explained by the ways in which your display device is different from your
PostScript device.
Solution: Make the Aspect Ratios of Graphics Windows the Same Size
So, the first rule of creating virtually identical graphical output is to be sure that your
display window and your PostScript window have the same aspect ratio. This is quite
simple to do. Just calculate the aspect ratio of your current display window and set
your PostScript window accordingly. For example (assuming you have a graphics
window open on your display), you might type this:
IDL> aspectRatio = ~ l o a t ( ! D . Y _ V S ~ Z/~!D,
) x-vSize
IDL> thisDevice = !D.Name
IDL> Set-Plot, 'PS'
IDL> Device, ~ S i z e = 5 ,YSize=5*aspectRatiot /inches
IDL> Plot, LoadData ( l )
IDL> Device, /Close File
IDL> set-plot, thisDevice
The chances of the plot looking the same in the display window and in your Postscript
output are now much higher than they were before.
Creating Hardcopy Grapltics Olctpzct
I like to use the PSWirrdow command to create a PostScript graphics window that has
the same aspect ratio as the current display window. (The program ps~virzdo~czp~.~ is
among the files you downloaded to use with this book.) The program returns (in
inches, by default) the sizes and offsets necessary to create the largest graphics
window possible on the PostSciipt page with the same aspect ratio as the current
graphics window. The return value is used to set the appropriate Device command
keywords, usually through its -Extra keyword. (See "Passing Undefined Keywords
by Keyword Inheritance" on page 216 for additional info~mationabout the -E,~tla
keyword.)
To see how it is used, first open a graphics display window and display a line plot.
IDL> Window, XSize=400, YSize=300 ; Aspect Ratio = 0.75
IDL> curve = LoadDat a (l)
IDL> Plot, curve
Now create a PostScript window with the same aspect ratio and draw the plot in it.
Type:
IDL> rightsize = PSWindow ( )
IDL> thisDevice = !D.Name
IDL> Set-Plot, l P S 1
IDL> Device, -Extra=rightSize, /inches, File=Itest.ps1
IDL> Plot, curve
IDL> Device, /Close-~ile
IDL> Set-Plot , thisDevice
If you have a PostScript printer available or a PostScript previewer, send the file to it.
Compare the output with what is in your display window. Is it identical?
No? But almost? Ok, read on.
150 to 250 pixels in the Y direction on your display. You might create and draw the
box like this:
In a 400-by-400 pixel display window, the ratio of box size to window size is going to
be 1:16. In a 10-centimeter by 10-centimeter PostScript window (about 4-by-4
inches), the ratio of box size to window size is going to be 1:10,000! This is a pretty
small box and almost certainly not what you wanted.
Another way the ratio of character size to resolution affects output is in the way the
graphic output is placed in the graphics window. Recall that by default IDL uses
margins to place the graphic in the window and that margins are calculated based on
character size. If the character size is different on your display and on the PostScript
output, this will affect your graphics output slightly.
You can compensate for this by positioning your plots with the Positiorz keyword,
which uses normalized coordinates to place the axes at an exact location in the output
window, whether you are using a display window or a PostScript window. (See
"Setting the Graphic Position" on page 46 for more information.)
The simple plot above could be put into either window in the same way by using this
command.
Plot, LoadData(1) , Position= [O.l, 0.1, 0.95, 0.951
keyword to the XYOutS command, if possible. You see an example of the difference
between Hershey fonts and true Postscripts fonts in Figure 76.
0.5-
-
U
.- 0.0-
m
-0.5-
-1.0 3 , , , c 0 . 1 m . . 1 . . . l . m . -1.0-...I..,I.,.Im..I...-
0 20 40 60 80 100 0 20 40 60 80 100
Time Time
Figure 76: Tlzefigure oiz the left was created witlz tlte Hershey Simplex Romait font.
The figure on tlze right was created with PostScript Helvetica foizt. Tlze
two plots look similal; but not tlze same.
To select a true PostScript font, set the !P.Folzt system variable or Font keyword to
zero. In the absence of any other information, IDL maps the Hershey fonts to
PostScript fonts according to the mapping in Table 14. You can always discover what
this mapping is in IDL by typing this:
Set-Plot, IPS1
Help, /Device
Notice from the table below that the normal default font, Simplex Roman, is mapped
to Helvetica. The Helvetica font is proportionally larger than the Simplex Roman font
at the same font size in the PostScript output. This means that you must take care
when using font substitution to position text in your graphic output in a way that is
reasonably portable from the display to hardcopy.
In practice, this usually means centering or left and right justifying the output text to a
normalized coordinate. For example, here is code that will build a simple legend on a
plot:
plots, [0.2, 0.31, [0.7, 0.71, /Normal
Plots, [0.2, 0.31, [0.6, 0.61, LineStyle=3, /Normal
XYOUTS, 0.32, 0.7, 'Normal b i a s 1 , /Normal, ~ l i g n m e n t = O . ~
XYOUTS, 0.32, 0.6, 'No b i a s 1 , /Normal, ~lignment=O.O
This code is positioned at the same relative location in both the display window and in
the PostScript window.
Note that you can change the font mapping with the Device command. For example,
font !4 is normally mapped to the Helvetica-Bold PostScript font. If you want to
change this to a Palatino-Bold-Italic font, you can do this:
Device, / ~ a l a t i n o ,/Bold, /italic, Font_Index=4
To use the Palatino-Bold-Italic font in the legend above, you would type:
Creatirrg Qztality Ort@zit O i l Postscript Devices
! 10 Special ZapfDingbats
! 19 Undefined NewCenturySchlbk-Bold
Table 14: The default mappiizgs from Hershey vector fonts, which are itormally
used on the display, and the Postscript fonts that are used in PostScript
output.
PlotS, [0.2, 0.31, r0.7, 0.71, / ~ o r m a l ,Font=O
PlotS, [0.2, 0.31, L0.6, 0.61, LineStyle=3, /Normal
XYOUTS, 0.32, 0.7, !4Normal b i a s ! X 1 , /Normal, Align=O.O
XYOUTS, 0.32, 0.6, !4No bias!Xr, /Normal, Align=O.O
The !X in the strings above causes the default font to revert back to whatever font was
in place before it was switched to the !4 font. See "Adding Text to Graphical
Displays" on page 5 1 for more information about using the XYOutS command.
But the PostScript device essentially ignores any background color infoimation and
always renders the background in white. (OS,more accurately, the PostScript device
doesn't draw a background at all. The color of the paper in the printer is the
backgsound color.) For example, you might try to type these commands to get a green
plot on a chascoal background:
TVLCT, [O, 701 , [255, 701 , [O, 701 , 100
Plot, LoadData(l), Color=100, Background=lOl
While these two commands render the output correctly on the display, they render a
green plot on a white background in PostScript. This is essentially because filling the
page with a backgsound color is a raster operation (i.e., individual pixels are
rendered), whereas most direct graphics commands in IDL are vector operations. To
get the PostScript device to perform raster operations, you have to issue a raster
command such as TV or Polyfill, as described below.
0 0
m
0 5 10
Figzcre 77: PostScript output reverses the background and plotting colors for most
plots, so that output that appears white on black on your display, is black
oiz white in PostScript.
Creating Qzcality Output on PostScript Devices
The plotting color, unlike the background color, is always honored by the Postscript
device. Thus, you can set !l? Color to point to a color index other than 0 and you will
render your plots in that color, both on the display and in your PostScript output. You
can also render plots with the Color keyword and the color specified will be honored
both on the display and in PostScript, but o i ~ l yif the Color keyword is set for the
PostScript device. For example, these two comnlands always draw the plot in red,
whether on your display or in PostScript output if you have turned color PostScript on:
TVLCT, 255, 0 , 0 , 1 0 0
P l o t , LoadData (11) , Color=100
Note that on gray-scale printers, colors are represented as dithered lines, and so show
up as dotted or broken lines, depending on the resolution of your printer and the
"color" that is being represented. In practice, this often means that if you plan to print
your output on a gray-scale printer you will want to make sure that your drawing color
is black. Also note that if you want gray-scale output you must set the Color keyword.
If you don't, you will always get just strict black and white output, no matter what
"color" you are trying to represent.
Problem: Postscript Devices Often Have More Colors Than the Display Device
Another way that PostScript devices are usually different from your display device is
in the total number of colors they use. PostScript devices are always capable of
displaying at least 256 colors. Normally, you use less than 256 colors on your display
device if you run on an 8-bit display device. You can tell how many colors you are
using in your current IDL session by opening a graphics window and printing the
value of the !D.N-Colors system variable, like this:
IDL> W i n d o w
IDL> P r i n t , ! D . N - C o l o r s
Usually, the number of colors will be in the range of 200 to 240 on an 8-bit display.
The value will be "thousands" or "millions" of colors on a 16-bit or 24-bit display.
The number of colors will always be less than 256 if you are running IDL on a
personal computer with an 8-bit graphics card, and will probably be less on other
computers with an 8-bit graphics card unless you have a private color map. The
difference can affect how your output looks, if you are not careful in how you display
your data.
For example, suppose you have 200 colors in your IDL session and you want to
display an image with a gray-scale color table. You might load the color table like this:
IDL> L o a d C T , 0
This command finds the red, green, and blue color vectors that make up the gray-scale
color table in the color table file, and re-samples those vectors so they represent the
number of colors used in the IDL session. In this case, the vectors that are loaded into
the physical color table are 200 elements in length. To display the image, you might
type something like this:
IDL> i m a g e = L o a d D a t a ( 7 )
IDL> T V S c l , i m a g e
The image might look like the left-hand image in the illustration in Figure 78.
If you now wanted this image saved to a PostScript file you might try setting the
PostScript device to be the current graphics device (with the Copy keyword to copy
the current color table into the Postscript file), and re-issuing the TVScl command
above. For example, you might type something like this:
IDL> t h i s D e v i c e = ! D . N a m e
IDL> S e t - P l o t , P S r , /Copy
IDL> D e v i c e , X S i z e = 3 , Y S i z e = 3 , /Inches, / c o l o r , $
Creating Hardcopy Graphics Orrtprrt
Bits_Per_Pixel=8, File=limage.psl
IDL> TVScl, image
IDL> Device, /Close-File
IDL> Set-Plot, thisDevice
But if you did this, you would probably be disappointed in the result. Your output
would look something like the right-hand image in the illustration in Figure 78. That
is, the shades of gray in your Postscript output will be different from the shades of
gray on your display.
Figure 78: If you are not careful about colors, the outpzct oiz your display (left illzis-
tratiorz) will be differentfront the output in your PostScriptfile (right iG
lustratioit). In particular, those pixels with vabes greater than the
number of colors yozc have on your display device will be rendered incor-
rectly. In this case, many of the pixels are rendered too lightly.
The reason for this stems from the way the color tables are loaded into the PostScript
device. When you issued the Set-Plot command above, IDL copied the colors in the
first 200 colors of the display color table into the equivalent colors of the PostScript
color table. (This happens whether you use the Copy keyword or not.) But it does not
affect the colors above color index 200, which had previously been initialized to the
gray-scale color table. You can see what is happening by looking at a plot of the red
color vector as it appears in the PostScript file. The vector should be linear, but you
see in Figure 79, that it has a sharp discontinuity at color index 200. What happens to
the image is that any pixel having a value greater than 199 is being displayed in the
incorrect color in the PostScript output.
3 0 0 ~ ' ' ' ' ~ ' ' " ' ' ' " ' " " " " " ~
a, 250 1
want to see all 256 colors you must store eight bits of pixel information. This is set
with the Bits-Per-Pixel keyword to the Device command, like this:
Device, Bits_Per_Pixel=8, Color=l
Figure 80: Postscript uses scalable pixels to fit iinages into tlze output window size.
Here the size is two inches by two iizches.
IDL> P l o t s , [O, 1 , 1, 0 , 0 1 , [ O , 0 , 1, 1, 0 1 , / N o r m a l
IDL> TV, i m a g e
IDL> D e v i c e , / C l o s e - ~ i l e
You see the result of these command in Figure 81. Notice that the image is now just a
l-inch-by-l-inch image, and only fills up half the output window.
Figure 81: W e n tlze output wiitdotv has a different aspect ratio than the image, the
image is sized so that it maiittains its aspect ratio and completely fills oize
dimensioit of tlze ozctput window.
Figztre 82: This illustratiort is similar to Figzcre 81, except that tlze output wirzdow is
twice as big irz X direction as it is in tlze Y direction.
the image. You might use these commands to display and position the image in the
window:
IDL> image = LoadData (7)
IDL> image = Congrid(image, 400, 400, /1nterp)
IDL> Window, XSize=500, YSize=500
IDL> TV, image, 0.1, 0.1, / ~ o r m a l
IDL> Plot, FIndGen (100), /NoData, / ~ o ~ r a s e$,
Position=[O.l, 0.1, 0.9, 0.91
You can see the output of these commands, issued when your display is the current
graphics device, in Figure 83.
Figzcre 83: Art image witlz box around it rendered on the display.
But if you issue these same commands (without the Wirzdow command) in the
Postscript device, you may get a much different picture. In particular, the image is
sized according to the settings of the output window, which is likely to result in the
box not fitting around the image properly, as illustrated in Figure 84
Figzcre 84: Ziz PostScript the image is sized according to tlze ozctpzct ~virzdo~v
size,
which may not be what you want, as illustrated here.
Figure 85: The proper way to size images and place them irt a Postscript wi~tdo~v
is
to use the sizing arzdpositioizing capabilities of the TV command. Com-
pare this illustratiorz to Figure 84.
If you wanted to write a generic IDL program like the one above that would work no
matter what size window it was going into, and would work on your display or in a
Postscript file, you might calculate the size of the image and its position in the display
window based on device coordinates. The only real difference in working in
PostScript and on your display would be how the image is sized. Your program, which
might be called i??zagecrx.pro,might look like this. (This program is among the
programs you downloaded to use with this book.)
PRO ImageAx, image, Position=position
Creating Qztality Ozityrit oiz PostScript Devices
Figzcre 86: Ruiznirzg the ZrnageAx progranz into art outpzlt ~virzdowof 3.5 inches by
2.5 inches. Notice the aspect ratio of the image has not beerz preserved,
althozcglz its positiorz in the wirzdow Itas been preserved.
This is enough information for only 16 different colors or shades of gray. If you want
256 colors, you must set the Bits-Per-Pixel keyword to 8, like this:
lDL> Set-Plot, ' P S 1
IDL> Device, Bits_Per_Pixe1=8, Color=l
Offset
X Offset
I
I
Figure 87: Tlze window sizes arzd offsets for PostScriptportrait and larzdscape mode.
Notice that irz larzdscape mode, tlze eiztire page is rotated 90 degrees aizd
that tlze offsets (but rzot tlze window sizes) have rotated ~vitlzit. Note that
this is rzot trzce of tlze Printer device, whiclz ahvays calcz~latesits offsets
fror~zthe lower-left corner of the page.
m B
Figure 88: If you don 'tpay atterztiorz to how tlze Iarzdscape offsets work, you may ro-
tate your graphic output right off the page.
determine where on the page the PostScript output should be placed and to set other
configurations of the PostScript device. You see an illustration of PSCorzfig in
Figure 89. The program is called like this:
European users can obtain an A4 page size by using the European keyword, like this:
The draw widget on the right-hand side represents the Postscript page. The small plot
window inside the draw widget delineates the position of the output on the PostScript
page. Notice that the plot window can be resized with the mouse as well as dragged
around in the window. To center the plot window, click the middle mouse button
inside the draw widget window. Pull-down menus provide access to many of the
PostScript device configuration parameters. You can specify a file name in the center
of the dialog.
Creating Hardcopy Grapltics Ozitpzit
Figzcre 89: Tlze dialog ~vidgetprogram PSCorzjIg. This program provides art inter-
active way for tlze zcser to provide irzput to how the PostScript device is
configzired.
When you have things the way you like them, click the Accept button. PSCorlfig
returns a structure, the fields of which are valid Device command keywords. Here is
the way PSCorzfig can be used to configure the PostScript device and draw a simple
line plot:
deviceKeywords = PSConfig(Cancel=canceled)
IF NOT canceled THEN BEGIN
currentDevice = !D.Name
Set-Plot , PS l
Device, -Extra=deviceKeywords
Plot, LoadData (l)
Device, /Close-File
Set-Plot, currentDevice
END IF
Notice that one nice feature of PSCorlfig is that the user is shielded from the rotation
of the offset point when the device is set to landscape orientation. To the user it
appears as if the offset is always calculated from the lower-left-hand corner.
To see how PSCorzfig can be used, try calling the program XWirzdow, which you
downloaded for use with this book. XWir?do~jis a "smart" graphics window that can
resize itself, load color tables that apply only to it, and send its output to a Postscript
file. Try calling it like this:
Try using the XWirzdotv program to produce a PostScript file of the window contents.
Much of the rest of the book is devoted to a discussion of how to write a program like
XWirtdow.
command was used instead to access the machine-specific printer configuration dialog
for the default printer for your machine. Explaining how default printers are set up and
configured for each computer platform is well beyond the scope of this discussion, but
generally printer configuration dialogs give you more options for configusing the
p~interitself and fewer options for positioning the graphical output then, say, you have
with the Postscript device.
To give the user more options for easily positioning graphics with the Printer device,
Research Systems introduced Device keywords for the Printet- device in IDL 5.1.1.
(Note that these keywords apply only when you are sending direct graphics commands
to the printer.) These keywords-XSize, YSize, XOfSset, and YOffset-work much like
the same keywords in other hardcopy output devices, although not exactly. Some of
the differences will be pointed out below.
To access the printer configuration dialog for your default printer, type this:
The dialog on a Windows machine looks like the illustration in Figure 90.
The important thing to know about using the Printer device is that output is only
ejected from the printer when the Cl~se~Docu~nerzt keyword is used with the Device
command. The correct sequence of commands to produce a line plot, for example, will
look like the code below. Closing the printer document is essential. If you forget to do
this, you will not get output out of your printer. Since this code has an IF loop, the
code below should be entered into a text editor just as you see it as a main-level IDL
program. Save the file as selzdprirzte~pro.You can find this program among the files
you downloaded to use with this book.
data = LoadData (l)
ok = Dialog-Printersetup()
IF ok THEN BEGIN
thisDevice = !D.Name
Set-Plot, 'PRINTER'
Plot, data
Device, /Close-~ocument
Set-Plot, thisDevice
ENDIF
END
Creating Hardcopy Grapltics Ozrtput
To run this main-level program and send the output to the default printer attached to
your machine, type:
IDL> .Run Sendprinter
Display Images Differently" on page 193.) The main difference is that image aspect
ratios are not preserved by the Printer device like they are for the Postscript device.
But, like the Postscript device, the PI-inter device expects you to use the XSize and
YSize keywords on the TV or TVScl commands to size the image appropriately. The
image offsets can be set with the XOflset and YOfSset keywords to the device
command.
For example, suppose you will to place a normally square image in the center of the
page with an aspect ratio on the page of 213. You might type something like this:
IDL> thisDevice = ! D.Name
IDL> Set-Plot, 'PRINTER1
IDL> Device, XOffset=1.25, YOffset=3.5, /inches
IDL> image = LoadData (7)
IDL> TV, image, XSize=6, YSize=4, /inches
IDL> Device, /Close-~ocument
IDL> Set-Plot, thisDevice
This problem too is taken care of by the TVI~lzngecommand, which will simply fill up
the window created with the PSWindow program with the image. (Or, you can use the
Positiorz keyword with TVIInnge to position the image in the window in the normal
fashion.)
IDL> keywords = ~ S ~ i n d o w ( / ~ r i n t e/Landscape)
r,
IDL> thisDevice = !D.Name
IDL> Set-Plot, 'PRINTER'
IDL> Device, -Ext ra=keywords
IDL> TVImage , LoadData ( 7 )
IDL> Device, /Close-Document
IDL> Set-Plot, thisDevice
Another huge advantage of the TVItnnge command is that it will work equally well
with 8-bit and 24-bit images when outputting to the Printet; which is an 8-bit device.
Figzcre 91: A demorzstratiotz of a Prirzter bug that occzcrs when a sirzgle color is load-
ed while in tlze Printer device.
Chapter Overview
The purpose of this chapter is to learn the fundamentals of IDL programming.
Specifically you will learn:
The difference between an IDL batch file, main-level program, procedure and
function
How to pass information into and out of IDL programs
How to use positional and keyword parameters in IDL programs
How to compile and l-un IDL programs
The syntax of many common programming control statements
If you think of an IDL program as a sequence of IDL commands in a file, then there
are four types of IDL programs-called IDL program modules-you can write: (1) a
batch file, (2) a main-level IDL program, (3) an IDL procedure, and (4) an IDL
function.
To execute the commands in the file, you place an @ character as the first character on
the IDL command line and follow it with the name of the file. (The .pro extension is
assumed. But if you have given the file some other kind of file extension, you will
have to include it with the file name.) For example, like this:
Notice that the file name is not in quotes. This is different from the normal protocol
with file names in IDL.
IDL interprets the commands in the batch file exactly as if you were typing the
commands at the IDL command line. This means that you might need to use line
continuation characters ($) and other command line syntax to make the command
identical to the command you would type at the IDL command line. If you write the
commands incorrectly in the file, you will get the same kind of errors you get if you
had typed the commands incorrectly at the command line.
Suppose you had eight to ten image files that you wanted to view this way. You would
have to open each image file, read the data into the ilnage variable, and run this batch
file to display the image in a window for each image. But suppose you wanted to
automate this process still more.
You could, for example, automate the data reading and display process like this:
theseFiles = Find~ile('*.img',~ount=numFiles)
Print, 'Number of files found: l , numFiles
FOR j=O,numFiles-l DO BEGIN
OpenR, lun, these~iles[j], et-~un
image = B y t ~ r r(512, 512)
ReadU, lun, image
Free-Lun, lun
this~mage= ~ytScl(image,Top=199)
LoadCT, 5, NColors=200
S = Size(image, /~imensions)
Window, / ~ r e e ,XSize=s [O], ~ ~ i z e [l]
=s
TV, thisImage
ENDFOR
But this file is not appropriate for a batch file because of the multi-line FOR loop. This
kind of program syntax is difficult to write on an IDL command line without a lot of
line continuation ($) and command concatenation (&) characters. To automate the
execution of IDL commands that contain multi-line control statements, it is better to
use a main-level IDL program.
But what if you forgot to pass a positional parameter in the call to Ir~zageOzit?What
would happen if you just typed this:
IDL> ImageOut
In this case, nothing would be passed into the variable lookHela, so that variable
would be undefined (which is a valid type for a variable) inside the procedure.
Unfortunately, this will cause difficulties for you because the first statement in your
procedure uses the variable 1ookHel-e as a parameter for the CD command. It is an
error to use an undefined variable in the CD command in this way. Thus, it becomes
imperative to know how your procedure was called.
Knowing how many positional parameters your procedure was called with gives you
the opportunity to decide whether your parameters are going to be optional or required
parameters for that particular procedure. For example, suppose you want to make the
1ookHei.e parameter a required parameter for this procedure. The first several lines of
code in your procedure might look like this:
PRO ImageOut, lookHere
numparams = N-Params ( )
IF numParams EQ 0 THEN $
Message, 'Must supply one parameter to this procedure.'
CD, lookHere
In this case, the Message command generates an error condition, IDL stops executing
commands in the procedure, and the message string is sent to the command log or
output window. The result is similar to calling the Plot command with no data
parameter.
But it probably makes more sense in this IrnageOut procedure to make the lookHere
parameter an optional parameter. If the parameter is not supplied, then the current
working directory can be assigned to the variable 1ookHere. The code to implement
this functionality might look like this:
PRO ImageOut, lookHere
numParams = N-Params ( )
IF numParams EQ 0 THEN CD, Current=lookHere
CD, lookHere
In this case, if the ZookHere parameter is not supplied in the call to Ir?zageOut,then the
CD command is used with the Curreizt keyword to place the current working directory
into the lookHere variable. (The Currerzt keyword is an output keyword in this case.
You will learn more about this in just a moment.)
A rule of thumb or convention used in defining parameters for IDL procedures and
functions is that positional parameters are used for required parameters and keyword
parameters are used for optional parameters. While this convention is often broken for
positional parameters, it is almost never broken for keyword parameters. That is to
say, you frequently find positional parameters that are optional parameters, but you
rarely find keyword parameters that are required parameters.
Writing a11 IDL Pi.ocedrtre
The entity on the left-hand side of the equal sign is the keyword name. This is the
name that is used when the keyword is used. The entity on the right-hand side is the
variable that holds the value of the keyword inside the procedure or function. For
example, here is how you can define a keyword named ColorTable for the OnngeO~rt
procedure:
PRO ImageOut, lookHere, ColorTable=thisColorTable
The keyword name ColorTable is what you will use when you call the I~nageOut
procedure with a color table value. For example, suppose you want to see these
images using the Red-Temperature color table, or color table 3. You might call this
procedure like this:
IDL> ImageOut, IC:\Data1, ColorTable=3
Be sure you make a distinction between the nanze of the keyword (i.e., how it is used
on the IDL command line) on the left-hand side of the keyword definition and the
variable that holds the value of the keyword on the right-hand side of the definition.
The parameter to N-Elemerzts must be the keyword variable, not the keyword nanze.
This point is often misunderstood because some IDL programmers (including me) like
to have the spelling of the keyword name and variable exactly the same. In other
words, if I were writing this procedure for myself, I would write it like this:
PRO ImageOut, lookHere, ColorTable=colorTable
numParams = N-Params ( )
IF numParams EQ 0 THEN CD, Current=lookHere
CD, lookHere
IF N-Elements(colorTab1e) EQ 0 THEN colorTable=5
There is no correct method, but there is certainly a distinction. IDL will keep the
distinction straight, so you are well advised to do the same. Remember that you are
checking the keyword variable, not the keyword lzanze. I like to have the keyword
variable and name spelled the same because if I do I have less chance of making a
typing mistake. I can think I am checking the keyword name if I like, when what I am
really doing is checking the keyword variable.
It is possible that the image data files may already be scaled, so that this is an
unnecessary step. If this is the case, you may want to define a keyword, perhaps you
could name it Scale, that when set allows the scaling to take place, but when not set
allows the scaling to be skipped. Such a keyword has a binary property: it is either set
or it isn't. Other keywords can also have binary properties. They are true or false, yes
or no, 1 or 0.
IDL provides a special command to deal with keywords like this. It is the command
Keyword-Set. Like N-Elenzerzts, the parameter to Keyword-Set will be the keyword
variable, not the keyword name. But Keyword-Set acts a bit differently. If the
parameter to Keyword-Set is of type undefined or if it has a value of 0, then
Keyword-Set returns a 0, If the parameter is anything else at all, then Keyword-Set
returns a 1 . Thus, you can only get a 0 or a 1 returned from Keybvord-Set.
Many IDL programmers misuse Keyword-Set and treat it as if it meant keyword used.
It doesn't. Using it to determine if a keyword was used or not will eventually result in
an IDL program that fails. Use it only with keywords with binary characteristics.
To make sure the scaling step is only invoked if the Scale keyword is set, the
Imageout code might now be written like this:
PRO ImageOut, lookHere, ColorTable=thisColorTable, $
Scale=scaleIt
numParams = N-Params ( )
IF numParams EQ 0 THEN CD, Current=lookHere
CD, lookHere
theseFiles = FindFile(l*.img',Count=numFiles)
Print, 'Number of files found: l, numFiles
FOR j=O,numFiles-l DO BEGIN
OpenR, lun, theseFiles ( j ) , et -Lun
image = BytArr(512, 512)
ReadU, lun, image
Free-Lun, lun
ZDL Progranntziizg Fz~rzda~tzentals
IF Keyword-Set(scale1t) THEN $
thisImage = BytScl(image, Top=199) ELSE $
thisImage = image
LoadCT, thisColorTable, NColors=200
S = Size(image, /~imensions)
Window, /Free, XSize=s[Ol, YSize=s[l]
TV, thisImage
ENDFOR
END
Now if you wish the data to be scaled, you can call the InrageOut procedure like this:
IDL> ImageOut, /Scale
Remember that the syntax /Keyword simply means Keyvvord= I . This shorthand way
of specifying keywords is often used with keywords that have binary characteristics.
not the Order keyword, and the TV command will do just the opposite, which is
exactly what you had in mind.
Keyword inheritance is so useful that many IDL programmers just add an -Extra
keyword to every procedure or function they write, even if they can't think of a reason
for using it light away.
But let me give you a couple of words of caution. First, it is possible to have a
keyword in the inherited structure that is the same as a keyword already used for the
command. For example, suppose I was already assigning a title to the windows in the
111lageOutprogram with the Title keyword. When I pass the extra structure along to
the Windobc)command it will have two Title keywords defined for it. This would cause
an error if the command was executed, for example, on the IDL command line. But in
the keyword inheritance mechanism, the first use of the keyword is ignored and the
keyword value in the inherited sttucture is honored.
The second word of caution is more subtle. If you have keyword inheritance turned on
by defining an -Ext~-akeyword, you can sonzetimes be confused about why your
program is not working the way you expect. For example, if you now call your
I~nageO~rt program like this, no errors will be reported, even though you have mis-
spelled the Scale keyword:
IDL> Imageout, /Scalee
At the same time, the images will not be scaled. The absence of error messages might
convince you that the scaling portion of your code wasn't working, when what was
really wrong is you mis-spelled a keyword. These kinds of errors can be hard to catch
occasionally. Try to be aware of the possibility.
data is made and passed into the program module. Program modules that work on
copies of the data cannot affect the original data at all.
In IDL, as it turns out, all variables are passed by reference. Anything else-and this
includes such things as subscripted variables, system variables, expressions, structure
de-references, and constants-are passed by value.
Here is a simple example to illustrate the point. Type the following small program into
a text editor. The program resizes an image and displays it in a 150 by 150 display
window. Save the program as resizeit.pl-0.
PRO ResizeIt, image
image = Congrid(image, 150, 150)
Window, 0, XSize=150, YSize=150
TVSCL, image
END
Now load the world elevation data set with LoadData, like this:
IDL> image = LoadData (7)
Use the Help command to examine the variable:
IDL> Help, image
IMAGE BYTE = Array(360, 360)
Now call the ResizeIt program with the image data as a parameter. Re-examine the
image variable. It has changed to a 150-by-150 byte array.
IDL> ResizeIt, image
IDL> Help, image
IMAGE BYTE = Array(l50, 150)
The inzage variable changed because it was passed into the ResizeIt program by
reference. In other words, it had scope inside the Resizelt program. In this case, the
image variable served both as an input variable (information was passed into the
program) and as an output variable (different information was passed out of the
program). Variables can easily be input variables, output variables, or both, depending
entirely upon how you write the code inside the IDL program.
Suppose you wanted to pass the image data into the program, but you don't like the
fact that it gets changed inside the program. You can do one of two things: ( l ) rewrite
the ResizeIt program so that it doesn't change the image variable, or ( 2 )pass the image
variable into the current program by value instead of by reference. In the first case, the
program could be rewritten like this:
PRO ResizeIt, image
Window, 0, XSize=150, YSize=150
TVSCL, Congrid(image, 150, 150)
END
In the second case, you can use an expression for the positional parameter, like this:
IDL> ResizeIt, image + OB
Sometimes you will have the opposite problem. You want something to change inside
a procedure or function, but it doesn't. This is almost always because the parameter
you passed into the procedure or function is not a variable. It may be part of a
variable. For example, it might be a field of a structure. But structure de-references,
system variables, and any kind of expression are all passed by value, not by reference.
Only variables (and then, only complete variables) are passed by reference.
For example, suppose the image above were in a system variable that you had defined
like this:
Wiitiitg an ZDL Procedzlre
passes keywords by reference was added in IDL 5.1. This mechanism uses a
Ref-Extra keyword definition mechanism. You can use either the -Ext~.a or
-Ref-Extra mechanism, but not both at the same time.
Since ImngeOut doesn't yet have a Return statement, the function returns an implicit 0
as the return value.
But suppose you wanted I~nngeOutto return the number of files that were opened and
displayed, You might write the program like this, with changes indicated by bold type,
below:
FUNCTION ImageOut, lookHere, ColorTable=thisColorTable, $
Scale=scaleIt, -Extra=extra, Filenames=theseFiles
numParams = N-Paramso
IF numparams EQ 0 THEN CD, Current=lookHere
CD, lookHere
theseFiles = FindFile('*.imgl,Count=numFiles)
ZDL Programntiizg Fzmdait~eittals
An odd, non-zero value for byte, integer, and long integer data types.
Any non-zero value for floating-point, double-precision, and either single or dou-
ble-precision complex data types.
Q
Any non-null string for string data types.
Any condition or expression that is not true in IDL evaluates as false using Boolean
logic.
ZDL Programming Fztridmtentnls
Note that pointers and objects are not evaluated as true or false. Rather, they are eval-
uated as valid or izot ~raliclby using the Ptl.-Valid or Obj-Valid commands, sespec-
tively.
An example of a control statement of the first type is the classic IF...THEN ...ELSE
control statement. It is written like this in IDL:
IF test THEN statementl
where test is a Boolean expression, usually, and stnteinentl is the kind of IDL
command you might type at the IDL command line.
Note that test must always evaluate to a scalar value and it 111ustbe defined for the
expression to be processed correctly by IDL. For example, this expression will fail
because the variable coyote is undefined:
IDL> IF coyote EQ 'tricky1 THEN Print, 'Missed him!
An example of a control statement of the second type is the classic FOR loop. In a
FOR loop you execute the same statement over and over again until the counter gets to
a preset value. It is written like this:
FOR j=0,10 DO statementl
where j is the counter and staternerltl is the IDL command to be executed. For
example, this Priizt command would be executed l 1 times and print the values 0 to 10:
IDL> FOR j=0,10 DO Print, j
..
The IF. THEN...ELSE Control Statement
One of the most widely used control statements is the IF. ..THEN ...ELSE control
statement. It works on the basis of a Boolean test or other expression that always
evaluates to true or false. (See "True and False Expressions in IDL" on page 223 for
more information.) Here is an example of such a statement:
IF (num GT 10) THEN index = 2 ELSE index = 4
The ELSE part of the statement is completely optional. The control statement can also
be written like this:
IF (num GT 10) THEN index = 2
The syntax of an IF...THEN ...ELSE control statement using multiple statements is just
a bit tricky. Remember that this must appear to the IDL interpreter to be a single IDL
command. If you were at the IDL command line trying to write a multiple line
command for the IDL interpreter, you would have to use line continuation characters
and line concatenation characters to accomplish your purpose. For example, a multi-
line IF...THEN ...ELSE control statement might be written like this on the IDL
command line:
IDL> IF (num GT 10) THEN BEGIN $
index = 2 & $
nurn = 0 & $
ENDIF ELSE BEGIN $
index = 4 & $
nurn = -10 & $
ENDELSE
The line continuation and concatenation characters are completely unnecessary when
this code is written in a file that is compiled before the code is executed. For example,
this code would be written like this in a file that is part of a main-level program or IDL
procedure or function:
IF (num GT 10) THEN BEGIN
index = 2
nurn = 0
ENDIF ELSE BEGIN
index = 4
nurn = -10
ENDELSE
The BEGIN and END statements imply the line concatenation and continuation for
the compiler, as opposed to the IDL interpreter. But, the syntax of the command is not
entirely arbitrary for the compiler, either.
For example, some programmers like to align their BEGIN and END statements so
they can see at a glance what they refer to, like this:
IF (num GT 10) THEN
BEGIN
index = 2
nurn = 0
ENDIF ELSE
BEGIN
index = 4
nurn = -10
ENDELSE
ZDL P~.ogramittiitgFlcrzdai~zentals
But this code cannot be written this way in IDL. The reason is that the
IF...THEN ...ELSE control statement is broken up inappropriately and does not appear
to the compiler as a single statement. If you wish to use the code format above, you
will have to use line continuation characters in the code, like this:
I F (num G T 1 0 ) THEN $
BEGIN
index = 2
num = 0
ENDIF E L S E $
BEGIN
index = 4
num = - 1 0
ENDELSE
In this statement, the test (~zurt?GE 10) is evaluated first. If this test is true, the index
variable is set equal to the first variable (this could itself be an expression) to the right
of the question mark operator and to the left of the colon. If the test is false, the index
variable is set equal to the variable (or expression) to the right of the colon.
In this case, the counter j starts at l and goes to 10. Thus, the statements are executed
10 times. If you wanted j to increment by something other than 1, you can also specify
the increment. For example, to have j increment by 2, you can type this:
a = l
FOR j = l , 1 0 , 2 DO B E G I N
Print, j
a = a * j * 2
ENDFOR
Whereas CASE executes at most one statement within the CASE block, SWITCH
executes the first matching choice and any following choice in the SWITCH block.
Once a match is found in the SWITCH block, execution continues to any remaining
choices. For this reason, the BREAK control statement is commonly used in SWITCH
choices to force an immediate exit from the SWITCH statement block. The ELSE
clause of the SWITCH statement is optional. If included, it matches any SWITCH
expression, causing its code to be executed. For this reason, it is usually written as the
last choice in the SWITCH statement.
The ELSE statement is executed only if none of the preceding choice expressions
match. If an ELSE clause is not included and none of the choices match the SWITCH
expression, program execution continues immediately below the SWITCH statement
block without executing any of the SWITCH choices.
...
RETURN
Problem: Print, 'Problem reading data. Returning . . . l
RETURN
END
Notice the colon after the program label, just like in the GOT0 statement. There does
not have to be a valid IDL statement on the program label line.
Table 15: The possible actiorts that the On-Error statement cart take wherz a
programinirtg error occurs.
New users of IDL programs are often confused when an error occurs, because the
default behavior (On-Error is set to 0) is to stop in the context of the program module
that caused the error. Because they have an IDL prompt, many users think they are at
the main IDL level. They are often dismayed when they type a Help command to see
that all of their variables have "disappeared.
What they are actually looking at are the local variables that reside inside the crashed
program module. Typing the command RetAll (Return All the way back to the main
IDL level) will restore their disappeared variables and get them back to where they
think they are.
I often joke in my IDL programming courses that RetAll is the programmer's most
important tool. Seasoned IDL programmers almost always type RetAll as an automatic
response to any program that fails to exit normally. It is a good habit to get into, espe-
cially if you are writing widget programs, which can behave very strangely indeed if
you forget this important command.
To prevent confusion among the people who are using your program, it is sometimes a
good idea to add an Orz-Error; 1 to the top of your program code once your program
has been debugged. Then, if and when the program crashes, IDL will execute its own
implicit RetAll command and take the user back to the main IDL level.
where tl?eErl.oris the name of a progranl variable (it can, of course, have any name
you like). When the statement is executed, IDL registers a Catcl?error handler for that
particular program module. Only one Catch error handler can be registered for a
module at any one time. At the same time that the error handler is registered, the
progranl variable (tlzeErt-01.in this case) is set to the value 0.
If a run time error occurs in the program module that has the Catch error handler
registered for it, then the program variable is set to the proper error number (each
programming error has its own associated number), and program execution is
transferred to thefirst line of code after the Catclz error handler.
In practice this line contains a block of error handling code. For example, suppose you
were going to read a file from within your code. File reading is notorious for
producing all kinds of program errors. You might want to protect that section of code
with a Catcl?error handler, like this:
Catch, theError
I F t h e E r r o r NE 0 THEN BEGIN
Catch, /Cancel
P r i n t , ' P r o b l e m reading d a t a f i l e . R e t u r n i n g . . . l
I F N - E l e m e n t s ( l u n ) NE 0 THEN F r e e - L u n , lun
RETURN
ENDIF
O p e n R , l u n , f i l e n a m e , fi et-~un
data = B y t A r r ( 2 5 6 , 2 5 6 )
R e a d U , l u n , data
Free-Lun, lun
Catch, /Cancel
Notice that the Catcl? error handler can be canceled at any time. In this particular
example, it is canceled as the first line in the error handler code. This prevents an
infinite loop if, heaven forbid, you introduce program errors in your error handling
code! Later in the code a new Catch error handler could be established. There is no
limit to how many Catch statements you can have in your code, but only one Catch
error handler can be registered at any particular time.
Notice, too, that this Catclz handler is being used to catch a file inputloutput
programming error. What about On-ZOError?
Catcl? error handler, then an error in program module D will be handled by the Catch
error handler in module B. Similarly, if module C had an 011-Error equal l (return to
the main program level) error handling, the error in module D would skip over
modules B and A in going directly back to the main program level.
Reporting Errors
Errors, when they occur, are reported to the user via the !Error-State system variable.
(Note that !Error-State replaces the !Error; !Ei.l.-Sti-irzg, !SysErr-String, !SysErroi;
and !Msg-Prefix system variables. You may still find these old system variables used
in legacy code.) The !Er!-ocState system variable is a structure containing a number
of important fields, but the two most useful in general programming are the Code and
Msg fields. The Code field holds the error number associated with the error condition.
(All errors in IDL are assigned a particular error number.) The Msg field contains the
text of the error message.
For example, type these commands in IDL:
IDL> Print, xxx
IDL> Help, ! Error-State, /Structure
You see the following information in your command output log:
% PRINT: Variable is undefined: XXX
MSG-PREFIX STRING 1% I
You can see how the message prefix and message were used to construct the error
message on the first line. Note also that this particular error is assigned a number or
code of -167. You could use the Code field of the system variable to trap a particular
type of error, if you know its code number.
Using !Error-State.Msg will allow you to write a more general puipose Catch error
handler than the one above. For example, you might report the error message to the
user like this:
Catch, theError
IF theError NE 0 THEN BEGIN
Catch, /Cancel
Print, !Error-State.Msg
RETURN
ENDIF
Rather than just printing the error out into the user's command log window, you
sometimes want to make more of an issue of it and stop all program execution until
the user acknowledges that an error has occurred. The Dinlog-Message command is
useful for this purpose, since it is a modal widget program that must be dismissed by
the user before program execution can continue. For example, the Catclz error handler
can be written like this:
Catch, theError
IF theError NE 0 THEN BEGIN
Catch, /Cancel
ok = Dialog-Message(!Error-State.Msg, /Error)
Using Prog~mnCorztrol Stntements
RETURN
ENDIF
Keywords on the Dinlog-Message command can change the way the platform-
dependent dialog appears to the user. No keywords at all presents what is called a
"warning" dialog. The Error keyword used above produces an error dialog. The
Zifol-~zatioi?keyword will produce an informational dialog, as shown in Figure 92.
And, of course, it is possible to put any message text you like as the argument to the
Dialog-Message command. You are not restricted to the error message text at all. In
the examples shown below the error message was the string "Error Message Here".
Figzcre 92: The differerzt types of dialogs available with Dialog-Message. Tlzese di-
alogs are from a Wiizdo~vscompzctec Yours inay look differentfiont tlze
oizes illzcstrated here.
Note that prior to IDL 5.3 the Dialog-Message command could only be used if the
current graphics device supported widgets. This limited its usefulness in IDL pro-
grams that were designed to lun in a graphics device independent way. Error handlers
had to be written to check whether widgets were supported (the system variable
!D. Flags was used for this purpose) and then call either Dinlog-Message or the Print
command. (The Message command, described below, was also sometimes used.) To
circumvent these problems and to incorporate other features, the Error-Message pro-
gram was written. This program is among those you downloaded to use with this
book. It is described in more detail below.
Generating Errors
There are two ways to generate an error condition in IDL: (1) make an error (bad), or
(2) generate your own error condition as a result of some test (good). Well-written
programs try to avoid the first by making extensive use of the second. Your users will
appreciate this because you will have very helpful error messages. Won't you? I mean,
the alternative is to just let errors occur and allow IDL to supply the (sometimes
cryptic or unhelpful) error messages in !Erro~"-State.Msg.
You can generate your own error condition with the Message command in IDL. The
text argument will be your especially helpful error message:
Message, 'Whoops, an error occurred. Sorry. : - ) l
The text passed to the Message command will be placed into the !Error-State.Msg
field, and the Code field will be set to -7. (The Message command has no provision
for setting the error code number, unfortunately). The Message command is often used
in conjunction with a Catch error handler: For example, suppose this is your Catch
error handler code in an IDL program module named Display-Zimzge:
Catch, theError
IF theError NE 0 THEN BEGIN
Catch, /Cancel
ok = Dialog-~essage(!Error-state.Msg, / ~ r r o r )
RETURN
ENDIF
ZDL Prograinming Fzcizdameiztals
And suppose you were checking to see if an inzage variable was really a 2D array, like
this:
ndims = Size(image, /N-~imensions)
IF ndims NE 2 THEN BEGIN
Message, 'Image argument must be 2D array.'
END IF
If the user passed a 24-bit image into the program as an argument (a 24-bit image is a
3D array), the error message would appear to the user as in Figure 93, below.
Figure 93: The Dialog-Message commarzd displaying the error message generated
by the Message command.
Note that the name of the program module in which the error occurred is placed before
the error message. This is why you want to use the Message command to print the
error message to the display, rather than, for example, the Print command. In other
words, this capability is part of the error handling machinery built into IDL and can be
accessed by the Message command.
Tracing Errors
Sometimes it is important to know where an error has occurred in your program.
Especially if you want to fix it. You can use the Tmcel?ack keyword to the Help
command after an error has occurred, for example, to find this information.
IDL> Help, /Traceback
And you can find out which program module generated the error by examining the
calling stack with the Help command's Calls keyword:
IDL> Help, Calls=callStack
This works well for the case in which program operation stops when an error occurs
(e.g., when On-Error is set to 0). But it often is inadequate when you are catching
errors that may have occurred in a module other than the one which registered the
Catch command.
For this reason (and others) I have written the Error-Message command, which is
among the programs you downloaded to use with this book. Called without positional
parameters, the Error-Message command uses either the Dinlog-Message or the
Message command with the Irzfon?zatiorzal keyword set, depending upon whether
widgets are supported by the current graphics device, to print the !Error-State.Msg
field. Or, you can supply your own text message as an argument to the command. For
example, the error handler above can be re-written more simply as this:
Catch, theError
IF theError NE 0 THEN BEGIN
Catch, /cancel
o k = ~ r r o r - ~ e s s a g(e/ ~ r r o r )
RETURN
ENDIF
Cornpililtg nrtd Rzcnning IDL Pr.ogrnln Modzcles
A nicely formatted esror traceback will be written to the command log window.
machines it is a good idea to keep all file names in lowercase letters. This is essential
for the automatic compilation of files. (See "Rules for Compiling IDL Program
Modules Automatically" on page 236 for more infolmation on automatic
compilation.)
Note that to run a program module that has been compiled, you must use the module's
name in an IDL command. For example, to use the CIizdex. procedure that was com-
piled above, you would type:
IDL> CIndex
The module's name is not case sensitive inside of IDL.
What this means in practice is that program modules impostant enough to be called
from the IDL command line or used in more than one other procedure or function
should be placed in their own files and the files should be given the names of the
program modules (with a .pro file extension). Any other modules that are included in
the file should be located before the main program module and should be utility
routines for the main program n~odule.
Note that IDL system routines (e.g., Plot, S~rlface,etc. always have precedence over
any other command name. You should not try to give your programs the same name as
built-in IDL commands.
files are always guaranteed to be compatible from one IDL version to another.) End
users will, in general, have to restore the files using the same version of IDL that you
used to save them. This can also be important if you upgrade to a new version of IDL.
Writing an DL Graphics Display Program
Chapter Overview
The purpose of this chapter is more ambitious than most. Even though IDL is a
programming language, it is impossible to find anywhere in the official IDL
documentation how to write an IDL program. I don't mean to suggest there is only one
right way. But anyone who has looked over the shoulders of as many IDL
programmers as I have knows there is definitely a distinction between a good IDL
program and one that is not so good. As someone who spends a lot of time with people
who are trying to learn IDL for the first time, I see a lot of not-so-good programs.
I am convinced the problem is lack of information. Most people using IDL are, after
all, scientists, not computer programmers. They are bright and they are trying to get
their work done. They are not trying to write elegant computer programs.
But, still... If only a couple of simple principles were followed, their programs would
be so much better and so much more useful to them. This chapter is an attempt to
specify what those principles might be, while at the same time showing you how to
assemble and use many of the techniques that have been discussed in this book.
The task I have set for myself is to show you how to write a reasonably complex
graphics display program that you can use from the IDL command line. But I want to
write this program in such a way that the output can be displayed in a resizeable
graphics window, printed directly from the IDL command line, or made into a
Postscript file with little or no effort. The program should use colors in an intelligent
way that doesn't depend upon the visual depth or color decomposition state of the
output device.And finally, it should be simple to add a graphical user interface to this
program, so that it can be used by someone unfamiliar with it.
Most programmers know that the best way to learn programming is by understanding
code that has been written by others. But most of the time this is a daunting task, given
the general lack of documentation in code and the absence of a mentor who can
explain the unfamiliar elements of the code to you. My purpose here is not just to
show you how to write a program, but to explain why the program is written in this
particular way. Writing programs always involves making choices. The choices we
make play a pivotal role in how useful the program is to us, and how easy it is to
maintain and extend the program over time. In other words, we can make both helpful
and non-helpful choices. By the end of the chapter you should be well on your way to
understanding the distinction.
Writing an IDL Grnplzics Display Prograitt
control as I can over how things are going to look, since I know users almost always
have a different aesthetic sense than I do. S o I will almost always have keywords that
will allow the user to choose colors.
Here is what the procedure definition statement will look like:
PRO HistoImage, $
image, $
AxisColorName=axisColorName, $
BackColorName=backcolorName, $
Binsize=binsize, $
ColorTable=colortable, $
DataColorName=datacolorName, $
Debug=debug, $
The first and only positional parameter, image, is the image data that will be passed
into the program. I am going to have to check whether this is a 2D array, since it
doesn't make much sense to calculate the histogram of a 24-bit or 3D image, but I will
delay this for a moment.
You see three "color" name keywords in the list: AxisColorName, BackColorNal~ze,
and DataColorNcn?ze. These will be the names of colors to use for the axes and other
annotations, the background, and the data, respectively. I am going to use color names
for these because one of my goals for the program is to use colors that are independent
of the color decomposition state or depth of the display. I know that the Getcolor.
program described in "Obtaining Device Independent Colors" on page 87 has the
ability to give me a such a color if I ask for one of the 16 colors it knows about by
name. By allowing the user to select these colors themselves, I give them some control
over how things look on the display. I also allow the user to specify a color table index
number with the Colortable keyword. The image data will be scaled into indices
loaded by the color table.
Image colors almost always present a dilemma for me. On the one hand, I really want
to set the image colors up correctly, especially if I am calling this program from the
IDL command line. But on the other hand, I often prefer that color manipulation be
done outside the program code. For example, in widget programs image colors are
often controlled by a color table changing tool like XLoadCT or XColors. Loading a
color table inside a graphics display program will often interfere with the colors that
are being manipulated elsewhere.
I compromise in this program by defining two additional keyword parameters:
NoLoadCT and ImageColoi-S.NoLoadCT will be a flag that if set will prevent the
program from loading the color table specified by the ColorTable keyword. In other
words, I will have a way to turn color table loading off from outside the program, if
this is what I choose to do. I~nageColoi-swill be an output keyword that will allow me
to learn from outside the program how many colors the image data should be scaled
into. This is essential information if I am to manage the colors from outside the
program. (Here I will be loading image colors starting at color table index 0. If this
were not the case, I might also define a keyword, say Bottomlrzdex, that would indicate
the bottom of the image color indices.)
The BirzSize keyword is a Histogram command keyword. I will use the Histogranz
command inside this program to calculate the histogram plot and I may want the user
Writing mt ZDL Graphics Display Progranz
to be able to configure the properties of this command. Note that I don't provide all of
the keywords that are available for Histogranz, only those that I specifically want the
user to manipulate. Similarly, the Mas-Value keyword is a Plot command keyword I
often find useful in histogram plots, so I define it here.
It is entirely possible (especially with the Plot command) that I will want to
manipulate other his to gran^ or Plot or TVI~lzngekeywords that are not defined here.
For example, I may want different axes annotations or tick marks. For this reason, I
include an -E,~tra keyword to take advantage of keyword inheritance, which was
described in "Passing Undefined Keywords by Keyword Inheritance" on page 216.
There are two keywords, XScaIe and YScale, that will be used to define the range or
scale of the axes that surround the image. These input keywords will be two-element
arrays defining the minimum and maximum extent of the axes.
Finally, there is a Debug keyword, which I intend to use in the error handler portion of
the code to force a traceback of where the error occurred. I typically don't like to write
error tracebacks into the command log window unless the user explicitly asks for such
a thing. Using a Debug keyword gives me an easy way of debugging my code without
frightening unfamiliar users with long lines of error text.
because you will be using the variables somewhere in the code to follow and it is not
possible to use undefined variables in IDL expressions. (Methods for checking
positional and keyword parameters are discussed in "Writing an IDL Procedure" on
page 209.)
reasonably good looking plot almost always. I will have to calculate the bin size
appropriately for this. The code will look like this:
IF N Elements(binsize) EQ 0 THEN BEGIN
range = Max (image) - Min (image)
binsize = 2.0 > (range / 128.0)
ENDIF
IF N-Elements(max-value) EQ 0 THEN max-value = 5000.0
The bin size will either be 2, or it will be the image data range divided by 128,
whichever number is larger. I am making the assumption here that I am not going to
have floating point image data, that ranges from 0.0 to 1.0, for example. My program
will look lousy with such data, but the chances seem so low of this happening that I
am willing to chance it. And, anyway, if the user did have image data like that, they
could always specify an appropriate bin size for viewing the histogram.
Note the use of the IDL "greater than" operator (>). This operator returns the larger of
the two values being compared. Note, too, the parentheses about the value I want to
compare to the right of the operator. Without the parentheses 2.0 would be compared
to the range, and then tlznt value would be divided by 128. Not what I want at all! This
happens because the greater tlznn operator has the same order of precedence as the
division operator. This is a common kind of error to make in IDL programs.
Another common error occurs within the parentheses. Notice I have made the number
128.0 a floating point number by adding a decimal point to the number. You might
easily make the mistake of writing this number as an integer (e.g., 128). Then, for byte
or integer image data, you would be dividing an integer value (the mrzge) by another
integer value. This might easily give you a consistent bin size of 0. A serious error.
The Mnx-Value keyword variable is assigned a value of 5000, a value I know works
with most of the example image data sets distributed with IDL.
Next, I can test for the XScaIe and YScnle keyword values. If these are not supplied,
1'11 use the dimensions of the image for scale values. I'll also test to be sure the values
are two element arrays. If not, I'll issue error messages. The code will look like this:
S = Size(image, /~imensions)
IF N-Elements (xscale) EQ 0 THEN xscale = [O, S [O]]
IF N-Elements (xscale) NE 2 THEN $
Message, 'XSCALE must be 2-element array1, / N o ~ a m e
IF N-Elements(ysca1e) EQ 0 THEN yscale = [Or S [l]]
IF N-Elements(ysca1e) NE 2 THEN $
Message, IYSCALE must be 2-element array1, / ~ o ~ a m e
Notice a new keyword for the Size command here: Dimensions. As opposed to the
N-Dimerzsions keyword, which caused Size to return the number of dimensions of its
argument, the Dimensions keyword causes Size to return a vector containing the size
of each of its dimensions. In other words, with a 2D image array, I will get a two-
element array containing the X size and Y size of the image, respectively.
Finally, I can check for the color keywords. Because I want to write device
decomposed-state independent code, I am going to use the GetColor program to
specify drawing colors. (GetColor is discussed in "Obtaining Device Independent
Colors" on page 87.) GetColor "knows" the names of 16 drawing colors that I use
frequently and can obtain those colors for me in a device independent way. (You can
easily add more colors to GetColor.) I check the keywords like this:
IF N-Elements(dataCo1orName) EQ 0 THEN $
dataColorName = "Redn
IF N-Elements(axisColorName) EQ 0 THEN $
axisColorName = NNavyll
Tlze Histoli~zgeProgram
IF N-Elements(backcolorName) EQ 0 THEN $
backcolorName = "WhiteM
I could check to be sure the color names passed into the program are string variables,
but I know GetColor is going to do that anyway. And I know that it is going to return
to the caller of the program that called it. Thus, I will be able to catch that error in my
error handler when it comes back from GetColol: Thus, there is no need to check for
possible errors here.
Note that I make the background color white by default. This is certainly not
necessary, and more often than not I like to have a nice charcoal or gray background
color. I choose white here because it is sometimes easier to visualize what the results
are going to look like when I make a Postscript file from this program. Recall that you
can have any background color you like in PostSc~ipt,as long as that color is white.
(This problem is discussed in "Problem: PostScript Devices Use Background and
Plotting Colors Differently" on page 189.)
The most important thing is that I need some way to clzange the drawing colors,
because I almost certainly lvill have to change them when I send the output to a
PostSc~iptprinter. By making the default colors suitable for printing on a PostScript
printer now, I won't have to worry about resetting colors. I'll just call the HistoOnage
program to draw the graphics in its default colors when I want to make a Postscript
file.
If the user doesn't supply a color table index number, I choose color table 4.
IF N-Elements(colortab1e) EQ 0 THEN colortable = 4
colortable = 0 > colortable < 40
Note that there are only 41 color tables supplied with IDL (although users could have
modified this number, certainly). Here you see a bit of checking to force the colortable
value into a number between 0 and 40. The IDL greater than and less thal?operators
are used in a left to right fashion to force the value into the correct range of numbers.
(In other words, 0 is compared to colortable and the largest value is return. Then that
value is compared to 40 and the smallest of those two values is returned into the
colortable variable.) This kind of proactive error checking can avoid problems later
on.
Next, I'll supply the IinageColoi-s keyword with a value. Note that I am going to use
three drawing colors in this program. I prefer to load my drawing colors at the top of
the color table, although other people prefer to load them at the bottom of the color
table. It doesn't matter much where you load them, as long as you know what you are
doing when you manipulate the color table. But while I like to load drawing colors at
the top of the color table, I don't like to use the top index number of the color table.
The reason I don't is that vely often this index is used for the !l? Color system variable.
And many, many programs are written assuming this color is going to be either white
or black. I don't like to break these programs, if I can help it. So I leave this index
alone. I load my three drawing colors starting from the fourth index from the top. This
means that the color indices I have for the image display ranges from 0 to the fifth
index from the top of the color table. This is !D. Table-Size -4 total colors. This is what
I assign to the ZmageColors keyword value in the program.
imagecolors = !D.Table-Size-4
Note that I don't have to check this output keyword. If the user wants the value he or
she can get it back from the keyword. If they don't want the value, fine. It didn't cost
me much of anything to assign it to a variable. (And I'll need the value later in the
program anyway.) There is no need to use something like Arg-Present in this case:
IF ~rg-Present(imageco1ors) THEN $
Writing ail ZDL Graphics Display Progrant
imagecolors = !D.Table-Size-4
This is overkill and results in the imagecolors variable only being defined if the user
passed in a variable reference with the keyword. The simpler construction is much
easier to type and I think makes the program easier to read, too.
Remember that the imagecolors variable is designed to help someone outside the
program control the image colors. I don't have any need for that right now, but I may
later and I want to be prepared for the eventuality.
The result, which doesn't look much like our definition of a "histogram" plot, is
illustrated in Figure 95.
Figzcre 95: A very simple plot of a histogram functiort. Note that it doesn't look
much like a histogram plot.
The second obvious approach is to set the PSynl keyword to 10, which will result in
the "stair-step" kind of plot we expect from a histogram plot. I can try this:
IDL> P l o t , b i n s , d a t a , YRange= [ O , l O ] , PSym=lO
The results look better, as shown in Figure 96, but they are still not right. In particular,
the bins are represented incorrectly. The first bin goes from 0 to 5 , the second bin from
5 to 10, and so on. But in the illustration, the first bin appears to go from 0 to 2.5, and
the second bin appears to go from 2.5 to 7.5, and so on. It appears as though each bin
is half a bin size off.
Figure 96: This plot looks more like a histograin plot, but it is still itot right. Notice
that the bins seem to be Izalf a bin size off.
I could try to fix the problem by adding a half bin size to each of the bins, like this:
IDL> P l o t , b i n s + 2.5, d a t a , YRange= [ O , l O ] , PSym=lO
You see the results in Figure 97. Again, this is close to being correct, but I have
problems at either end of the plot, where the lines should extend to the end of the plot
window. To really draw this plot correctly, I should duplicate the first and last values
of the histogram data and the bin values.
Figure 97: This plot has had Izalf a bin size added to the bin values. It is accurate,
but the plot lines do not exteizd to the ends of the plot ~vindolv.
249
Writing art ZDL Graphics Display Prograin
Figure 98: The histogrant plot I expected to get. It would be easy enozlglz to add ver-
tical liites with the Plots coinmaizd to create histogram boxes for each
bin.
YStyle=l, $
YTicklen=-0.025,$
-Extra=extra
END
Notice that I scale the image data into the number of image colors and that I position
both the image and the axes about the image with the irnagePos variable.
The work-around for this bug, which will make the code completely device
independent, is to simply get and re-load the color table vectors after the last single
color is loaded into the color table. In the HistoIi7znge code, find this line:
IF NOT Keyword-Set(no1oadct) THEN $
LoadCT, colortable, NColors=imagecolors, /Silent
The line above should be replaced with this code:
IF NOT Keyword-Set(no1oadct) THEN BEGIN
LoadCT, colortable, NColors=imagecolors, /Silent
ENDIF ELSE BEGIN
IF !D.NAME EQ 'PRINTER' THEN BEGIN
TVLCT, r, g, b, /Get
TVLCT, r, g, b
ENDIF
ENDELSE
This will cause the correct colors to be loaded when the program is sent to the Printer
device.
display, the program will work identically. Nor will it matter whether you have color
decomposition on or off if you are on a 24-bit display. This is because we have used
color-aware programs such as GetColor and TVI~nngeto load drawing colors and
display the image.
We have written the Histol~nngeprogram with an -E,vtm keyword defined for it. This
allows us to pass "extra" keywords into the Plot, Cololbal; and TVInzage commands
inside the program. But it also does something far more useful. It allows us to write a
program that can automatically re-display the graphic on 24-bit displays when we
change color tables. (See "Automatic Updating of Graphic Displays When Color
Tables are Loaded" on page 66 for additional information.)
For example, open a text editor and create this simple file, which you can name
Izistoil7znge-redisplay.pro. (This program is one of the programs you downloaded to
use with this book, if you prefer not to type it.)
PRO HistoImage-~edisplay, Image=image, -Extra=extra
IF N-Elements(image) EQ 0 THEN image = LoadData(7)
HistoImage, image, / ~ o ~ o a d C T-Extra=extra
,
END
This program will allow us to change the color table associated with Histolnznge and
see the effects immediately. (This will only be necessary on 24-bit displays,
remember. On 8-bit displays, the colors are updated automatically.) Notice that the
NoLondCT keyword is set. This is necessaly, you recall, for an outside entity to
control the colors. If this keyword were not set, Histol~nagewould always load its
own color table rather than using the colors of the current color table.
First, call the program normally and find out how many image colors there are. The
ImageColors output keyword is used for this purpose.
IDL> image = LoadData (13)
IDL> HistoImage, image, ColorTable=33, ImageColors=ncolors
Next, call XColors to load different color tables. (XColors is a program you
downloaded to use with this book. I use it exclusively in place XLondCT, which is
supplied with IDL, for reasons you will learn about in the following sections of this
chapter. It has many advantages to XLoadCT, one of which is that I think it has a more
natural syntax for automatically updating graphical displays on a 24-bit device.)
IDL> XColors, NColors=ncolors, Image=image, $
Noti£yPr~=~HistoImage-Redisplay'
If you have both XColors and your open graphics window on the display so you can
see them both, you will notice that as you select color tables from the list of color
tables in XColors, that the colors are automatically updated in the display window.
Note that if you are running IDL on an 8-bit device, you need only call XColors like
this:
IDL> XColors, NColors=ncolors
Now, close your XColors window if it is still on your display.
The XColors program will work with the HistoImage-Redisplny program no matter
what keywords you use with HistoImage. For example, you can type this:
IDL> HistoImage, image, BackColorName=Igray1, $
AxisC~lorName=~yellow', ImageColors=nco~ors
IDL> XColors, Image=image, NColors=ncolors, $
Ba~kColorName=~gray', AxisC~lorName=~yellow', $
N~tifyPro=~HistoImage-Redisplay1
The Histol~~tnge
Prograln
TV command and another 12 lines follow it, just to make the TV command work
properly on every device!
On a 24-bit displays, of course, this all happens independently. What you will notice
on 8-bit displays, however, is that the color will be correct for the window that has the
current keyboard focus. In other words, each window "knows" which colors are sup-
posed to be loaded and loads them when it has the focus.
Note that on an %bit display, the FSC-Window program may not have a Prirlt and
PostScript File button as shown in Figure 100. When the display device does not have
as many colors as the PostScript or printer devices, it is impossible to always get the
colors correct for PostScript and printer output. Too many factors are involved and it
depends too much on how the graphics command that is executed is written. For
example, it is not possible to get correct PostScript output from our HistoIrnnge pro-
gram when it is running on an 8-bit display with color table loading turned off, as is
the case currently.
You will learn more about how to fix these kinds of problems in the chapters to follow,
but for now you should know that you can get both a Print button and a PostScript
File button on your FSC-Wirtdo~lprogram on an 8-bit display, but you have to set
them explicitly. For example, you could do this:
IDL> FSC Window, TVImage , LoadData (7), /WColors, $
/ W ~ o s t ~ c r i ~/WPrint
t,
But even this command would not print correctly or make a correct PostScript file on
an 8-bit display. To make the output correct, the image data will have to be scaled
correctly for both the display and for the Postscript and printer devices. The only way
Writing all ZDL Graplzics Display Progrant
this can be accomplished it to perform the scaling right in the TVI~llngecommand, like
this:
IDL> image = LoadData ( 7 )
D L > FSC-Window , TVImage ' , $
BytScl(image, Top=!D.Table-Size-l), /WColors, $
/ ~ ~ o s t S c r i p t/WPrint
,
Writing a Widget Program
Chapter Overview
The purpose of this chapter is to demonstrate how to write a basic IDL program using
the widget toolkit to provide a graphical user interface to the program. The name
widget refers to a graphical element that the user interacts with to convey information
into or out of a program. Buttons, slider bars, and fields where you type text are all
examples of widgets. The widget toolkit is a set of IDL commands that together form
a special application programming interface (API) for building programs with
graphical user interfaces. Specifically, you will learn:
The difference between a widget definition module and a widget event handler
module
How to write a widget definition module
How to write a widget event handler module
* How to interpret and understand widget event stsuctures
How to create different types of widgets, including top-level base widgets, draw
widgets, and pull-down menus
How to pass information between widget progam modules without using com-
mon blocks
m How to write a widget program with a resizeable graphics window
How to add simple controls to a widget program
How to change and protect colors in a widget program
How to prevent memory leakage in a widget progam
The program you write in this chapter, named Histo-GUI, will be a graphical user
interface for the HistoImnge program you wrote in the previous chapter. Initially, this
program will simply be a resizeable graphics window for the HistoImage program. In
the next chapter you will add more features and complexity to the program.
module (either an IDL procedure or fhnction) in which the widgets themselves are
created or defined. And there is always at least one event I?nnrlle~-nodule, which is the
program module (either an IDL procedure or function) which processes or responds to
widget events, the triggers or user interactions that drive the widget program. (See
Figure 101, below.)
Even simple widget programs may have several event handler modules, each of which
responds to a different kind of event or trigger. Program information often needs to be
shared among these event handlers and the widget definition module. Much of the art
of widget programming is learning how to pass program information between the
various program modules that constitute a widget program or application.
Structure
Figure 101: Theflow of ir2formatiort irz a widgetprogranzpasses from the widget def-
irzitiorz module, where the widgets are defined and the progranz is real-
ized on the display, through everzt structzcres to the widget eveitt handler
modules. Ziz geizeral, code iit the widget definition rnodule is executed
orzly once, whereas code in the event handler modules may be executed
over and over again, each time there is aiz event appropriate for that
event handler.
Widget programs are said to be event driven. This means that the program does not
know in advance the order of program execution. Events are generated one at a time
by users interacting with the graphical elements of the widget program, and event
handler modules respond to each event, separately, in the order in which the events are
generated. Each event is processed completely before the next event is processed. If
the program generates events faster than they can be processed, the events get queued
to await processing at the first opportunity.
In general, the interface or visual display of the widget program is generated in the
widget definition module. And the "action" of a widget program is generated in the
event handler modules. In fact, the code in the widget definition module of most
widget programs is executed only once, while the code in the event handler module or
modules is executed many, many times. In fact, the event handler code is executed
each time there is an event generated that is appropriate for that event handler.
How Do Widget Progranis Respolzd to Events?
those mistakes pose for us. My teaching style is to let users make lots of mistakes.
Sometimes I deliberately lead users into mistakes, so they get used to figuring things
out. You cannot possibly be a good IDL progranlmer without this essential skill.
But I also realize we are all busy and sometimes we appreciate shortcuts. I don't
believe there are any shortcuts when it comes to learning a programming language,
but I am sympathetic none the less. To that end, I have prepared a series of files that
represent the Histo-GUI program in various stages of development. These files are
among the files you downloaded to use with this book. For example, the file that
represents the Histo-GUIprogram as I describe it in this section is named
kisto_gui.l.pro. I have added the extra "1" extension so that if you type Histo-GUI at
the IDL command line, you cannot mistakenly run a file I provided for you, rather
than the one you are working on. To run this file (and the others like it), you must first
compile the file explicitly:
As with all procedures and functions, the next step is to check for the presence of
positional and keyword arguments, and define values for any that are not there. (See
"Checking for Positional and Keyword Parameters" on page 242 for additional
information.) The code will look remarkably similar to the code in HistoDnage:
IF N-Elements(image) EQ 0 THEN image = LoadData(7)
ndim = Size(image, /N-~imensions)
IF ndim NE 2 THEN $
Message, I2D Image Variable Required.', /~oName
IF N-Elements(binsize) EQ 0 THEN BEGIN
range = Max (image) - Min (image)
binsize = 2.0 > (range / 128.0)
ENDIF
IF N-Elements(max-value) EQ 0 THEN maxvalue = 5000.0
IF N-Elements(tit1e) EQ 0 THEN title = 'Histo-GUI Program1
S = Size(image, /Dimensions)
IF N-Elements (xscale) EQ 0 THEN xscale = [O, S [O]1
IF N-Elements (xscale) NE 2 THEN $
Message, IXSCALE must be 2-element array1, /NoName
IF N-Elements (yscale) EQ 0 THEN yscale = [0, S [l]1
IF N-Elements(ysca1e) NE 2 THEN $
Message, 'YSCALE must be 2-element array1, /NoName
IF N-Elements(dataCo1orName) EQ 0 THEN $
dataColorName = "Red1'
IF N-Elements(axisColorName) EQ 0 THEN $
axisColorName = "NavyM
IF N-Elements(backcolorName) EQ 0 THEN $
backcolorName = I1White1'
IF N-Elements(colortab1e) EQ 0 THEN colortable = 4
colortable = 0 > colortable < 40
imagecolors = !D.Table-Size-4
The next step is to create the program's widgets.
where the NAME is the type of widget that can be created. Possible values are: Base,
Button, Draw, Droplist, Label, List, Slider, Table, and Text. The parentID variable is
the identifier of the widget's parent, or widget directly above this widget in a widget
hierarchy. (See Figure 102 for an example of a widget hierarchy.)
Widget hierarchies resemble family trees when diagrammed. Thus, we speak of
widgets as if they have family relationships. We often talk about the parent, child, or
sibling of a widget. What we are describing is how the widgets are connected or
related to one another. It is the purpose of the widget definition module to create each
widget and describe its relationship to other widgets in the program through the
pnre1ztID parameter.
Note that widgets only have one parent. I'm not a biologist, so I don't know how this
is done, but I hear it is a fascinating story.
Every widget hierarchy has at its head a top-level base widget. A base widget is a
container for holding other widgets. The top-level base widget is the container that
Writing a Widget Progrant
holds the other widgets that together constitute the graphical user interface of the
program. It is, essentially, the program window that appears on the display. The top-
level base widget is the only widget that can be created in a widget program without a
parentID parameter. All other widgets require a single parentID parameter be
specified in the widget creation function.
Children of Widaet A
Notice that as functions, the widget creation routines always return a value. This
widgetID value is the identifier of the particular widget that is created. It is guaranteed
to be a unique long integer number. If you want an analogy from the real world, you
can think of this number as the widget's social security or tax identifier number. It is a
number that identifies this widget as different from all other widgets, even those with
the same name. If you wish to work with this widget in the future (and you certainly
will with many widgets), then you will need to keep track of this number in your
program, as this is the way this particular widget will be identified by the program.
In this case, the rnenubnseID variable is undefined until the WidgetBnse conlnland
puts a value into it. The merzubnl-ID variable makes it possible to put buttons into the
menu bar of the top-level base, since the merzzrbanlD variable can be used as the parent
identifier for the menu bar buttons.
Notice how the iuerzubnl-ID variable is used as the parent of the File button. The File
button, in return, is the parent of the Quit button (through itsfileID identifier). Button
widgets cannot normally be the parents of other button widgets. It is the Menu
keyword which is set for the File button that makes this possible. The File button with
the Merzu keyword set becomes a pull-down menu button, which cnrz have children.
Note that the Merzu keyword is not strictly required in the button creation function for
the File button, although it does no ham1 to set it in this case. Any button that is a child
of a menu bar (via the MBnl- keyword in a top-level base creation routine) is, by defi-
nition, a menu bar button and the start of a pull-down menu. Menu bar buttons never
generate events on their own. They only expose the buttons that are their children.
That is, the buttons that are associated with their pull-down menu.
The values of these two buttons are the text strings ("File" and "Quit") that are written
on the buttons. In general, if you deal with the values of buttons in event handlers, you
should remember that these strings are case sensitive.
Notice the Everzt-Pro keyword in the Quit button creation routine. I am using this
keyword to assign the name of an event handler module to this Quit button. Every
widget you create can have it's own event handler module assigned to it, although
most programs are not written this way. Some programmers write a single event
handler module that handles all of the events in the widget program. Other
programmers (and I am among them) find it easier to maintain and extend programs if
there are multiple event handler modules in a program. I know for a fact that it is
easier for people just learning about event handlers to have separate event handler
modules in their programs. It makes the program mucl? easier to understand. Note that
the event handler module will need a unique name. Typically, I use the name of the
widget definition module (Histo-GUI, in this case) with an extension that tells me
something about the purpose of the event handler. This makes it much easier to read
my code later on.
Event handlers may either be procedures or functions. The Everzt-Pro keyword
attaches an event handler procedure. If I wanted the event handler to be a function, I
would use the Everzt-Func keyword to assign it.
Note that you should never assign an event handler to the top-level base using these
keywords. You will see in just a minute that the event handler for the top-level base
must always be a procedure and it must always be assigned with the Everzt-Hnrzdler
keyword to the XMarzager command.
Creating the Graphics Window for the Program
The next widget you should create is the program's graphics window. This will be a
draw widget. A draw widget is similar to a normal IDL graphics window, although not
exactly the same. For example, you do not want to ever use a Cursor command in a
draw widget window, although you can use a Cursor command in a regular IDL
graphics window.
Writing n Widget Progrnnt
(Using the Cursor command in draw widget windows may cause strange things to
happen in widget programs. Things which would appear to have nothing whatsoever
to do with a Culsol. command. As you will see, using a Cursor-command in a draw
widget window is completely unnecessary, since the draw widget event structure itself
already contains the information you seek with the Cursor command.)
To create the draw widget and make it a child of the top-level base type:
Notice that the sizes of the draw widget are in pixel units, just like the sizes of a
regular IDL graphics window.
find it among the files you downloaded to use with this book.) You see now the
advantages of writing the HistoIr?zageprogram the way you did. Since you wrote the
program in such a way that it can go into the current graphics window without regard
for the size of the window, it can be called like this:
HistoImage , image, $
AxisColorName=axisColorName, $
BackColorName=backcolorName, $
Binsize=binsize, $
ColorTable=colortable, $
DataColorName=datacolorName, $
-Extra=extra, $
ImageColors=imagecolors, $
Max-Value=max-value, $
XScale=xscale, $
YScale=yscale
Note that the variable inzagecolors is an output variable that tells us the number of
colors associated with the image data. This is important in this widget program
because we eventually want to control those colors with a color table changing tool.
We will add that capability to the program in the next chapter.
command (in IDL 5.3 and higher). Thus, an anonymous structure is much easier to
work with.
In this program, I am going to need several pieces of information. Certainly I will
need the image data and other information associated with calling the HistoZ~nage
program correctly. In addition, I will need the draw widget identifier (so I can resize
the draw widget when I need to) and the window index number of the draw widget (so
I can make it the active graphics window before I draw into it). You might want to
define the ilfo structure like this:
info = { image:image, $
axisColorName:axisColorName, $
backColorName:backcolorName, $
binsize:binsize, $
dataColorName:datacolorName, $
imageColors:imagecolors, $
m a x ~ v a l u e : m a x ~ v a l u e$,
title:title, $
xscale:xscale, $
yscale :yscale, $
extra: extra, $
drawID : drawID, $
wid:wid $
1
But there are two problems with this.
The first and most obvious problem will probably occur when you try to run this
program the first time. This line will have an error in it. The error will be that the extra
variable is undefined.
Recall that the extra variable is not really being created in your program. IDL itself is
creating it if extra variables are passed into the program via the keyword inheritance
mechanism. (See "Passing Undefined Keywords by Keyword Inheritance" on
page 216 for more information.) You could, obviously, check to be sure it is defined in
the program, but what if it isn't? What value should you assign to it?
Humm. Good question. Let's forget this for a moment and come back to it after we
consider the second problem.
The second problem isn't really a problem now, but it could become a problem later. It
concerns the irnnge variable. There is no problem putting the intnge variable into this
ilzfo structure the way I just suggested. IDL will examine the variable and allocate
enough memory space in the structure to accommodate the image. But what if you
decide you want to load a new image into the program? (Have you ever seen a
program with a graphical user interface that didn't allow you to load new data?) What
if that image is a different size than the old one? It will either not take up the same
amount of room in the structure (if it is smaller than the current image), or it will not
fit into the structure (if it is larger than the current image) and will cause an error. In
either case, you have problems.
As it turns out, both of these problems can be solved by using pointer variables.
No, IDL pointers are almost as z~sejirlas C pointers, but they are a lot less lethal. They
are actually a special type of variable called a heap ~~arinble.
(Objects are another type
of heap variable.) And don't confuse the word I~eapwith, for example, the heap in the
C programming language. It is just an area of global memory where these special
types of variables can be stored.
The huge advantage of heap variables is that they are in some sense global in scope.
Well, global in the sense that, say, common blocks are global in scope. That is to say,
if you have the "address" of the pointer data, you can access the data. The "address" is
the pointer variable reference itself, a lightweight (only 4 bytes) token that can easily
be passed around among progam modules.
Pointers are perhaps easier to understand by example. Suppose we want to store an
image at a pointer location. We create the pointer and store the image variable on the
heap with the Ptr-New command, like this:
IDL> image = LoadData (5)
IDL> Help, image
IMAGE BYTE = Array[256, 2561
IDL> ptr = Ptr-New(image)
The variable pti.is the pointer reference. You can see that it is a pointer by typing this:
IDL> Help, ptr
PTR POINTER = <PtrHeapVarl>
And you can see what the pointer points to, by using the Heap keyword to the Help
command, like this:
IDL> Help, / ~ e a p
Heap Variables :
# Pointer: 1
# Object : 0
There are two ways to create a null pointer. You can use the Ptr-New command with
no arguments, or you can free the pointer with the Ptr-Free command, like this:
IDL> nullptr = Ptr-New()
IDL> Ptr-Free, ptr
IDL> Print, ~tr-Valid(nullptr), Ptr-Valid (ptr)
Writing a Widget Progranz
Notice that null pointers are invalid pointers. In other words, they cannot be
dereferenced. For example, suppose you try to store another image at the pointer
location now, like this:
IDL> i m a g e = L o a d D a t a ( 7 )
IDL> * p t r = i m a g e
% Invalid pointer: PTR.
You get an error message indicating you tried to de-reference an invalid pointer.
Occasionally, however, you would like to store an undefined variable at a pointer
location. (An undefined variable is a perfectly valid variable type in IDL.) The reason
you would want to do so is because this kind of pointer is a valid pointer than can be
de-referenced. You create a pointer to an undefined variable by using the
Allocate-Henp keyword in the Ptr-New command. Type these commands:
IDL> n e w p t r = ~ t r - ~ e ( w /~llocate-Heap)
IDL> P r i n t , P t r - V a l i d ( n e w p t r )
IDL> * n e w p t r = i m a g e
IDL> H e l p , * n e w p t r
< P tr H e a p V a r 3 > BYTE = A r r a y 1360, 3601
Another nice thing about IDL pointers is that unlike C pointers, you don't have to
worry about all kinds of memory management. IDL takes care of all of that for you.
For example, if you want to change the data the variable points to, you can do so
without worrying about de-allocating the current memory, allocating more memory
for the new variable, storing the new variable, etc. All you have to do is something
like this:
IDL> * n e w p t r = [ 3 , 5 , 4 ]
IDL> H e l p , n e w p t r , * n e w p t r
NEWPTR POINTER = <PtrHeapVar3>
<P t r H e a p V a r 3 INT = A r r a y [3]
This is, of course, exactly the way memory management normally works with
variables in IDL. But it is nice to know that pointers act the same way. Of course, you
can get into trouble with it, too. For example, you might type this:
IDL> n e w p t r = [ 3 , 5 , 4 , 5 , 91
IDL> H e l p , n e w p t r
NEWPTR INT = A r r a y [5]
Whoops! Now the variable newptr isn't a pointer at all. It is a five-element integer
array. What happened to the information that was previously stored on the heap? Well,
it is still there. But you just destroyed the only reference you had to the heap data. This
is what is called memory leakage, and is one of the things you want to avoid at all cost
in your programming.
You can see that the memory is still allocated on the heap by looking at the heap:
IDL> H e l p , /Heap
Heap Variables:
# Pointer: 1
# Object : 0
You have a couple of options for getting rid of that leaking memory. You can use the
command Heq-GC (for Heap Garbage Cleanup) to have IDL delete all the pointers
and objects on the heap that no longer have any valid references to them. This takes
some time (all current variables have to be checked), but it works. I would say this
option is a last resort and is always an admission of some kind of failure on your part.
I'd only use the command if I had closed the door to my office and sent my office
mate down to the canteen for a couple of Danish. I would certainly never, under any
circumstances, put the command into a piece of code I wrote. It would shatter the
illusion I was a competent programmes,
Your other option is to call PtcValid with the pointer index number (3, in this case)
and the Cast keyword set. Under these circumstances, IDL will return a pointer to the
leaking memory. You can then free this pointer properly. The code might look like
this:
IDL> 1eakingPtr = Ptr-Valid(3, /Cast)
IDL> Ptr-Free, leakingptr
Note that using Ptr-Valid without any arguments at all causes P t ~ V a l i dto return an
array of pointers to all the pointers on the heap, including those that have no current
reference and those that have current references. I've seen programmers try to clean
up leaking memory like this:
IDL> leakingptrs = Ptr-Valid ( )
IDL> Ptr-Free, leakingptrs
This works, of course, if the program leaking memory is the only program currently
running in the IDL session. But it frees valid pointers as well as ones that have lost
their references. Hence, this is a dangerous practice in general.
The variable irfo is currently a local variable that will be cleaned up and destroyed
when the widget definition module code is finished executing. If you don't put the ilzfo
structure in a global memory location, it won't exist when the event handler module
needs the information.
As it happens, widgets are also created and stored in a global memory location. But
even more important, each widget is created with a built-in container of sorts, called a
zlser value, where you can store any kind of IDL variable. The user value is available
to you for whatever purpose you choose. In this case, you can use the user value of a
widget to store the irfo structure.
But which widget's user value should you use? The truth is, it doesn't really matter
much. You can use the unused user value of any widget in your program for this
purpose. In practice and by convention, it is the user value of the top-level base that is
used for this purpose. There are advantages to this choice that will be obvious shortly.
I I
Figure 103: A diagram of the widgets created in the widget defiizitioiz module of
Histo-GUI. The iitfo structzcre is stored iiz the zcser value of the top-level
base.
To copy and store the irfo structure information in the user value of the top-level base,
type this in your program file:
Widget-Control, t l b , S e t - W a l u e = i n f o , /No-Copy
I just said I wanted to copy the irlfo structure into the user value of the top-level base,
and yet I set a No-Copy keyword when I executed the command. Isn't that
contradictory? What is really going on here? The secret is in practicing good memory
management.
I already mentioned that the irzfo structure is where I put all the information I need to
run the program. So you can imagine that in a real program there might be quite a lot
of information. Making three copies of something that takes up a lot of memory is rzot
good memory management, not by a long shot. But what can you do about it?
Figure 104: If you are rtot careful, yozc can make three separate copies of tlte same
program irtformatiorz. This is rtot what you want to do. To avoid it, use
the No-Copy keyword wherz you move irzforrnatiort to arzd from the user
v a b e of the top-level base.
IDL has a rather strange concept of rzot copyirlg information into variables. To
understand it you have to realize a variable in IDL is a complicated C structure. One
field in that structure is an actual C pointer to a memory address where data is stored.
There is a rule in IDL, however, that there can only be one variable pointing to an area
of machine memory at a time. (This is to keep users from crashing IDL by overwriting
memory.) So, when you type these commands:
IDL takes the information in the variable n and copies it into a new variable b. This
includes making a new copy of the value 5 , storing it in memory, and returning a new
C pointer to that machine memory address.
However, if you wanted variable b to keep all the information in a, include its data
pointer, you could do this:
IDL> b = Temporary (a)
This essentially just reuses the memory already allocated for the variable n in the new
variable b. The interesting side effect of this, however, is that the variable n no longer
has a valid data pointer (a result of the rule above allowing only one variable at a time
to point to an area of machine memory). A variable without a valid data pointer is, by
definition, an undefined variable.
IDL> Help, a
A UNDEFINED = <Undefined>
This, essentially, is what is done with the No-Copy keyword. By issuing this
command:
Widget-Control, tlb, Set-Walue=info, /NO-copy
we define the user value of the top-level base to point to the area of machine memory
where the local variable irfo was stored. But in doing so, we urzdefiize the variable
info. This is not a hardship, normally, since the transfer usually takes place just before
Writing a Widget Prograr~t
the call to XMnrzngel; which is the last line in the widget definition module to be
executed. You are done with the ilfo structure anyway. You see the effect of this
operation in the illustration in Figure 105, below.
I J
Figure 105: Traitsferrirzg the irzfo structure front the widget defirzitiorz module to the
riser value of the top-level base with a No-Copy keyword makes the user
value poirtt to the area irz memory that has already been allocated for the
inforiizatiorz. However,. the local variable irzfo is now undefirzed by this
transfer process.
Similarly, you do not want to copy the information from the user value of the top-level
base into the local variable irzfo in an event handler module. Thus, we will use a
command like this in our event handler (which we have yet to write) to transfer the
information from the user value to a local info variable.
Widget-Control, event.top, Get-Walue-info, /No-Copy
Again, you see the effect of this operation in the illustration in Figure 106, below. The
effect of this operation is to undefine the user value. Once again, this is not a problem,
because it is the information in the local irzfo structure that is of concern to you.
Figure 106: Transferring the informatioizfrom the user value to the local info struc-
ture irz the event handler with the No-Copy keyword makes the info vari-
able point to the original area of memory, but has the effect of
urtdefinirzg the user value.
But, if this is all you do you will be in great trouble. The event handler, remember, is a
one-shot event handler which processes events one at a time. The code is executed
Writing the Widget Definition Modztle
each time there is an appropriate event to be handled. When the event is finished being
processed, IDL exits the event handler module, cleaning up any local variables and the
portion of memory they point to. This will include the local irgo variable and the
information in its memory location.
If this happened, your event handler would only work the first time it processed an
event. For subsequent events, it would look for program information in the user value
of the top-level base and it would not find it there. (The user value is undefinecl.) This
problem usually manifests itself in your program with the elror irzfo l n ~ ~be
s tn
structure in this context.
To avoid this problem, it is essential that before you exit the event handler module you
put the irzfo structure back into the user value of the top-level base with a No-Copy
keyword. The code in your event handler (which we are yet to write) will look like
this:
event handler module associated with this top-level base would be name
Histo-G UI-Event.
Finally, the presence of pointers in this program presents a perfect opportunity to have
memory leaking like a sieve. To prevent that we have to free our pointers properly
when we exit the program. We are going to do this with a clean-up procedure. We
name that procedure by using the Clearlup keyword on the XMarzagel. command.
To register the program and end the widget definition module, type these two
commands:
XManager, lhisto-guil, tlb, /No-Block, $
Event-Handler='Histo GUI-TLB-Events' , $
Cleanup= ~ i s t o - ~ ~ ~ - ~ i'e a n u ~
END
If the program doesn't compile or run, there are probably errors in the code. Remove
the widget program from the display with your mouse, type RetAll (Return All the way
back to the main program level), fix the errors, and try again. Remember this sequence
of operations, because you should do this every time you crash a widget program.
What would happen if you did generate an event? Try it. Why does this happen?
Only two widgets can generate events in this program: the top-level base when it is
resized by the user, and the Quit button when it is selected by the user. Draw widgets
can generate events, but they must be explicitly set up to do so and that has not been
done here. The File button cannot generate events because it is a menu button (i.e., it
has the Merzu keyword set for it.)
If you try to generate an event in the Histo-GUI program an error occurs. This is
because you haven't written the event handler module for this program. You will do
that in just a moment.
Close your Histo-GUZ program with your mouse. Look at your heap area.
IDL> Help, /Heap
Heap Variables:
# Pointer: 1
# Object : 0
and managing events. When an event occurs, the window manager packages
infoimation about the event up and sends it to IDL. IDL takes the event information
and re-packages into an event stmcture, which is then passed directly to the proper
event handler module as its one and only positional parameter or argument.
The event structure has different fields in it, depending upon which widget caused the
event. (See Appelzdi,~A: Widget Everit Strwctz~i-esfor more information about widget
event structures.) All widget event structures, however, have three fields in common:
ID, Top, and Handler. It is important to know what these three fields mean.
Event Handler
W Event Structure
{ I D : G,
Top: A,
Haitdler: A,
...
Figzcre 107: A simple widget program witlz widgets A throzcglt G defilzed. Aiz event
handler (filled circle) is associated with tlze top-level base. If art event oc-
1
curs at widget G, the everzt bzcbbles zcp to the everzt handler associated
with widget A.
Event Handler
Event Structure
( I D : G,
Top: A,
Event Handler
Ha~zdler:F,
pseudoButtonEvent = { PSEUDO-BUTTON-EVENT, $
ID : event.handler, $
Top:event.top, $
Handler : OL, $
Select : 1L }
RETURN, pseudoButtonEvent
END
Notice the Hnrzdlel- field is filled out with the long integer 0, an invalid widget
identifier. IDL will take this return value, see that the ID, Top, and Hnr?dlel-fields are
defined properly as long integers, and will then fill in the proper value for the Hnrzdler
field. You do not have to concern yourself with identifying the proper handler initially.
It is important, however, to get proper widget identifiers into the ID and Top fields of
such a return value. The values shown in this example are typical.
( info ) Events
Figure 109: The event harzdler modules are izo~vassociated wit12 specific ~vidgetsirt
this diagram of the Histo-GUI prograr~t.
The only trick is that you do not have the identifier of the top-level base, tlb, available
to you in this event handler. The variable tlb is a local variable in the widget definition
module. That variable was destroyed as soon as the code for the widget definition
module was executed. What you have in this module is the equivalent of tlb. You have
the Top field of the event structure.
The entire event handler, defined with one and only one positional parameter (which is
the event structure that is passed to it from IDL), looks like this. Please type this code
before the widget definition module code in the file 1zisto-gui.pro.
PRO Histo-GuI-Quit, event
Widget-Control, e v e n t . t o p , / ~ e s t r o y
END
Note that the event structure does not have to be named event. You can give it any
name you like. I always call it event so that I know what I am working with when I see
it in my widget program code.
Finally, put the ir~fostructure back into the storage location in the user value of the
top-level base. If you fail to do this, your program will work for the first event and fail
for the second with the error ZNFO inztst be a structure ir?this case.
Widget-Control, event.top, Set-Walue=info, /NO-Copy
END
If you run a widget program and you get the error message ZNFO ~nustbe CI stl-~lcture
ill tlzis case, then 99 times out of 100 you have made one of two errors: (1) you have
checked the irzjo structure out of the user value of the top-level base with a No-Copy
keyword, but you have forgotten to return it to the user value before you exited the
event handler, or (2) you have tried to use the irzjo structure after you have checked it
into the user value with a No-Copy keyword, in which case it is currently an unde-
fined variable. You can identify the first error is your program processes one event,
but not two in a row. Look for the error in the previous event handler that worked, not
the one that just failed!
If the irfo structure is defined (it should be) then you will clean up the appropriate
pointers yourself. The clean-up routine is where you clean up anything that may take
up memory: pointers, objects, pixmaps if you have used them, etc.
Note that you do not have to put the irfo structure back into the user value. There is
really no need here, since the top-level base has already been destroyed and the iifo
structure itself will be cleaned up as soon as the program exits.
m If you are running IDL on a Windows 95 or Windows NT operating system, you may
notice that the window flickers and resizes slowly. This is because the "Show window
contents while dragging" option is turned on for your display. You should disable this
option. Open the Display control panel (you can right click on your desktop, if you
like) and select the Plzrs tab. In the Viszlal Settings section of the control panel, de-
select the "Show window contents while dragging" option.
This single command is the most used command in the IDL language. If it's not,
you are writing programs that are way too easy for you. Try something harder.
3. Fix the error in your program, re-compile, and try running it again.
Writing n Widget Progrnnr
If you follow these three steps you can get your widget programs running again 99.9%
of the time. You do not have to exit IDL and start all over, as too many beginning
widget programers are prone to do.
Very occasionally (and especially if you are running older versions of IDL), you can
cause a crash that widget programs just can't seem to recover from. On those
occasions (and they haven't happened to me in over two years now), you might want
to throw in a single XMnnngel. command right after the RetAll. This will really wake
the widgets up. But don't do it too often or they will be so hyped up they will be hard
to handle.
IDL> X M a n a g e r
Do these three steps appear to be the same three steps you should use to recover from
mzy programming error? Uh, well ... yes. But you would be surprised at how often
people who are writing widget programs forget them. Following them will guarantee
success. Well ... more or less. Read on.
If you are on an 8-bit display, the colors (both image colors and drawing colors) in the
display window changed immediately. On a 24-bit display nothing bad has happened
yet. But now resize the graphics window.
Whoops! The image is now using the red-temperature scale colors. This is not what
we want. We want to keep using the original color table until the program decides to
change it. In other words, we want to protect our program's colors.
There are various ways to do this, but they all involve the program keeping track of its
own color vectors and restoring them at the proper time. I am going to demonstrate a
method of color protection that involves keyboard focus events. In other words, when
this program gets the keyboard focus (the user toggles the program display window
forward on their display, they click in the display window, etc.) then the program's
color table will be reloaded and the program will update itself.
I can't prevent the program from momentarily having improper colors when I give the
user access to the command line. I can only make sure that when the user interacts
with the program in some way, that the proper colors are loaded.
Adding Color Protection
Just after the line creating the ilfo structure in your widget definition module (the line
you just modified in the code above), find this line of code:
Widget-Control, tlb, set-Walue=info, /NO-COPY
Change this to also turn on keyboard focus events for the top-level base:
Now that keyboard focus events are on, we have to modify the event handler for the
top-level base to respond to them properly.
Note that Tag-Names always returns the name of the structure in uppercase characters.
This is important if you are going to be doing some kind of string comparison, such as
in the IF statement shown or in a CASE statement.
We are now ready to add the code to respond to the keyboard focus events. First of all,
we only want to respond if this widget is gaining focus. We could try to do something
if the widget is losing focus, but such attempts are charged with uncertainty. It is better
to have the philosophy that each widget will look out for itself. This makes it possible
for many different widget programs to work together successfully.
Then, we clearly want to restore or reload the program's color vectors. Finally, if we
are running on a 24-bit display, we want to re-display the graphic. If we don't do this,
there is no guarantee the display will be shown in the correct colors. Insert this code
just before the final END statement in the Histo-GUI-TLB-Eventsts event handler:
IF thisEvent EQ 'WIDGET-KBRD-FOCUS1 THEN BEGIN
IF event.enter EQ 0 THEN RETURN
Widget-Control, event.top, Get-Walue=info, /No-Copy
TVLCT, info.r, info.g, inf0.b
Device, Get-Visual -Depth=theDepth
IF theDepth GT 8 THEN BEGIN
WSet, info wid .
HistoImage, *info.image, $
AxisColorName=info.axisColorName, $
BackColorName=info.backcolorName, $
Binsize=info.binsize, $
DataColorName=info.datacolorName, $
-Extra=*info. extra, $
Max-Value=info.max-value, $
NoLoadCT=l, $
XScale=info.xscale, $
YScale=info.yscale
ENDIF
Widget-Control, event.top, Set-Walue=info, /No-Copy
ENDIF
END
Test this program to see if the colors are truly protected. (If you did not enter the
program above into a file named Izisto_gui.pero, you can use the file lzisto_gui.2.pero,
which you downloaded to use with this book. This file must be explicitly compiled to
be used. See "The Advantage of Mistakes" on page 261 for more information.) Run
several versions of Histo-GUI at the same time with different color tables. Are the
correct color tables loaded when the focus is in the right window? What happens when
the user loads a color table from the IDL command line?
IDL> Histo-GUI, Colortable=3
IDL> Histo-GUI, Colortable=l
IDL> LoadCT, 0
max~value:max~value, $
title:title, $
xscale:xscale, $
yscale:yscale, $
extra:~tr-New(extra), $
r:r, $
g:g, $
b:b, $
drawID:drawID, $
wid:wid, $
pix1D:pixID $
1
There are two other places where we are displaying graphics in this program, both in
the Histo-GUI-TLB-E~~e~ztsts event handler. First, consider the case in which the top-
level base is resized. In this case, you resize the draw widget. Unfortunately, it is
impossible to resize a pixmap window. The only alternative is to delete the last
pixmap window and create a new one of the proper size. Find this code in the
Histo-GUZ-TLB-E~~erztsevent handler:
IF thisEvent EQ 'WIDGET-BASE' THEN BEGIN
Widget-Control, event.top, Get-Walue=info, /NO-copy
Widget-Control, info.drawID, Draw-XSize=event.x, $
Draw-YSize=event.y
WSet , in£o.wid
~ i s t o ~ m a g e*info.image,
, $
AxisColorName=info.axisColorName, $
~ackColorName=info.backcolorNarne, $
Binsize=info.binsize, $
DataColorName=info.datacolorName, $
Extra=*info.extra, $
Max-Value=info.max-value, $
NoLoadCT=l, $
XScale=info.xscale, $
YScale=info.yscale
Widget-Control, event.top, Set-Walue=info, /NO-copy
ENDIF
Change the code to relocate the WSet command and add the following lines (shown in
bold) below:
IF thisEvent EQ 'WIDGET-BASE1 THEN BEGIN
Widget-Control, event.top, Get-Walue=info, /No-Copy
Widget-Control, info.drawID, Draw-XSize=event.x, $
Draw-YSize=event.y
WDelete, info.pixID
Window, XSize=event.x, YSize=event.y, / p i m a p , /J?ree
info.pixID = !D.Window
HistoImage, *info.image, $
AxisColorName=info.axisColorName, $
BackColorName=info.backcolorName, $
Binsize=info.binsize, $
DataColorName=info.datacolorName, $
-Extra=*info.extra, $
Max-Value=info.max-value, $
NoLoadCT=l, $
XScale=info.xscale, $
Writing a Wiciget Progmnr
WSet, info.wid
Device, Copy=[O, 0, !D.X-Size, !D.Y-Size, 0, 0, info.pixID]
Widget-Control, event.top, Set-Walue=info, /No-Copy
ENDIF
The other change occurs in the code that handles keyboard focus events. Find this
code in the Histo-GUI-TLB-EI~~~ZS event handler:
IF theDepth GT 8 THEN BEGIN
HistoImage, *in£o . image, $
AxisColorName=info.axisColorName, $
BackColorName=info.backcolorName, $
Binsize=info.binsize, $
DataCo~orName=info.datacolorName,$
Extra=*info.extra, $
Max-Value=info.max-value, $
NoLoadCT=l, $
XScale=info.xscale, $
YScale=info.yscale
ENDIF
Relocate the WSet command and add the following lines (shown in bold) below:
IF theDepth GT 8 THEN BEGIN
WSet, info.pixID
HistoImage, *in£o . image, $
AxisColorName=info.axisColorName, $
BackColorName=info.backcolorName, $
Binsize=info.binsize, $
DataColorName=info.datacolorName, $
-Extra=*info.extra, $
Max~Value=info.max~value, $
NoLoadCT=l, $
XScale=info.xscale, $
YScale=info.yscale
WSet, info.wid
Device, Copy= [O, 0 , !D.X-Size, !D.Y-Size, 0, 0, info.pixID1
ENDIF
The final change you have to make is to be sure to delete the program pixmap in the
Histo-GUI-Clea~zuproutine. Remember, the purpose of the clean-up routine is take
care of anything that might cause memory leakage. Un-deleted pixmaps will certainly
cause memory to be used inefficiently.
Find this code in the Histo-GUI-Clenrzup program module:
IF N-Elements(inf0) EQ 0 THEN RETURN
Ptr-Free, info.image
Ptr-Free, info.extra
END
Modify the code to add the line in bold, below:
IF N-Elements(inf0) EQ 0 THEN RETURN
Ptr-Free, info.image
Ptr-Free, in£o . extra
WDelete, info.pixID
END
Bzcffering the Graplzic Display for S~noother0zt ipztt
Re-compile your program and sun it again now. (If you did not enter the changes
above into a file named histodzii.pl*o,you can use the file kisto~zri.2.pr0,which you
downloaded to use with this book. This file must be explicitly conlpiled to be used.
See "The Advantage of Mistakes" on page 261 for more information.)
Are the gsaphics displays smoother? How about when you sesize the program?
Good, but there is still much to learn about widget programming in the next chapter.
Keep reading.
Widget Programming Techniques
Chapter Overview
The purpose of this chapter is to demonstrate a few of the most important and
frequently used widget programming techniques. Together with the techniques from
the previous chapter, these techniques will enable you to write powerful widget
programs. Specifically, you will learn:
How to extend widget program functionality
How to create pull-down menus
HOWto create a program "memory" and an "undo" feature
* How to communicate between widget programs and modules
How to allow color manipulation transparently on both 8-bit and 24-bit displays
How to create GIF, JPEC3, TIFF, and Postscript files from your graphics display
How to open a new file from a widget program
How to send your graphics display directly to the printer
A widget program that cannot be easily extended or added to is a poor widget
program, indeed. A primary goal, then, of any widget program we write should be the
ability to extend its functionality easily. One of the easiest ways to extend program
functionality is to write programs that can call (or be called from) other widget
programs. To a large extent this means writing programs in a modular or object-
oriented way so that program data and the actions to be performed on the program
data are localized in the program itself.
Consider the Histo-GUI program you wrote in the last chapter. As it stands, this
program is not very functional. In fact, it is really nothing more than a resizeable
graphics window for one particular type of display. It would be a better program if it
had a bit more functionality. This chapter will show you how to add functionality to
this program. (If you haven't written the program in the last chapter and need a
starting point, use the file histo_gui.2.pi-0, which is among the files you downloaded to
use with this book. Rename the program file histo_gui.pi-0.)
Widget Progranznziitg Techiziqzces
s m o o t h I ~= ~ i d g e t ~ B u t t o n ( p r o c e s s 1
Va ~ ~ e = ~ S r n o o t h i n g$~ ,
~l
/Separator, /Menu)
button = idg get-~utton(srnooth~~, Value=IMedian Smooth1)
button = Widget-Button(smoothID, V a l ~ e = ~ B o x c aSmooth1) r
edgeID = Widget-Button(processID, Value=IEdge Enhancea, $
/Menu)
button = Widget-Button(edgeID, Value=lSobelf)
button = Widget-Button(edgeID, Value=lUnsharp Masking1)
button = Widget-Button(processID, V a l ~ e = ~ O r i g i n aImage1)
l
Note that the event handler for these buttons is assigned to the Processirzg button with
the Event-Pro keyword. It is named Histo-GUI-Processirzg. This is the button that is
at the top of the pull-down menu in the menu bar. By assigning the event handler to
this button, we can capture all of the sub-button events by virtue of the fact that events
will "bubble up" the widget hierarchy until they are captured by this event handler.
Note, too, that only the menu buttons (i.e., those with the Menu keyword set) have
widget identifiers that are unique. All the other buttons have generic "button"
identifiers. We obviously will have to "identify" these buttons in the event handler in
order to respond appropriately to them. But rather than keeping track of their widget
Adding Zinage Processing Capability
identifiers, we will identify a button by each button's value. In other words, by the text
that is written on the button. The text for each button is, of course, unique.
You can con~pileand sun the program after you make the change. Although be careful
not to cause an event. The event handler for the Processil~gbutton has not been
written yet. The pull-down menu should looks similar to the illustration in Figure 110.
sequentially to the image and have the processing accumulate on the image. This
program doesn't allow us to do that.
But there is an even more significant problem. Try this. Start your Histo-GUI program
and apply the Urzsharp Masking image processing button. Now resize the graphics
window. What happened?
Right. The original image appears in the window instead of the processed image. That
is not right at all. When you resize the graphics window, whatever happens to be in the
window should be reproduced in the resized window. This program has no-for lack
of a better term-mer?zoty of what has been done to the image. Hence, it can't
reproduce it properly.
Both of these problems can be solved by having a second image in the program. I like
to call this image the "process" image, because it is the image I am going to display in
the graphics window. Modify your info structure in the widget definition module to
accept a second process image. Of course, initially the process image is identical to
the original image data.
info = { image : Ptr-New (image), $
process:Ptr-New(image), $
axisColorName:axisColorName, $
backColorName:backcolorName, $
binsize:binsize, $
dataColorName:datacolorName, $
imageColors:imagecolors, $
max~value:max~value,$
title:title, $
xscale:xscale, $
yscale:yscale, $
extra : Ptr-New (extra), $
drawID:drawID, $
wid:wid, $
pix1D:pixID $
1
With a new pointer in the info structure, our Cleanup routine will also have to be
changed. Locate the Histo-GUI-Clearzup module and make this change (in bold) to
the code there:
ENDIF ELSE BEGIN
Ptr-Free, info.image
Ptr-Free, in£o . extra
WDelete, info.pixID
Ptr-Free, info.process
ENDELSE
Next, modify your Histo-GUI-Processing event handler to perform the image
processing on the process image and not the original image. Find every instance of the
variable processI17znge in the event handler and change it to *irzfo.process. The code
segments, with changes in bold, are shown below:
CASE StrUpCase (buttonvalue) OF
'MEDIAN SMOOTH1: *info.process = Median(*info.process, 5)
'BOXCAR SMOOTH1: *info.process = Smooth(*info.process, 7, $
/~dge-~runcate)
'SOBEL1: *info.process = Sobel(*info.process)
'UNSHARP MASKING1: *info.process = Smooth(*info.process, $
7) - *info.process
Widget Progranzmiizg Teclzrtiqztes
Finally, find every instance in your program where you are passing '"irzfo.ilnage to the
HistoZ~.tzagecommand. There should be two instances currently, both in the
H~s~o-GUZ-TLB-EIJ~I~S event handler. Both of these instances should be changed so
that '"irfo.inzage is changed to :kirzfo.pi-ocess.Be sure you do this mice.
Save the program, compile and test it now. Be sure that when you resize the graphics
window that what is currently in the window is reproduced faithfully. And you should
be able to apply image processing steps sequentially.
WSet, info.wid
Device, Copy=[O, 0, !D.X-Size, !D.Y-Size, 0, 0 , i n f o . p i x ~ ~ ]
Widget-Control, event.top, Set-Walue=info, /NO-copy
END
Save the file, recompile your program, and test it. (If you did not enter the program
above into a file named histo~ui.pro,you can use the file Izisto~ui.3.pr0,which you
downloaded to use with this book. This file must be explicitly compiled to be used.
See "The Advantage of Mistakes" on page 261 for more information.)
Does the Undo/Redo button work as it is suppose to? The program with the Undo
button should look similar to the illustration shown in Figure 111.
lmclye Value
Figure I l l : The Histo-Gui program with Unsharp Masking applied and the Undo
button ready to reverse the processing step.
Adding Color Controls to the Program
The next step in program development is to add the capability of controlling the
programs colors from the graphical user interface. In the HistoIinnge program, there
are the three drawing colors, which we are asking for by name, and the image colors,
which we wish to control externally.
Note that the event handler for the drawing colors is assigned to the Dimviizg Colors
button, which cannot generate its own events, since it is a menu button. Events from
this button's children widgets will bubble up and be captured by this event handler.
You will be able to detesmine which button it is that is generating the event by
examining that button's user value.
To get the button's user value, type this line of code directly after you get the irfo
structure out of its storage location:
Widget-Control, event.id, Get-UValue=buttonWalue
You will branch on this value in a CASE statement.
What we plan to do in this event handler is call a program named PickColorNanze that
is anlong the program files you downloaded to use with this book. PickCoIorNanze is
what I like to call a modal dialog forr?? tvidget. I mean by this that the program is a
widget program that can be called from within another widget program to gather
information from the user (usually on some kind of form or graphical layout) and
return that information to the caller. This information will be used in the calling
program when the form widget is destroyed. In this case, the information we are
looking for is the name of a new color to use. This will be returned to you when the
form widget is destroyed, either by clicking the Carzcel or the Accept button.You will
learn how to write dialog form widgets in the next chapter.
For now it is enough to know that the PickColorNarne program is written as a modal
dialog widget. That is to say, this program will stop all other programs from accepting
user input until it is destroyed. This includes the Histo-GUI program it was called
from. The purpose of a modal widget is to stop everything until some information is
collected from the user, and then continue program execution.
Modal widgets, or widgets that stop all other user interaction, cannot be created
without the presence of a group leader: A group leader in IDL is a widget, typically
the top-level base widget of the calling program. The idea is that if the group leader is
destroyed, all members of that group should be destroyed as well. You will learn more
about group leaders in just a moment. But you should be aware that in this program
the only way to be certain PickColorNar?ze will be a modal widget is to pass it a group
leader.
There is only one positional parameter to the PickColorNarne program, tlzeNanze,
which is the name of the color PickColorNarne will start with. The names
PickColorName is familiar with are the same color names you use with GetColor: For
example, names like: Black, Magerzta, Cymz, Yellow, Green, Red, Blue, Navy, Pink,
Aqua, Orchid, Sky, Beige, Charcoal, Gray, and Wlzite. To see a complete list of names,
type this:
IDL> Print, GetColor ( / ~ a m e s )
If you mis-spell a name, or ask for a name that is not recognized, or don't include a
name, you start with a white color. For example, try this at the IDL command line:
IDL> colorname = PickColorName ( )
You should see a program that looks similar to the illustration in Figure 112.
The return value of the function is the name of the selected color. Try selecting a
different color by clicking on the different colors in the first two color rows. The name
of the color and the color itself are loaded in the color patch in the middle of the
program window.
IDL> Print, colorname
Beige
If you wanted to start PickColorNanze with a red color, for example, you type this:
IDL> colorname = PickColorName ( red )
In addition to the one positional parameter, PickColorName is defined with five
keyword parameters:
Adding Color Controls to tlze Progranz
that called PickColol-Nmne and the appropriate action will take place. It is a
consequence of the way we have written our color protection scheme.
Test the program by saving, compiling, and running it. Can you change the
background and data colors? Does it work the way you expect it do?
copy of the iifo structure, and the user delayed loading the color table, we can imagine
that the information in the copied iifo structure (say, for example, "'iifo.pi-ocess,the
process image) would be out of date by the time the procedure was called into action.
A likely scenario would be to get the color table tool on the display, change the color
table, change the background color with the PickColorNaine program, and then resize
the image. The background color would be the color of the graphics display when the
color table tool was called, not the current background color.
Both of these problems can be solved by pointers. We can either make each field in the
iifo structure a pointer, or we can make a pointer to the iigo structure itself, and pass
this to the color table tool. But this seems like a lot of work to me. And, quite frankly,
you are not going to find many IDL programs written this way. What happens when
you want to add a color table tool to those programs?
So, I prefer to communicate to other widget programs in a way that seems more
natural to me in a widget program. I prefer to communicate by means of widget
events.
Since communicating via widget events is not possible with the IDL-supplied
XLoadCT program, we are going to use XColors as the color table tool in the
Histo-GUI program. (You will learn more about communicating via widget events in
"Creating a Non-Modal Widget Dialog" on page 340.)
loaded color table. And the Nanze field contains the name of the cunently loaded color
table.
Once the event stl-ucture is created, it is sent to the widgetToNotifi widget by means of
the Send-Event keyword to the Widget-Contlvl command, like this:
Widget-Control, widgetToNotify, Send-Event=xcolorsEvent
This xcolorsE~)elztevent structure is just like any other event structure in IDL. That is
to say, it goes through the same process of getting into the event queue to be processed
in order, etc. IDL will check to see, as it puts the event structure on the event queue,
which widget is associated with the event handler for the voidgetToNotifi widget, and
will fill out the Handler field of the event structure with the proper widget identifies.
Of course, the event handler that will receive this event stl-ucture will have to be
written in a way that anticipates receiving an event stsucture like this.
In practice, the ~tlidgetToNotifywidget is usually the same button widget that was
selected to get you into the event handler that called XColors in the first place. That is
to say, usually we call XColors like this:
XColors, N o t i f y ~ ~ = [ e v e n t . i d event.top]
,
For example, in the Histo-GUI program, event. id would refer to the Ilnage Colors
button. And evel?t.top would be the top-level base identifier for the Histo-GUI
program.
With this in mind, we can start to fill out the Histo-GUI-Il~zage-Colors event handler.
The first order of business is to detennine what kind of an event has come into the
event handler. It could either be a WIDGET-BUTTON event structure (if it comes
from selecting the Inzage Colors button), or it could be this new XCOLORS-LOAD
event structure (if XColors loaded a color table). It is possible to distinguish between
the two by using the Tag-Names command with the Structure-Name keyword set to
return the structure name. So the first line of code in the Histo-GUI-Ilnnge-Colors
event handler after the irzfo structure is obtained is this (in bold type):
Widget-Control, event.top, Get-Walue=info, /NO-Copy
m Remember that Tag-Names returns uppercase string variables. This will be critical in
the CASE statement that follows.
On the following line, we can add the skeleton of the CASE statement, so that the rest
of the event handler looks like this:
CASE t h i s E v e n t OF
'WIDGET-BUTTON1: BEGIN
END
'XCOLORS-LOAD1: BEGIN
END
ENDCASE
idg get-control, event.top, Set-~alue=info, /NO-copy
END
Now it is just a matter of filling out what should be done for each case. In the button
case, of course, we want to call the XColors program.
Another feature of the XColors program that makes it more attractive to use in widget
programs than the IDL-supplied XLoadCT program, is that it is possible to have
multiple copies of the program on the display at the same time. This is not possible in
Widget Progmnurting Techiziques
Notice that XRegistered has the nice effect of bringing the program forward on the
display if it is already registered.
Programs with common blocks can protect those common blocks by having code
similar to this line from XLoadCT in the first couple of lines of the widget definition
module, before any widgets are defined:
IF XRegi~tered(~x1oadct~)
NE 0 THEN RETURN
But, as I say, XColors doesn't suffer from this limitation, since it doesn't use common
blocks. (Nor do any of the widget programs you downloaded to use with this book. I
write all of my widget programs without common blocks.) XColors does have a
limitation that you can only have one XColors with a particular title on the display at
any one time. I impose this limitation because I don't want XColors programs
proliferating like rabbits every time you click an I~nngeColors button!
But, I would like to have a different XColors program associated with each Histo-GUI
program I wanted to run. That way I could change the colors in each Histo-GUI
program independently of any others. To do that, I need to give the XColors program a
unique title in each Histo-GUI program I run.
Humm. How can I do that? What is there that is unique in each Histo-GUI program?
Actually, there are many things unique about each Histo-GUI program. All the widget
identifiers are unique, for example. But I often prefer to use the window index
number, since that is a nice simple, two-digit number. And if I use the two-digit
number on both the Histo-GUZprogram window and the XColors window, then I have
a way of telling which XColors program changes colors for which Histo-GUI
window.
We are almost ready to write the event handler code, I promise. Just one more point.
When XColors (or XLondCT for that matter) in invoked, it has no way of knowing
what has occurred to the color table vectors prior to that time. It simply gets the color
table vectors that are currently loaded and uses those as the starting colors. If you want
these starting colors to reflect the colors in the Histo-GUI window, you will have to
load those color vectors before invoking the XColors program.
Alright, then. Here is the code for the WZDGET-BUTTON case. New code is added in
bold characters:
'WIDGET-BUTTON': BEGIN
TVLCT, info.r, info.g, inf0.b
colorTitle = info. title + ( " + StrTrim(info.wid12) + (l)
Widget-Control, event.top, TLB-Set-Title = colorTitle
XColors, NColors=info.imagecolors, $
NotifyID= [event.id, event. top] , $
Title=colortitle + ' Colorsl
END
Notice the title we are putting on the top-level base widget is composed of the original
title with the window index number set inside of parentheses. The StrTriin command
is used to convert the window index number to a string, while at the same time
trimming blank characters from both ends of the string. (This is the meaning of the
number 2 in the Stl"l3-i~~command.) String concatenation occurs by means of the plus
("+") operator in IDL.
Notice, too, that the XColors program is restricted to changing just the image colors
and not the data colors in the color table by the use of the NColors keyword and the
iifo.imngecolors value.
Next, we are ready to write the code for the XCOLORS-LOAD case. What do we want
to do when a different color table is loaded? See the new colors in the Histo-GUI
display, obviously. What is required to do that? Just two things: (l) we need to save
the new color table vectors in the appropriate fields of the iifo structure, and (2) we
need to re-draw the graphics display if we are on a 24-bit display device.
Notice the difference between what we have to do here and what we had to do in the
Histo-GUI-Drn~lklg_Colors event handler. There we could rely on the keyboard
focus event generated when the PickColorNnnze program was destroyed to re-draw
the graphics display, here we have to do it ourselves. The reason for this is that the
focus may not be returning to the Histo-Gui program immediately. Since XColors is a
non-modal widget dialog, the keyboard focus may remain on the XColors program.
But we still wish to see the new colors reflected in the Histo-GUI display window, no
matter where the keyboard focus is located.
The code for the XCOLORS-LOAD case will look like this. Add the code in bold
characters:
'XCOLORS-L O A D ' : BEGIN
inf0.r = event.r
inf0.g = event.g
inf0.b = event.b
Device, Get-Visual Depth=theDepth
I F theDepth GT 8 BEGINTHEN
WSet, info.pixID
WSet, info.wid
Device, C o p y = [ O , O , ! D . X ~ S i z e ~ ! D . Y ~ S i z e ~ O ~ O , i n f ~ ~ p i x I D ]
ENDIF
END
Save your program, re-compile it, and run it now.
IDL> .Compile h i s t o g u i
IDL> Histo-Gui
Tly changing the image colors. You program should look similar to the illustration in
Figure 113. What happens if you have several Histo-GUI programs running at the
same time. Can you control each window's colors independently?
Figzcre 113: Tlze Histo-GUIprogram aizd the XColors program oiz the display at the
same time. Notice the titles oiz tlze two programs.
programming practice to clean up after yourself. What you would like it have happen
is to have the XColors program disappear when the Histo-GUI progranl disappears.
This kind of behavior is easily accomplished with widgets by making one widget the
group leader of another. When the group leader widget is destroyed, all of the
members of that group are also destroyed. In this case, we would like to make the
Histo-GUI top-level base widget be the group leader for the XColors progranl.
As it happens, the XColors program already has a Group-Leader keyword defined for
exactly this purpose. All you have to do to take advantage of it, is pass the identifier of
the group leader. In this case, that value is in the variable everzt.top.Modify the lines in
the Histo-GUI-Irnnge-Colors event handler in which XColors is invoked by adding
the code in bold below:
XColors, NColors=info.imageco~ors,$
NotifyID=[event.id, event.top1, $
Title=colortitle + ' Colors', $
Group-Leader=event.top
Close any XColors progranl you currently have on your display. Re-compile the
Histo-GUI code after making this change, and select the Image Colors button. Now
when you quit the Histo-GUI program, the XColors program should also be
destroyed.
This ability to make a widget program part of another widget group is so powerful that
it is doubtful that we would write a widget program without a Group-Lender keyword
defined for it. For example, we might want to use Histo-GUI functionality in some
other widget program we are writing. If we can get the Histo-GUI program to be
destroyed when its group leader is destroyed, then we can use its functionality any
time we need it.
It is so trivially easy to make a program respond to a g o u p leader's death, that we
should do it as a matter of course on all widget program we write. The first step is to
define a Gro~lp-Leaderkeyword for the program. This is done on the procedure
definition statement of the widget definition module. Make the following addition (in
bold characters) to the Histo-GUI code:
PRO Histo-GUI, $ ; The program name.
image, $ ; The image data.
AxisColorName=axisColorName, $ ; The axis color.
BackColorName=backcolorName, $ ; The background color.
Binsize=binsize, $ ; The histogram bin size.
ColorTable=colortable, $ ; The colortable index.
DataColorName=datacolorName, $ ; The data color.
-Extra=extra, $ ; Extra keywords.
Group-Leader=group-leader, $ ; The group leader.
Max-Value=max-value, $ ; The histogram max value.
Title=title, $ ; The program title.
XScale=xscale, $ ; The X image scale.
YScale=yscale ; The Y image scale.
It is not even necessary to check this keyword to see if it's value is defined, like we do
for most input keywords. Rather, we can pass the gro~rp-leadervariable along directly
and assign it as the group leader for the top-level base widget by using the
Group-Leader keyword on the XMnlzager command. Find the XMarzngercommand at
the end of the Histo-GUI widget definition module (the program module you just
modified). Make the following change (in bold) to the code:
XManager, 'histoguil,tlb, /NO Block, $
Event-Handler= ~iS~O-GUI-TLB>V~~~ S', $
Cleanup='Histo-GUI-Cleanup', Group-Leader=group-leader
Widget Progralnrning Techniques
Save your file, re-compile, and test the progsam. (If you did not enter the program
above into a file named lzisto_gui.yro, you can use the file histo_gui.4.p1.0, which you
downloaded to use with this book. This file must be explicitly compiled to be used.
See "The Advantage of Mistakes" on page 261 for more information.)
IDL> .Compile h i s t o - g u i
IDL> H i s t o-G U I
Does the program work as you expect it to? Good. Then let's add even more
functionality.
Figure 114: The Save As meitzc iiz the Histo-GUI prograin. Note tlze separator Eiite
above the Qztit buttoit. This provides the user with a visztal c b e that the
Quit operatioit is very differeizt froin a Save As operatioiz.
'TIFF F i l e 1 : BEGIN
END
IPostScript File1: BEGIN
END
ENDCASE
Widget-Control, event.top, Set-Walue=info, / ~ o - ~ o p y
END
234, and 235. Our image colors are loaded in indices 0 to 232.We have loaded the
colors and saved the color vectors in the ilfo structure.
Now we make the PostScript device the current graphics and we run the Histollnnge
program to draw graphics. Because there are 256 colors in the PostScript device, the
drawing colors are loaded in indices 252,253, and 254. The image colors should be
loaded in indices 0 to 25 1, which are the indices that the image data is scaled into. But
we are not loading the color table. Because if we do there is an excellent chance that
what appears in the Postscript file will not look anything at all like what the user saw
on the display. (For example, the user might have stretched the color table, or changed
the gamma function.) If we don't load a color table, we can at least copy the color
vectors into the PostScript file. But those color vectors use indices 0 to 235, and they
contain our drawing colors!
Our output will look similar to the illustration in Figure 115. Notice the colors from
. .
Image. Histogram
I:'
5000
.-
I;
4000 - -
.- I//:
g 3000 -
;/:I
-
h -
L__1
0 50 100
lmage Value
150 200
250 l
Figure 115: The Postscriptfile created orz a rnachirte with arz 8-bit display before tlze
image colors were re-sampled into the rzumber of iinage colors available
in the PostScript device.
index 232 on are not correct. And you can see vividly the previous drawing colors (the
bands across the color bar). One possible solution is to rebin the image colors from the
color vectors stored in the itfo stsucture into the number of image colors used in the
PostScript device. In other words, re-sample the 232 colors from the 8-bit display into
252 colors. The code to do the re-sampling and load the color table in the PostScript
device (don't add these commands to your file yet) will use the TVLCT and Congrid
commands, and look like this:
topcolor = info.imagecolors-l
TVLCT, Congrid(info.r[O:topcolor], !D.Table-Size-4), $
Congrid(info.g[O:topcolor], !D.Table-Size-4), $
Adding File Ozitput Fzcrzctioirnlity
Another point to consider in creating PostSciipt output is the kind of fonts you would
like to use. Hardware or true-type fonts almost always look better in Postscript output
than the vector fonts typically used on the display. Changing the font makes the output
look slightly different from the output on the display. (See "Problem: PostSciipt
Devices Can Use Different Display Fonts" on page 187 for more information.) But it
is essential if you want nice looking Postscript output. Be sure to use true-type fonts if
you have text rotated in 3D space. If your text is only rotated in 2D space, as here, then
hardware fonts are perfectly acceptable.
So, given these considerations, the complete text of the PostScript portion of the
CASE statement (in bold) looks like this:
' P o s t s c r i p t File': BEGIN
thisDevice = !D.Name
thisFont = !P.Font
!P.Font = 0
Set-Plot, l PS l
IF theDepth EQ 8 THEN BEGIN
topcolor = info.imagecolors-l
ncolors = !D.Table-Size-4
TVLCT, Congrid(info.r[O:topColorl, ncolors), $
Congrid(info.g[O:topColor], ncolors), $
Congrid(info.b[O:topColor], ncolors)
END IF
Device, -Extra=keywords
HistoImage, *info.process, $
Binsize=info.binsize, $
-Extra=*info.extra, $
Max-Value=info.max-value, $
NoLoadCT=l, $
XScale=info.xscale, $
YScale=info.yscale
Device, /Close-File
Set-Plot, thisDevice
!P.Font = thisFont
END
Notice that the !P.Font system variable is set to use hardware fonts (!P.Font=O). The
image colors are scaled into the correct number of Postscript colors if the program is
running on 8-bit displays. And that the Ax-isColorNnme,BnckColorNn~ne,and
DntnColorNnlne keywords have been left off the call to Histolnznge, thereby invoking
the default drawing colors for the program.
Notice too the Device command with the Close-File keyword set. This command is
essential if you want to be able to print the resulting PostScript file.
The PostScript output will look similar to the illustration in Figure 116.
Save your program, re-compile it, and run it. (If you did not enter the code above into
a file named Izisto_gui.pro, you can use the file Izisto_gui.5.pm, which you down-
loaded to use with this book. This file must be explicitly compiled to be used. See
"The Advantage of Mistakes" on page 261 for more information.)
Does the program work the way you expect it to? Good. Then let's add a Print
capability.
5 0 0 0 : , , ' ' m ' ' "
lmage
g ' .
Histogram
,4000 -
.-
+
3000- .. l:
.,
0 50 100 150 200 250
lmage Value
300
200
100
0
0 100 200 300
Figzcre 116: Tlze Postscript file ozctput from the Histo-GUI program.
Finally, we close the Prirzter device, and clean-up. Type these commands (in bold) just
before the command that puts the iizfo structure back in the user value of the top-level
base.
Device, /Close-Document
Set-Plot, thisDevice
!P.Font = thisFont
!P.Thick = thickness
Widget-Control, event.top, Set-WaZue=info, /No-Copy
END
Save the file, re-compile it, and test it. (If you did not enter the program above into a
file named Izisto~cri.pro,you can use the file lzisto_gui.6.plv, which you downloaded
to use with this book. This file must be explicitly compiled to be used. See "The
Advantage of Mistakes" on page 261 for more infoilation.)
IDL> . Compile hiSto-gui
IDL> Histo-GUI
Can you get output from your printer? Good. Then you are ready to move onto the
next chapter, where you will learn even more widget programming techniques.
Creating Dialog Form Widgets
Chapter Overview
The purpose of this chapter is to demonstrate two ways to create dialog form widgets.
Dialog form widgets are widget programs that collect information from the user and
pass that infonnation along to another widget program module or to another widget
program altogether. Among the techniques you will learn are these:
* How to write modal or blocking dialog form widgets
* How to write non-blocking dialog form widgets
How to use pointers to store information in widget progams
* How to use pseudo events in widget programs
How to pass information between independent widget programs
button to release the block on the IDL command line. In this case, the progranl is said
to be a blockirzg widget program.
m There are several subtle differences between a modal and a blocking widget. If you
want to write dialog form widgets, you must understand the difference between a
blocking and a modal widget and how they work, exactly.
Notice that your IDL command line prompt either disappears or gets dim. It is
impossible to enter any more commands at the IDL command line and have them
executed. We say that the command line is blocked. (It is possible to enter commands
when you have a dimmed command line prompt, but they are not executed until the
command line is unblocked.)
Normally, any IDL program you run blocks the IDL command line. In other words,
you can't type another command and have it executed until the command currently
executing is finished. Up until IDL 5, the same could be said about widget programs.
Once you started a widget program running, you had no access to the IDL command
line until that widget program finished executing.
But this behavior changed in IDL 5 and it is now possible to have a widget progranl
running nrzd have access to the IDL command line where you can type commands and
have them executed immediately. We call such a widget program a rzon-blocking
widget program. To create a non-blocking widget program, you use the No-Block
keyword on the program's XMnrznger command. This is exactly what you did with the
Histo-GUI program you wrote earlier.
The XMaizager command in PickColoi-Name,however, is written without the
No-Block keyword. Hence, it is a blocking widget.
What "blocking" means in this context is that IDL stops executing the code in the
PickColorName widget definition module as soon as the XMclrznger command is
executed. Any code below the XMarznger command in the widget definition module
(and there is some, as you will see in a moment) is not executed until the widget
program is destroyed, thus freeing the block.
This is perfect for a program like PickColorNarne because the block gives the user
time to fill out the information on the form or dialog. When the user gets the form
filled out, they click either the Cnlzcel or Accept button. In either case, the widget is
destroyed, releasing the block and the program is free to do whatever it likes with the
information it has collected. In the case of PickColorNnme, this means collecting the
name of the selected color and then returning that name to the user as the result of the
function. All of this collecting and returning is done after the XMarzager command in
the widget definition module of the program.
All good so far. But here is the subtle part of blocking that is easy to overlook. It is
only thefirst program that calls XMarzager as a blocking widget program that blocks!
All subsequent blocking programs run rlzough their blocks and act as if they were non-
blocking widget programs.
This kind of behavior can be a complete disaster for a program like PickColorNanze,
because the program will return a color name even before the user has had a chance to
touch the form! As some of you may imagine, that will make it hard to change colors
in your program.
Creating a Modal Dialog Forit1 Widget
mouse nor remove. Windows on the display that I can't control annoy me more than I
can safely say in a book!
Anyway, if the group leader is specified, make the top-level base modal. If the group
leader is not specified, then you will have to hope the program is being called from the
IDL command line.
IF N ~lements(group-leader) NE 0 THEN $
tlb = idg get-Base(Column=l, $
Title='Enter File Information . . . ' , $
Group-Leader=group-leader, / ~ o d a l ,/~loating, $
/Base-~lign-Center) ELSE $
tlb = Widget-Base(Column=l, $
Title='Enter File Information...', $
/Base-Align-Center)
Notice the Floating keyword set on the modal top-level base. AfZoatir~gwidget
always floats above the program that is its group leader. This prevents the program
from getting lost behind other windows. The Base-Align-Cerzter keyword makes sure
that the children of this top-level base are centered in the top-level base.
they are supposed to) and is checking it to see whether to continue program execution,
then errors will not interrupt program operation.
Error Handling
It is a hard and fast rule of widget programming that gathering information of any kind
from a user is filled with danger. Users can't spell, they can't (or won't) read direc-
tions or program documentation, and they are completely incapable of providing
accurate information. In short, it is a miracle that anyone, let alone a prestigious Uni-
versity, gave them an advanced scientific degree. But, uh, they will be using your
programs. So you better be prepared for them.
Sometimes you can anticipate errors users might make, sometimes (even as cynical as
you are) you are surprised by them. In any case, your program should catch and
handler all errors with aplomb. We need a Cntclz error handler here. (See "The Catch
Control Statement" on page 230 for additional information.)
One error I can anticipate is that the user will delete all the text in one of the text
widgets and then hit the Accept button. I'm not sure if this will cause an error in the
program (I'd have to read the CW-Field documentation to find out, and I really don't
have time for that!), but I can imagine it might and I'd like to be prepared for it. A
little research shows that this particular error is likely be noticed when an undefined
variable is used in an expression.
For example, if I get a value out of the X size widget and try to store that value, like
this:
Widget-Control, info.xsizeID, Get-Value=xsize
(*info.ptr).xsize = xsize
I will cause an error if the xsize variable is undefined. (This is really a hypothetical
situation, since I know perfectly well that CW-Field does not return undefined
variables in this case. It returns a 0, which is even worse in my humble opinion,
because it pushes the error off until it is farther from its source and harder to find. But
I am going to give up on CW-Field in just a moment anyway because of some of its
other limitations, so bear with me just a little longer.)
Creating Dialog Form Widgets
Using an undefined variable in a program is error number -167. So I could trap for this
error in particular, and for all other errors in general, by writing a Catclz error handler
like this. Add the code in bold to the OpenZr7lnge-E~~entsevent handler code as the
very first line of code after the procedure definition statement.
Pro OpenImage-Events, event
Catch, theError
IF theError NE 0 THEN BEGIN
Catch, /Cancel
IF !Error-State.Code EQ -167 THEN BEGIN
ok = Error-Message('A required value is undefined.')
ENDIF ELSE BEGIN
ok= Error-Message ( )
ENDELSE
IF N-Elements (info) NE 0 THEN $
Widget-Control, event.top, Set-Walue=info, /No-Copy
RETURN
ENDIF
eventName = Tag-Names(event, /Structure-Name)
Notice that I used the Error-Message command to report the error, which is a program
you downloaded to use with this book. (The Envr-Message program is described in
more detail in "Tracing Errors" on page 234.) I also check the irfo structure back into
the user value of the top-level base, but only if it is checked out currently. This is
absolutely necessary if I want the program to continue running properly.
As long as we are handling errors, it might be a good idea to think of other kinds of
errors we could handle right here in the event handler that the user could fix. For
example, if the user spelled the name of the file incorrectly, we wouldn't be able to
find the file when we looked for it. We could catch that error and let the user have
another chance at it. (I'm not a big proponent of making users continue to do
something they just made a mistake doing. It is bad for their self-esteem. But
correcting obvious errors seems more like help than a penalty.)
So before we store the information we collect in the pointer location, let's do some
preliminary checking of the information. Make the changes in bold to the Accept
button's code:
'Accept : BEGIN
Widget-Control, info.fileID, Get-Value=filename
filename = filename [O]
Widget-Control, info.xsizeID, Get-Value=xsize
Widget-Control, info.ysizeI~,Get-Value=ysize
dummy = Findfile(filename, Count=theCount)
IF thecount EQ 0 THEN $
Message, 'Requested file cannot be found. + $
'Check spelling.', /NoName
IF xsize LE 0 OR ysize LE 0 THEN $
Message, 'File sizes must be positive1, /NoName
(*info.ptr). filename = filename
(*info.ptr).xsize = xsize
(*info.ptr).ysize = ysize
(*info.ptr).cancel = 0
Widget-Control, event.top, /~estroy
END
ENDCASE
END
Creating a Modal Dialog Forirt Widget
Figure 11 7: The OperzZmage modal dialog form widget progrant. Notice that the X
Size and Y Size text widgets do rzot look editable i ~ zthis Wi~tdowsversion
of CW-Field. This can be co~zfusirzgto users.
Notice that the background of the Filellalne text widget appears white and editable.
But the fields of the X Size and Y Size text widgets appear gayed out and do not look
editable, even though the user may change these values. This occurs only on Windows
platforms, but it can be terribly confusing to the user. Notice too that the Filelzarne, X
Size, and Y Size titles do not line up properly. What I would like is for the three
editable text widgets to align under one another for aesthetic reasons, with the titles on
the left arranged appropriately beside them. There is no way to do this using
CW-Field.
(The reason the X Size and Y Size text widgets do not appear editable is that, in fact,
they are not editable. That is to say, the Editable keyword is turned off for these text
widgets. The event handler, however, is receiving all the events you type. So that if
you type an integer, which is what you said should go into the text field, IDL will
place the proper integer character in the field. This gives the appearance of editability,
but allows the code to screen character input. A nice idea with an unfortunate
consequence.)
Note that when you select either the Cancel or Accept button, the widget disappears.
The form information is returned to you as a result of the function. Type this:
IDL> Help, f ileinfo, /structure
To use FSC-InputField, find these four lines in the Openlnzage widget definition
code:
fileID = CW-Field (filebase, Title= ' Filename : ' , $
Value=filename, XSize=filesize)
browseID = Widget-Button(filebase, Val~e=~Browse', $
Event P r o = ~ O p e n I m a g e ~ B r o w s e F i l e s ~
xsizeID = CW-Field(subbase, Title=IX Size:', $
Value=xsize, /integer)
ysizeID = CW-Field(subbase, Title='Y Size:', $
Value=ysize, /integer)
Replace the CW-Field lines with these:
fileID = FSC InputField(filebase, Title=lFilename:l, $
Value=filename, XSize=filesize, LabelSize=50, $
/S tring~alue)
browseID = Widget-Button(filebase, V a l ~ e = ~ B r o w s e$~ ,
Event~Pro=lOpen~mage~Br~~seFiles'
xsizeID = FSC-InputField(subbase, Title=&X Size:', $
Value=xsize, / ~ n t e g e r ~ a l u eLabelSize=50,
, Digits=4)
ysizeID = FSC-InputField(subbase, Title='Y Size:', $
Value-ysize, /Integervalue, LabelSize=50, Digits=4)
Notice the LnbelSize keywords. This sets the label portion of the compound widget to
be the same size for all three fields, thus aligning their text widgets. The Digits
keyword for the integer fields permits the integer to have only four digits. This is
another feature missing in CW-Field and helps prevent user errors.
The unusual feature about the FSC-I~lp~ltField compound widget, however, is that
instead of returning a widget identifier, it returns an object reference. Objects are heap
variables, like pointers. The huge advantage of writing compound widgets as objects
is that they then become much more versatile in how they can be manipulated after
they are created. For example, we can set up a tabbing sequence for these compound
widgets by calling the SetTnbNext method to tell the widget which other widget to go
to when a Tab character is detected. (The GetTextID method returns the widget
identifier of the text widget in each compound widget.) For example, type this code
immediately after the code above:
The only other changes you need to make in the OpenInxzge program are in the two
event handler modules. First of all, you need to set the value of the file name text
widget differently. Find this code in the Opeiil~~znge-BrowseFiles event handler:
Widget-Control, event.top, Get-Walue=info, /NO-copy
Widget-Control, info.fileID, Set-Value=filename
Widget-Control, event.top, Set-Walue=info, /NO-copy
Change the code like this:
Widget-Control, event.top, Get-Walue=info, /NO-Copy
info.fileID->Set-Value, filename
Widget-Control, event.top, Set-Walue=info, /NO-copy
And in the Oper7Imnge-Eve~ztsevent handler, where you are responding to the Accept
button, find these lines of code:
'Accept : BEGIN
Widget-Control, info.fileID, Get-Value=filename
Creating a Modal Dialog Forrtt Widget
filename = filename[O]
Widget-Control, info.xsizeID, et-~alue=xsize
Widget-Control, info.ysizeID, et-~alue=ysize
Modify the code to obtain the text widget values from the objects like this. (Make the
changes in bold.)
'Accept1 : BEGIN
filename = info.fileID->Get-Value()
filename = filename [O]
xsize = info.xsizeID->Get-Value()
ysize = info.ysizeID->Get-Value()
Save and re-compile the program. It should now look similar to the illustration in
Figure 118.
IDL> . Compile openimage
IDL> fileinfo = OpenImage ( )
Try the program and see how different it is from the one you had working previously.
Try tabbing from one field to another. What happens if you eliminate the value in, say,
the XSize field and then select the Accept button? Is this what you expect?
You can find a copy of the Oper?Irnngeprogram among the programs you downloaded
to use with this book. It is named operzimnge.pr'o.
~roup-Leader=event.top)
IF cancelled THEN RETURN
When the Openlinage program is destroyed, the keyboard focus will return to the
Histo-GUI program. This will cause a re-draw of the program in the window. Since
this is really not necessary in this case (we haven't changed any colors, for example),
we may wish to turn keyboard focus events off while we have the Openlinage
program on the display. If that is the case, you can add this code (in bold) to the two
lines you just typed:
Widget-Control, event.top, KBRD-Focus-Events=O
fileInfo = OpenImage(Cancel=cancelled, $
~roup-~eader=event.top)
Widget-Control, event.top, KBRD-Focus-Events=l
IF cancelled THEN RETURN
Now you have (presumably) the information you need to read the image file. Let's do
so, the code (placed immediately below the lines above) looks like this:
newimage = BytArr(fileInfo.xsize, fileInfo.ysize)
OpenR, lun, fileInfo.filename, /Get-Lun
ReadU, lun, newimage
Free-Lun, lun
Remember, we are assuming the file contains a 2D byte array.
Now that we have the new image, we need to store it in the iifo structure. So here is
where we obtain the iifo structure. (This line of code is already in the file.)
Storing the new image is as simple as pointing the image pointers to the new image.
You do not have to wony at all about de-allocating or freeing the previous pointer.
IDL takes care of all the memory management for you. The code looks like this:
Widget-Control, event.top, Get-Walue=info, /No-Copy
*info.image = newimage
*info.process = newimage
*info.undo = newimage
Since we can't undo this operation, we should make the Undo button insensitive. Add
this line of code.
Widget-Control, info.undoID, Sensitive=O
All that remains is to re-display the graphics. The final code looks like this. (Add the
lines in bold type.)
WSet, info.pixID
HistoImage, *info.process, $
AxisColorName=info.axisColorName, $
BackColorName=info.backcolorName, $
Binsize=info.binsize, $
DataColorName=info.datacolorName, $
Extra=*info.extra, $
Max-Value=info.max-value, $
NoLoadCT= l, $
XScale=info.xscale, $
YScale=info.yscale
.
WSet , inf o wid
Device, Copy= [O, 0 , !D.X-Size, !D.Y-Size, 0, 0 , info.pixID]
Widget-Control, event.top, Set-Walue=info, /NO-Copy
Creating Dialog Forin T17idgets
END
Save the file, re-compile it, and test it. (If you did not enter the program above into a
file named histojui.pro, you can use the file Izistojui. 7.pr0, which you downloaded
to use with this book. This file must be explicitly compiled to be used. See "The
Advantage of Mistakes" on page 261 for more information. The program as written is
also listed in Appendix C: IDL Progrnrn Code as the Histo-GUI program. See
page 428.)
Does the program work as you expect? Try to generate some errors. This shouldn't be
hard, since you probably know nothing about the files in the IDL distribution. If you
want to know more about files that you might possibly try to open, you can find file
descriptions in "Appendix B: Data File Descriptions7'on page 397. What happens if
you open a file that is not on that list? What happens if you mis-spell a filename or
type an incorrect file size?
Figure 119: The rzoiz-modal dialog form ~vidgetprogram Readinmge. Notice that it
looks almost identical to the modal dialog form wldget program Operzlm-
age in Figure 117. Ortly the buttorz labels are a clue to its non-blockirzg
status.
Creating a Non-Modal WidgetDiaIog
Although the names of the widget buttons have changed to Disrniss and Apply, their
functions are similar to the functions of the Car7cel and Accept buttons in the GetDnta
program:
dismissID = Widget-Button(butbase, Value='Dismissl)
applyID = Widget-Button(butbase, V a l ~ e = ~ A p p l y I )
Changing the button names is one more visual cue to the user about the function of
this widget. In other words, users who see Carzcel and Accept buttons should expect
modal functionality. While users who see Disrniss and Apply buttons should expect
non-modal functionality. Users will be confused less often if you keep your interface
as consistent as possible.
There is no pointer required in this program because there is no need for a global
memory location. But the vector of widget identifiers (identifying those widgets that
are to be notified of an event) must be stored in the irzfo structure, since these will be
needed in the event handler module:
info = { notifyIDs:notifyIDs, $
fileID:fileID, $
xsizeID:xsizeID, $
ysize1D:ysizeID )
The final change in this widget definition module is to make the widget a non-
blocking widget by using the No-Block keyword on the XMnrznger command:
XManager, 'readimagel, tlb,
Event-Handler=lReadImage-EventsI, $
/No- lock, Group-Leader=group-leader
Notice that this is where I assign the group leader for the top-level base.
not destroyed, it is essential to put the irfo structure back in it storage location in the
user value of the top-level base before exiting this event handler.
The ReadImage program is among the programs you downloaded to use with this
book. Its name is 1.endimnge.pl.o. The complete program listing, for the Readlrlznge
program is also available in "Appendix C: IDL Program Code'' on page 43 1.
*info.undo = newimage
Chapter Overview
The purpose of this chapter is to demonstrate how to build graphics display objects.
These are objects that use direct graphics commands for graphics display. We often
think of these objects as "smart" objects or "sticky" objects since their properties are
persistent. This is unlike most IDL graphics display commands, whose properties are
ephemeral. Among the techniques you will learn are these:
* How object oriented design principles are implemented in IDL
* How objects are created in IDL
* How to to write initialization and clean-up lifecycle methods
* How to write object methods
* The meaning of such words as encapsulation, inheritance, and polymorphism
be thought of as an object metl7od, a way of interacting with the data in the program.
The irgo structure can be thought of as the very heart of the object itself, being the
place were the data and the information required to work with the data resides. In
some sense, objects just confirm and extend our notions about what a properly written
IDL program looks like.
Creating Objects
Objects are created by using the Obj-New command to create an object. For example,
the programs you downloaded to use with this book contain a Postscript configuration
object with a class name FSC-PSConfig. (The file is named fscgscorzfigdefirze.pro.
Notice the two underscore characters before the word define in the file name. This is
important, as you will see in just a moment.) A specific instance of this object class
can be created by typing this command:
A Quick Object Overview
One of the methods you see described there is a GUI method. This method puts up a
graphical user interface that allows the user to configure the Postscript device in any
way that suits him or her. An object method is invoked with an "arrow" operator,
which is just a hyphen followed by the greater-than sign, like this:
IDL> thisObj ect - >GUI
Like any other procedure or function in IDL, object methods can also be passed posi-
tional and keyword arguments. For example, hit the Cnrzcel button on the graphical
user interface on your display, and re-invoke the GUI method like this:
IDL> this0b-ject - >GUI, Cancel=cancelled, / N o ~ l o c k
Note that calling the GUI method is no different from calling a GUI procedure, except
that the object reference and the arrow operator are in front of the name of the method
or procedure.
Play with some of the settings for a moment. When you have the configuration the
way you like it, hit the Accept button. The current configuration is now saved inside
the object. If you hit the Dismiss button to destroy the graphical user interface and
Creating Graphics Display Objects
Figure 120: The result of applyiizg the GUZ method to the FSC-PSCOIZ~~~
object.
then invoke the GUI method again, the graphical user interface comes up with the last
settings you had accepted before you dismissed it. This type of persistent or remem-
bered behavior is one of the great benefits of objects.
In order to configure the Postscript device with the object, we need to be able to get
'the configuration values out of the object. There are several ways to do this, but the
easiest is to just get a structure of the appropriate "device" keywords that we can pass
to the Postscript device using the keyword inheritance mechanism. We can do this by
invoking a function method on the object named GetKeywords, like this:
Notice that in this case the G e t K e y w o l d s method contains no arguments, but the
parentheses indicate it is a function. (It is similar to the function N-Paranzs in this
sense.) You can see what the return value of the function method is by typing this:
IDL> H e l p , p s K e y w o r d s , /Structure
You see something similar to this, where each field in the structure is the name of a
keyword that can be used to control the Postscript device.
** S t r u c t u r e < 1 5 6 9 b d O > , 24 t a g s , l e n g t h = 6 8 , r e f s = l :
BITS-PER-PIXEL INT 8
COLOR INT 1
ENCAPSULATED INT 0
FILENAME STRING 'C:\RS1\IDL53\coyote\idl.ps1
FONT-S I Z E INT 12
INCHES INT 1
ISOLATINl INT 0
PREVIEW INT 0
TT-FONT INT 0
XOFFSET FLOAT 1.50000
XSIZE FLOAT 8.00000
YOFFSET FLOAT 9.50000
YSIZE FLOAT 5.50000
LANDSCAPE INT 1
PORTRAIT INT 0
Creating a New Object Class
HELVETI CA INT
BOLD INT
BOOK INT
DEMI INT
ITALIC INT
LIGHT INT
MEDIUM INT
NARROW INT
OBLIQUE INT
In practice, the Postscript device is configured with the FSC-PSCorzfig object like
this:
Set-Plot, 'PS'
Device, -Extra=thisObject->GetKeywords()
Destroying Objects
As heap variables, objects persist in the IDL environment until they are explicitly
destroyed. Objects are destroyed (and removed from the heap) with the Obj-Destroy
command, like this:
IDL> Obj-Destroy, thisObj ect
Structure Review
Structures are variables that hold heterogeneous data. Data in structures can be distin-
guished by its logical relationships, rather than because its form or type is the same. A
named structure is a structure that has a name. Here is a named structure, that has the
name Employee:
IDL> struct = { ~ m p l o y e e ,BadgeID:OL, Age:O, Sa1ary:O.O)
Creating Graphics Display Objects
There are three fields in this structure: BadgeID, a long integer; Age, an integer; and
Salary, a floating value. It groups information about an "employee" together in a sin-
gle vai-iable.
The statement above is what I like to call afonnal stlatctzr~-edefirzitiolz statement. That
is to say, the E~nployeestsucture is defined in a fonnal sense by that statement, since
the data type and size of each field is filled out explicitly.
More often, especially in your colleague's code, you will see what I call an infom?al
structure definitiori stnte~nerit.It might look like this:
IDL> struct = {Employee, BadgeID: 58637L, Age: 29, $
Salary:47598.0)
In this statement, we have not only defined the structure Er~zployee,we have filled it
with specific data, which is stored in the variable strzrct.
It doesn't matter whether you have defined the structure formally or informally, IDL
saves the defiriitiol-Iof the named sti-ucture internally. It does not save the specific data
of the structure in the structure definition. Saving the definition of the stiucture makes
it easy to create another of those Employee structures. You do it like this:
IDL> joe = {Employee)
IDL> joe.badgeID = 4983252
IDL> joe.age = 34
IDL> joe.salary = 34,568.0
In this case, the variable joe is one of those Employee structures defined above. Notice
that I can immediately start to fill the fields of the structures, because IDL alseady
knows those fields are defined and what they are defined as. In fact, I can even create
a specific instance of the Enzployee structure in a variable vzary like this:
IDL> mary = {Employee, 594832L, 26, 45346.0)
As long as the values and order of the input matches the definition and order of the
defined fields in the Enzployee structure, IDL is content to let me define a particular
instance of a stsucture this way.
What if sometime later I want to add a picture of the employee to the structure? I
might try to re-define the structure like this:
IDL> struct = { ~ m p l o ~ e eBadgeID:OL,
, Age:O, Salary:O.O, $
Picture:BytArr (300,300))
IDL doesn't let me do this and an error to the effect that you have defined the "wrong
number of tags" results. This is may or may not alert you to what you are doing
wrong. But, take my word for it, you cannot extend a named structure this way. The
only way to do this is to exit IDL (for all versions of IDL prior to 5.3) or to use the
.Reset-Sessiorz executive command to reset the entire IDL session. Doing this will
completely remove all main-level program variables, all compiled procedures and ses-
sions, and all named common blocks, as well as removing all current named structure
definitions. Be sure you know the consequences of your action.
IDL> .Reset-Session
IDL> struct = { ~ m p l o y e e ,BadgeID:OL, Age:O, Salary:O.O, $
Picture:BytArr (300,300))
Since objects are implemented in IDL as named structures, you find the
.Reset-Sessiolz command immensely helpful during object program development!
Creating a New Object Class
END
Notice this is a structure definition, not the actual data. IDL calls this routine oizly to
get the object class definition. You will populate this object with actual data in the
next step. Since this is a named structure, and since we have no way of knowing ahead
of time about how some of the fields will actually be defined (Is the ilnnge field going
to be a 300 by 300 byte array, or a 5 12 by 440 float array?), we define all fields that
will have changing or variable size or type data to a null pointer type. In other words,
later this field will be a real pointer to some real data. At the moment we are just sav-
ing enough space in our structure (object) definition for a pointer data type.
This is the program module that will get called when you create an object of class
Boxlmage. (But please don't type this command yet. We want to initialize the object
properly, and that will be difficult if we create the object now. The reasons for this
will be explained in the next section.)
myOb ject = Obj-New ( BoxImage )
In other words, by typing the command above, you ask IDL to create an object refer-
ence to a class of objects named BoxIi?inge.IDL knows that objects are implemented
as named structures, so it goes looking for a file with the name boxii?znge-define.pr-o.
If and when it finds it (we just wrote it) IDL compiles the file until it finds a module
with the same name, in which case it stops compiling and runs that module, thereby
providing a definition for the BoxInznge object class.
You can think of this as a formal definition of the object class, if you like, in a way
that is analogous to the formal definition of a structure.
But, a specific object does get created. The object named nzyobject in the command
above is a specific instance of an object of class BoxIrnage, with specific (formalized)
object data. Most of the time we would prefer the object to have other, particular, data.
The question is, how does an object get assigned that particular data? Most of the
time, it does so by automatically calling an Iizit methodfor the Boxlr?znge object class.
Objects do not have to be written with Iizit and Clenizzrp methods, but in practice
almost all objects are. If the methods are available, they are called when the object is
created or destroyed.
Creating the lnit Method
The Iizit method is afiri~ction.If you have a red pen, get it out and underline that last
sentence. You will inevitably make your Iilit method a procedure the first three or four
times you create an object. If you don't get totally discouraged when your objects
never work and give up object programming entirely, you might remember I told you
that the Iilit method must be a function. It is critically important.
Let's see. Did I mention that the Illit method must be a function? If so, it bears repeat-
ing. Your objects won't work if you write the Irzit method as a procedure.
Here is why. The purpose of the Iilit method is to return a l if the Irzit method works
properly. It is suppose to return a 0 if anything at all goes wrong. Procedures always
return an implicit 0 to the caller. This means that if you make the Iizit method a proce-
dure, IDL will think your Iizit method failed every time and you will never get a valid
object reference. Please, please, please make it a function. (Alright, enough pleading.
It's just that every time I go look over the shoulder of a new, struggling object pro-
grammer they have made the Irzit function a proc ... Oh, never mind. I'm sure you have
the idea by now.)
The Iizit method function can be written just exactly like any other IDL function. You
can have any number of positional and keyword arguments. Those arguments can be
input or output parameters, etc. The only thing different about this function is the way
you specify its name. It rnust be named (no exceptions) with the object class name,
followed by two colons, and then the word Iizit. It cannot be named Iizitinlize, or any-
thing else. It must be called Irzit.
Open up the boximage-define.pro file and add the Iizit method code ilzfr-orzt of the
object definition module (BoxIrnageDefine) in the file. You do this for the very
same reason that you add event handler code in front of the widget definition module
code in a widget program file: because you want all the helper modules to be compiled
properly before they have to be used.
Note that methods can be put into their own individual files, although I don't know
anyone who does this (except inexperienced programmers). If you choose to do this,
the file should be named with two underscore characters instead of two colons. For
example, you could put the Iizit method in a separate file name boximnge-iizit.pl-o.
The function definition statement of the Iizit method looks like this:
Function BoxImage::Init, $ I The name of the method.
image, $ I The image data.
AnnotateColor=annotatecolor, $ ; The annotation color.
BackColor=backcolor, $ I The background color.
ColorTable=colortable, $ I The colortable index.
NColors=ncolors, $ I Number of image colors.
Position=position, $ I Position in window.
Vertical=vertical, $ I Vertical colorbar flag.
XScale=xscale, $ I The scale on X axis.
XTitle=xtitle, $ I The title on X axis.
YScale=yscale, $ I The scale on Y axis.
YTitle=ytitle, $ I The title on Y axis.
-Extra=extra Holds extra keywords
Notice the way the Init method name is composed. You must use the object class
name, two colons, and the name of the method. Here we define a single positional
Creating Graphics Display Objects
parameter: the image to be displayed, and a number of keyword parameters that will
set properties for this object.
Recall that the Init method is to return a 0 if anything goes wrong. In practice, you
usually Catch all errors in the hzit method and return a 0 if an error occurs. The error
handling code might look like this:
Catch, theError
IF theError NE 0 THEN BEGIN
Catch, /cancel
ok = Error-Message(!Error-State-Msg + ' Returning...', $
Traceback=l, / ~ r r o r )
RETURN, 0
ENDIF
As in all procedures and functions, the next step is check to be sure all the positional
and keyword parameters have values. The code will look like this:
IF N-Elements(image) EQ 0 THEN image = LoadData(7)
ndims = Size(image, /N-~imensions)
IF ndims NE 2 THEN $
Message, 'Image must be 2D array.', /NoName
; Check for keyword parameters
IF N-Elements(annotatecolor) EQ 0 THEN $
annotatecolor = "NAVY1I
IF N-Elements (backcolor) EQ 0 THEN backcolor =
IF N-Elements(nco1ors) EQ 0 THEN ncolors = !D.Table-Size - 3
IF N-Elements(position) EQ 0 THEN $
position = [0.15, 0.15, 0.9, 0.91
vertical = Keyword-Set(vertica1)
IF N-Elements(xtit1e) EQ 0 THEN xtitle =
IF N-Elements(ytit1e) EQ 0 THEN ytitle =
S = Size(image, /~imensions)
IF N-Elements (xscale) EQ 0 THEN xscale = [O,S [OI1
IF N-Elements (yscale) EQ 0 THEN yscale = [0,S [l]]
There is nothing unusual here. We will provide an image if one isn't passed into the
program. We will have a white background color and a navy annotation color. The
axes scales will be taken from the size of the image if nothing is provided.
The Colortable keyword gives us a bit more challenge. In the Histobiznge program we
wrote previously (see the "The HistoImage Program" on page 240 for additional
information) we chose to actually load a color table. This gave us problems when we
wanted to control colors from outside the program as in the Histo-GUI program and
required us to define a NoLoadCT keyword to prevent color table loading. The persis-
tence of objects gives us another way to handle this problem.
In this program, what we want to do is store the actual RGB vectors that contain the
color indices of the 2D image. But to do that properly, we need to resample the actual
color table vectors, which are always 256 elements in length, into the number of col-
ors we are going to use to display the image. Getting those color vectors without
loading the color table is the trick we need here.
Fortunately, IDL has provided an answer in the form of the IDL graphics object
library, and the IDLgrPnlette object in particular. We can create the palette object,
load the color table indicated by the user into the palette with its LoadCT method, then
get the RGB vectors from the palette object to use in this object. The Getproperty
method of the IDLgrPnlette object will allow us to retrieve the currently loaded color
Creatiizg a New Object Class
vectors via output keywords. In this way, no color table vectors ever have to be loaded
into the physical color table of the device. The code to do so will look like this:
IF N-Elements (colortable) EQ 0 THEN BEGIN
colors = Obj-New (nIDLgrPalettell)
colors - zLoadCT, 0
colors->GetProperty, Red=r, Green=g, Blue=b
Obj-Destroy, colors
ENDIF ELSE BEGIN
colors = Obj-New(I1IDLgrPaletteN)
colors->LoadCT, 0 > colortable < 40
colors->GetProperty, Red=r, Green=g, Blue=b
Obj-Destroy, colors
ENDELSE
r = Congrid(r, ncolors)
g = Congrid(g, ncolors)
b = Congrid(b, ncolors)
Notice that if the user does not specify a color table, we will display the image in a
gray-scale color table. Notice, too, that the IDLgrPalette object is destroyed when we
are finished with it. This is so we don't have any leaking heap memory in the program.
Now, recall that I said the purpose of the Init method was to initialize or populate with
specific data a particular instance of the object. That particular instance of the object,
inside each of the object's methods, is a variable named self. You don't have to
declare it. It is just there, automatically, in each object method. It is that particular
instance of that particular object class. And inside the methods you can refer to that
selfobject as if it were a structure, defined as in the object class definition. This is the
only place you can do so. (You can also refer to the selfobject as an object reference,
which it really is, and call methods on it, and so forth.)
So, the next step is to populate the selfobject. We refer to the structure definition
statement, and populate the self object like this:
self .image = Ptr-New(image)
self.process = Ptr-New(image)
self .undo = Ptr-New (image)
self.position = position
self.ncolors = ncolors
self.annotatecolor = annotatecolor
self.backcolor = backcolor
self.r = Ptr-New (r)
self .g = Ptr-New (g)
self .b = Ptr-New (b)
self.vertica1 = vertical
self.xscale = xscale
self.yscale = yscale
self.xtitle = xtitle
self.ytitle = ytitle
self.extra = Ptr-New (extra)
Note that the extra variable will be created by IDL if "extra" keywords are passed into
the program via the -Extra keyword inheritance mechanism. Extra keywords are those
keywords that are not specifically defined for the Init function. I am thinking, in par-
ticular, that users might want to set keywords for the TVImage, and Colorbal- com-
mands that I will eventually use in a display method I still have to write. I want to
have a place to store those keywords. If the extra variable is undefined at this point,
then no extra keywords were used, and sel$extra is a pointer to an undefined variable.
Creating Graphics Display Objects
Recall that this is a valid type of pointer (unlike the null pointer, for example, that is
an invalid pointer), and that it can be dereferenced in the normal way.
If we get to this place in the Irzit function code, we have successfully populated the
object with data. The only thing to do now is return a 1 to indicate success. The code
looks like this:
RETURN, 1
END
Did you get any errors from the Init method? If so, fix them and try again. If some-
thing goes wrong, then your object will be a null reference. For example, if you have
an error in your Iizit method code and the Init method returned a 0 instead of a 1, then
when you perform a Help on the object reference you will see something like this:
IDL> Help, theObject
theobject OBJREF = <Nullobject>
If you created the object successfully, you should see something similar to this:
IDL> Help, theObject
theobject OB JREF = cObjHeapVar4(BOXIMAGE)s
Creating a New Object Class
Remember that Heap-GC is a last ditch solution and you should not use it in your
code, but only from the IDL command line. Well written programs shouldn't leave
anything on the heap to clean up.
There is no need to recreate your object, simply use the last one you created with the
Earth data set and call the Draw method on the object, like this:
Creating Graphics Displny Objects
Figzcre 121: The graphics display after iizvokiizg tlze Boxlinage Draw method.
Prove to yourself that your program is device independent by, for example, sending
the output directly to your default printer. Type these commands:
IDL> thisDevice = !D.Name
IDL> Set-Plot, l PRINTER1, /Copy
IDL> thisobject->Draw, Font=O
IDL> Device, /Close-Document
IDL> Set-Plot, thisDevice
You can find a copy of the BoxInzage object program as it is written so far among the
programs you downloaded to use with this book. You must explicitly compile this file
in order to use it. The file is named boxir?mgedefine.l.pro.
IDLz .Compile boximage-define.l.pro
IDL> scan = LoadData (5)
IDL> thisobject = Obj-New('BoxImagel, scan, Colortable=5, $
XScale= [-l,l] , YScale= [-l,l] )
IDL> thisObject - >Draw
END
In this method, we load color table 0 if a color table index number is not provided.
Otherwise we load the specified color table. Notice how we scale the color vectors
into the number of colors we are using to display the image.
Re-compile your program file, and then t ~ the
y LoadCT method. Type this:
IDL> .Compile boximagedefine.pro
IDL> thisObject- BLoadCT, 3
IDL> thisObject->Draw
Notice how you had to call the Draw method after you changed the color table to see
the new colors in the display window. The LoadCT method simply loads new color
vectors in the object, it doesn't load them in the device color table, nor does it re-dis-
play the graphics.
But since I often want to change a property in my object and see the change go imme-
diately into effect in the graphics window, I like to have the ability to call the Draw
method from the method that is changing the property. I usually do this by defining a
D r a ~keyword
, for the method that is setting the property. Make the changes in bold to
the code you just typed above:
PRO BoxImage::LoadCT, colortable, Drawzdraw
IF N-Elements(colortable) EQ 0 THEN BEGIN
colors = Obj-New(I1IDLgrPaletteM)
colors->LoadCT, 0
colors->GetProperty,Red=r, Green=g, Blue=b
Obj-Destroy, colors
ENDIF ELSE BEGIN
colors = Obj-New ("IDLgrPalettel1)
colors->LoadCT,0 > colortable < 40
colors->GetProperty,Red=r, Green=g, Blue=b
Obj-Destroy, colors
ENDELSE
Creati~tgGraphics Display Objects
While writing a method to set every possible property in the object is possible, it is not
usually done this way. Usually, a generic SetProperty method is written that can
change many properties at the same time via keywords to the method. For example,
the procedure definition statement for such a SetProperty method could be written like
this. Place this code somewhere in front of the BoxZi7zngeDefiile module in the pro-
gram file.
PRO BoxImage::SetProperty, $
Image = image, $
AnnotateColor=annotatecolor, $
BackColor=backcolor, $
ColorTable=colortable, $
Draw=draw, $
NColors=ncolors, $
Position=position, $
Vertical=vertical, $
XScale=xscale, $
XTitle=xtitle, $
YScale=yscale, $
YTitle=ytitle, $
-Extra=extra
In this method we can change the image itself, all the drawing and image colors, the
position of the graphic in the display window, the scales and titles on the axes, etc. We
have also defined a D ~ C Ikeyword,
MJ which will act like the D ~ M J
keyword on the
LondCT method you just created, allowing us to see the effects of these changes to the
object immediately in the display window.
The next step is to write an eiror handler for this method. Add this code to the file.
Catch, theError
IF theError NE 0 THEN BEGIN
Catch, /Cancel
ok = Error-Message(!Error-State.Msg + Returning...',$
Traceback=l, /~rror)
RETURN
ENDIF
Now, we simply set the object properties if the value of the keyword is defined in the
program. The code looks like this:
IF N-Elements(backcolor) NE 0 THEN $
self.backcolor = backcolor
IF N-Elements(annotatecolor) NE 0 THEN $
self.annotatecolor = annotatecolor
IF N-Elements(ncolors) NE 0 THEN self.ncolors = ncolors
IF N-Elements(position) NE 0 THEN self.position = position
IF N-Elements(vertica1) NE 0 THEN self.vertica1 = vertical
Creating a New Object Class
Figure 122: The BoxImage object after the SetProperty method is applied.
You see the annotation color change to the color you selected.
While it is often useful to write get property functions, especially if you plan to use the
return value of the function in an expression (as you did in the code above), it is some-
times more useful to get several properties at once via a generic GetProperty method.
The GetProperty method is analogous to the SetProperty method you just wrote. It is a
procedure and object properties are returned to the user via output keywords. For
example, add this code to your program file somewhere in front of the
BoxIrnageDefirze module in your file.
PRO BoxImage::GetProperty, $
Image = image, $
BackColor=backcolor, $
AnnotateColor=annotatecolor, $
ColorTable=colortable, $
NColors=ncolors, $
Position=position, $
Creatitzg a New Object Class
Catch, theError
IF theError NE 0 THEN BEGIN
Catch, /Cancel
ok = Error-Message(!Error-State.Msg + Returning...', $
Traceback=l , /Error)
RETURN
END IF
; Set properties if keyword is present.
IF Arg-Present(colortab1e) THEN colortable = self.colortable
IF Arg-Present(backco1or) THEN backcolor = self.backcolor
IF Arg-Present(annotateco1or) THEN $
annotatecolor = self.annotatecolor
IF Arg-Present(nco1ors) THEN ncolors = self.ncolors
IF Arg-Present(vertica1) THEN vertical = self.vertica1
IF Arg-Present(xtit1e) THEN xtitle = self.xtitle
IF Arg-Present(ytit1e) THEN ytitle = self.ytitle
IF Arg-Present(xsca1e) THEN xscale = self.xscale
IF Arg-Present(ysca1e) THEN yscale = self.yscale
IF Arg-Present(image) THEN image = *self.image
END
There are two things to notice about this code. First, I am only returning a value if the
user has used the keyword and provided a valid IDL variable to receive the value. This
is the purpose of using AI-gPlsserzt.It is overkill in this case, because normally I
wouldn't care about whether the user actually asked for a value and provided a vari-
able to hold it. I would just assign each keyword variable its appropriate object value.
I do it here only because there are times when providing a value is very expensive in
terms of time or computer memory, and you don't want to go to the trouble unless the
user insists on it. Using Arg-Preserzt is how this functionality is achieved.
The second thing to notice is that I am returning the actual image data to the user who
asks for it with the Z~mzgekeyword. What I could have returned is the pointer to the
image data. In other words, I could have done this:
IF Arg-Present(image) THEN image = self.image
But this completely defeats the spirit of object encapsulation, because now someone
can manipulate the object data from outside the object, rather than go through the
object's methods. So while it is possible to do this, it is almost never a good idea. If
you keep your data encapsulated inside your objects, everyone will be much happier
and your programs are likely to work much better.
Re-compile your program code and test your new GetProperty method. Type this:
IDL> thisobject->GetProperty, NColors=ncolors, XTitle=xtitle
IDL> Print, lNColors: l, ncolors
IDL> Print, 'X Title: l, xtitle
You can find a copy of the BoxZrnnge object program with the changes made so far
among the programs you downloaded to use with this book. The file should be explic-
itly compiled before it is used. It is named boxinzngedefirze.2.pro.
Creating Grclplzics Display Objects
You should see something similar to the illustration in Figure 123 appear on your dis-
play. Select a new annotation color and see what happens to your object display.
Figure 123: The PickColorNaine program called from the AnrzotateColor method.
You can write a similar method (or modify this one if you like) to modify the back-
ground color of the object. Add this method to your program file:
Creating a Nerv Object Class
Notice the -Extra keyword that is defined for this LoadColorVectors method. XCo1oi.s
has the ability to pass "extra" keywords to this method that are collected on the XCol-
ors command line. In this case, we are not expecting any keywords, but the -Extra
keyword must still be defined. If keywords do come into the method, they will be
ignored.
Notice that the color vectors are obtained from the current color table values, and the
object's graphical display is drawn after the color table values have been stored. This
puts the new colors on the display. (This is not strictly required if you are running on
an 8-bit display. Can you modify the routine to reflect this fact?)
Save your program and try using the XColors method. Does it work the way you
expect it to?
You can find a file with the additions and modifications you have made so far among
the program files you downloaded to use with this book. The file is named
boxinzagedefirze. 3.~1-o.
Figure 124: The Boxlmage object after a cozcple of irnage processirzg steps are ap-
plied with the Process method.
You should be back to the unsharp masked image illustration in Figure 124.
You can find a copy of the BoxI~nageobject program with the additions we have
added so far among the programs you downloaded to use with this book. The file is
named boxii~zagedefirze.pla.The complete code is also listed in Appendix C of this
book on page 399.
Object Inheritance
The strongest argument for object-oriented programming methods comes from the
ability to re-use objects that have already been created to create new objects. This is
known as object irzlzeritarzce. If you design objects sensibly, with object inheritance in
mind, then you can find yourself saving a great deal of work as you create new, more
powerful objects out of the objects you have in your personal object library.
An object that is inherited by another object is called the superclass object. The object
that is doing the inheriting is called the subclass object. (I always have to think about
this a minute because I tend to get these names mixed up. It helps if I think of the
object coming first as being above the object that is inheriting it, sending roots down
through it.)
Subclass objects can inherit not only the encapsulated data of a superclass object, but
they can inherit the superclass object's methods as well. This means that if a subclass
object is not too different from a superclass object, then most of its methods will con-
tinue to work without having to make any changes. At most, you may have to create a
few new methods and you may have to modify a few superclass methods. The modifi-
Object Znlzeritnlzce
struct = { HISTOIMAGE, $
INHERITS BoxImage, $
binsize: 0.0, $
max-value: 0.0, $
datacolor: Illr $
1
END
Note the word INHERITS in the second line of the structure definition. Notice there is
no colon, comma, or any other punctuation between the word and the next word that
follows it on the line. The INHERITS word adds the BoxZnznge structure definition in-
line at this location. In other words, it is just as if you had typed the entire structure
definition (all of the structure fields) of BoxIrnnge at this location in the HistoI17znge
structure definition. The INHERITS word can go anywhere in the structure definition
and there can be more than one of them. If there are more than one, then the order in
which they occur becomes important.
Note that the fields in nrzy structure definition in IDL must be unique. The same goes
for the fields defined by an inherited structure. Every field in the structure must be
unique. If you keep this in mind when you are writing objects you believe might be
subclassed, then you will save yourself time and effort latter on.
While the code above is a structure definition (objects are implemented as named
structures), it is more importantly an object class definition. And it is critical to realize
that in an object class definition not only are the superclass object's structure fields
inherited in the subclass definition, but that the superclass object's methods are also
inherited.
Consider, for example, an object, Object4, that was subclassed from three superclass
objects, like this:
PRO Object4-Define
struct = { OBJECT4, $
INHERITS Objectl, $
Creating Grapltics Display Objects
INHERITS Object2, $
INHERITS Object3, $
1
END
If a SupelSize method were called on the object like this:
IDL would look first to see if a method Object4::Szipersize was defined for this object.
If it was not, then it would look to see if a method 0bjectl::Sz~persizewas defined,
since that is the first inherited object. Failing to find such a method there, IDL would
look for a method named Object2::SupelSize, because that was the next object inher-
ited, etc. If Object2 was itself a subclass of Object5, then IDL would search for the
method Object5::Super;Sizebefore it looked for Object3::Supei;Size, since Object5 is
at a higher inherited level than Object3. Only if IDL could not find a SuperSize
method in any of the inherited objects would it cause an error.
In fact, all five objects discussed here could each have its own Supel-Size method
defined for it. IDL would use the first SzrpeidSizemethod it came to as it worked its
way down the list of inherited objects. This ability to supersede a superclass method
by one at a higher inherited level is called rnetlzod oven-iding and is one of the features
that makes object programming so powerful.
So, the general rule is this. Any positional parameters on superclass objects should be
re-declared on the function definition statement of the subclass Iiiit method. Any
superclass keyword parameters can be gathered using the keyword inheritance mecha-
nism provided by the -E,vtrn keyword. (Of course, you can write your objects any way
you like, and ignore any "rule" you like. I sometimes re-define all the keyword param-
eters for the subclass object, simply because it makes the subclass object easier to
document internally.)
The next step is to add error handling and a check for all the defined positional and
keyword parameters. Add these lines to your program code:
Catch, theError
IF theError NE 0 THEN BEGIN
Catch, /cancel
ok = Error-Message(!Error-State.Msg + Returning...', $
Traceback=l, / ~ r r o r )
RETURN, 0
ENDIF
IF N ~ l e m e n t s
(image) EQ 0 THEN image = LoadData (7)
ndims = Size(image, /N-Dimensions)
IF ndims NE 2 THEN $
Message, 'Image must be 2D array.', / ~ o ~ a m e
IF N-Elements(dataco1or) EQ 0 THEN datacolor = "REDH
IF N-Elements(max-value) EQ 0 THEN max-value = 5000.0
IF N-Elements(binsize) EQ 0 THEN BEGIN
range = Max (image) - Min (image)
binsize = 2.0 > (range / 128.0)
ENDIF
You see another reason for having the image in this module: it is needed to calculate
the bin size if a bin size is not supplied to the program.
The next step is to pass on the extra keywords to the BoxIrnnge::Iizit method. You may
recall I said the Init method, as a lifecycle method, cannot be called directly. It can
only be called by IDL when an object is created. This is almost true. There is only one
exception. The Irzit method of an object can be called directly, but only if it is being
called from the Irzit method of a subclassed object.
Recalling that the Irzit method is a function, and that a 0 should be returned if the Irzit
method fails for whatever reason, we call the BoxImnge Init method like this:
IF NOT self-zBoxImage::Init(image, -Extra=extra, $
NColors=!D.Table-Size-4) THEN RETURN, 0
Notice how the name of the superclass object is inserted in front of the method name.
The NColovs keyword is a BoxImnge::Irzit method keyword and is used here to set the
number of colors set aside for the image colors. This is required in this instance
because the default number of image colors, as determined by Boxlrnnge, assumes
only two drawing colors. This Histolrnnge object uses three drawing colors. Without
setting this keyword, the image colors would impinge on the drawing colors. Note that
if the keyword is used with the HistoIrnnge::Iizit method and is present in the extm
structure, its value will override the value set here. This could cause a potential prob-
lem. If you thought the chance of a problem was high enough, you would probably
choose to define an explicit NColors keyword for the HistoImnge::Init method.
If the BoxIinnge::Irzit method is called successfully, most of the self fields have now
been populated correctly with data. It remains only to populate those selffields that
Creating Grapltics Display Objects
are new in the Histolrnnge object. The final lines of code in the I~zirmethod look like
this:
self.max-value = max-value
self.datacolor = datacolor
self.binsize = binsize
RETURN, 1
END
The purpose of the Clenrzup method, of course, is to free pointers and other things that
use memory in the object. Since we have added nothing like that in the Histolrnnge
object, we can continue to use the BoxIrnnge::Clem?up method. We don't have to do
anything special to do so, except just not create a Clearzcrp method for this object. The
inherited BoxIrnnge::Clemz~rppmethod will be found and called automatically when
the Histolrlznge object is destroyed.
PRO Histo1mage::GetProperty
END
PRO Histo1mage::Draw
END
Overriding the SefProperty method is similar to oveniding the Inif method, as we just
did. We need to define keywords to set the new properties, but we can collect all the
old BoxI~nagekeywords with the -Extra keyword inheritance mechanism and pass
these keywords along to the Bo,~I~nage::SetProperty method.
The filled out SetProperty method will look like this, with the additions written in
bold text:
PRO HistoImage::SetProperty, $
Binsize=binsize, $
DataColor=datacolor, $
Max~Value=max~value, $
-Extra=extra
Catch, theError
IF theError NE 0 THEN BEGIN
Catch, /Cancel
ok = Error-Message(!Error-State.Msg + Returning...', $
Traceback=l, /Error)
RETURN
ENDIF
IF N Elements(binsize) NE 0 THEN self.binsize = binsize
IF ~ I ~ l e m e n (datacolor)
ts NE 0 THEN $
self.datacolor = datacolor
IF N-Elements(max-value) NE 0 THEN $
self .max-value = max-value
END
The GetProperty method is similar, but with a small twist. The -Extra keyword inher-
itance mechanism we used in the Setproperty method is a pass by 17alue type of
system. That is to say, the keywords packaged by IDL into the extra variable are
passed to the next routine (the BoxI~nage::SetPropertymethod, in this case) by value
and not by reference. I n other words, a copy of the keyword value is passed to the rou-
tine, rather than the keyword variable itself.
This is not a problem when you are accepting input keywords, but it becomes a serious
deficiency when you are trying to pass output keywords into a superclass method and
obtain information back from the method. Practically speaking, this is because you
can't get any information back from a pass by value keyword. (See "Passing Informa-
tion by Reference or b y Value" on page 217 for additional information.) The only
thing to do, is to use the -Ref-Extra keyword inheritance mechanism, which passes
the keywords by reference, rather than by value. (See "Using Keyword Inheritance
with Output Parameters" on page 219 for additional information.)
The GetProperty method looks like this. Add the code in bold below to the GetProp-
erty stub code you wrote earlier.
PRO HistoImage::GetProperty, $
Binsize=binsize, $
DataColor=datacolor, $
Max-Value=max-value, $
-Ref-Extra-extra
Creating Grapltics Display Objects
Catch, theError
IF theError NE 0 THEN BEGIN
Catch, /Cancel
ok = Error-Message(!Error-State.Msg + Returning...', $
Traceback=l, /Error)
RETURN
ENDIF
IF Arg-Present(binsize) NE 0 THEN binsize = self.binsize
IF Arg-Present(dataco1or) NE 0 THEN $
datacolor = self.datacolor
IF Arg-Present(max value) NE 0 THEN $
max-value = self.max-value
END
Note the way the -Ref-Extra keyword works. We have to use a -Ref-Extra keyword
to collect the keywords in this method. But once the keywords are collected, they are
passed along to the BoxIinage::GetProperty method using the -Extra mechanism.
The final (and most important) method to override for the Histohlznge object is the
Di-a~,method. It is the way the graphics are displayed that is the primary difference
between the Bo,~Iinageand Histolinage objects. But I would like to delay writing the
Draw method for just a couple of minutes more. I wish to talk about another property
of objects: polyinorphisin. And that property is easily illustrated in the way we will
write the Draw method. For now, let's move on to the one new method we need to
write for the Histoln~ageobject, the Datacolor method.
Object Polymorphism
I think the word polymorplzism has frightened more people away from objects than
any other of the big words tossed around by the object-oriented crowd to protect their
turf. Most people are quite disappointed to learn that it doesn't mean anything exotic.
It simply means that a single method is able to do different things in different ways
without the user of the method knowing (or caring, generally) how it happens.
Object Zizlzeritnnce
For example, sending graphics output to the display, to a file, and to a printer are three
different ways to display graphics output. But these different functions can be incor-
porated into a single D m v method. That is the essence of object polymorphism.
We will incorporate object polyn~orphisminto the Drmv method of the HistoIl~zage
object by writing it in such a way as to be able to output the graphics display to a win-
dow on the display device (or to a draw widget, for that matter), to a BMP, GIF, JPEG,
PICT, PNG, TIFF, or Postscript file, and directly to the default printer.
Replace the HistoI~nage::Drawmethod code stub you wrote earlier with this code.
PRO HistoImage::Draw, $
Font=font, $
BMP=bmp, $
GIF=gif , $
JPEG=jpeg, $
PICT=pict, $
PNG=png, $
TIFF=Tiff, $
PostScript=postscript, $
ps=ps, $
Printer=printer, $
Extra=extra
The For~tkeyword will allow the user to specify a different type of font (e.g., hard-
ware or true-type) for output display. The other keywords allow the user to select a
different kind of file or printer output. If these keywords are not set, the output is sent
to the current graphics device, as normal. Notice that both a PostScript and a PS key-
word are defined. We will write the code so that the user can use either of these two
keywords to select Postscript file output. The -Extra keyword will pick up other key-
words that we might want to use to configure programs like PSCor?fig or PSWilzdow,
o r the Z graphics buffer, which we will be using in the program.
Next, add the error handling code. This is standard operating procedure by now.
Catch, theError
IF theError NE 0 THEN BEGIN
Catch, /cancel
ok = Error-Message(!Error-State.Msg + Returning...', $
Traceback=l , /Error)
RETURN
ENDIF
The next step is to check the value of the keywords. We will use the file output display
keywords to set the value of an output variable. This will mean that only one of these
keywords can be active at any one time. The code will look like this:
IF N-Elements(font) EQ 0 THEN font = !P.Font
output = " "
IF Keyword-Set(bmp) THEN output = ' B M P 1
IF Keyword-Set(gif) THEN output = 'GIF1
IF Keyword-Set (jpeg) THEN output = ' JPEG1
IF Keyword-Set(pict) THEN output = 'PICT1
IF Keyword-Set(png) THEN output = 'PNG'
IF Keyword-Set(tiff) THEN output = 'TIFF'
IF Keyword-Set(postscript) THEN output = 'PS'
IF Keyword-Set (ps) THEN output = ' PS '
IF Keyword-Set(printer) THEN output = 'PRINTER'
Creating Graphics Display Objects
If the output variable is not a null string, then we are going to switch to another graph-
ics device to display program output. We should save the current device name so we
can restore it later. Type this command:
IF Output NE THEN this~evice= !D.Name
The next step is to perfornl different setup operations, depending upon the value of the
oz~tputvariable. The Case statement outline allowing us to do this will look like this:
CASE output OF
" " : BEGIN
END
PS : BEGIN
END
"PRINTER": BEGIN
END
ELSE: BEGIN
END
ENDCASE
Consider first the case when the output variable is a null string. This indicates that the
object's graphics output will be sent to the display. The only preparation required in
this case is to load the drawing and image colors. The code (in bold) looks like this:
"'l : BEGIN
annotatecolor = GetColor(self.annotatecolor, $
!D.Table -Size-2)
backcolor = GetColor(self.backColor, $
!D.Table Size-3)
datacolor = GetColor(self.dataColor, $
!D.Table-Size-4)
TVLCT, *self.r, *self.g, *self.b
END
If the output variable is set to indicate PostScript output, then we should configure the
PostScript device according to the users wishes. We can do this with the PSColTfig
program you downloaded to use with this book. Moreover, we will want to make sure
we are selecting proper colors for PostScript output. We will select colors here that
will be appropriate for either black and white, or color PostScript output. (I am mak-
ing arbitrary decisions about these things, based on the kinds of printers I have in my
office and am likely to print the output on. Your decisions should be based on your
own local situation. You may not change the objects drawing colors at all, for exam-
ple, if you always send output to a color PostScript printer.) The code will look like
this:
PS : BEGIN
keywords = PSConfig(Color=l, -Extra=extra, $
Filename=ghistoimage.ps', Cancel=cancelled)
IF cancelled THEN RETURN ELSE keywords.color = 1
Set-Plot, 'PS1
Device, -Extra=keywords
annotatecolor = G e t C ~ l o r ( ~ N a v y ~!D.Table
, -Size-2)
backcolor = GetColor('Whitel, 1D.Table-Size-3)
datacolor = GetC~lor(~Blackl,!D.Table-Size-4)
TVLCT, *self.r, *self.g, *self.b
END
Object Irtlzeritaitce
Notice that I set the Color keyword in PSCorlfig to start with. And I make sure it is set
again before I pass the keywords to the Postscript device. This is because I will not be
able to get accurate Postscript output of my graphical display unless this keyword is
set. Once in the PostSc~iptdevice, I load my drawing and image colors.
The next case deals with the Printel- device. Getting reliable output to the Prirzter
device is always tricky. There are so many different kinds of printers, with so many
piinter drivers, etc. Sometimes I think it is remarkable that we can get any output,
period.
One of the interesting things I have learned about the Prilzter device is that I almost
always have better luck loading colors outside the device and copying the colors to the
device than I do loading the colors once I am inside the device. I don't know why this
is. Nothing in the IDL documentation suggests this should be an issue, but whenever I
have problems getting output to show up on the printed page it involves some kind of
a color loading problem. (And there are known bugs in the Printer device with respect
to loading color tables. See "Loading Colors in the Printer Device" on page 204 for
additional information.)
So, with this in mind, I will write the Printer device part of the code like this:
"PRINTER": BEGIN
ok = Dialog-Printersetup ( )
IF NOT ok THEN RETURN
annotatecolor = G e t C ~ l o r ( ~ B l a c k !D.Table
~, Size-2)
backcolor = GetColor(~Charcoall,!D.Table-Size-3)
datacolor = GetColor ( l Black1, !D.Table-Size-4)
TVLCT, *self.r, *self.g, *self.b
keywords = PS~indow(/Printer,/~andscape, $
Fudgez0.25, Extra=extra)
set-plot, l PRINTER' , /copy
Device, Landscapes1
Device, -Extra=extra
thisThickness = !P. Thick
thisFont = !P.Font
font = 1
!P.Thick = 2
END
There are several things to notice in this code. First, I allow the user access to the
Printer device dialog. This will allow the user to select a different networked printer,
etc. If the user cancels out of this dialog, I will exit this method. Second, I load the
drawing and image colors before I make the Printer device the current graphics
device. Third, I am going to make a window on the Prirzter device with the current
window's aspect ratio. I am arbitrarily selecting landscape output and I am not offer-
ing the user a choice about this. (Such choices could easily be built into the object
itself, of course.)
Notice that I set the Lnndscizpe keyword on the Device command independently of
setting the other keywords. I've found this is the only reliable way to make sure the
offsets and sizes returned from the PSWilldow function (which you downloaded to use
with this book) are recognized by the Printer device. Notice, too, that I have set a
Fudge factor of 0.25 inches. This corresponds to the printable area on my printer
where the offset point is calculated. Your printer may use the corner of the actual page
for calculating offsets, or you may need other fudge factors than these. You will have
to determine this empirically from your printer output. (See "Positioning Graphics
with the Printer Device" on page 202 for additional information.)
Creating Graphics Display Objects
Fourth, I change the default font to true-type output and I set the thickness of all plot-
ted lines to double thickness. I do this because single lines on my 600 dpi printer are
usually too thin to show up well.
The final case involves any other kind of file output. In such a situation, rather than
drawing the graphics output into a window, I am going to draw the output into the Z-
graphics buffer. This way I will be able to take a snapshot or screen dump of the Z-
buffer and make the proper type of output file with the snapshot. The code for this
case will look like this:
ELSE: BEGIN
ncolors = !D.Table-Size
Set-Plot, ' Z '
Device, Set-Resolution=[500, 5001, $
Set~Colors=ncolors,_Extra=extra
Erase
annotatecolor = GetColor(self.annotatecolor, $
ncolors-2)
backcolor = GetColor(self.backColor, ncolors-3)
datacolor = GetColor(self.dataColor, ncolors-4)
TVLCT, *self.r, *self.g, *self.b
END
ENDCASE
Notice that this kind of file output will always result in a 500 by 500 image. Also, I
will be using the number of colors available in the current graphics device (or 256,
'whichever is smaller), rather than the number of colors available in the Z-graphics
buffer. This will result in graphics output that is identical (essentially) to the output I
see on the display, no matter what depth of graphics display I have. The Erase com-
mand erases the display, so there are no left-over graphics in the Z-buffer.
The next step in creating the D m w method is to calculate positions for the image,
color bar, and histogram plot from the overall position of the output in the object. This
will be different if we have a vertical color bar as opposed to a horizontal one. The
code looks like this:
I F self.vertica1 THEN B E G I N
p = self.position
length = p[2] - p [O]
imgpos = [p101 , p [l1 , (p[Ol + (0.75*length)) , $
p [3]- (length*O.350)1
cbpos = [p121-0.05, p[ll, p [21 , p[31- (length*0.35)1
hpos = [p[Ol , imgpos L31 +O.1, P [21, P [311
E N D I F ELSE B E G I N
p = self.position
height = p [3] - p [l]
imgpos = [p [O], p [l], p [2], p [l]+ 0.4*heightl
cbpos = [p [O], imgpos [3] height*^. 1, p [2], $
imgpos [3]+height*O .l51
hpos = [PLO], cbpos[3l+height*O.125, p[21, p[311
ENDELSE
Next, I would like the character size in the graphics output to vary depending upon the
size of the output window. I can use the Str-Size program you downloaded to use with
this book to calculate a character size for me. The only time I don't want a variable
string size, is when I am sending output to the Printer device. The code will look like
this:
Object Irtheritcrrzce
/No~ata,Ticklen=-0.025,-Extra=*self.extra, $
~harsize=this~harSize, Font=font
Colorbar, Range= [Min(*self.process) , Max (*self.process)1 , $
Divisions=8, -Extra=*self.extra, Color=annotateColor, $
Position=cbpos, Ticklen=-0.2,Vertical=self.vertical, $
NColors=self.ncolors, Charsize=thisCharSize, Font=font
The only thing that remains to be done is to clean up based on the value of the outpzrt
variable. This involves closing files, writing file output, and making sure we restore
any system variables or graphics output devices, if they have changed. We can use the
TVRend program you downloaded to use with this book to take a screen dump of the
Z-graphics buffer and write the appropriate output file. The code looks like this:
CASE output OF
I1BMPn: image = T V R ~ ~ ~ ( / B M P Filename=Ihistoimage1)
,
I1GIFI1: image = T V R ~ ~ ~ ( / G I FFilename='histoimagel)
,
"JPEGl1:image = T V R ~ ~ ~ ( / J P EFilename=lhistoimagel)
G,
"PNGII: image = T V R ~ ~ ~ ( / P N G Filename=lhistoimagel)
,
"PICTU:image = T V R ~ ~ ~ ( / P I CFilename=lhistoimagel)
T,
I1TIFFu:image = T V R ~ ~ ~ ( / T I FFilename=Ihistoimage1)
F,
I1PSn:Device, Close-File=l
"PRINTERI1: BEGIN
Device, Close-Document=l
!P.Thick = thisThickness
!P.Font - thisFont
END
ELSE :
ENDCASE
IF output NE I l l 1 THEN Set-Plot, thisDevice
END
Can you make a JPEG file from this output? Type this command to exercise the
object's polymorphism:
Figure 125: The graphics display of the HistoZmage object Draw method.
When you are finished testing the object, be sure to destroy it, like this:
IDL> Obj-Destroy, thisobject
Appendix A: Widget Event Structures
The Tag field contains the tag name of the field that changed. The Value field contains
the new value of the changed field. The Quit field contains a zero if the quit flag is not
set, or one if it is set.
The Value field is the value of the slider. The Drag field indicates (with a one) that the
events are updated continuously (as the user drags), or only when the user releases the
slider (with a zero).
The Value field is either the Index, ID, Name, or Full-Nanze of the button, depending
on how the widget was created.
The R, G, and B fields contain the red, green, and blue value of the selected color
triple, respectively.
XColors sends a widget event to widgets identified with the NotifyID keyword. The R,
G, and B fields contain the culrent red, green, and blue color table vectors,
respectively. The Irzdes field will be set to a -1 or to the index number of the currently
loaded color table after a color table has been loaded. The Nnnze field contains the
name of the selected color table.
Here is a descriptive list of all the data files used with this book. The files can be found
in the IDL distribution in the !DZWee~nn~ples/nt directory. The files can also be
copied from their location in the IDL distribution to a specified directory. See
"Copying the Data Files" on page 5 for additional information.
Table 16: These are the data files that shozcld be dowrtloaded or copied for use
with this book. Use the CopyDataprogranz to copy the data files from
the IDL distribution to your local directory.
Appendix C
PRO Box1mage::LoadColorVectors
; Get the current color vectors.
TVLCT, r, g, b, /Get
; Pull out the image colors.
*self.r = r [O: self .ncolors-l]
*self.g = g[O:self.ncolors-l]
*self.b = b[O:self.ncolors-l]
; Redraw the image.
self->Draw
END
PRO BoxImage::GetProperty, $
Image = image, $
BackColor=backcolor, $
AnnotateColor=annotatecolor, $
ColorTable=colortable, $
NColors=ncolors, $
Position=position, $
Vertical=vertical, $
XScale=xscale, $
XTitle=xtitle, $
YScale=yscale, $
YTitle=ytitle
; This method gets the properties of the object
; Error handling.
Catch, theError
IF theError NE 0 THEN BEGIN
Catch, /Cancel
ok = Error-Message(!Error-State.Msg + Returning...', $
Traceback=l, /Error)
RETURN
END1F
; Set properties if keyword is present.
IF Arg-Present(colortab1e) THEN colortable = self.colortable
IF Arg-Present(backco1or) THEN backcolor = self.backcolor
IF Arg-Present(annotateco1or) THEN annotatecolor = self.annotatecolor
IF Arg-Present(nco1ors) THEN ncolors = self.ncolors
IF Arg-Present(vertica1) THEN vertical = self.vertica1
IF Arg-Present(xtit1e) THEN xtitle = self.xtitle
IF Arg-Present(ytit1e) THEN ytitle = self.ytitle
IF Arg-Present(xsca1e) THEN xscale = self.xscale
IF Arg-Present(ysca1e) THEN yscale = self.yscale
IF Arg-Present(image) THEN image = *self.image
END
FUNCTION BoxImage::GetAnnotationColor
; This method returns the annotation color.
Appendix C: IDL Progimn Code
RETURN, self.annotatecolor
END
; Error handling.
Catch, theError
IF theError NE 0 THEN BEGIN
Catch, /Cancel
ok = Error-Message(!Error-State.Msg + ' Returning...',$
Traceback=l, /Error)
RETURN
ENDIF
; Set properties if keyword is present.
N-Elements(backcolor) NE 0 THEN self.backcolor = backcolor
N-Elements(annotatecolor) NE 0 THEN self.annotatecolor = annotatecolor
N-Elements(ncolors) NE 0 THEN self.ncolors = ncolors
N-Elements(position) NE 0 THEN self.position = position
N-Elements (vertical) NE 0 THEN self .vertical = vertical
N-Elements(xtit1e) NE 0 THEN self.xtitle = xtitle
N Elements(ytit1e) NE 0 THEN self .ytitle = ytitle
~I~lement (xscale)
s NE 0 THEN self .xscale = xscale
N-Elements (yscale) NE 0 THEN self.yscale = yscale
N-Elements(extra) NE 0 THEN *self.extra = extra
N-Elements (image) NE 0 THEN BEGIN
*self.image = image
*self.process = image
*self.undo = image
END IF
IF N-Elements(co1ortable) NE 0 THEN BEGIN
colors = Obj-New ( "IDLgrPaletteH)
colors->LoadCT,0 > colortable < 40
colors->GetProperty,Red=r, Green=g, Blue=b
Obj-Destroy, colors
*self .r = Congrid(r, self .ncolors)
*self.g = Congrid(g, self.ncolors)
*self.b = Congrid(b, self.ncolors)
ENDIF
IF Keyword-Set(draw) THEN self->Draw
END
; Error handling
Catch, theError
IF theError NE 0 THEN BEGIN
Catch, /Cancel
ok = Error-Message(!Error-State.Msg + Returning...', $
Traceback=l, /Error)
RETURN, 0
ENDIF
; Check for positional parameter. Define if necessary
IF N-~lements(image) EQ 0 THEN image = LoadData (7)
ndims = Size(image, /N-~imensions)
IF ndims NE 2 THEN Message, 'Image must be 2D array.', / ~ o ~ a m e
; Check for keyword parameters
IF N-Elements(annotatecolor) EQ 0 THEN annotatecolor = "NAVY1'
IF N-Elements(backcolor) EQ 0 THEN backcolor = "WHITEu
IF N-Elements(ncolors) EQ 0 THEN ncolors = !D.Table-Size - 3
IF ~ ~ ~ l e m e (position)
nts EQ 0 THEN position = L0.15, 0.15, 0.9, 0.91
vertical = Keyword-Set (vertical)
IF N-Elements (xtitle) EQ 0 THEN xtitle =
IF N-Elements (ytitle) EQ 0 THEN ytitle = " "
S = Size (image, /Dimensions)
IF N-Elements (xscale) EQ 0 THEN xscale = [O,s [Oil
IF N-~lements(ysca1e)EQ 0 THEN yscale = [O,S[l]1
IF N-Elements(co1ortable) EQ 0 THEN BEGIN
colors = Obj-New ( "IDLgrPaletteH)
colors->LoadCT, 0
colors-zGetProperty, Red=r, Green=g, Blue=b
Obj-Destroy, colors
ENDIF ELSE BEGIN
colors = Obj-New ( "IDLgrPaletteN)
colors->LoadCT, 0 > colortable 40
BoxZ11zage-Define Object Progrant
PRO Box1mage::Cleanup
; The clean-up routine for the object. Free all
; pointers.
Ptr-Free, self.image
Ptr-Free, self.process
Ptr-Free, self.undo
Ptr-Free, se1f.r
Ptr-Free, self.g
Ptr-Free, self.b
Ptr-Free, self.extra
END
PRO BoxImage-Define
; The definition of the BOXIMAGE object class.
struct = { BOXIMAGE, $ ; The BOXIMAGE object class.
image: Ptr-New 0 , $ ; The original image data.
process: Ptr-New(), $ ; The processed image data.
undo: Ptr-New(), $ ; The previous processed image data.
position: FltArr(4), $ ; The position of the graphics output in window.
r: Ptr-New(), $ ; The red color vector associated with image colors.
g: Ptr-New(), $ ; The green color vector associated with image colors.
b: Ptr-New(), $ ; The blue color vector associated with image colors.
ncolors: OL, $ ; The number of image colors.
annotatecolor: $ ; The name of the annotation color.
backcolor : " , $ ; The name of the background color.
xscale: FltArr (2), $ ; The scale for the X axis of the image plot.
yscale: FltArr (2), $ ; The scale for the Y axis of the image plot.
Appendix C: ZDL Progranz Code
HDFRead Program
This is an example program for reading an HDF file that is created with HDFWrite. It demonstrates how to find
and access both the data and the data attributes in an HDF file. Additional information about HDF files can be
found in "Reading and Writing HDF Data" on page 161. The file can be downloaded from the coyote anony-
mous ftp site. The URL is:
HDFWrite Program
This is an example program for writing an HDF file that can be read with HDFRend. It demonstrates how to
create SDS data sets and both data and file attributes in an HDF file. Additional information about HDF files
can be found in "Reading and Writing HDF Data" on page 161. The file can be downloaded from the coyote
anonymous ftp site. The URL is:
lat = X * (24./l. 0) + 24
lon = y * 50.0/1.0 - 122
temp = distribution(x*40, y*40) * 273
; Select the name of a new file and open it
IF N-ELEMENTS(fi1ename) EQ 0 THEN $
filename = DIALOG-PICKFILE(/W~~~~, File='example.hdfl)
IF filename EQ THEN RETURN
PRINT, ' '
PRINT, 'Opening HDF file " ' + filename + ' l 1 . . . '
; If there is a problem opening the file, catch the error
CATCH, error
IF error NE 0 THEN BEGIN
PRINT, ' '
PRINT, 'Unable to obtain an SDS file ID.'
PRINT, 'This file may already be open by an HDF routine.'
PRINT, 'Returning...l
PRINT, ' '
RETURN
ENDIF
fileID = HDF-SD-START(filename, /CREATE)
CATCH, /CANCEL
; Write some attributes into the file.
PRINT, 'Writing file attributes . . . l
version = 'MacOS 4.0.1b1
date = I1Jan 1, 199711
experiment = "Experiment 25A36MI1
name = 'David Fanning'
email = '[email protected]
HDF-SD-ATTRSET, fileID, 'VERSION', version
HDF-SD-ATTRSET, fileID, 'DATE', date
HDF-SD-ATTRSET, fileID, 'EXPERIMENT',experiment
HDF-SD-ATTRSET, fileID, NAME ' , name
HDF-SD-ATTRSET, fileID, 'EMAIL ADDRESS', email
; Create the SDS data sets for the raw data.
PRINT, 'Writing raw data . . . l
latsdsID = HDF-SD-CREATE ( fileID, "Raw Latitude", [4OL] , /FLOAT)
lonsdsID = HDF-SD-CREATE(fileID, "Raw Longitudeu, [40L], /FLOAT)
tempsdsID = HDF-SD-CREATE ( fileID, "Raw Temperature1',[40L], /FLOAT)
; Write the raw data.
HDF-SD-ADDDATA, latsdsID, lat
HDF-SD-ADDDATA, lonsdsID, lon
HDF-SD-ADDDATA, tempsdsID, temp
; Terminate access to the raw data SDSs.
HDF-SD-ENDACCESS, latsdsID
HDF-SD-ENDACCESS, lonsdsID
HDF-SD-ENDACCESS, tempsdsID
; Grid the irregularly spaced, raw data.
latMax = 49.0
latMin = 24.0
lonMax = -67.0
lonMin = -125.0
mapBounds = [lonMin, latMin, lonMax, latMax]
mapspacing = [O.5, 0.251
TRIANGULATE, lon, lat, FVALUE=temp, SPHERE=triangles, /DEGREES
gsidData = TRIGRID(temp, SPHERE=triangles, /DEGREES, $
/EXTRAPOLATE, mapspacing, mapBounds)
; Calculate vectors corresponding to gridded data.
S = SIZE(gridData)
gridlon = FINDGEN(s (l)) * ( (lonMax - lowin)/ (S(l)-l) ) + lonMin
gridlat = FINDGEN (S(2)) * ( (latMax - lat~in)
/ (S(2)-l)) + latMin
; Now store the gridded data.
PRINT, 'Writing gridded data . . . l
Histolmage Program
This is an example of a graphics display program. It is described in detail in the section "The HistoImage Pro-
gram" on page 240. The source code for this program can be downloaded from the Coyote's Guide to IDL Pro-
grammiizg anonymous ftp site. The URL is:
Appendix C: IDL Program Code
Histo-GUI Program
This is an example of a widget program. It is the final Histo-GUI program that is described in the chapters
Wr-itirzga Widget Program, Widget Progmmming Techrziques, and the first part of Creatirtg Dialog Form Wid-
gets. The source code for this program can be downloaded from the coyote anonymous ftp site. The URL is:
*info.process = newimage
*info.undo = newimage
; No way to UNDO. Turn undo button off.
Widget-Control, info.undoID, Sensitive=O
; Redisplay the image data.
WSet, info.pixID
HistoImage, *info.process, $
AxisColorName=info.axisColorName, $
BackColorName=info.backcolorName, $
Binsize=info.binsize, $
DataColorName=info.datacolorName, $
Extra=*info.extra, $
Max-Value=info.max-value, $
NoLoadCT=l, $
XScale=info.xscale, $
YScale=info.yscale
WSet, info.wid
Device, Copy= [ O , 0, !D.X-Size, !D.Y-Size, 0, 0, info.pixID]
Widget-Control, event.top, Set-Walue=info, /NO-copy
END
; This event handler changes the drawing colors of the graphic display
Widget-Control, event.top, Get-Walue=info, /No-Copy
; Which color are we changing? The button W A L U E will tell us.
Widget-Control, event.id, Get-Walue=buttonWalue
; Change it by calling the modal dialog PickColorName.
CASE buttonWalue OF
'ANNOTATION': BEGIN
colorname = PickColorName(info.axisColorName,$
Cancel=cancelled, Group-Leader=event.top, $
Title='Select Annotation Color', $
Index=!D.Table-Size-2, Bottom=!D.Table-Size-21)
IF NOT cancelled THEN info.axisColorName = colorname
END
'DATA': BEGIN
colorname = PickColorName(info.dataColorName, $
Cancel=cancelled, Group-Leader=event.top, $
Title='Select Data Color', $
Index=!D.Table-Size-3, Bottom=!D.Table-Size-21)
IF NOT cancelled THEN info.dataColorName = colorname
END
'BACKGROUND': BEGIN
colorname = PickColorName(info.backColorName,$
Cancel=cancelled, Group-Leader=event.top, $
Title='Select Background Color', $
Index=!D.Table-Size-4, Bottom=!D.Table-Size-21)
IF NOT cancelled THEN info.backColorName = colorname
END
ENDCASE
; Retrieve the new color table. The keyboard focus events will redraw the
; graphics display.
TVLCT, r, g, b, /Get
Binsize=info.binsize, $
DataColorName=info.datacolorName, $
-Extra=*info.extra, $
Max~Value=info.max~value, $
NoLoadCT=l, $
XScale=info.xscale, $
YScale=info.yscale
Widget-Control, event.top, Set-Walue=info, /No-Copy
WSet , in£o.wid
Device, Copy= [ O , 0 , !D.X-Size, !D.Y-Size, 0 , 0 , info.pixID]
END
; Copy the graphical output from the pixmap to the display window.
WSet, wid
Device, Copy= [O, 0, !D.X-Size, !D.Y-Size, 0, 0, pixID]
; Obtain the current RGB color vectors.
TVLCT, r, g, b, /Get
; Create the info structure with the information
; required to run the program.
info = { image:Ptr-New(image), $ A pointer to the image data.
process : Ptr-New (image), $ A pointer to the process image.
undo:Ptr-New(image), $ A pointer to last processed image.
undoID:undoID, $ The identifier of the UNDO button.
axisColorName:axisColorName, The name of the axis color.
backColorName:backcolorName, The name of the background color.
binsize:binsize, $ The histogram bin size.
dataColorName:datacolorName, The name of the data color.
imageColors:imagecolors, $ The number of colors used for image.
max~value:max~value, $ The maximum value of histogram plot.
xscale:xscale, $ The X scale of the image axis.
yscale:yscale, $ The Y scale of the image axis.
title:title, $ The window title.
extra:Ptr-New (extra), $ A pointer to ltextralt
keywords.
Histolinage-Define Object Progrant
; The
R color vector.
; G color vector.
The
; B color vector.
The
; The
identifier of the draw widget.
; The
index number of graphics window.
; The index number of the pixmap window
; Store the info structure in the user value of the TLB. Turn keyboard
; focus events on.
Widget-Control, tlb, Set-Walue=info, /No-copy, / ~ ~ ~ ~ - F o c u s - ~ v e n t s
; Set up the event loop. Register the program with the window manager.
XManager, 'histo-guil,tlb, Event-Handler=lHisto-GUI-TLB-Eventsl, $
/No-Block, Clean~p=~Histo-GUI-Cleanup', Group-Leader=group-leader
END
; Error handling.
Catch, theError
IF theError NE 0 THEN BEGIN
Catch, /Cancel
ok = Error-Message(!Error-State.Msg + ' Returning...', $
Traceback=l, /Error)
RETURN
ENDIF
; Set properties if keyword is present.
IF Arg-Present(binsize) NE 0 THEN binsize = self.binsize
IF Arg-Present(dataco1or) NE 0 THEN datacolor = self.datacolor
IF Arg-Present(max-value) NE 0 THEN max-value = self.max-value
; Pass extra keywords along to the BoxImage superclass.
self->BoxImage::GetProperty, -Extra=extra
END
; Error handling.
Catch, theError
IF theError NE 0 THEN BEGIN
Catch, /Cancel
ok = Error-Message(!Error-State.Msg + Returning...',$
Traceback=l, /Error)
RETURN
ENDIF
; Check keywords
HistoZ11tageDej1teObject Progrant
FUNCTION HistoImage::Init, $
image, $
Binsize=binsize, $ ; The bin size of the histogram.
DataColor=datacolor, $ ; The data color.
Max-Value=max-value, $ ; The maximum value of the histogram plot
-Extra=extra ; Holds extra keywords.
; The initialization routine for the object. Create the
; particular instance of the object class.
; Error handling.
Catch, theError
IF theError NE 0 THEN BEGIN
Catch, /Cancel
ok = Error-Message(!Error-State.Msg + ' Returning...', $
Traceback=l, /Error)
RETURN, 0
ENDIF
; Check for positional parameter. Define if necessary.
IF N-Elements(image) EQ 0 THEN image = LoadData(7)
ndims = Size(image, /N-Dimensions)
IF ndims NE 2 THEN Message, 'Image must be 2D array.', /NoName
; Check for keyword parameters
IF N-Elements(dataco1or) EQ 0 THEN datacolor = "REDu
IF N-~lements(max-value) EQ 0 THEN max-value = 5000.0
IF N Elements(binsize) EQ 0 THEN BEGIN
range = Max (image) - Min (image)
binsize = 2.0 > (range / 128.0)
ENDIF
; Initialize the BoxImage superclass object.
IF NOT self->BoxImage::Init(image,
-Extra=extra, NColors=!D.Table-Size-4) THEN RETURN, 0
; Populate the rest of the self object
self.max-value = max-value
self.datacolor = datacolor
self.binsize = binsize
Appendix C: ZDL Progra~~t
Code
RETURN, 1
END
PRO HistoImage-Define
; The definition of the HISTOIMAGE object class
struct = { HISTOIMAGE, $ ; The HISTOIMAGE object class.
INHERITS BoxImage, $ ; Inherit the BoxImage object class.
binsize: 0.0, $ ; The histogram bin size.
max-value: 0.0, $ ; The maximum value of the histogram plot.
datacolor : l 1 $ ; The data color name.
1
END
Openlmage Program
This is an example of a modal or blocking dialog form widget program. Additional information about this pro-
gram can be found in "Creating a Modal Dialog Form Widget" on page 325. The source code for this program
can be downloaded from the coyote anonymous ftp site. The URL is:
Pro OpenImage-~rowseFiles,event
filename = Dialog-Pickfile(Filter='*.datt)
IF filename EQ " l 1 THEN RETURN
; Update file name text widget.
Widget-Control, event.top, Get-Walue=info, /No-Copy
info.fileID->Set-Value, filename
Widget-Control, event.top, Set-Walue=info, /NO-copy
END
Function OpenImage, $
Filename=filename, $ Initial name of file to open.
;
Group-Leader=group-leader, $Group leader of this program.
;
XSize=xsize, $ Initial X size of file to open.
;
YSize=ysize, $ ; Initial Y size of file to open.
Cancel=cancel ; An output cancel flag.
; Make a button base with frame to hold CANCEL and ACCEPT buttons.
butbase = Widget-Base(tlb, Row=l)
cancel = Widget-Button (butbase, Value= ' Cancel ' )
accept = Widget-Button(butbase, Value='AcceptT)
; Center the program on the display
screensize = Get-Screen-Size()
geom = idg get-Info(tlb, /~eometry)
Widget-Control, tlb, $
XOffset = (screenSize[Ol / 2) - (geom.scr-xsize / 2), $
YOffset = (screensize[l] / 2) - (geom.scr-ysize / 2)
; Realize top-level base and all of its children.
Widget-Control, tlb, /Realize
; Create a pointer. This will point to the location where the
; information collected from the user will be stored. You must
; store it external to the widget program, since the program
; will be destroyed no matter which button is selected. Fill the
; pointer with NULL values.
ptr = PtrNew({~ilename:'' , Cancel:l, XSize:O, YSize:O})
; Create info structure to hold information needed in event handler.
info = { fileID:fileID, $ ; Identifier of widget holding filename.
xsizeID:xsizeID, $ ; Identifier of widget holding xsize.
ysizeID:ysizeID, $ ; Identifier of widget holding ysize.
ptr:ptr } ; Pointer to file information storage location.
; Store the info structure in the top-level base
Widget-Control, tlb, Set-Walue=info, /No-Cop~
; Register the program, set up event loop. Make this program a
; blocking widget. This will allow the program to also be called
; from IDL command line without a GROUP-LEADER parameter. The program
; blocks here until the entire program is destroyed.
XManager, 'openimage', tlb, E v e n t ~ H a n d l e r = l O p e n I m a g e ~ E v e n t ~ ~
; OK, widget is destroyed. Go get the file information in the pointer
; location, free up pointer memory, and return the file information.
fileInfo = *ptr
Ptr-Free, ptr
; Set the Cancel flag
cancel = fileInfo.cance1
; Return the file information.
RETURN, fileInfo
END
Readlmage Program
This is an example of a non-modal dialog form widget program. Additional information about this program can
be found in "Creating a Non-Modal Widget Dialog" on page 340. The source code for this program can be
downloaded from the coyote anonymous ftp site. The URL is:
ftp://ftp.dfanning.com/pub/dfanning/outgoing/coyote2nd/readimage.pro
PRO ReadImage, $
notifyIDs, $ ; A vector of widgets and their TLBs to notify.
Filename=£ilename, $ ; Initial name of file to open.
Group-Leader=group-leader, $ ; Group leader of this program.
XSize=xsize, $ ; Initial X size of file to open.
YSize=ysize ; Initial Y size of file to open.
; This is a pop-up dialog widget to collect the filename and
; file sizes from the user. The widget is non-modal.
; Only one READIMAGE program at a time.
IF XRegistered('readimage9) NE 0 THEN RETURN
On-Error, 2 ; Return to caller.
; Check parameters and keywords.
IF N-Elements(notify1Ds) EQ 0 THEN Message, 'Notification IDs are a required parameter.'
IF N-Elements(fi1ename) EQ 0 THEN $
filename=Filepath(SubDirectory=['examples~,~data~],~ctscan.dat~)
IF N-Elements (xsize) EQ 0 THEN xsize = 256
IF N-Elements(ysize) EQ 0 THEN ysize = 256
; Create a top-level base.
tlb = Widget-Base(Column=l, Title='Enter File Information . . . l , /~ase-~lign-center)
; Make sub-bases.
subbase = Widget-Base (tlb, Column=l, Frame=l)
filebase = Widget-Base (subbase, Row=l)
; Create widgets for filename. Set text widget size appropriately.
filesize = StrLen(fi1ename) * 1.25
fileID = FSC-InputField(filebase, Title='Filenarne:',Value=filename, $
XSize=filesize, LabelSize=50, /Stringvalue)
browseID = Widget-Button(filebase, Value='Browsel,Event-Pro='ReadImage-BrowseFilesl)
xsizeID = FSC-InputField(subbase, Title='X Size:', $
Value=xsize, /Integervalue, LabelSize=50, Digits=4)
ysizeID = FSC-InputField(subbase, Title='Y Size:', $
Value=ysize, /Integervalue, LabelSize=50, Digits=4)
; Set up Tabing between fields
; Make a button base with frame to hold DISMISS and APPLY buttons.
butbase = Widget-Base(tlb, Row=l)
dismissLD = Widget-Button(butbase, Value='Dismissl)
applyID = Widget-Button(butbase, Value='Applyi)
; Center the program on the display
screensize = et-Screen-Size()
geom = midget-Info(tlb, /Geometry)
Widget-Control, tlb, $
XOff set = (screensize[O] / 2) - (geom.scr-xsize / 2), $
YOffset = (screensize[l] / 2) - (geom.scr-ysize / 2)
; Realize top-level base and all of its children.
Widget-Control, tlb, /Realize
; Create info structure to hold information needed in event handler.
info = { notifyIDs:notifyIDs, $ ; The list of widgets to notify.
fileID:fileID, $ ; Identifier of widget holding filename.
xsizeID :xsizeID, $ ; Identifier of widget holding xsize.
ysize1D:ysizeID $ ; Identifier of widget holding ysize.
1
; Store the info structure in the top-level base
Widget-Control, tlb, Set-Walue=info, /NO-Copy
; Register the program, set up event loop. Make this program a
; non-blocking widget.
XManager, 'readimage',tlb, Event-Handler='ReadImage-Events', $
/No-Block, Group-Leader=group-leader
END
Index
A base widget event structure 389
AddPntlz command 5 base widgets
animating data 102, 104 floating 329
annotating plots 52 modal 328
Arg-Present comnland 220 batch files
arguments. See parameters 211 commands in 207
array subscripting 222 BEGIN ...END statement blocks 224
arrays 9 blocking widgets 326, 331
changing size of 68 box
creating 12 drawing 111
gridding 106 rubberband 118
resizing 68 box axes 26
square bracket subscripting 13, 223 BoxIllmgeDefilze object program code 399
ASCII-Tenzplnte command 139 BREAK statement 227
aspect ratio buffering 287
of images 71 Butterworth frequency filter 79
of windows 185 button widget event structure 389, 390, 391, 392
Assoc command 146 BytScl comnland 62
associated variables 145
advantages of 146
defining 146 capitalizing commands 3
axes CASE statement 227
3D 99 Cntch command 230
adding to plot 29 Catch error handling 230, 242
annotating 92 character size
box style 26 changing 247
multiple on same plot 29 setting 22
setting style of 26 ClzalSize keyword 22, 247
table of values for Style keywords 26 circle
axes range creating in IDL 57
setting 25 Cleanup method of object 356, 360
setting exact range 25 Cleanup routines
axis in widget programs 282
setting tick intervals on 92 code See program modules 209
Asis command 29 color 86
dynamic displays 83
on surface plots 32
background color setting on line plots 24
setting 24 static displays 83
base widget color aware programs 284
top-level base 264 color bar 251
creating 58, 70
color decomposition 2 executive 209
for image display 66 journal of 8
on or off? 86 multiple 224
color displays multiple on same line 224
depth of 83 on-line help for 8
dynamic 83 saving 8
static 83 conunent character 3
type of 83 comments
color palettes in IDL code 3
in HDF files 173 common blocks
color Postscript output 182 protecting in widget programs 308
color tables .Conzpile command 235
automatic update of 66 compiler options
changing 89 long integers 13
creating 89 square bracket array subscripting 13, 223
editing 63 compiling IDL programs 235
multiple 63 from within a procedure 237
on 24-bit displays 64 rules for automatic compilation 236
on 24-bit display 88 compound widget event structures 393
saving 91 conditional expressions 226
updating with XColors 66 Corzgrid command 68
updating with XLondCT 66 CONTINUE statement 227
color visual classes 83 Corztozir command 36
Color24 command 86 contour plots 36
Colorbar command 58, 98, 251 adding color to 41
colors 63, 64 algorithms for drawing 38
color models in IDL 81 customizing 39
editing color tables 63 downhill direction of 41
gray-scale only 2 filled contours 42
in object programs 370 on map projections 44
in Postscript files 189 in 3D space 42, 102
in widget programs 301 labeling contour levels 39
Indexed Color Model 82 missing data in 96
loading drawing colors 87 positioning in window 44
not displaying 2 selecting contour intervals 38
notifying objects of change in 371 selecting line styles of 39
number in IDL session 60 setting line thickness 40
obtaining 24-bit value 86 contours
obtaining by name 87, 244 downhill direction 41
obtaining device-independent colors 87 drawing in color 41
obtaining the current color table vectors 89 filled 42
on 24-bit display 85, 88 labeling 38
on plots 24 selecting intervals 38
otaining the current color vectors 285 selecting line styles of 39
protecting in widget programs 284 setting thickness of lines 40
required to work with this book 2 Corzve~~-Coord command 58
RGB color model 85 convex hull 107
selecting by name 302 Co~~l>ol command 76
selecting name of 370 convolution kernels 76
column formatted files 137 convolution of images 76
comma separated files 141 coordinate system
command continuation character 4 data 52
commands 207 device 52
anatomy of 6 normalized 52
as functions 7 coordinate systems 19
as procedures 7 converting from one to another 58
capitalizing 3 coordinates
collecting in journal 123 converting from one system to another 58
continuing on next line 4 Copy keyword 116
CopjlDam comnland 5 Directcolor visual class 83
web page 6
Co.yote's Guide to IDL Progrc~~ninii~g directory
crashes home 4
recovering from 283 directory name
current graphics window 109 selecting 131
cursor 109 documentation
behavior 110 on-line 8
drawing a box with 111 double buffering 287
for annotating plots 111 Draw method of objects 362
positioning in window 110 draw widget event structure 390
with images 112 draw widgets
Cursor command 109 creating 265
in draw widgets 265 making current window 266
CW-Field conlmand 329 using C~trsorcommand in 265
compound widgets value of 266
329 window index number of 266
drawing color
setting 24
!D.N-Colors system variable 60 droplist widget event structure 390
data dynamic color displays 83
encapsulation 350
formatted 133
gridding 106 edge enhancement
missing 95 of images 77
not a number 96 encapsulated Postscript output 181
range of 25 encapsulated Postscript preview 182
types of 10 encapsulation
unformatted 142 in objects 350
data animation 102 Erase command 18
data coordinate system 52 in Postscript file 180
converting to device 58 erasing display window 60, 114
data files 4 erasing graphics windows 18
column format 137, 139 error condition
copying from IDL distribution 5 generating 212
downloading 5 error handing
installing 5 with Catch 358
locating 130 error handling 212, 229
reading ASCII data 140 example of catching error 231
reading with associated variables 145 generating the error condition 233
selecting 130 hierarchy of handling 231
selection 130 in programs 242
skipping records in 134 tracing the error 234
template for reading 139 with Catch 230
unformatted 142 with Error-Message 234
decomposition on or off 86 with 011-Error 229
Delaunay triangulation 106 with 011-IQError 229
Device command 116, 176 Erro~Messagecommand 234, 242
device coordinate system 52 errors
device copy method of erasing 114, 116, 287 file 110 229
DialogPickJiIe command 130 generating 233
Dinlog-Printe~set~cpcommand 321 generating error messages 233
DialogPri~~terSetlcpp command 176, 200 handling with Error-Message 234
dialogs program 230
modal 327 recovering from 230, 283
non-modal 340 reporting 232
DICOM files tracing 234
reading 157 event driven programs 260
using the IDLffrDICOM object 157
event handler file pointers 134
assigning to top-level base 265 Filepath conlrnand 131
event liaiidler module 260 files
event handler modules help with 133
example for Quit button 279 locating 130, 131
example for resizeable graphics window 280 logical unit numbers of 131
writing 276 opening for reading 129
event handlers opening for updating 129
as functions 278 opening for writing 129
assigning to the top-level base 279 selecting 130
assigning to widgets 279 selecting names of
naming 279 used with this book 4
event loop 261 files See nlso data files
creating 275 filled contours 42
event structure filtering
for base widget 389 of images 78
for button widget 389, 390, 391, 392 filters
for compound widgets 393 building image filters 78
for draw widget 390 Findfile command 131
for droplist widget 390 Flontiizg keyword 329
for keyboard focus events 395 floating widgets 329
for kill widget events 395 Follolcl keyword 38
for label widget 390 fonts
for list widget 390 hardware 52
for slider widget 391 Hershey 187
for table widget 391 names of available hardware fonts 52
for text widget 392 names of available true-type fonts 52
for timer events 395 names of available vector fonts 52
for tracking events 395 Postscript l87
event structures 261 rotating 53
fields in 277 selecting 53
Handler field 277 table of 53
ID field 277 table of Hershey to PostScript 189
Top field 277 true-type 51
Ei~eizt-Haizdler keyword 265 FOR loops 226
events Fomlat keyword 140
creating pseudo events 342 format specifiers 140
sending events to widgets 344 formatted data
sending from other programs 306 explicitly formatted 140
widget 261 reading 133
exchrsive OR method of erasing 114 writing 133
executing in batch mode 207 Foiward-Fzinctio~~command 223
executive commands 209 Free-Llrn command 132
explicit file formats 140 frequency domain filtering 78
-Extra keyword 379 FSC-PSColzjig object 350
FSC-Wii~dow command 256
ftp
false condition in IDL 223 downloading book files 5
Fanning Software Consulting functions
contacting 6 calling 9
Fast Fourier transform 78 declaring for compiler 223
FFT command 78 writing 221
file headers 137, 144
file V 0 errors 229 G
file names Get-Lull command 132
constructing 131 Get-Visual-Depth keyword 83
specifying in device-independent way 131 Get-Vislral-Name keyword 83
file path 131 GerColor command 87, 244
GIF file output 312, 315 selecting graphics device 175
GIF files 147 specifying sizes of 178
color 147 hardware fonts 52
creating 150 names of 52
license required 150 HDF data 161
writing 150, 151, 154 description of data objects 164
Go executive command 209 self-describing format 162
GOT0 statement 229 tags 163
graphic margin 44 types of data objects 163
graphic position 44 HDF data files
graphic region 44 adding color palettes to 173
graphics closing 166
buffering display of 287 closing SDS files 169
device copy method of erasing 114 creating a new SDS 169
in resizeable windows 256 creating attributes 170
object 19 creating SDS files 169
positioning in window 246 defining attributes for 167
raster 19 dimension scales 167
graphics device number of tags in 166
an X Windows display (X) 176 opening 165
CGM 176 opening SDS files 169
default setup 176 predefined attributes of 167
hardcopy output 175 scientific data sets in 166
HPGL plotter 176 selecting SDS files 169
PCL printer 176 table of routines for 168
Postscript l76 HDFRend program code 406
graphics devices HDFWrite program code 407
CGM 176 headers 137, 144
HP 176 heap variables
MAC 176 pointers 268
PCL 176 Heap-GC 271
PRINTER 176 help
PS 176 contacting the author 6
WIN 176 6
CO-yote'sGuide to IDL Proggi.nmr?~irtg
X 176 heap variables 351
Z 176 on-line 8
graphics display 287 with files 133
graphics function 11 4 with objects 351
graphics window Hershey fonts 53, 187
in widget programs 265 hierarchical data format See HDF data
pixmap 11 6 high-pass filter 79
graphics window. See window Hist-Eqrml command 74
grid lines on plots 27 Histogrnrn command 248
gridding histogram equalization 74
Delaunay triangulation method 106 histogram plots
spherical 108 displaying 248
gridding data 106 Histolri~ngeprogram 240
group leaders hourglass cursor
in widget programs 310 setting 321
I
hardcopy output 175 IDL
closing the file 177 required version 2
closing the printer document 177 IDL code
controlling the device 176 supplied with the book 261
landscape mode 178 IDL commands. See commands
portrait mode 178 IDL home directory 4
selecting a file name 177
IDL programs JPEG files 147
source code of 399 color 154
IDL source code 399 creating 154
IF statements 225 reading 155
IF...THEN...ELSE statements 225 writing 155
image data 59
scaling 62 K
image processing 74
Keep-Aspect-Ratio keyword 71
filtering 78
keyboard focus event structure 395
histogram equalization 74
keyboard focus events
in objects 372
in widget programs 285
smoothing 75
keyword inheritance 216, 219
image registration 123
keyword parameters 7
images
keyword parameters. See also parameters
(0,O) point in 64
Ke)i,~~orrl_Setcommand 215
24-bit images 64
keywords
24-bit on 8-bit display 65
as optional parameters 214
band interleaved 64
checking for 215, 242, 243
changing size of 68
defined? 214
colors in PostScript 183
defining
convolution of 76
inheritance 216
display order 68
inherited 219
displayed in Postscript files 193
passing to other commands 216
displaying 60
present? 220
displaying 8-bit on 24-bit display 66
used? 214
edge enhancement 77
with binary properties 215
filters for 78
kill widget event structure 395
in 24-bit environment 64
pixel interleaved 64
positioning in window 69
PostScript 183 label widget event structure 390
reading from display 72 labels
removing noise from 77 on contour plots 39
row interleaved 64 lifecycle methods of objects 361
scaling 62 line plots 20
sizing in Postscript files 69, 194 annotating 52
true-color 64, 183 axes style 26
upside down 68 drawing lines on 56
using cursor with 112 drawing symbols on 56
warping 123 establishing another axis on 29
Indexed color model 81 filling with color 57
irzfo structure grids on 27
creating 267 limiting data range on 25
inheritance line styles for 22
in objects 374 margin around 46
inheriting object methods 375 missing data in 96
inheriting structures 375 multiple data sets on 28
Init method of object 356 multiple in window 47
initialization plotting symbols 23
of objects 361 position of 46
integers region of 47
forcing four-byte integers 13 using color with 24
line plots See also plots
line styles
selecting 22
Joztmal command 8
table of 22
journal file 123
line symbols 23
journal of IDL commands 8
line thickness
JPEG file output 312, 315
setting 22
Index
his is the book that will put IDL in context for you and
I wish someone had given nle when I was learning to use IDL,
ISBN 0-9662383-2-X