TCL (Tool Command Language) is a scripting language
developed for scientific and engineering applications. It is
implemented in the command interpreter program wish along with the TK (Tool Kit) graphical interface. TK provides a
collection of commands so that a script application can use graphical interface
items such as buttons and menus. There
are many extensions that can be combined with TCL/TK. One such extension is the BLT toolkit, which
provides commands for creating X/Y graphs and barcharts.
There are numerous web sites
and books which offer detailed tutorials and guides for the use of TCL/TK. Currently, some sites which provide documentation
of the standard TCL/TK and BLT commands are:
TCL Commands: http://www.tcl.tk/man/tcl8.4/TclCmd/contents.htm
TK
Commands: http://www.tcl.tk/man/tcl8.4/TkCmd/contents.htm
BLT Commands: http://tcltk.free.fr/blt/BLT.html
A major component of the NMRPipe system is the program called nmrWish
which is our customized version of the TCL/TK command
interpreter wish. It includes all of
the standard commands which form TCL, TK, and BLT, as referenced above. It also includes more than 150 additional
commands for manipulating NMR spectra and parameters, performing spectral
graphics, manipulating text tables such as peak tables and chemical shift
assignments, and for manipulating PDB files and
molecular structures.
As such, nmrWish
can be used to create most any type of custom automated or interactive NMR
application. And, many applications
which are part of the NMRPipe system have been
implemented in this way, including TALOS and ACME.
The program nmrWish is an interpreter, much like the C-shell program csh. Like the
program csh, nmrWish
can be used to create a command-line where commands can be typed on the
keyboard, one line at a time. As an
example, consider the following simple interactive session with the C-shell,
which includes the following steps:
The session above might look
something like this
in this example, the text typed
by the user is given in red type:
/u/frank% csh
% echo hello world
hello world
% exit
/u/frank%
We can perform a similar set
of steps with the nmrWish command-interpreter. In this case, the following steps would be
used:
/u/frank% nmrWish
% puts "hello world"
hello world
% exit
/u/frank%
Interestingly, we can use
the same simple approach, this time with TK commands,
to create a simple one-button graphical interface which displays a
message. Surprisingly, these two simple TK
commands do the work of a 150+ line C program using the X11 graphics library
alone (such an example C/X11 program is shown at the
link here). The steps for building this simple
graphical interface using TK are as follows:
When TCL
commands are invoked interactively at the command-line, many of them print a return
value after they are executed. In this
example at the nmrWish command-line, the button command prints its return value, which in this case
is the name of the button created, .b
the pack command has no return value, so no text is printed
after it is executed at the command-line:
/u/frank% nmrWish
% button .b -text "hello
world" -command exit
.b
% pack .b
(click button with mouse)
/u/frank%
Any of the nmrWish commands, including the standard TCL, TK, and BLT commands, can be
used from the command-prompt. And, you
can use the TCL command info commands to generate a list of all the standard and custom nmrWish built-in commands which are available:
/u/frank% nmrWish
% info commands
dynSetOmega vsFix
vRMS vPDF subst modelXY pwd
beep ...
A Quick Overview of TCL
TCL is a text-based language. This means that the
contents of variables are generally stored and manipulated as text, even when
they are numerical values. TCL commands have a simple fixed syntax, but can be
formatted freely. This is discussed in
more detail below.
Since TCL
variables are text-based, they can be used to store many types of information,
both numerical and otherwise. TCL variables are assigned a value using the set command. When
used interactively at the command-line, the set command prints the value that
was assigned. The value assigned to a variable is extracted by substitution,
whenever the variable name is prefixed by the $ dollar-sign. For example, at the nmrWish
command line:
% set r 1.6
1.6
% puts "The value of r
is $r"
The value of r is 1.6
% set r 1.600
1.600
% puts "The value of r
is $r"
The value of r is 1.600
% set r Frank
Frank
% puts "The value of r
is $r"
The value of r is Frank
Note again that TCL variables are set and retrieved as text values. So, the expression set r 1.600 really means set variable r to the 5-character
text 1.600
this fact must be kept in mind when performing
numerical calculations or comparisons, as will be discussed later.
TCL substitutes variable values before executing a
command. So, in TCL,
it is possible to use a variable's value as a command:
% set cmnd
expr
expr
% set val "2 + 2"
2 + 2
% $cmnd
$val
4
Numerical expressions are
evaluated using the TCL command expr, which can evaluate algebraic expressions, comparisons,
trig functions, exponents, etc. The expr command will also distinguish between values that
take the form of integers, and treat them accordingly in expressions. The details are similar to numerical
expressions in the C programming language, and can be found in standard TCL documentation. So, as in C, a value of 0 (zero) is
considered "false" in a logical operation, and any non-zero value is
considered "true". For
example, at the nmrWish command line:
% expr 1 + 1
2
% expr 1.0 + 1
2.0
% expr cos(3.141592)
-1.0
% expr 3.14 + (1.0/3)*exp(0.5)
3.6895737569
% expr 5 > 2
1
% expr 5 < 2
0
Some TCL
commands operate on variables directly, assuming that they contain numerical
values. For example, the TCL command incr will increment the value of a variable by one. It requires that the current text value of
the variable is an integer. For example,
at the nmrWish command line:
% set i
0
0
% incr i
1
% incr i
2
% puts "Value of i is $i"
The value of i is 2
The TCL
[ ] square brackets are used to substitute the result of the enclosed TCL command within another command. For example, at the nmrWish
command line:
% set r 1.6
1.6
%
puts "Radius: $r Area: [expr 3.141*$r*$r]"
Radius: 1.6 Area: 8.04096
A TCL
command enclosed in [ ] square brackets can act as a single argument. So, the following TCL
command is correct, since puts expects one argument,
and the command within [ ] acts as a single argument:
% puts [expr
3.14*1.6*1.6]
The \ backslash character is
used to suppress the special meaning of the character that follows it. For example, we can use it to suppress the
variable substitution performed by $ dollar-sign: At the nmrWish
command line:
% puts "An example TCL command is: puts \$r"
An example TCL command
is: puts $r
An important use of the \
backslash is to allow a command to continue on multiple lines, much like in the
C-shell. In these cases, the backslash
is being used to suppress the special meaning of the hidden newline character, which marks the end of a command
line. So, the backslash must be used as
the last character on the line; it will
not work to continue the command if there are any trailing whitespace characters after the backslash. At the nmrWish
command-line:
% puts \
"This long puts command was written on 2 lines"
This is long puts command was written on 2 lines
Text in " " double
quotes can not be continued on
multiple lines using the \ backslash character within the double-quotes. But, commands within [ ] square brackets can
be continued on multiple lines using the \ backslash character within the
square brackets.
Like the common UNIX shells,
TCL uses the hash sign # at the start of a line to
mark that line as a comment. But, unlike
the UNIX C-shell, bourne shell, and bash shell, the \
backslash character at the end of a comment line will continue the comment onto
the next line. For example, at the nmrWish command line:
% # this is a comment \
and this is still
part of the same comment
%
A key aspect of TCL syntax involves grouping together collections of expressions
or commands to form individual arguments.
As we have already seen, the double-quotes
can be used to group together whitespace-separated
items into a single argument. The [ ] square
brackets group the items in a TCL command into a
single argument, which is substituted by the return value of that command.
In addition to grouping via
" " double-quotes and [ ] square brackets, TCL
can group items using { } curly brackets, which have two special
behaviors. First, the { } curly brackets
suppress variable substitutions by $ dollar sign and also suppress command
substitutions by [ ] square brackets.
Second, the { } curly brackets can be used to group multiple lines into
a single argument, without \ backslash line-continuation. For example, at the nmrWish
command line:
% set r 1.6
% set s 5.0
% puts $r
1.6
%
puts "$r $s"
1.6
5.0
%
puts [expr $r + $s]
6.6
%
puts [expr \
$r + $s]
6.6
%
puts {$r $s}
$r
$s
%
puts {[expr $r + $s]}
[expr
$r + $s]
% puts {
this is line one
and this is line
2}
this is line 1
and this is line 2
%
In summary, the key special
characters in TCL syntax are as follows:
\ backslash Takes
away the special meaning of the following
character.
$ dollar
sign Substitute the value
of the following variable.
double quotes Groups together items, with $ and [ ] substitution;
cant be continued on multiple lines using \ backslash.
[
] square braces Substitute the results of the
enclosed TCL command;
can be continued on multiple lines using \ backslash.
{
} curly braces Groups together items and
multiple lines, without
$ and [ ] variable and
command substitution.
# hash sign At
the start of a line, introduces a comment;
can be continued on multiple lines using \ backslash.
%
set days "Mon Tue Wed Thu Fri Sat Sun"
Mon
Tue Wed Thu Fri Sat Sun
%
llength $days
7
%
lindex $days 0
Mon
%
lindex $days 1
Tue
%
lrange $days 3 5
Thu
Fri Sat
%
lrange $days 5 end
Sat
Sun
%
lsearch $days Wed
2
The variables we have seen
so far are examples of TCL "scalar"
variables, which store a single text value.
The text value might be a single numeric item, or a whitespace-separated
collection of items of any length. TCL also includes associative
arrays which allow many values to be assigned to a variable according to an
array index. In the case of many
programming languages such as C, an array index will be one or more integer
numbers. However in TCL,
the array index can be any text value. This is a powerful feature for organizing
data, and it also allows us to simulate the use of conventional
multidimensional arrays in TCL.
TCL array variables are specified by an array name and
an array index in ( ) parenthesis. As
noted, the array index can be any text value, numeric or otherwise. The assigned value can also be any text
value, numeric or otherwise. For
example:
set monthInfo(1)
Jan
set monthInfo(2)
Feb
set dayInfo(30)
"Sep Apr Jun Nov"
set dayInfo(31)
"Jan Mar May Jul Aug Oct Dec"
set dayInfo(28)
"Feb"
set mass(H) 1.0
set mass(He) 4.0
set mass(C) 13.0
Values of an array variable
are extracted using $ dollar-sign substitution, as with scalar variables. The interesting detail is that the array index
can also be specified in terms of a TCL
expression. So, given the definitions
above, from the nmrWish command line:
% puts $dayInfo(30)
Sep Apr Jun Nov
% puts $monthInfo([expr 1 + 1])
Feb
% set atom He
He
% puts $mass($atom)
4.0
To simulate a conventional
multidimensional array, we can use a TCL array index
composed of one or more integers, usually separated by a character such as the
"," comma. For example, we can
build an array index which simulates a three-dimensional matrix location, by
using three integer values to create the index:
set ix 0
set iy
0
set iz
0
set a($ix,$iy,$iz)
1.14
Note again that the TCL array index is interpreted as an exact text value, not as a numeric one. So, space characters, decimal points, etc.,
change the interpretation of the array index.
In the example above, the array index is the exact five-character text
value "0,0,0"
for another example, the following cases
all refer to different array
elements:
$m(1)
$m(1.0)
$m(1.00)
$m( 1)
$m(001)
The standard TCL command "info exists"
can be used to test whether or not a given array value has been defined.
As we have seen, in its
simplest form, the TCL command puts takes a single
argument, which is the text to print.
This could be a single item like a substituted variable. As we have also seen, it can also be a series
of items grouped together with double quotes " ", curly brackets { },
or square brackets [ ].
So for example, this is
wrong:
% puts hn n co
This is a corrected version,
with the text items grouped together with double-quotes:
% puts "hn n co"
TCL commands have a simple, fixed syntax. But, because of the various ways that items
can be grouped together, and because of the ability to continue a command over
more than one line, there are often many possible ways to format a given TCL command.
Consider the TCL "for" command,
which is used to perform a for-loop. The
TCL "for"
command takes four arguments:
for initializeArg conditionArg incrementArg
bodyArg
where
initializeArg is a collection of one or more commands that is
executed before the loop begins.
conditionArg is a TCL "expr" format expression which is evaluated as true
or false at the start of every loop iteration.
The loop iterations will stop if the expression evaluates to false.
bodyArg is a collection of one or more commands that will be
executed during one iteration of the loop
incrementArg is a collection of one or more commands executed at
the end of each iteration, before the loop is repeated.
Consider this simple for-loop,
which can be written on one line, with each of the four arguments grouped with
{ } curly brackets:
for {set i 0} {$i < 5} {incr i} {puts $i}
The same loop can also be
formatted over multiple lines, which in many cases will make it clearer to read
and adjust:
for {set i 0} {$i < 5} {incr i} \
{
puts $i
}
In TCL,
a for-loop can always be re-written as a while-loop. The syntax of a while loop is a simplified
version of the for-loop, with two of the same kinds of arguments as in the
for-loop:
while conditionArg bodyArg
So, in this case, the
for-loop above can be written as:
set i
0
while {$i
< 5} \
{
puts $i
incr i
}
Both the for-loop and
while-loop above will produce the same output shown here, and in both cases,
the final value of variable "i" will be 5 after the loop is finished:
0
1
2
3
4
TCL includes the foreach-loop
for iterating over the items in a list.
The loop will have as many iterations as there
are values in the list. At the start of each iteration, a loop variable will be set according to
the next value in the list. The syntax
of the foreach-loop is:
foreach variableNameArg listArg bodyArg
where
variableNameArg defines the name of a TCL
variable which will be updated with the next list item value at each iteration
in the loop.
listArg is a list of items for generating the loop; each
item in the list will generate one iteration of the loop.
bodyArg is a collection of one or more commands which will
be executed during each iteration of the loop.
For example the following TCL code
set mass(H) 1.0
set mass(He) 4.0
set mass(C) 13.0
foreach atom {H He C} \
{
puts
"The mass of $atom is $mass($atom)"
}
will produce this output:
The mass of H is 1.0
The mass of He is 4.0
The mass of C is 13.0
Other versions of the foreach loop above are this:
foreach atom "H He C" \
{
puts
"The mass of $atom is $mass($atom)"
}
and this:
set
atomList "H He C"
foreach atom $atomList \
{
puts
"The mass of $atom is $mass($atom)"
}
The TCL
for-loop, while-loop, and foreach-loop can all be
controlled with the standard TCL commands "continue" and "break"
used in the body of the loop. The "continue" command will jump immediately to the next
iteration of the loop without executing any of the following commands in the
loop body, if any. The "break" command will exit the loop completely without
executing any other parts of the loop.
In its simplest form, the TCL "if"
command takes two arguments:
if conditionArg bodyArg
where
conditionArg is a TCL "expr" format expression which is evaluated as true
or false.
bodyArg is a collection of one or more commands that will be
executed if the conditionArg of the loop evaluates to
true (non-zero).
The TCL
"if" command also has forms which accommodate any number of "elseif" branchs, and one
possible "else" option, which must be the last branch if it is used.
We have already seen
conditional arguments used as parts of for-loops and while-loops. We discuss them in more detail here.
A TCL
"expr" format expression can contain
comparison operations such as:
== Equal to
> Greater
than
>= Greater
than or equal to
!= Not equal
to
etc
since TCL is a text-based
language, these comparison functions will operate on both text data and numerical data.
In the case of text data, the comparison functions test whether two text
strings are identical on a character-by-character basis, or whether one text
string is alphabetically greater than or less that the other. TCL will attempt to choose automatically
whether to compare items numerically or as text. In some special cases, TCL
might mistake text values for numerical values, and perform a comparison in an
unintended way. Also, in TCL there are no strict data types which differentiate
integers from floating point values, and the results of calculations are stored
as text and subject to rounding. So, the values produced by TCL
text-based numerical calculations may be different than expected.
When comparing
text, always use specific string comparison functions rather than the generic
comparison functions like "==" and "<". TCL has built-in
functions such as "string compare", and nmrWish has implementations of the C-style string
comparison functions such as "strcmp" and "strcasecmp"
Do not assume
that the result of a numerical calculation or function will be an integer,
unless the the calculation is arranged specifically
so that it always returns an integer.
Take care when
comparing floating point values, since rounding errors can effect
results.
Creating TCL
Scripts
As with the C-shell and the
other UNIX scripting languages, TCL commands can be
entered into a text file, which can then be executed at the UNIX command
line. In practice, this is the most
common way that TCL is used, and it is also the way
that custom applications are constructed using nmrWish.
UNIX is designed
specifically to allow the execution of scripts.
When a script is executed, the UNIX operating system will read the first
line of the script to determine which interpreter program should be used to
execute the contents of the script. UNIX
expects that this definition begins with the two characters "#!" (hash sign and exclamation point) as the very first
characters in the file, followed by the exact location of the interpreter
program. On a typical UNIX system, the
C-shell interpreter program "csh" is stored
in the "/bin" directory. So,
as NMRPipe-users will know, all C-shell scripts begin
with this line:
#!/bin/csh
In the case of an nmrWish script, we don't know ahead of time where the
"nmrWish" program might be installed on a
given system. We can assume that it is
available somewhere on the user's executable environment variable PATH, which
lists all of the directories that should be searched in order to find and
execute a command or script.
To accommodate this
situation, so that an nmrWish script will run
regardless of where the "nmrWish" program
is installed, we use a recipe which
first starts the script using the UNIX shell "/bin/sh". All nmrWish scripts will begin with the formula:
#!/bin/sh
# The next line restarts
using nmrWish \
exec nmrWish
"$0" -- "$@"
The formula depends on the
difference between comments in the shell and TCL. Heres how it works:
Now, we are ready to build
an nmrWish script by inserting TCL
commands into a text file. This will
usually be done via a plain-text editor such as the UNIX command "vi"
or the graphical editor "nedit". Note that word processing programs such as MS
Word are usually not suitable for
creating scripts, since they will often put "invisible" formatting
characters in the text.
Here is our first example
script, which we can save in the file called "button.tcl"
in most cases, we will create scripts with the ".tcl"
extension, although this is not a requirement.
File "button.tcl":
#!/bin/sh
# The next line restarts
using nmrWish \
exec nmrWish
"$0" -- "$@"
button .b -text "Hello
World" -command exit
pack .b
Once the script file is
created, we can adjust its UNIX permissions so that it can be executed, and
then we can execute it by typing its name at the UNIX command line:
/u/frank% chmod a+x button.tcl
/u/frank% button.tcl
TCL scripts can include definitions for new commands
which act like the existing built-in commands.
These are called procedures,
and they are defined with the TCL "proc" command.
Like a built-in function, a TCL procedure can
accept arguments, and can produce a return value. TCL procedures
provide a way to organize components of a script into smaller, well-defined
tasks. For example, if we wanted to
build a script with would ask for a list of numbers and report the average value, we might build a procedure that computes the average
of a list of numbers as one component of the script.
The syntax of a TCL procedure is:
proc procedureName argumentArg bodyArg
where
procedureName is name used to invoke the procedure.
argumentArg is the list of variable names which represent the
required arguments to the procedure, if any.
The procedure will receive copies
of any argument data; this means that the procedure can change the values in
its arguments without affecting that part of the script where the procedure was
invoked (the "caller" of the procedure).
bodyArg is the collection of commands which will be executed
when the procedure is invoked. By
default, any variables used or created in the procedure body will be
"local" to that procedure; this means that the procedure's variables
can not be seen or manipulated outside of the procedure. It also means that variables used elsewhere
in the script can not be seen or manipulated inside the procedure.
A TCL
procedure can use the "return" command in its body to stop execution
of the procedure and continue execution of the script at the point where the
procedure was invoked. The
"return" command can also be given an argument, which will be the
return value that the procedure produces.
For example, the following TCL procedure will compute and return the average of a list
of numbers provided as its argument. There
are some "fine points":
It will return
0.0 if the input list is empty, to avoid division by zero. Note the use of the idiom "!$n" which means "return true if variable n is
zero".
The initial
value of the summation for computing the average is 0.0 (floating point
zero). Also, the final division to
compute the average uses the double() floating-point
conversion function. So, the result from
this procedure will always be computed as a floating point value, even if all
the items in the input list are integers.
There are also some
weaknesses in this version:
It will not test
whether all of the items in the input list are valid numbers.
It will not test
for possible (but unlikely) overflow of the computation during formation of the
sum.
Here is the procedure:
proc getAvg
{ rList } \
{
set n [llength $rList]
if {!$n} {return 0.0}
set avg 0.0
foreach r $rList
\
{
set avg [expr
$avg + $r]
}
set avg [expr
$avg/double($n)]
return $avg
}
Here are examples of how the
getAvg procedure could be used:
set a [getAvg "1.0 2.0 3.0"]
set aList "1.0 4.0 12.0 13.0"
set b [getAvg $aList]
if {[getAvg $aList] > 5.0} {puts
"Avg is greater than 5"}
As noted above, by default,
variables defined inside a procedure are "local" to that
procedure. And, the argument variables
of the procedure contain local "copies" of the argument data used to
invoke the procedure. These local
variables can not be seen elsewhere in the script, and they only exist while
the procedure is being executed. In the
"getAvg" procedure
above, the local variables created in the procedure are "n" "r" and
"avg".
TCL provides two commands to change the scope of a
variable inside a procedure, the "global"
command and the "upvar"
command.
The "global"
command is used to declare that one or more variables are not local to any
procedure. The value of a global
variable will persist until it is explicitly changed. So, a variable which is declared with the
"global" command can be seen and changed from inside any procedure
which also declares that variable with the "global" command. Variables which are defined outside of any
procedure are automatically global by default.
The TCL
"global" command declares that references to a given
variable should be global rather than local.
However, the "global" command does not
create or set the variable
this must be done by other means, most commonly by
the TCL "set"
command.
For example, here is an
adjusted version of our averaging procedure which saves the input list length
in the global variable "currentLength" so that other parts of the script can access
this information after "getAvgN" is called:
proc getAvgN
{ rList } \
{
global currentLength
set currentLength [llength $rList]
if {!$currentLength} {return 0.0}
set avg 0.0
foreach r $rList
\
{
set avg [expr
$avg + $r]
}
set avg [expr
$avg/double($currentLength)]
return $avg
}
Then, this adjusted version
"getAvgN" could be
used elsewhere as follows:
global currentLength
set thisList
"1.0 2.0 3.0"
set a [getAvgN
$thisList]
puts "List: $thisList Length: $currentLength Avg: $a"
We can also use global
variables as an alternative to procedure arguments. For example, we can make a version of our
averaging application which assumes that the input list is stored in a global
variable called "currentList":
proc getCurrentAvg
{ } \
{
global currentList currentLength
set currentLength [llength $rList]
if {!$currentLength} {return 0.0}
set avg 0.0
foreach r $currentList
\
{
set avg [expr
$avg + $r]
}
set avg [expr
$avg/double($currentLength)]
return $avg
}
Then, this adjusted version
"getCurrentAvg" could
be used elsewhere as follows:
global currentList
currentLength
set currentList
"1.0 2.0 3.0"
set a [getCurrentAvg]
puts "List: $currentList Len: $currentLength Avg: $a"
A procedure can use global
variables for persistent storage of information, including the possibility to
test whether the procedure has been called previously; this is useful for
procedures that might need to perform a one-time initialization. In these cases, a procedure will use a global
variable which is not set anywhere else.
This means, the first time the procedure is called, the global variable
will not yet exist (recall that the "global" statement declares that
a variable will be accessed as a global variable, but it does not define or
create the variable itself).
The TCL
command "info exists" will evaluate
to true if the given variable exists.
For example, suppose we wanted to make a version of our procedure "getAvg" which keeps an internal count of how many
times it has been called.
In this version, we use a
global variable named "callCount_getAvg" to keep track of the number of times "getAvg" is called.
Because this global variable will actually be used to store information
for the specific use of the "getAvg" procedure, we need to choose a global variable
name which will not be used for a similar purpose in some other procedure. The first time "getAvg" is called, the global variable does not yet
exist, and must be set to zero.
proc getAvg
{ rList } \
{
global callCount_getAvg
if {![info exists callCount_getAvg]} \
{
set callCount_getAvg 0
}
incr callCount_getAvg
puts "getAvg has been called
$callCount_getAvg times"
set n [llength $rList]
if {!$n} {return 0.0}
set avg 0.0
foreach r $rList
\
{
set avg [expr
$avg + $r]
}
set avg [expr
$avg/double($n)]
return $avg
}
A more flexible way to
manipulate persistent data is to use global arrays rather than scalar
variables. For example, instead of the
procedure-specific scalar variable "callCount_getAvg" used above, we can use a general-purpose array
"callCount()" which could be used to record the call counts
of any number of procedures, by using the procedure name as the array
index. Many nmrWish
TCL scripts use global arrays in this fashion, to
simplify the sharing of many data values between procedures.
Here is a version of the
"getAvg" procedure
with the call count tallied in a global array location
note that an array is
declared global simply by listing its name in a "global" command, exactly as for a scalar variable; no
( ) parenthesis or index values are used:
proc getAvg
{ rList } \
{
global callCount
if {![info exists callCount(getAvg)]} \
{
set callCount(getAvg)
0
}
incr callCount(getAvg)
puts "getAvg has been used $callCount(getAvg) times"
set n [llength $rList]
if {!$n} {return 0.0}
set avg 0.0
foreach r $rList
\
{
set avg [expr
$avg + $r]
}
set avg [expr
$avg/double($n)]
return $avg
}
We have already seen that TCL procedures can generate a return value as a way to pass
information back to their caller. And,
we have also seen that global variables can be used to share information between
parts of a TCL script, and so these also serve as a
mechanism for returning information to a caller.
TCL includes the "upvar"
command as a method for a given procedure to change the values of variables in
the scope of its caller. This provides a
way for a procedure to provide additional information to the caller, besides by
using the procedure's return value.
In the "upvar" scheme, a procedure's caller provides the
names of one or more of its own variables as arguments to the procedure. The procedure then uses the "upvar" command to map these variables from the caller
onto variables in the procedure. For
example, here the caller passes its variable
name "count" as the first argument
to procedure "getNAvg":
set count 0
set a [getNAvg
count "1.0 2.0 3.0 4.0"]
Then, in this version of
procedure "getNArg"
the "upvar"
command is used to map the first argument value "$nPtr" onto the procedure's variable called "n"
this means that whenever the procedure gets
or changes the value of variable "n" it
will actually be using the caller's variable "count".
proc getNAvg
{ nPtr rList } \
{
upvar $nPtr
n
set n [llength $rList]
if {!$n} {return 0.0}
set avg 0.0
foreach r $rList
\
{
set avg [expr
$avg + $r]
}
set avg [expr
$avg/double($n)]
return $avg
}
As
we have seen, procedures provide a way to organize a script into simpler,
well-defined tasks. And, after a
procedure is defined, it can be used like any other built-in command in TCL. An NMRPipe installation includes a script library of such TCL procedures, for performing generally useful tasks, in
particular performing computations on numerical lists (like our "getAvg"
procedure), and extracting arguments from the script command-line.
In
order to make these library functions available in a script, we use the TCL global variables "auto_path" and "env" to
specify the directory in the NMRPipe installation
where the script library is found. This
is defined by the NMRPipe installation environment
variable "TCLPATH". Most nmrWish scripts will also define global variables "ARGC" and "ARGV" with
information from the script's command-line.
So, in most cases, nmrWish TCL
scripts will begin with the three lines which launch the script with the "nmrWish" interpreter, as we've seen already, followed
by three additional lines which identify the location of the nmrWish script library, and create variables ARGC and ARGV for script
command-line parsing.
#!/bin/sh
#
The next line restarts using nmrWish
\
exec nmrWish "$0" --
"$@"
set auto_path "[split $env(TCLPATH) :] $auto_path"
set ARGV [concat $argv0 $argv]
set ARGC [llength $ARGV]
Many
of the nmrWish facilities for manipulating spectral
data are based on "regions of interest" (ROI) . In this context,
a region of interest is an extract or projection of all or part of a given
spectrum. Some examples of ROIs include:
An individual 1D slice extracted from any dimension of a multidimensional
spectrum.
A 1D summation projection of a 2D region.
A 2D region extracted from a larger 2D spectrum.
A 2D plane extracted from any pair of dimensions in a 3D or 4D spectrum.
A 2D strip created by
summation projection of
a 3D region.
A 3D region extracted from a larger 3D or 4D spectrum.
In
the case of spectral graphics, nmrWish uses the
concept of a cell, which is a rectangular drawing area inside a window on the
screen or on a page of a PostScript plot.
So, the general paradigm for generating spectral graphics with nmrWish is:
In
the general scheme above, there is no specific limit to the number of cells or ROIs that can be manipulated. It is also possible to overlay any number of
1D and 2D ROI graphics in the same cell.
When
using "readROI" to
create an ROI, we specify the coordinates of a region
to extract from an original data set. We
also specify how this extracted region will be re-arranged to create the ROI. The input
region is specified on the basis of axis names. The axis names are the same ones
as specified during conversion of the spectrum (conversion arguments -xLAB -yLAB etc).
The "readROI" command assumes that each
axis in the spectrum has a unique name. As an alternative, the "generic" axis names
"X_AXIS", "Y_AXIS",
etc can also be used.
The
coordinates of the ND region to extract are specified in terms of separate coordinate
ranges for each of the dimensions. The coordinates for a given dimension can be
specified as an upper and
lower bound, or as a center position and a +/- width. The coordinates can be specified with no
units (i.e. in points), or with the labels "hz",
"ppm", or "%". If no range is specified for a given
dimension, the entire spectral range is assumed.
Extracted
regions can centered
or aligned automatically through interpolation, so
that the extracted
data corresponds to the requested
chemical shift coordinates regardless of
the digitization of the
data. Furthermore, extracted regions
which exceed the measured chemical shift window are unfolded and sign-adjusted
automatically, so that regions from related spectra can be manipulated or
displayed consistently even if they have differing spectral windows.