nmr image

Under Construction – Last Updated Sept 14, 2009

Introduction

 

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.

Using the Interpreter nmrWish

 

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:

 

  1. Start the C-shell interpreter by typing “csh” … when the interpreter starts, it will print a command prompt, such as the percent sign “%”.

 

  1. Use the C-shell command “echo” to print a message.

 

  1. Use the C-shell command “exit” to exit the C-shell interpreter.

 

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:

 

  1. Start the NMRWish interpreter by typing “nmrWish” … when the interpreter starts, it will print a the percent sign “%” as a command prompt.  It will also create a small blank window on the screen labeled “nmrWish”, as shown below.

 

  1. Use the TCL command “puts” to print a message.  Unlike the C-shell case, in this case the text of the message will be enclosed in double-quotes.

 

  1. Use the TCL command “exit” to exit the TCL interpreter; this will also close the nmrWish window.

 

nmr image
 

 

 

 


/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:

 

  1. Start the NMRWish interpreter by typing “nmrWish” … when the interpreter starts, it will print the percent sign “%” as a command prompt.  It will also create a small blank window on the screen labeled “nmrWish”.

 

  1. Use the TK command “button” to define a graphical button.  In this case, the button will be called “.b” and its command parameters will define the text message displayed on the button (“hello world”) and the TCL command which will be executed when the button is pressed (“exit”).  Note that the button will not yet be drawn on the screen at this stage.

 

  1. Use the TK command “pack” to place the button on the screen; this will alter the “nmrWish” window so that it displays the button.

 

  1. Use the mouse to click on the button; this will close the nmrWish window and exit the interpreter.

 

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:

 

 

nmr image

 

 

 

 

/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;

                                                can’t 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.

 

TCL Lists and Arrays

TCL has many facilities for manipulating lists, which are collections of items separated by whitespace. For example, "llength" returns the number of items in a list, "lindex" returns the Nth item in a list starting at item 0, "lrange" returns a range of items from a list, and "lsearch" returns the location of a list item that matches a given value:

 

% 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.

TCL Syntax and Looping

 

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.

 

TCL and Conditional Expressions

 

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.  Here’s how it works:

 

  1. The script is invoked, for example by typing its name at the UNIX command-line, along with any arguments.

 

  1. As directed by the first line of the script, the shell program "/bin/sh" is executed.  When executed by this script mechanism, the shell program is automatically given the name of the script along with any other command-line arguments.

 

  1. Once the shell starts interpreting the script, it will ignore both the first line and the second line, because they begin with the # hash-sign comment.

 

  1. The shell will then execute the third line of the script, which uses the shell command "exec" to start nmrWish. The exec command actually replaces the running shell with a new running command … so, after the "exec" command is executed, the shell is no longer running, and nmrWish has taken its place.

 

  1. nmrWish is started with the script name ($0) as its first argument, followed by any other arguments originally specified when the script was first invoked ($@).  So, nmrWish will start interpreting the script.

 

  1. Once nmrWish starts interpreting the script, it will ignore the first line, since it begins with the # hash-sign comment.

 

  1. While interpreting the script, nmrWish will also ignore the second line and the third line with the "exec" command.  This is because in TCL syntax, unlike for the shell, a comment can be continued on multiple lines by the \ backslash character.  So, to nmrWish, the second and third lines are part of the same comment.

 

  1. The remainder of the script will be interpreted "normally" by nmrWish.

 

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 Procedures

 

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"}

 

TCL Variable Scope: Local and Global variables

 

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

}

 

TCL Variable Scope and the upvar Command

 

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

}

 

Procedure Libraries and Script Command-Line Arguments

 

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]

 

Using nmrWish to Read, Draw, and Plot Spectral Data

 

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:

 

  1. Prepare a TK graphics canvas or PostScript file for spectral drawing.

 

  1. Read a region of interest (ROI) with the nmrWish command "readROI".  The ROI is identified by an ID number which is assigned by an argument to "readROI".

 

  1. Define a drawing area (cell) with the command "defineCell".  The like an ROI, a cell is also identified by an ID number which is assigned by a command-line argument.

 

  1. Use the "drawROI" command to draw the specified ROI into the specified cell, as a 1D spectrum, 2D contour plot, or 2D pixmap image.

 

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.