Alexander Kaplan

Email: <Alexander.Kaplan AT SPAMFREE desy DOT de>

Tel: -4321

Personal Runlists


VIM Cheat Sheet:

'='  indent current line
'=G' indent whole buffer


So long and how do I now write an MARLIN Processor that really works on (CALICE) data?

Please read this introduction first: How to use the new software

And then: Let us develop a simple processor that accesses each ADC value for each channel read out via the CRC Modules and creates a histogram for each.

(Sebastian: Please oh please no using in header files... Although nobody would include a processors header :)

#ifndef CRCDATA_HH
#define CRCDATA_HH


#include <marlin/Processor.h>
#include <EVENT/LCEvent.h>
#include <EVENT/LCCollection.h>
#include <AdcBlock.hh>

//root stuff
#include <TFile.h>
#include <TH1F.h>
#include <TObjArray.h>

using namespace std;
using namespace marlin;
using namespace lcio;
using namespace CALICE;


class CrcData : public marlin::Processor
{

public:
    CrcData();
    virtual ~CrcData();

    CrcData* newProcessor() { return new CrcData(); }

    virtual void init();
    virtual void processEvent(lcio::LCEvent*);
    virtual void end();

protected:

private:
    string _icName; //input collection name
    string _rfName; //output root file name
    
    TObjArray histArray;
    map<string,TH1F *> histMap;
};

#endif

OK, as you can read our Processor is derived from marlin::Processor and we implement a constructor, deconstructor, as well as the methods newProcessor(), init(),processEvent() and end(). We'll have a look at them later. Now for the private data members: We need an input collection (LCCollection), as all data in Marlin is provided as LCObjects stored in LCCollections. This collection has a name and we store it in the variable _icName. We create historgrams for each channel using ROOT and save them in a root file, whose name is stored in _rfName. The histograms we store in histArray. We give each channel read out a name and want to access the histograms by this channel name. For this we need the map histMap. Ok, questions? You might want to read some documentation for deeper understanding: e.g. for LCIO, Marlin and ROOT or better simply live with some ignorance for now.

Here's the implementation of the CRCData class' methods:

CrcData::CrcData()
    : marlin::Processor("CrcData")
{
    _description = "Creates a histogramm for each crate/slot/fe/chip/chan";
}

Well, this is trivial, we simply provide the name of our processor to the constructor of the parent class {marlin::Processor} and save a description of our processor in the variable _description (btw. defined in marlin:::Processor).

CrcData::~CrcData(){}

The deconstructor needs to be defined as well. (Sebastian: As far as I know this is not necessary. Because you should not do anything in the constructor than registering your steering parameters you never have to delete something in the destructor. What the user should do is new s.th. in init() and delete it in end(). Any guru opinion to this?)

void CrcData::init()
{
    registerProcessorParameter( "InputCollectionName",
                                "Name of the input collection",
                                _icName,
                                std::string( "CALDAQ_ADCCol" ) );
    
    registerProcessorParameter( "OutputRootFileName",
                                "Name of the root file to be created",
                                _rfName,
                                std::string( "/tmp/CrcData.root" ) );
}

(Sebastian: Okay, now I started the nitpicking... One should really start to use registerInputCollection and the equivalent for Output to make use of the type checking features of marlin.)

The init() method is called once, when the processor is loaded. In there we register two processor parameters, that can be provided in the marlin steering file. Even without looking at the documentation of the registerProcessorParameter() method of marlin::Processor you can easily guess that we give the following arguments (in this order): Name of the parameter, description, associated variable, default value.

void CrcData::processEvent(LCEvent* evt)
{
    LCCollection* inCol;

    try {
        inCol = evt->getCollection( _icName );
        
        for(int i=0; i < inCol->getNumberOfElements(); i++) {
            AdcBlock* adc = static_cast<AdcBlock*>( inCol->getElementAt(i) );

            int crate = adc->getCrateID();
            int slot  = adc->getSlotID();       
            int fe    = adc->getBoardFrontEnd();
            int chan  = adc->getMultiplexPosition();

            for(int chip=0; chip<12; chip++) {
                float adcval = adc->getAdcVal(chip);

                stringstream ss;
                ss  <<"C"<<crate<<" S"<<slot<<" FE"<<fe
                    <<" Chip"<<chip<<" Chan"<<chan;

                string histName = ss.str();

                if(histMap[histName]==0) {
                    histMap[histName] =
                        new TH1F(histName.c_str(),histName.c_str(),200,0,0);
                    histArray.Add( histMap[histName] ); 
                    cout<<_typeName<<"::processEvent(): "
                        <<"creating TH1F(\""<<histName<<"\",\""<<histName
                        <<"\","<<"200,0,0);"<<endl;
                }

                histMap[histName]->Fill( adcval );
            }
        }

    }
    catch( DataNotAvailableException & e ) {
        cout<</*this->typeName()*/_typeName<<"::processEvent():"
            << "data not available exception" << std::endl;
    }

}

(Sebastian: Instead of using _typeName one should use streamlog which does the same thing plus output levels.)

This is probably the most interesting part. Marlin runs over LCIO files that store events represented in C++ by objects of class type LCEvent. To give you the possibility to work with the data, Marlin calls the processEvent() method of each registered processor for each event contained in the LCIO file.

The data itself, e.g. the ADC value for one channel in your favorite detector, is represented by C++ objects whose type is derived from the class LCObject. These objects in turn are stored in container objects of type LCCollection, which can store any number of objects of type LCObject.

OK sounds all very complicated but let me explain: We are interested in ADC values which are stored in blocks represented by the class AdcBlock. They are contained in the a collection named "CALDAQ_ADCCol" (you might have above noticed that this is the default value for the processor parameter InputCollectionName represented by the variable _icName).

We are requesting the collection from the LCEvent object evt via the method getCollection(). Then we loop over all LCObjects stored in this collection. We have to convert the LCObjects to AdcBlock objects which is done via a static_cast in C++. Then we get the crate, slot, front end (fe) and channel (chan) and loop over all 12 chips (chip) in the AdcBlock. The ADC value it self is provided via the method AdcBlock::getAdcVal().

From the information we got we create a name for the channel using the scheme "Ccrate Sslot FEfe Chipchip Chanchan" and store it in the variable histName. This name is used as key to access the corresponding histogramm for a specific channel in the map histMap. If the map entry points to 0, the histogramm does not exist yet and is created. In addition, each histogram is also stored in histArray. The histogram then is filled via the Fill() method provided by the ROOT histograms of type TH1F.

void CrcData::end()
{
    //save histograms to disk!
    TFile f( _rfName.c_str(), "recreate" );
    histArray.Write();
    f.Close();
}

This method is called once for each registered processor when Marlin finished running over all events. Thus it is the right moment to write our histograms to a root file. This is quite easy as we just have to open a ROOT file (represented by f of type TFile) and call the Write() method of histArray. In the end we close the file.


CategoryHomepage

AlexKaplan (last edited 2009-10-21 12:10:56 by BenjaminLutz)