4.2. The API and the spectrum dictionary

The Spectrum part of the API allows you to manipulate the spectrum dictionary. This allows you to define spectra of all supported spectrum types as well as to enter them in the spectrum dictionary and to iterate over that dictionary.

Another important capability is the ability to add spectrum dictionary observers. A spectrum dictionary observer is invoked whenever the spectrum dictionary changes either due to a spectrum creation or a spectrum deletion.

Suppose, for example, we have some imaging detector and want to make SpecTcl display an image (such as the projection of track in a TCP onto some plane). We could do this by defining an dummy 2-d spectrum (a 2-d spectrum on parameters that are never set), and filling in that spectrum with the appropiate image when requested.

Assuming we have an event processor that can put the projection data into some array. Doing this is could be as simple as an event processor that:

We will show the code for this assuming that:

Imagine, therefore a class like the one whose header is shown below to contain the projection. The object in this class will be stored globally in the object projection

Example 4-2. The projection class header


#ifndef PROJECTION_H
#define PROJECTION_H


class Projection {
private:
  void* m_projectionData;
  int   m_xdim;                           (1)
  int   m_ydim;

public:
  typedef struct  _Pixel {
    int    x, y;
    int    z;                            (2)
  } Pixel, *pPixel;

  
  
public:
  Projection();
  ~Projection();                        

  // Setting the projection data:
  
  void clear();                         (3)
  void setDimension(int x, int y);      (4)
  void setPixel(int x, int y, int z);   (5)

  // Iterating nonzero pixels of the projection.
  
  pPixel begin();                       (6)
  pPixel end();                         (7)
  pPixel next(pPixel p);                (8)
};

#endif
                
            
(1)
This class is just a placeholder, so don't take it too seriously. We assume it contains information about the dimensionality of the image and the pixels.
(2)
As described in the lead in to this example, we'll assume that we can get a set of non zero pixels defined by their coordinates in the image and intensities. This struct defines how this looks programmtically. We use a typedef to define both the Pixel types and a type that is a pointer, pPixel to Pixel.
(3)
This method is intended to be used by code we're not going to show. Calling it releases the image storage and should be done prior to creating a new projection image.
(4)
Once an image is cleared, the code that creates the image is going to call this to define the image size and allocate storage for it.
(5)
Again intended to be used by the images setting code to set the value of a pixel in the image.
(6)
This method is part of the iteration interface. It returns a pointer to the first pixel with a nonzero value. If there are no non-zero pixels, the result will be the same as that returned from the end.
(7)
This method is also part of the iteration interface. It returns the pointer that e.g. begin or next would return if iteration has completed. It is important to note that the end pointer does not point to a valid pixel and should not be dereferenced (very likely it's the nullptr).
(8)
Given a pixel pointer, returns a pointer to the next nonzero pixel. Note that if there are no more non zero pixels, this will return the same value that end returns. This means a typical iteration over the image might look like:


Projection*  someProjection=gProjection;
for (pPixel p = someProjection->begin(); p != pProjection->end(); p = pProjection->next()) {
    // do something with the pixel pointed to by p.
}
                    

Now that we have an API to program against, let's look at the header for our event processor. We're going to need to store a few bits of information:

Example 4-3. Header for an event processor that fills in a spectrum.


#ifndef FILLIMAGE_H
#define FILLIMAGE_H

#include <EventProcessor.h>
#include <string>
#include <TreeParameter.h>

class Projection;

class FillImage : public CEventProcessor
{
private:
  Projection&    m_rProjection;
  std::string        m_spectrumName;
  CTreeVariable      m_updateFlag;

public:
  FillImage(Projection& p, const char* spName, const char* flagName);

  Bool_t operator()(const Address_t pEvent, CEvent& rEvent,
                    CAnalyzer& rAnalyzer, CBufferDecoder& rDecoder
                    );



};

#endif
                
            

This should be reasonably self explanatory. The constructor will initialize the member variables and the operator() function call operator will get called for each event.

The constructor implementation is trivial:

Example 4-4. FillImage event processor constructor


#include "FillImage.h"
#include "projection.h"


FillImage:: FillImage(Projection& p, const char* spName, const char* flagName) :
  m_rProjection(p),
  m_spectrumName(spName),
  m_updateFlag(flagName, 0.0, "Flag")
{}

            

This should also be fairly self explanatory.

Now let's see what the function call operator looks like it'll need to:

Example 4-5. FillImage::operator() implementation


Bool_t
FillImage::operator()(const Address_t pEvent, CEvent& rEvent,
                      CAnalyzer& rAna, CBufferDecoder& rDecoder)
{
  if (m_updateFlag) {
    m_updateFlag = 0.0;                           (1)
    SpecTcl*   pApi  = SpecTcl::getInstance();
    CSpectrum* pSpec = pApi->FindSpectrum(m_spectrumName);  (2)

    if (pSpec) {
      CSpectrum2DL* p2d = dynamic_cast<CSpectrum2DL*>(pSpec); 
      if (p2d) {                                   (3)
        p2d->Clear();                           (4)
        Size_t xdim = p2d->Dimension(0);      
        Size_t ydim = p2d->Dimension(1);        (5)
        UInt_t indices[2];


        Projection::pPixel p;
        for (p = m_rProjection.begin(); p != m_rProjection.end(); p = m_rProjection.next(p)) {
          if ((p->x < xdim) && (p->y < ydim)) {  (6)
            indices[0] = p->x;
            indices[1] = p->y;
            p2d->set(indices, p->z);            (7)
          }
        }

      }
    }
  }
  return kfTRUE;                                  (8)
}
                
            
(1)
The if checks for a on zero flag value. If the flag is non zero, it is set back to zero so that this code only happens once..
(2)
Attempts to find the spectrum whose name we were constructed on. This method of the API will either return a pointer to the spectrum's CSpectrum object or it will return a nullptr. Only if the spectrum is found can we go further.
(3)
The dynamic cast converts the generic spectrum into a pointer into a 2-d longword spectrum. This is really only needed to ensure the spectrum is, in fact a 2-d longword spectrum. If it isn't the dynamic cast returns a nullptr and the remaining code is bypassead.
(4)
Only once wwe know we have a 2-d long spectrum, one we can work with, do we clear any prior image from the spectrum.
(5)
Since we need to ensure we don't write past the ends of the spectrum, we fetch the X and Y dimensions of the spectrum.
(6)
Clips the pixel against the limits of the spectrum. The assumption is that the x and y coordinates are actually unsigned.
(7)
Copies the pixel value into the spectrum.
(8)
Be sure to always return kfTRUE from event processor methods if you don't wan the event processing pipeline to abort.