Chapter 5. Tcl bindings to the C++ API

The Tcl bindings to the C++ API closely mimic the C++ API. Tcl is a command based language. The C++ API is object oriented. The standard way to do object oriented programming or present object oriented APIs is through command ensembles.

A Tcl command ensemble is a command with subcommands. You can think of a command as a class or an object and the subcommand as methods (class or object level). You can imagine a Tcl command that represents a class. It might have construction subcommand that would dynamically generate a new command ensemble that represents an object. This is the approach taken by the Tcl bindings.

The Tcl bindingsx are in a package called SpecTclDB which is stored in the SpecTcl Tcl package library. There are two ways to bring that library directory tree into the Tcl package search path:

  1. Via an environment variable. For example, suppose the environment variable SPECTCLHOME is defined and is the top level directory in which the version of SpecTcl you are using is stored. TCLLIBPATH=$SPECTCLHOME/TclLibs tclsh will run a Tcl shell that includes the SpecTcl Tcl library tree in the search path.

  2. Extension of the the Tcl auto_path variable. Suppose, as before SPECTCLHOME is defined to be the top level installation directory of your SpecTcl version, the following script fragment will bring the SpecTcl Tcl packages into your search path.

    
lappend auto_path [file join $::env(SPECTCLHOME) TclLibs]
                

    The global env array contains the values of environment variables indexed by variable name. The file join command joins file system path elments and lappend appends items to a list, in this case the auto_path search list.

Our examples will use the second form. They will mirror closely the examples in the C++ API section. The examples are deployed in the share/dbexamples directory.

Example 5-1. Creating an empty database in Tcl (makedb.tcl)


#!/bin/sh       (1)
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}


lappend auto_path [file join $::env(SPECTCLHOME) TclLibs] (2)
package require SpecTclDB                   (3)

if {[llength $argv] != 1} {                 (4)
    puts stderr "Usage: makedb.tcl database-file"
    exit -1
}

DBTcl create [lindex $argv 0]           (5)

        
(1)
This bit of boilerplate is a recommended way to prefix executable Tcl scripts. It starts the /bin/sh shell, which is almost always a legal shell and then some continuation line magic executes the Tcl interpreter passing the file back to itself as a parameter. The \ in the line before the exec command ensures that line is not seen by the Tcl intepreter as it continues the comment line. The Tcl interpreter then continues to interpret the script in the remainder of the file.
(2)
As described previously, this line appends the SpecTcl Tcl library directory tree to the package require search path.
(3)
Brings the SpecTclDB package into the script. This defines the command ensemble that makes up the package.
(4)
The argv list contains the command parameters that follow the command. Note that the command itself is not included.
(5)
Creates the database file specified by the first command argument and initializes it to contain the SpecTcl database Schema.

The next example shows how to make a save set in an existing database file. We'll learn how to form a connection to a database and what that means. We'll then use that connection to create a new saveset.

Example 5-2. Creating savesets in a database (makesaveset.tcl)


#!/bin/sh
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}


lappend auto_path [file join $::env(SPECTCLHOME) TclLibs]
package require SpecTclDB

if {[llength $argv] != 1} {
    puts stderr "Usage: makesaveset.tcl database-file"
    exit -1
}

set status [catch {    (1)
    set db [DBTcl connect [lindex $argv 0]]  (2)
    set saveset [$db createSaveset "a saveset"] (3)
    $saveset destroy                         (4)
    $db      destroy
} msg]

if {$status} {
    puts "Error: $msg"       (5)
    exit -1
}
        
(1)
This program demontsrates very simple error handling. The Tcl catch command performs the commands in the script passed as its first argument. If there is an error, it returns 1, if not, 0. This value is captured in the status variable.

The optional second argument to catch is the name of a variable. If the command is successful, this will hold the result of the script. If not, it will hold the error message associated with the failure.

(2)
The DBTcl connect command creates a new command ensemble that can perform operations on the database file passed to the DBTcl connect command. This is analagous to constructing a new object. The subcommands of the command captured in the db variable are analogous to object methods.

This command ensemble is referred to as a data base instance command.

(3)
Similarly the createSaveset subcommand of the databae instance command creates a new save set and returns another newly created command ensemble whose ensemble commands operate on the save set. As you might imagine, this command, captured in the saveset varible is called a saveset instance command.
(4)
All instance commands have a destroy ensemble command. This destroys the command and releases any resources it might have. This command is analogous to a destructor method
(5)
This part of the script executes if any errors are detected by the catch command. To see what it does, try running the script twice on the same database. The secone time the attempt to make a saveset will fail because the save set already exists.

Example 5-3. Listing savesets in a database (lssaveset.tcl)


#!/bin/sh
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}


lappend auto_path [file join $::env(SPECTCLHOME) TclLibs]
package require SpecTclDB

if {[llength $argv] != 1} {
    puts stderr "Usage: makesaveset.tcl database-file"
    exit -1
}

set status [catch {
    set db [DBTcl connect [lindex $argv 0]]

    puts "Savesets in [lindex $argv 0]"

    foreach name [$db listSavesets] {  (1)
        puts $name
    }
    $db destroy
} msg]

if {$status} {
    puts "Error: $msg"
}
        
(1)
The listSavesets subcommand of the database instance command returns a properly formatte Tcl list of savesets contained by that database. This foreach loop outputs them to stdout.

Example 5-4. Defining parameters (pardef.tcl)


#!/bin/sh
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}


lappend auto_path [file join $::env(SPECTCLHOME) TclLibs]
package require SpecTclDB

if {[llength $argv] != 1} {
    puts stderr "Usage: pardef.tcl database-file"
    exit -1
}

set status [catch {
    set db [DBTcl connect [lindex $argv 0]]
    set saveset [$db getSaveset "a saveset"]   (1)
    
    $saveset createParameter p1 100            (2)
    $saveset createParameter p2 101 -10.0 10.0 100 "cm"
    
    puts "Parameters defined:"
    foreach param [$saveset listParameters] {  (3)
        puts [dict get $param name]
    }
    
    $saveset destroy                            (4)
    $db destroy
} msg]


if {$status} {
    puts stderr "error: $msg"
}                
            

This example program creates two parameters named p1 and p2. Parameter p2 has full metadata. After creating the parameters the names of all parameters in the saveset are listed to stdout.

(1)
The getSaveset subcommand of a database instance command ensemble, looks up the named save set (in this case a saveset) and, if found creates a command ensemble that can manipulate that saveset. If the saveset does not exist, an error is thrown.

The command ensemble that getSaveset produces, is called a save set instance command and is captured in the saveset variable.

(2)
The createParameter subcommand of the saveset instance command ensemble creates new parameter definitions in the databsase. Mirroring SpecTcl, there are two types of parameters that can be created. Raw parameters make a correspondence between parameter name and parameter number (in SpecTcl, parameter number is the index into a CEvent) object at which the parameter should be stored and retrieved. Tree parameters have additional metadata.

The first command creates a raw parameter named p1. The second command creates a tree parametr with metadata. The metadata provides hints to both the user and SpecTcl GUI scripts about how to define axes of spectra that depend on this parameter. In this case a range of [-10.0..10.0) with 100 bins is recommended. In addition, the units of measure of the parameter are documented as centimeters (cm).

(3)
The listSpectrum subcommand of a save set instance command ensemble creates a Tcl list of dicts that describe the spectra that have been defined in the saveset. The keys of the dict are:

id

Contains an integer that is the primary key of the spectrum in the spectrum definition table. This is useful in programs that may do ad-hoc queries of the database for advanced applications.

name

Text string that is the name of the parameter.

number

THe number of the parameter. This differs from the id key. It's a value that the program supplies, rather than a value assigned by the database engine.

low (optional)

This key is only present for tree parameters. It's the recommended low limit of spectrum axes defined on this parameter.

high (optional)

This key is only present for tree parameters. It is the recommended high limit for axes defined on this parameter.

bins (optional)

This key is only present for tree parameters. It is the recommended number of bins on axes defined on this parameter.

units (optional)

This key is only present for tree parameters. It is the units of measure defined for the parameter. Note that an empty string is perectly allowed an usually means the parameter is unit-less (e.g. raw ADC values).

(4)
Because the saveset instance command is dynamically created by the database instance command, when you are done with it you should destroy it and the resources associated with it. The destroy subcommand does this just as it also destroys database instance commands and their associated resources.

Example 5-5. Defining Spectra (specdef.tcl)


#!/bin/sh
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}


lappend auto_path [file join $::env(SPECTCLHOME) TclLibs]
package require SpecTclDB

if {[llength $argv] != 1} {
    puts stderr "Usage: specdef.tcl database-file"
    exit -1
}

set status [catch {
    set db [DBTcl connect [lindex $argv 0]]
    set saveset [$db getSaveset "a saveset"]
    
    $saveset createSpectrum s1 1 p1 [list [list 0 1023 1024]]  (1)
    $saveset createSpectrum s2 2  \                            (2)
        [list p1 p2] [list [list 0 1023 1024] [list -10.0 10.0 100]]
    
    puts "Spectra in saveset:"
    foreac spectrum [$saveset listSpectra] {   (3)
        puts [dict get $spectrum name]
    }
} msg]

$saveset destroy
$db destroy

if {$status} {
    puts stderr "error: $msg"
}                
            

This example creates a 1d and a 2d spectrum named s1 and s2 respectively.

(1)
The createSpectrum saveset instance subcommand creates spectrum definitions in the database. This command takes several parameters; A spectrum name, the a type code for the spectrum type, a list of parameters needed by the spectrum, a list of axis definitions and an optional channel data type.

In the sample command, the spectrum name is s1. The spectrum type code, 1 means that the spectrum is a 1-d spectrum. p1 is a single element Tcl list that says the spectrum increments the p1 value for each event in which it is defined. Note that had the spectrum type been s (summary spectrum), or g1 (gamma 1-d) this parameter list would, in general contain additional parameter names. After the parameter is a list of axis definitions. Each axis definition, in turn is a three element list containing the axis low limit, high limit and number of bins in that order. Thus p1 will have an axis that runs from 0 to 1023 and has 1024 channels.

By default, SpecTcl spectra contain longword (32 bit) channel values. The createSpectrum subcommand also uses this as the default channel datatype. You can explicitly provide a data type as an additional command parameter. This additional parameter can have one of the following values:

byte

Channels will be 8 bit values.

word

Channels will be 16 bit values.

long

Channels will be 32 bit values

(2)
This command creates a 2-d spectrum. The principles are the same as the previous command, but two parameters (x and y axis in that order) are provided as are two axes (x and y axis in that order).
(3)
The listSpectra subcommand returns a Tcl list whose entries describe the spectra defined in the saveset. Each list item is dict that describes a single spectrum. The dicts have the following key/value pairs:

id

The primary key of the spectrum in the top level spectrum definition table. This is really not useful to Tcl scripts that only use the API. It can be useful to scripts that also do ad-hoc queries of the database (e.g. via the sqlite3 package).

name

Name of the spectrum.

type

The spectrum type code.

parameters

The list of parameters the spectrum depends on.

axes

List of axis definitions for each of the free defined axes of the spectrum. Note that each axis definition is a three element list containing in order, the axis low limit, high limit and number of bins.

datatype

The data type code as described above.

As with the C++ API, there are a family of subcommands that create gates of different types. The sample script below creates several types of gates.

Example 5-6. Defining gates (gatedef.tcl)


#!/bin/sh
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}


lappend auto_path [file join $::env(SPECTCLHOME) TclLibs]
package require SpecTclDB

if {[llength $argv] != 1} {
    puts stderr "Usage: gatedef.tcl database-file"
    exit -1
}

set status [catch {
    set db [DBTcl connect [lindex $argv 0]]
    set saveset [$db getSaveset "a saveset"]
    
    $saveset create1dGate slice  s p1 100 200    (1)
    $saveset create2dGate contour c [list p1 p2] \ (2)
        [list {100 10} {200 100} {200 200} {100 200} ]
    $saveset createCompoundGate and * [list slice contour] (3)
    $saveset createMaskGate  mask em p1 0x55555555 (4)
    
    puts "Gates in saveset:"
    foreach gate [$saveset listGates] {    (5)
        puts [dict get $gate name]
    }
    
    $saveset destroy
    $db destroy
} msg]



if {$status} {
    puts stderr "error: $msg"
}
        
(1)
Creates a 1d gate. 1d Gates are gates that have a single low and high limit defining a slice in some 1-d space. The create1dGate subcommand takes 5 additional parameters. The name (slice), the gate type (s) the list of parameters the gate is checked against (p1), note that there are 1d gates that can have more than one parameter; gamma slices gs for example. The final two parameters are the low and high limits of the gate.

Note that the gate limits for this and all gates with limits are in raw parameter coordinates. There is often a temptation to believe that since gates are clicked on on spectra they are accepted in spectrum coordinates. This is not the case.

(2)
Creates a 2-d gate. With the exception of the gate limits, this subcommand takes the same parameters as create1dGate. However, instead of two limit parameters, the command takes on parameter that is a list of X/Y pairs that define a figure in some 2-d space of parameters. In the example, the gate is a contour, which is a closed figure. The coordinates are that of a square.
(3)
Creates a compound gate. A compound gate is one whose truth or falsity only depends on the truth or falsity of other gates. The form of the createCompoundGate subcommand calls for a gate name, a gate type and a list of the gates the compound depends on.

In this example a new gate and is created that is an and gate (type *) that depends on the gates slice and contour. This gate will only be true of both gates are true in an event.

(4)
Creates a mask gate. Mask gates assume their parameter is actually an integer and perfrom some bitwise operation and comparison on it. The createMaskGate subcommand requires a gate name (mask), a gate type (em mask and compare for equality), and a bitmask (0x55555555).
(5)
The listGates command returns a list of dicts that describe the gates saved in the saveset. Each gate is described by a dict. Interpreting this dict requires knowing the type of gate as the set of keys provided dpends on the type of gate. The dict introduces the concept of a gate classification which defines the set of keys a gate will have.

Here's list of the gates an when they are present.

classification (all)

Gives the gate classification. All gates will be one of point, compound, or mask gates. In the keys below, after the key term parenthesized text will describe which keys are present in which clasifications. Note that (all) as in above means that all classification types have this key.

id (all)

The primary key in the top level table used to define gates. This is normally not useful to programs that use only this Tcl API. It can be useful if a script also uses e.g. sqlite3 to perform arbitrary queries of the database.

name (all)

The name of the gate.

type (all)

The gate type code string.

parameters (point, mask)

The parameters that are checked by the gate. Note that mask gates only have one parameter.

gates (compound)

The names of the gates this gate depends on

points (point)

A list of two element sub lists. Each element is a point in the gate. For 1d gates, the Y coordinate is meaningless and there will only be two points. For 2-d gates, the X and Y coordinate are both meaningful and there can be be an arbitrary number of points.

mask (mask)

The bitmask for a mask gate.

Example 5-7. Applying gates to spectra (applydef.tcl)


#!/bin/sh
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}


lappend auto_path [file join $::env(SPECTCLHOME) TclLibs]
package require SpecTclDB

if {[llength $argv] != 1} {
    puts stderr "Usage: applydef.tcl database-file"
    exit -1
}

set status [catch {
    set db [DBTcl connect [lindex $argv 0]]
    set saveset [$db getSaveset "a saveset"]
    
    $saveset applyGate slice s2  (1)
    
    foreach app [$saveset listApplications] { (2)
        set gate [dict get $app gate]
        set spec [dict get $app spectrum]
        puts "$gate is applied to $spec"
    }
    
    $saveset destroy
    $db destroy
} msg]



if {$status} {
    puts stderr "error: $msg"
}            
        

Gates are objects that are totally passive unless applied to a spectrum. When applied to a spectrum, a gate conditionalizes the normal incrementation of that spectrum. If a gate is applied to a spectrum, the spectrum will only be incremented if the gate is true.

(1)
The applyGate subcommand takes a gate name and a spectrum name in that order. The gate is entered in the database as being applied to the spectrum.
(2)
The listApplications subcommand returns a list whose elements are dicts. Each dict describes a gate application and has the following keys.

id

Contains the primary key of the application in the gate applications table. This is only useful for ad-hoc queries of the database performed by by e.g. the sqlite3 package.

gate

The name of the gate

spectrum

The spectrum to which that gate is being applied in this entry.

Example 5-8. Saving and recovering tree variables (vardef.tcl)


#!/bin/sh
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}


lappend auto_path [file join $::env(SPECTCLHOME) TclLibs]
package require SpecTclDB

if {[llength $argv] != 1} {
    puts stderr "Usage: vardef.tcl database-file"
    exit -1
}

set status [catch {
    set db [DBTcl connect [lindex $argv 0]]
    set saveset [$db getSaveset "a saveset"]

    $saveset createVariable slope 1.23 KeV/lsb (1)
    $saveset createVariable offset 10  KeV
    $saveset createVariable gainmatch 1.23    (2)
    
    puts "Tree variables: "
    foreach var [$saveset listVariables] {    (3)
        set name [dict get $var name]
        set val  [dict get $var value]
        set units [dict get $var units]
        
        puts "$name : $val$units"
    }
    
    $saveset destroy
    $db destroy
} msg]



if {$status} {
    puts stderr "error: $msg"
}            
        
(1)
The createVariable subcommand creates a new tree variable definition. The command requires a name, a value and optionally units of measure.

This and the next line of code create a pair of variables that might be used to do an energy calibration of a parameter. The units of measure clearly show what the units of the calibrated parameter will be; KeV.

(2)
If the variable has no units of measure, you can leave the units parameter out. In that case the actual value of the units of measure for the variable will look like an empty string.
(3)
The listVariables subcommand returns a list of dicts that describe the tree variables saved in this saveset. Each list element is a dict that contains the following key/value pairs.

id

Primary key of the variable in the tree variable table. This is useless for Tcl scripts that only use the published API. It can be useful if a script does ad-hoc database queries (e.g. using the sqlite3 package).

name

Name of the treevariable.

value

Value of the tree variable (double).

units

Units of measure text string. If no units were supplied, this will be an empty string.

Example 5-9. Storing spectrum contents (specstore.tcl)


#!/bin/sh
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}


lappend auto_path [file join $::env(SPECTCLHOME) TclLibs]
package require SpecTclDB

if {[llength $argv] != 1} {
    puts stderr "Usage: specstore.tcl database-file"
    exit -1
}

set status [catch {
    set db [DBTcl connect [lindex $argv 0]]
    set saveset [$db getSaveset "a saveset"]

    $saveset storeChannels s1 \    (1)
        [list {100 123} {101 2000} {102 400} {103 60}]
    
    set channels [$saveset getChannels s1] (2)
    puts "Channels in s1: "
    foreach chan $channels {
        set x [lindex $chan 0]
        set v [lindex $chan 2]             (3)
        puts "Channel $x:   $v"
    }
    $saveset destroy
    $db destroy
} msg]



if {$status} {
    puts stderr "error: $msg"
}
        
(1)
The storeChannels command stores channel values in a spectrum whose definition is already stored in the saveset. Two additional parameters are expected; the name of the spectrum and the values. Values are a list containing either two or three element sublists. For 1-d spectra, two elements are expected and they are the channel coordinate and channel value. For 2d spectra, the three elements are x channel, y channel and value.
(2)
The getChannels subcommand returns a list of channel values stored for the spectrum. Each channel is given as a three element list. The first element is an x channel number. The second element, is the y channel number if the spectrum is 2 dimensional or 0 if not. The third element is the channel value.
(3)
Since we stored a 1-d spectrum we need to pull out the first and last elements of each channel list and output them.

The Tcl interface supports some queries of the event data stored in a save set but does not support storing events or scaler readouts. See the reference section for more information about this.