D. Bazin, Ron Fox
National Superconducting Cyclotron Laboratory
Michigan State University
East Lansing, MI 48823, USA
Abstract
The TreeParameter and TreeVariable classes have been modified to take advantage of the new features present in SpecTcl 2.1, in particular the use of real valued parameters and arbitrary binning of spectra. The new version of these classes (1.2) is backward compatible with code previously written for earlier versions of SpecTcl. The Graphical User Interface (GUI) associated with these classes has been updated and improved.
Contents
Constructors
and initialization
Defining
the tree using classic structures
Defining
the tree using C++ classes
Additions
to the first event processor
Preserve
gate condition when rescaling spectra
The TreeParameter class provides a mean to implement parameters in SpecTcl in a tree-like structure and without the need to specify parameter ids. The parameters are referred to by their names only, which at the same time indicate their location in the tree. This reference mechanism is valid for both the coding part of SpecTcl (C++ code) and the interpreter part (Tcl/Tk shell). It eliminates the confusion induced by the use of numerals to refer to the parameters, and allows the user to organize the parameters in a logical fashion.
Most detector ensembles or complex devices can be easily represented by a tree-like structure. The top (or root depending on the growth direction) of the tree is usually the name of the device itself. Then each component of the device can be represented as branches of the tree containing themselves other branches and so on. The immediate advantage of this technique is that the name of any part of the tree is meaningful and shows its location in the tree. An example of an excerpt of such a tree is shown in the figure below.
In this example the TreeParameter s800.fp.ic.de.02 represents the second channel of the energy loss measurement from the ion chamber detector located in the focal plane of the S800 Spectrograph. In the C++ code of SpecTcl, this parameter name can be used directly on either side of any assignment.
The TreeVariable class is built on the same principles as
the TreeParameter class. TreeVariables are also organized in a tree-like
structure that can be either separate from or embedded in a structure already
containing TreeParameters. This proximity is very useful to keep track of
variables associated with parameters, as they bear similar locations in the
tree and are readily available when using C++ classes to build the tree. They
are typically used for calibrations, pedestals, switches or any purpose
requiring the possibility to vary a value dynamically in SpecTcl. Each C++
TreeVariable is linked to a Tcl variable with the same name. The user has then
full flexibility to either read or set the C++ TreeVariable from the Tcl/Tk
shell using the set Tcl command.
In previous versions of SpecTcl, parameters could only accept integer values that were directly mapped to the binning of the spectra where they were histogrammed. Although this approach is acceptable when histogramming data directly coming from Analog-to-Digital converters, it quickly breaks down as soon as results from calculations need to be visualized in a spectrum. The “old” way of dealing with this issue was to map a range of the calculated value on the integer parameter using a set of coefficients. This mechanism was provided by the old version of the TreeParameter class, in which the user had the possibility to specify this range in the C++ code and could also dynamically alter it from the Tcl shell. However, a major shortcoming was that the mapping was not passed on to the displayer Xamine, which continued to show useless bins instead of the corresponding real values of the parameter.
In SpecTcl 2.1 the arbitrary binning that was implemented in the TreeParameter class has been taken over by SpecTcl itself when using real valued parameters. The binning is then defined for each spectrum by specifying a lower and a higher limit, as well as a number of bins. This approach is much more flexible as it allows the definition of different spectra histogramming the same parameter but with a different binning and/or range. Xamine has now the capability of displaying the spectra as a function of the real values of the parameters by clicking on the “Map” button.
As a result, the internal mapping has been removed from the TreeParameter class, and the range specification is only used for default values when building a spectrum. The TreeVariable class hasn’t changed and is still restricted to the linking of real values (homogeneous to doubles in C++).
In SpecTcl 3.0, TreeParameter has been completely integrated with SpecTcl. As a result of this integration, much of the code you used to have to add to your SpecTcl initialization is no longer needed. Refer to the section “Implementation” for more information about this.
The following constructors are provided for defining TreeParameters
and TreeVariables as well as their derived arrays TreeParameterArray and
TreeVariableArray.
CTreeParameter
(string name, UInt_t bins, double low, double high, string unit)
In this constructor all members are spelled out. The name member should be identical to the path to the location of the object in the tree, using the dot (.) character to indicate the various branches. The bins, low, high and unit members are used as default values when defining spectra of this parameter.
CTreeParameter
(string name, double low, double high, string unit)
Everything but the number of bins is specified: bins = 100.
CTreeParameter
(string name, string unit)
With this constructor the unit is specified, so only the other members get the default values: low = 1; high = 100; bins = 100.
CTreeParameter
(string name)
If only the name of the TreeParameter is specified, the constructor assigns default values for the remaining members of the class: low = 1; high = 100; bins = 100; unit = “unknown”.
CTreeParameter
(string name, UInt_t bits)
This constructor is kept for backward compatibility with previous versions of SpecTcl. The number of bins is equal to 2bits, and the remaining members are set to the following: low = 0; high = bins; unit = “channels”.
CTreeParameter
(string name, UInt_t bits, double low, double other, string unit, bool choice)
Same as previous: this constructor is kept for backward
compatibility. The meaning of the parameter other depends on
value of choice: if choice = false, other is
equivalent to high; if choice = true, other is an
increment (number of “unit” per bin).
CTreeParameterArray
(string name, UInt_t bins, double low, double high, string unit, UInt_t size,
UInt_t first)
The first 6 entries of the full-fledged constructor are similar to those of the CTreeParameter full-fledged constructor. The two last entries indicate the number of elements in the array and the index of the first element. Note that a CTreeParameterArray object is treated in a slightly different way between the C++ and Tcl worlds. In C++ the array elements are addressed using the operator [], for instance by writing somearray[2]. In the Tcl world this element would addressed as somearray.2 if there are less than 10 elements, somearray.02 if there are less than 100 elements, and so on.
CTreeParameterArray
(string name, double low, double high, string unit, UInt_t size, UInt_t first)
Same as previous constructor but without the number of bins. The member bins = 100.
CTreeParameterArray
(string name, string unit, UInt_t size, UInt_t first)
Same as previous constructor but without the specification of limits and number of bins. Like its counterpart in CTreeParameter constructors, low = 1; high = 100; bins = 100.
CTreeParameterArray
(string name, UInt_t size, UInt_t first)
Same as previous constructor but with only name specification: low = 1; high = 100; bins = 100; unit = “unknown”.
CTreeParameterArray
(string name, UInt_t bits, double low, double other, string unit, bool choice,
UInt_t size, UInt_t first)
CTreeParameterArray
(string name, UInt_t bits, UInt_t size, UInt_t first)
Again for compatibility issues these two constructors are
maintained and have the same functionality as their CTreeParameter counterparts.
CTreeVariable
(string name, double value, string unit)
The CTreeVariable class has only one constructor in which
the name, an initial value and the unit are specified. As for TreeParameters,
the name should be identical to the path to the location of the object in the
tree.
CTreeVariableArray
(string name, double value, string unit, UInt_t size, UInt_t first)
Similar to the CTreeParameterArray class the CTreeVariableArray only constructor requires a number of elements as well as the index of the first element. All elements are initialized to the specified value.
In parallel to these constructors, the classes provide
overloaded Initialize methods with the same parameters that allow initializing
of the objects after their creation. This is especially useful when using C++
classes to build the tree. See the following sections for more details.
There are two basic ways of building the parameter tree using
the TreeParameter and TreeVariable classes. One involves the use of classic
C-language struct structures, and the other takes advantage of C++
classes. The second method has clear advantages over the first, therefore this
is the recommended one. In both cases two files need to be generated: a header
file (.h) containing the declaration of the objects, and a compile file (.cpp)
containing the definition or instantiation of the same objects. In both cases
it is also recommended to first draw a diagram of the tree similar to the one
shown in the figure of section I, before starting to write
any code.
Working from the bottom up, the declaration of the tree can start with structures containing only TreeParameters and TreeVariables, followed by higher level structures containing the lower level ones. For example, the partial tree illustrated in section I could be declared as follows in a header file:
struct ionchamber {
CtreeParameterArray& de;
CtreeParameter& sum;
};
struct focalplane {
struct ionchamber ic;
CtreeParameter& other;
…
};
struct spectrograph {
struct focalplane fp;
struct target ta;
…
};
Notice the use of the CTreeParameterArray class derived from the CTreeParameter class used to declare and build a one-dimensional array of parameters. An equivalent class CTreeVariableArray is derived from the CTreeVariable class (see section III).
The compile file performing the actual instantiation of the tree would be required to follow the same structure:
struct spectrograph s800 =
{
{
{
*(new CTreeParameterArray(“s800.fp.ic.de”, 4096, 0, 4095, “channel”, 16, 1)),
*(new CTreeParameter(“s800.fp.ic.sum”, 1000, 0, 999, “MeV”))
},
*(new CTreeParameter(“s800.fp.other”, “someunit”)),
…
},
{
definition following the struct target…
},
…
};
Notice the absence of comma after the last item of a given structure. It is very important that the Tcl name given as first parameter of the constructors match the C++ name used in the code. Although this method of building the tree works, it has several shortcomings. Any modification of the tree declaration requires a modification of the instantiation that can quickly become a headache for a large tree, especially when changing the level of structures. In addition, the C++ code using this tree will need to refer to each parameter by its full name, which can become rather long as the tree grows. The use of references and *(new …) avoids some nasty issues involving object registration, construction and destruction of temporaries.
The use of C++ classes allows a much simpler and neater
implementation of this kind of tree, in particular by adding methods that can
be specialized to a particular branch.
Following the same example as illustrated in the previous section, the creation of the tree using C++ classes first involves declarations in the header file:
extern spectrograph s800;
class ionchamber
{
private:
spectrograph* top;
public:
CTreeParameterArray de;
CTreeParameter sum;
public:
void Initialize(spectrograph* top, string name);
void Reset();
void SomeOtherMethod();
…
};
class focalplane
{
private:
spectrograph* top;
public:
ionchamber ic;
CTreeParameter other;
public:
void Initialize(spectrograph* top, string name);
void Reset();
};
class spectrograph
{
public:
focalplane fp;
target ta;
…
public:
spectrograph(string name)
void Reset();
};
The TreeParameters and branches of the tree need to be declared as public in order to be accessible anywhere in the C++ code. The private pointer top is used to point to the top of the tree, to access branches that are not sub-branches of the class. The extern declaration at the beginning of the file is needed to refer to the top class of the tree. The name of the object is unimportant. Each class can have any number of specialized methods for unpacking, calibrating, calculating and so on. The two methods illustrated in this example are used to initialize the whole tree after being constructed, and reset all parameters after each event.
The only constructor that needs to be implemented is the top of the tree. This is preferably done in a compile file as follows:
spectrograph::spectrograph(string name)
{
fp.Initialize(this, name+”.fp”);
ta.Initialize(this, name+”.ta”);
…
}
Note the use of the special pointer this to pass the address of the top of the tree to other classes. Also notice the way the name of the tree is propagated from top to branches using strings. This method is much more elegant and less prone to errors than when the whole branch name needs to be spelled out. The actual instantiation of the tree then simply looks like:
spectrograph s800(“s800”);
The same propagation method is used to reset the whole tree between each event, using the Reset method:
void spectrograph::Reset()
{
fp.Reset();
ta.Reset();
…
}
Therefore, simply calling the tree top method resets the whole tree:
s800.Reset();
The other classes in our small example are implemented as follows:
focalplane::Initialize(spectrograph* treetop, string name)
{
top = treetop;
ic.Initialize(top, name+”.ic”);
other.Initialize(name+”.other”, “someunit”);
}
focalplane::Reset()
{
ic.Reset();
other.Reset();
}
ionchamber::Initialize(spectrograph* treetop, string name)
{
top = treetop;
de.Initialize(name+”.de”, 4096, 0, 4095, “channel”, 16, 1);
sum.Initialize(name+”.sum”, 1000, 0, 999, “MeV”);
}
ionchamber::Reset()
{
de.Reset();
sum.Reset();
}
A tree built from C++ classes is therefore much easier
and quicker to maintain than a tree built with C structures. It also allows
more flexibility for implementing specialized functionalities that can be
embedded in the classes themselves. Finally, it makes the code much more
readable for other people than the writer.
In case arrays of classes need to be built, the name of the corresponding branches need to be prepared accordingly in the Initialize method. Suppose for example that the following tree is declared:
class hodoscope
{
public:
const int barNumber = 30;
scintillator bar[barNumber];
public:
hodoscope(string name);
Reset();
}
class scintillator {
private:
hodoscope* top;
public:
CTreeParameter left;
CTreeParameter right;
public:
Initialize(hodoscope* top, string name);
Reset();
}
Since this tree contains an array of classes of type scintillator, the proper implementation needs to contain a loop to initialize each element of the array:
hodoscope::hodoscope(string name)
{
char str[80];
for (int i=0; i<barNumber; i++) {
sprintf(str, “.bar%.2d”, i);
bar[i].Initialize(this, name+str);
}
a local character array is used to build the names of the
element arrays. In this particular example, the elements are referred to as hod.bar[i]
in the C++ code and hod.barxx in the Tcl interpreter where xx
are two digits corresponding to the element index.
The TreeParameter/TreeVariable package written by Daniel
Bazin has been completely integrated with standard SpecTcl. As a result of this integration,
many things that used to be
necessary to use TreeParameters are no longer needed.
A master include file includes all of the class definitions required for your use of TreeParameter and TreeVariables. To use these classes in your program, each program unit that needs to know the structure of these classes must:
#include <TreeParameter.h>
If you are converting older uses of TreeParameter to SpecTcl 3.0 and higher, you may get multiply defined references when you link your software. If you do, hunt for an remove the defnintions below, as they are no longer required:
CEvent* CTreeParameter::pEvent = NULL;
vector<CTreeParameter*> CTreeParameter::pSelf;
vector<CTreeVariable*> CTreeVariable::pSelf;
The TreeParameters are bound to the SpecTcl parameters only after SpecTcl has been started. This in effect creates SpecTcl parameters and binds them to their respective TreeParameters. Prior to SpecTcl 3.0, this action was done in the method CreateAnalysisPipeline, after all EventProcessors have been registered. Now this is done within SpecTcl proper. If you are migrating TreeParameter code to SpecTcl 3.0 or later, you should remove the line in red below from your copy of MySpecTclApp.cpp:
void
CSpecTclApp::CreateAnalysisPipeline(CAnalyzer& rAnalyzer)
{
RegisterEventProcessor(someEventProcessor);
…
CTreeParameter::BindParameters(rAnalyzer);
}
The TreeVariables similarly need to be linked to Tcl variables. This is done in the method BindTCLVariables, which, prior to version 3.0 you had to modify to add a call to CTreeVariable::BindVariables(rInterp). This is now done by SpecTcl for you. You should remove the line in red below if you are migrating old code to version 3.0 and higher:
void
CSpecTclApp::BindTCLVariables(CTCLInterpreter& rInterp)
{
CTclGrammerApp::BindTCLVariables(rInterp);
CTreeVariable::BindVariables(rInterp);
}
Finally, the Tcl GUI taking advantage of the tree structure of the parameters needs a few Tcl commands to operate. Prior to version 3.0, these were implemented in the AddCommands method. They are now implemented in SpecTcl’s base code and you may remove the lines in red below if you are migrating code from older versions of SpecTcl.
void
CSpecTclApp::AddCommands(CTCLInterpreter& rInterp)
{
CTclGrammerApp::AddCommands(rInterp);
CTreeVariableCommand*
m_treevariable;
m_treevariable
= new CTreeVariableCommand(&rInterp);
m_treevariable->Register();
CTreeParameterCommand*
m_treeparameter;
m_treeparameter
= new CTreeParameterCommand(&rInterp);
m_treeparameter->Register();
}
The TreeParameter class needs a pointer to the internal SpecTcl parameter array. prior to version 3.0 of SpecTcl, you had to add the following line to your the first event processor.
CTreeParameter::setEvent(rEvent);
This is now done by SpecTcl’s code. You may remove the line above from any
event processors you use.
One of the main motivations for implementing parameters as a tree structure is the possibility to order a large number of them in a logical manner. The Tcl/Tk framework from which SpecTcl is built offers great opportunities to use Graphical User Interfaces (GUI) for manipulating and using parameters arranged in such a structure. This is the goal of the SpecTcl GUI associated with the TreeParameter and TreeVariable objects. The standared SpecTclRC.tcl in the SpecTcl 3.0 and later Skeleton ($SpecTclHome/Skel/SpecTclRC.tcl) will start this GUI. If you are migrating from older versions of SpecTcl to 3.0 and later, you should modify your SpecTcl startup scripts so that the line below:
source
$SpecTclHome/contrib/treeparam/SpecTclGUI.tcl
Reads:
source
$SpecTclHome/Script/SpecTclGUI.tcl
Upon issuing this command, the window pictured below
appears. The default page is the Spectra page where the creation and
manipulation of spectra is performed. Other pages are manipulating Parameters,
Variables and Gates as indicated by the tabs.
The Spectra page allows the user to create, replace, modify, clear, duplicate and delete spectra in a convenient way. The typical sequence for creating a new spectrum is the following:
Select spectrum type (red outline)
Select data type (blue outline)
Select parameter using the tree menu
built from the parameter tree
Enter a spectrum name
Click Create/Replace to perform the
action
Once a parameter has been selected, the default values of the lower and higher limits, the number of bins of the future spectrum as well as the unit are shown as illustrated below. The lower limit, higher limit and number of bins can be edited before the spectrum is created. The name of the parameter can be accessed either through the menu or by directly typing its name in the entry box. This latter method can be faster for parameters with similar names. A check button located below the Create/Replace button and labeled “Array” can be used for defining individual spectra associated with a TreeParameter array. Note that this option is only available for 1D spectra.
The lower part of the spectrum manipulation page contains a scrollable list of defined spectra (see figure below). The columns are successively: name, type, x parameter, low, high limits and number of bins for x parameter, same for y parameter and finally gate condition if it exists. Each column is resizable and the list can be sorted alphanumerically according to any column by clicking on its name. By default the spectra are sorted by name.
Double clicking on any spectrum in the list automatically fills the entries above with its definitions. This is useful when deriving many similar spectra which differ only slightly like for instance with different gating conditions. One or more spectra can be selected in different ways for further action: a single click selects one spectrum at a time, holding the Shift key while clicking or dragging the mouse selects a range of adjacent spectra, and finally holding the Control key while clicking selects sparse spectra.
The bottom part of the window is composed of an updating button in case some spectra are defined by other means than the GUI (script or command line), and a spectrum mask entry allowing the user to selectively list the spectra according to a glob type string matching. The figure shows only spectra with names starting with the letter i. This feature is useful when a large number of spectra are defined.
The actions that can be performed on spectra selected in the list are the following:
Clear
Delete
Duplicate
Apply or remove gate condition
The check button labeled “All” bypasses the selection and applies the action to all spectra. The gate used in the gating condition can either be entered in the entry box or selected from a popup menu.
Finally, the upper right part of this page allows the user to save all definitions into a definition file written in Tcl and therefore editable. This file contains not only the spectrum definitions, but also the gate definitions, gating conditions, as well as TreeParameter and TreeVariable default values when they differ from their definition in the C++ code. This way the entire status of SpecTcl definitions can be restored from that only file.
When checked, the check button labeled “Failsafe”
indicates that a definition file named failsafe.tcl will be automatically
written each time a new definition is entered. This option is enabled by
default but can be disabled is it slows down the GUI response too much. The
other check button labeled “Cumulate” controls whether all previous spectra are
erased or not before a new definition file is loaded. It is unchecked by
default.
The parameter manipulation page allows the used to change the default values used in the spectrum definitions. The twenty identical rows can be used to set the lower and higher limits as well as the unit associated with any TreeParameter. The TreeParameter names can either be accessed through a cascading menu or typed directly into the entry box.
When the check button labeled “Array” is selected, all members of a TreeParameterArray have their characteristics set to the same values as the member specified in the Name entry box. This prevents the repetitive task of setting manually all members of parameter arrays.
After the characteristics of a parameter have been altered, the user has the possibility to transfer these modifications to all existing spectra defined from this parameter. This feature is useful when rescaling the range of parameters on an interesting region without having to redefine all the spectra that depend on that parameter. The buttons labeled “Change Spectra” perform this action, asking for confirmation for redefining the dependent spectra. Note that this action erases the contents of the spectra.
The variable manipulation page is in many ways very similar to the parameter manipulation page. The twenty identical rows give access to any of the variable tree through either a cascading menu or an entry box. The value as well as the unit of the variables can be set. As for the parameter manipulation page, a check button labeled “Array” allows the user to set an entire array to the same value. The Load and Save buttons located at the bottom of the page can be used to load or save only the variable to a Tcl file. Remember that the variables are also saved in the definition files, so this variable file can be used to update for instance a new set of calibrations to a previously written definition file. It is recommended to always save the spectra and gate definitions together with the variable definitions in order to avoid confusion.
The definition file also saves the configurations of the
parameter and variable manipulation pages, so that the relevant entries don’t
have to be filled each time SpecTcl is restarted.
The purpose of the gate manipulation page is to ease the definition of composite gates – gates that are a logical combination of “primitive” gates. In addition, the gate GUI can be used to alter the definition of existing gates, at least the simplest kind such as the Slice type. The “Gate Select” button is similar to the “Gate” button in the spectrum GUI and generates a menu of existing gates that can be entered in the gate dependency field located below. Alternatively, the gate names can be typed directly in the entry box, separated by space characters. A “Clear dependency” button is provided to quickly clear the dependency field, and the popup menu located to the right selects the type of gate to be generated. The primitive gate types are: Slice, Contour, Band, C2Band, GammaSlice, GammaBand and GammaContour. The composite gate types are: Not, Or and And. Please refer to the SpecTcl documentation for a description of the various gate types and their definition syntaxes. The name of the new gate can be typed in the entry box and the gate generated using the “Create/Replace” button.
Similar to the spectrum GUI, the list of gates can be sorted according to one of the columns by clicking on its title, and double clicking on a gate automatically fills the name and gate dependency fields. All or selected gates can be deleted using the corresponding buttons. The GUI asks for confirmation when deleting all gates.
Finally, the updating button located below the list
should be used after gates have been defined using Xamine or other means, and a
gate mask pattern similar to the one used in the spectrum GUI allows the user
to select which gates are displayed in the list.
The following is a non-exhaustive list of
changes/improvements for the future versions. More items can be added to this
wish list by sending an Email to fox@nscl.msu.edu,
or by registering an enhancement request on http://daqbugs.nscl.msu.edu/.
Version 2.1 of SpecTcl implements a new filtering
capability for reduction of large data sets. The format of the filtered data is
different from the original format, therefore a filtering unpacker has to be
added to the C++ code to take advantage of this feature. The new version of the
SpecTcl GUI will include a page to handle the filtering setup.
SpecTcl provides Tcl commands to write histograms to disk
using various formats. The new version of the GUI will include support for
saving all or selected spectra to a file and choose its format.
The feature allowing to rescale all spectra dependent on a given parameter (see Parameter manipulation, Chapter VI.b) doesn’t propagate the gate condition. This is a bug that will be fixed.