7.2. Customizing SpecTcl to analyze your data

In the reference section we will describe SpecTcl's event processing model in some detail. Here we are just going to see what is minimally needed to analyze a data set.

SpecTcl expects you to have supplied code that can take a raw event and

The glue-point for providing this code is a logical pipeline of event processors. Event processors are instances of a class that is derived from the CEventProcessor class. The CEventProcessor base class provides several virtual functions that you can override.

A minimal event processor looks like this

Example 7-1. Minimal SpecTcl Event Processor

Header file:


class MyEventProcessor : public CEventProcessor
{
public:
  virtual Bool_t operator()(const Address_t pEvent,      (1)
                            CEvent&         rEvent,  (2)
                            CAnalyzer&      rAnalyzer,
                            CBufferDecoder& rDecoder);
};
            

Implementation file segment:


Bool_t
MyEventProcessor::operator()const Address_t pEvent,
                            CEvent&         rEvent,
                            CAnalyzer&      rAnalyzer,
                            CBufferDecoder& rDecoder)
{
  TranslatorPointer<UShort_t> p(*(rDecoder.getBufferTranslator()), pEvent); (1)
  UShort_t  nWords = *p++;
  CTclAnalyzer&      rAna((CTclAnalyzer&)rAnalyzer);
  rAna.SetEventSize(nWords*sizeof(UShort_t)); // Set event size.      (2)


  // Application specific unpacking code goes here...
  
    …                                                 (3)
    
  // End of application specific unpacking code.
  
  return kfTRUE;                                            (4)
} 


            

Let's look at the callouts in the example above:

(1)
The operator() function take as its first parameter, an const Address_t, a generic pointer. This generic pointer, named pEvent in the example, points to the start of the event operator() is being asked to analyze.
(2)
The operator() member function is expected to produce a set of parameters. If you are not using the treeparameter package, this is done by reserving slots in an array-like object called a CEvent. Specific parameters will be stored in specific slots in that array. The second parameter to operator() is a reference to that array and has been called rEvent.

Note that event processors may read previously unpacked data from the rEvent object as well as, or even instead of, processing the raw event.

(1)
SpecTcl is a byte order neutral program. While not supported by the NSCL, ports of SpecTcl to big-endian systems have been done. To be byte order neutral, is to recognize that data may be analyzed on a system with a different byte ordering than the system that acquired it.

NSCL data buffers include byte ordering information. Most data acquisition systems also have markers from which it is possible to deduce the byte ordering of the acquisition system. To do so is the job of an object called the buffer decoder.

A discussion of buffer decoders is beyond the scope of this chapter. For now, all you need to know is that you can use the buffer decoder passed to the event processor along with a pointer to the raw event to construct a TranslatorPointer object.

TranslatorPointer objects are pointer like objects (objects that can, for all purposes be treated like pointers), that know how to transform integer data in the format of the buffer (acquisition system endian-ness) into integer data of the format of the computer running the program.

Creating a TranslatorPointer and using it rather than a raw pointer, makes your event processor independent of potential byte order differences between the acquisition system and the system running SpecTcl.

(2)
I have mentioned that some event processor must tell the SpecTcl framework the size of the event. This allows the event processor to locate the next event in buffers of events that it processes. Usually the first event processor should do this, as it is closest to the raw event structure. There is no harm in more than one event processor providing this information if you are not certain which will be first.

The code shown informs SpecTcl of the event size by re-creating the rAnalyzer parameter as a CTclAnalyzer, The actual type of the object that is managing the flow of control for data analysis and then calling its SetEventSize method passing the number of bytes in the event.

(3)
At this point you would insert your own processing code. The content of this code would be application specific and would depend on the event format and the set of parameters to be extracted from the event.
(4)
Successful event processors must return kfTRUE. If they do not return kfTRUE, event processing will be aborted. By this I mean that no event processors later in the pipeline than this one will be run, and that the parameters unpacked into rEvent will not increment any histograms.

Important

A common problem is to forget to return any value from operator(). Many versions of g++ do not consider this an error (although in my opnion they should).

If you do this you may note that random events won't get processed.

With this background we can see that the minimum set of things you need to do to get started with SpecTcl on a new data set is to:

  1. Get a copy of the SpecTcl Skeleton.

  2. Add a new event processor to the skeleton.

  3. Build your customized SpecTcl

7.2.1. Getting the SpecTcl Skeleton

This section is necessarily somewhat NSCL dependent as we cannot know where and how SpecTcl is installed on systems at remote institutions. At the NSCL, SpecTcl is installed in /usr/opt/spectcl/version. Where version is the Spectcl version number (e.g. 3.2). In addition, /usr/opt/spectcl/current is a symbolic link that points to the version I recommend for use in new applications.

If these locations don't work for you, work with the people who installed SpecTcl at your institution to find out what should be substituted for /usr/opt/spectcl/current in the discussion below.

To get a copy of the SpecTcl Skeleton, execute the following commands:

Example 7-2. Copying the SpecTcl Skeleton to a new directory named spectcl


                    
$  mkdir spectcl
$  cd spectcl
$  cp /usr/opt/spectcl/current/Skel/* .
            
                

7.2.2. Adding a new event processor to the skeleton.

In the Minimal SpecTcl Event Processor example, we showed what a minimal event processor looks like. We now need to add this to the skeleton SpecTcl that you have copied.

Adding the new event processor requires:

  1. Creating the event processor header and implementation files.

  2. Remove the registrations of the sample event processors from the SpecTcl event processing pipeline in MySpecTclApp.cpp

  3. Adding code to the MySpecTclApp.cpp implementation file to creat an instance of your event processor and to add that instance to the event pipeline.

To promote re-use and modularity, I strongly suggest that you not add your event processor definitions to MySpecTclApp.cpp, but instead, create separate header and implementation files. Doing this will also help insulate you from changes to the MySpecTclApp.cpp file that may occur in future releases (I promise to try hard not to do this but if there is a compelling reason to, I will break that promise).

Using your favorite editor, create the header file for the event processor. For the sake of this example, we'll call the header MyEventProcessor.h.

Example 7-3. Example Header for minimal SpecTcl Event processor


// MyEventProcessor.h :

#ifndef __MYEVENTPROCESSOR_H
#define __MYEVENTPROCESSOR_H     (1)

#include <EventProcessor.h>  (2)

class MyEventProcessor : public CEventProcessor   (3)
{
public:
virtual Bool_t operator() (const Address_t, pEvent,
                           CEvent&          rEvent,
                           CAnalyzer&       rAnalyzer, (4)
                           CBufferDecoder&  rDecoder);
};

#endif
                
(1)
This constrution is called an include guard. Include gaurds ensure that a header file can be included more than once without any ill effects. Once includeded, the pre-processor symbol __EVENTPROCESSOR_H gets defined so that on subsequent attempts (accidental or intentional) to include, the entire header is #ifdef'd out of existence.

Good programming practice requires include gaurds in all header files.

(2)
The EventProcessor.h header defines the CEventProcessor base class. You must include this header in order for the compiler to know how to extend the base class with your event processor class.
(3)
This line informs the compiler that your MyEventProcessor class will be derived from the base class CEventProcessor. All event processors must be derived from that base class.
(4)
Declares that we will override the operator() member function of the CEventProcessor base class to define our own functionality. In this case, to produce our own set of parameters.

Following our Minimal SpecTcl Event Processor example, use your favorite editor to create the following implementation file:

Example 7-4. Implementation file for a minimal event processor


Bool_t
MyEventProcessor::operator()const Address_t pEvent,
                            CEvent&         rEvent,
                            CAnalyzer&      rAnalyzer,
                            CBufferDecoder& rDecoder)
{
  TranslatorPointer<UShort_t> p(*(rDecoder.getBufferTranslator()), pEvent);
  UShort_t  nWords = *p++;
  CTclAnalyzer&      rAna((CTclAnalyzer&)rAnalyzer);
  rAna.SetEventSize(nWords*sizeof(UShort_t)); // Set event size.


  // Application specific unpacking code goes here...
  
     (1)
    
  // End of application specific unpacking code.
  
  return kfTRUE;                                            
} 
                
(1)
Here you should fill in your code to take the event that is pointed to by p and pull parameters out of that event into slots in the rEvent array. In SpecTcl at Run-Time we will describe how to link those slots to SpecTcl parameters, and define histograms on those parameters.

Now that you have created your event processor, you must get SpecTcl to use it. Doing this requires making an instance of the class (think of classes as data types, and instances as variables), and add that instance to the event pipeline.

To do that we must edit the file MySpecTclApp.cpp that was part of the SpecTcl skeleton files we copied in. First locate the top where headers are being included and add:


#include "MyEventProcessor.h"
                
Note that the header filname is in quotations not angle brackets. Angle brackets only let the library directories be searched for headers while quotations also search the current working directory.

Some developers prefer to isolate their header files in a separate include directory. If you do that You should use angle brackets and add that directory to the set of directories searched for library headers. In the next section we will point out how to do that.

Now that MySpecTclApp.cpp has been informed of the existence of our event processor we need to remove the registration of the sample event processors from MySpecTclApp.cpp and instantiate/register our event processor.

Locate the function CMySpecTclApp::CreateAnalysisPipeline. Delete the body of that function and instead make it read:


                
   RegisterEventProcessor(*(new MyEventProcessor), "Mine");
   
            

The RegisterEventProcessor member function takes two parameters. The first is a reference to an intance of an event processor (more precisely an instance of a class derived from CEventProcessor). The second parameter is optional and allows you to associate a name with this event processor. Event processors will be executed in the order in which they are registered, forming a logical pipeline in which the results (rEvent) of previous event processors are available to subsequent ones.

The construction *(new MyEventProcessor) constructs a new instance of a MyEventProcessor and passes a reference to it to RegisterEventProcessor. While ordinarily, we'd have to worry about ensuring that the event processor was destroyed after it was used, in this case, it is likely the event processor will live for the lifetime of each run of SpecTcl so that's not a crucial issue.

7.2.3. Building the customized SpecTcl

Building a customized SpecTcl means that we will have to

  1. Edit the Makefile provided with the skeleton to make it compile your event processor.

  2. Add any C++ compiler and linker flags needed to the Makefile

  3. Run make to create your customized SpecTcl

  4. Fix any errors and re-run make as needed until you get a clean build.

The SpecTcl makefile is organized so that in most cases you will not need to know much about Make to edit it. It does this by using make include files to get most of the muscle needed to compile and link programs to SpecTcl, and Makefile macro definitions to learn what has to be made. Let's have a look at the top part of the skeleton Makefile:

Example 7-5. The top part of the SpecTcl Skeleton Makefile


INSTDIR=/usr/opt/spectcl/3.2   (1)
# Skeleton makefile for 3.1

include $(INSTDIR)/etc/SpecTcl_Makefile.include  (2)

#  If you have any switches that need to be added to the default c++ compilation
# rules, add them to the definition below:

USERCXXFLAGS=                      (3)

#  If you have any switches you need to add to the default c compilation rules,
#  add them to the defintion below:

USERCCFLAGS=$(USERCXXFLAGS)

#  If you have any switches you need to add to the link add them below:

USERLDFLAGS=                       (4)

#
#   Append your objects to the definitions below:
#

OBJECTS=MySpecTclApp.o            (5)
(1)
The INSTDIR definition describes where SpecTcl is installed. Note that since /usr/opt/spectcl/current is a symbolic link that may change with time, this should never be the value of the INSTDIR variable.
(2)
Starting with SpecTcl version 2.2, a concerted effort has been made to keep the version specific parts of the Makefile isolated from the skeleton Makefile. The intent of this is that to move from version to version, all you will need to do with the Makefile is change the value of INSTDIR (see above). This has been very successful with versions 2.2 through 3.2 (although you may need to make adjustments to source code as in the transition from versiomn 2.2 to 3.0 and later).

This separation has been accomplished by extracting the SpecTcl version specific information needed by make into include files. This line includes the top level of that include file hierarchy.

(3)
The SpecTcl make include files also define how to compile both C++ and C sources to produce object files. C++ files are assumed to have the file type .cpp, .cxx or .cc, while C source files are assumed to have the .c file type.

The compilation rules defined by these include files specify the compilation flags needed to locate the SpecTcl include files. The USERCXXFLAGS definition allows you to add to that set of flags.

Suppose, for example, you decided to put all of your header files in an include subdirectory to the source directory. You could define:


USERCXXFLAGS=Iinclude
                        
to ensure that directory will be searched for headers.

The variable USERCCFLAGS allows you to define flags for C compilations. Usually these are the same as those required for C++ compilations, and the default skeleton Makefile therefore defines that variable to be the value of USERCXXFLAGS.

(4)
Similarly, the SpecTcl Makefile includes define how to link SpecTcl's object files and libraries to create the tailored SpecTcl image. You may, however need to add flags to that set of flags. If so you can define USERLDFLAGS as needed.

Suppose you have a library in ~/mylib named libmylib.a that is needed by your event processor. You could write:


USERLDFLAGS=-L~/mylib -lmylib
                        
to incorporate that library in the link.

(5)
Finally, your SpecTcl will almost certainly require additional object files. In our case, the files MySpecTclApp.cpp and MyEventProcessor.cpp must be compiled and linked to form our tailored SpecTcl the OBJECTS containst the objects required by the tailored SpecTcl. Since all SpecTcl's need MySpecTclApp.cpp the object from that file is already specified. We need to add MyEventProcessor.o:

OBJECTS=MySpecTclApp.o MyEventProcessor.o
                        

Once the makefile has been edited:


$  make
                
will build your tailored SpecTcl. The Makefile will produce an executable image named SpecTcl in the same directory as the Makefile.