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:
Monitors the vale of a tree variable used to indicate we want a snapshot of the projection.
When an event is encountered and the tree variable is non zero locates the dummy spectrum and fills it in with the projection.
Resets the tree parameter to zero so that the spectrum remains static until next set by the user.
We will show the code for this assuming that:
Some other event processor has produced an object containing the projection.
That object has an iterator that provides x,y,z values. Where x and y are the coordinates of the spectrum in channels and z is the value to store. We'll assume that only the non-zero channels will be returned by the iterator.
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;int m_ydim; public: typedef struct _Pixel { int x, y; int z;
} Pixel, *pPixel; public: Projection(); ~Projection(); // Setting the projection data: void clear();
void setDimension(int x, int y);
void setPixel(int x, int y, int z);
// Iterating nonzero pixels of the projection. pPixel begin();
pPixel end();
pPixel next(pPixel p);
}; #endif
Pixel
types
and a type that is a pointer, pPixel
to
Pixel
.
end
.
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).
end
returns. This means a typical
iteration over the image might look like:
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:
A pointer or reference to the projection object we'll pull data from.
The name of a spectrum that we'll put the data into
The tree variable that will serve as the flag to tell us to update the image..
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:
Do nothing if the m_updateFlag variable is zero. Otherwise:
Reset the m_updateFlag to zero.
Find the spectrum, ensure it's a 2d spectrum and clear it.
Iterate over the pixels in the projection and set them in the spectrum if their coordinates fit. Clip the image if not.
Return kfTrue indicating event processing can continue with the next element of the pipeline.
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;SpecTcl* pApi = SpecTcl::getInstance(); CSpectrum* pSpec = pApi->FindSpectrum(m_spectrumName);
if (pSpec) { CSpectrum2DL* p2d = dynamic_cast<CSpectrum2DL*>(pSpec); if (p2d) {
p2d->Clear();
Size_t xdim = p2d->Dimension(0); Size_t ydim = p2d->Dimension(1);
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)) {
indices[0] = p->x; indices[1] = p->y; p2d->set(indices, p->z);
} } } } } return kfTRUE;
}
CSpectrum
object or it
will return a nullptr. Only if the spectrum
is found can we go further.