Accessing Spectra from SpecTcl Event Processors

This application note describes how to access spectra from enent processors in SpecTcl 2.2 and earlier. SpecTcl 3.0 will have a formal API to the SpecTcl internals. I anticipate maintaining the informal interface documented in this application note for at least a year following the release of 3.0 to allow users to migrate to the formal API.

This application note documents this procedure by walking through a sample event processor. The sample event processor uses the informal interface to SpecTcl's internals to maintain a histogram of the size of events. It does this manually rather than creating an event size parameter.

Background Information

To extend this example, you will need to become familiar with a few SpecTcl header files and the global storage that links the SpecTcl library together into the SpecTcl program. This section:

Global Storage

The global storage area is defined in the header Globals.h. The relevant storage for this discussion is a pointer to SpecTcl's histogramming engine. We will need to create a spectrum and make it known to the histogramming engine, as the histogramming engine also:

SpecTcl's event processing model is a pipeline. Each stage of the pipeline may contain other internal pipelines as well. The overall pipeline consists of the following elements:

The global storage area contains the following:

   
Name Type Meaning
gpUnpacker CEventUnpacker* The head of the event processing pipeline.
gpBufferDecoder CBufferDecoder* The object that is used to do the top level buffer decode
gpEventSinkPipeline CEventSinkPipeline* Head of the event sink pipeline
gpEventSink CEvntSink* Actually a pointer to the histogram engine for historical reasons
gpInterpreter TCLInterpreter* Object wrapping of the TCL interpreter.
gpEventSource CFile* The event source.

there are other variables defined in Globals.h however these are either not yet used or not useful to the application. For this application, we will only need the gpEventSink.

Relevant Headers

The following headers will be relevant.

The example

We will describe in turn the following:

Our strategy will be as follows:

SampleProcessor's header

The key parts of the header for SampleProcessor is shown below:

 #ifndef __SAMPLEPROCESSOR_H
 #define __SAMPLEPROCESSOR_H
 // Headers requried by this header:  

 #ifndef __EVENTPROCESSOR_H
 #include <EventProcessor.h>
 #endif

 #ifndef __HISTOTYPES_H
 #include <histotypes.h>
 #endif

 #ifndef __STL_STRING
 #include <string>
 #ifndef __STL_STRING
 #define __STL_STRING
 #endif
 #endif

 // Foward definitions:

 class CSpectrum;		// Need a spectrum in our event processro
                                // event processor.

 // These types are passed by reference to the event pipeline callout functions.

 class CAnalyzer;
 class CBufferDecoder;
 class CEvent;

 class SampleProcessor : public CEventProcessor {
 private:
  std::string   m_SpectrumName;  //!< The name of the spectrum we will create.
  CSpectrum*    m_pSpectrum;	 //!< Filled in by OnBegin.

 public:
  //! Constructor: parameterized by spectrum name.

 #ifdef HAVE_STD_NAMESPACE
  SampleProcessor(std::string SpectrumName);
 #else
  SampleProcessor(string SpectrumName);
 #endif

  // The following override the base class event pipeline callout functions.

  virtual Bool_t OnAttach(CAnalyzer& rAnalyzer); //!< Called when we join the pipeline.
  virtual Bool_t OnBegin(CAnalyzer&      rAnalyzer,
			 CBufferDecoder& rDecoder); //!< Called on begin of run.
  virtual Bool_t operator()(const Address_t    pEvent,
			    CEvent&            rEvent,
			    CAnalyzer&         rAnalyzer,
			    CBufferDecoder&    rDecoder); //!< Called each event.

 };

 #endif // include gaurd.

Note the use of forward references to minimize the include file needs of this header. The header only defines the class interfaces and their visibility.

SampleProcessor constructor and OnAttach

The constructor will only initialize storage. The constructor does not know when in the lifetime of SpecTcl it is being invoked. It is therefore dangerous for it to attempt to create Spectra as SpecTcl's data structures for maintaining spectra may not yet be fully initialized:

// Headers required

 #include <config.h>		// This is required for SpecTcl 3.0 and later!!
 #include "SampleProcessor.h"
 #include <Analyzer.h>
 #include <TCLAnalyzer.h>
 #include <BufferDecoder.h>
 #include <Event.h>
 #include <Histogrammer.h>
 #include <Spectrum1DL.h>	// pulls in Spectrum.h
 #include <Parameter.h>
 #include <SpectrumFactory.h>

 #include <Globals.h>		// This will change with the invention of the 3.0 API

 #include <iostream>

 // This is useful and handy for compilers that hide e.g. string in the std
 // namespace:

 #ifdef HAVE_STD_NAMESPACE
 using namespace std;
 #endif

 /*!
   Construct a SampleProcessor.
   The only thing we are going to do is initialize the member data:
   - m_SpectrumName   - the name the user passes in.
   - m_pSpectrum      - to null.
 */
 SampleProcessor::SampleProcessor(string SpectrumName) :
  m_SpectrumName(SpectrumName),
  m_pSpectrum(0)
 {
  
 }

The constructor saves the name of the spectrum we will create and initializes the pointer to the spectrum object to null so that the remainder of the class knows the spectrum has not yet been located.

When we are attached to the event processor (this happens only once), we will create a 1-d spectrum with 1024 channesl We'll generate a parameter to do this as well, and give it an id of 10000. The parameter will not be made known to SpecTcl as it won't reall be used.

   
 Bool_t 
 SampleProcessor::OnAttach(CAnalyzer& rAnalyzer)
 {
  // The code below will need to change as the 3.0 API gets implemented:

  CHistogrammer* pHistogrammer = static_cast<CHistogrammer*>(gpEventSink);

  // There's a factory of spectra we can use to create a spectrum and
  // assign it to a unique id.  Note that the CParameter constructor
  // will create a parameter named "Undefined" and give it the id
  // 10000.   We won't register this parameter with the histgoramming
  // engine so that we don't have to worry about duplication.

  CSpectrumFactory fact;
  CSpectrum* pSpectrum = fact.Create1D(m_SpectrumName,
				       keLong, 
				       CParameter("Deleted", 10000, ""),
				       1024);

  // If the spectrum already exists we just yell at stderr.
  // We find out about this because AddSpectrum will
  // throw an exception.

  try {
    pHistogrammer->AddSpectrum(*pSpectrum);
  }
  catch(...) {
    cerr << "Unable to add spectrum " << m_SpectrumName 
	 << " most likely this spectrum already exists\n";
  }
  return kfTRUE;
  
  
 }

Some notes:

The SampleProcessor OnBegin function

At the beginnig of the run, we locate the spectrum and save it in m_pSpectrum. We're only going to save it if:

   If we are not able to find a suitable match we complain at stderr.

 Bool_t
 SampleProcessor::OnBegin(CAnalyzer&      rAnalyzer,
	 		  CBufferDecoder& rDecoder)
 { 
  // This code needs to change as the 3.0 API gets published.

  CHistogrammer* pHistogrammer = static_cast<CHistogrammer*>(gpEventSink);

  // Locate the spectrum in the histogrammer:

  CSpectrum* pSpectrum = pHistogrammer->FindSpectrum(m_SpectrumName);
  if(pSpectrum) {
    if(pSpectrum->getSpectrumType() == ke1D) {
      m_pSpectrum = pSpectrum;
    } else {			// Not 1d
      cerr << "Found " << m_SpectrumName << " but it's not 1-d\n";
      m_pSpectrum = (CSpectrum*)kpNULL;
    }
  }
  else {			// No such spectrum...
    cerr << "No such spectrum " << m_SpectrumName << endl;
    m_pSpectrum = (CSpectrum*)kpNULL;
  }
  return kfTRUE;
 }

Notes:

The SampleProcessor::operator() function

We're just going to manually increment the histogram in a way that shows both how to get and set channels. The nasty thing to remember here is that indices are arrays of indexes. This was the only way to standardize the interface to a histogram regardless of histogram type. Another nasty is that the operator[] is a 'readonly' operator. This has to do with the fact that the underlying histogram can be any of several data types.

 
 Bool_t
 SampleProcessor::operator()(const Address_t    pEvent,
			    CEvent&            rEvent,
			    CAnalyzer&         rAnalyzer,
			    CBufferDecoder&    rDecoder)
 {

  UShort_t* p = static_cast<UShort_t*>(pEvent);
  UInt_t  nWords = (ULong_t)*p;
  CTclAnalyzer& rA((CTclAnalyzer&)rAnalyzer);
  rA.SetEventSize(nWords*sizeof(UShort_t));

  // Ensure the spectrum exists and is 1-d:

  if(m_pSpectrum) {
    if(m_pSpectrum->getSpectrumType() == ke1D) {
      if(nWords < m_pSpectrum->Dimension(0)) { // Check against X dimension

	// Note that indexing is a readonly operation due to 'typeness'

	ULong_t newValue = (*m_pSpectrum)[&nWords] + 1;
	m_pSpectrum->set(&nWords, newValue);
      }
    }
  }
  return kfTRUE;
 }

Notes: