diff --git a/DataFormats/Detectors/CPV/CMakeLists.txt b/DataFormats/Detectors/CPV/CMakeLists.txt index acd427dca151e..408c28eeea50c 100644 --- a/DataFormats/Detectors/CPV/CMakeLists.txt +++ b/DataFormats/Detectors/CPV/CMakeLists.txt @@ -13,6 +13,7 @@ o2_add_library(DataFormatsCPV src/Digit.cxx src/Cluster.cxx src/TriggerRecord.cxx + src/CTF.cxx PUBLIC_LINK_LIBRARIES O2::CommonDataFormat O2::Headers O2::MathUtils @@ -25,4 +26,5 @@ o2_target_root_dictionary(DataFormatsCPV HEADERS include/DataFormatsCPV/CPVBlockHeader.h include/DataFormatsCPV/Digit.h include/DataFormatsCPV/Cluster.h - include/DataFormatsCPV/TriggerRecord.h) + include/DataFormatsCPV/TriggerRecord.h + include/DataFormatsCPV/CTF.h) diff --git a/DataFormats/Detectors/CPV/include/DataFormatsCPV/CTF.h b/DataFormats/Detectors/CPV/include/DataFormatsCPV/CTF.h new file mode 100644 index 0000000000000..55ed73bd7895a --- /dev/null +++ b/DataFormats/Detectors/CPV/include/DataFormatsCPV/CTF.h @@ -0,0 +1,57 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file CTF.h +/// \author ruben.shahoyan@cern.ch +/// \brief Definitions for CPV CTF data + +#ifndef O2_CPV_CTF_H +#define O2_CPV_CTF_H + +#include +#include +#include "DetectorsCommonDataFormats/EncodedBlocks.h" +#include "DataFormatsCPV/TriggerRecord.h" +#include "DataFormatsCPV/Cluster.h" + +namespace o2 +{ +namespace cpv +{ + +/// Header for a single CTF +struct CTFHeader { + uint32_t nTriggers = 0; /// number of triggers + uint32_t nClusters = 0; /// number of referred cells + uint32_t firstOrbit = 0; /// orbit of 1st trigger + uint16_t firstBC = 0; /// bc of 1st trigger + + ClassDefNV(CTFHeader, 1); +}; + +/// wrapper for the Entropy-encoded triggers and cells of the TF +struct CTF : public o2::ctf::EncodedBlocks { + + static constexpr size_t N = getNBlocks(); + enum Slots { BLC_bcIncTrig, + BLC_orbitIncTrig, + BLC_entriesTrig, + BLC_posX, + BLC_posZ, + BLC_energy, + BLC_status + }; + ClassDefNV(CTF, 1); +}; + +} // namespace cpv +} // namespace o2 + +#endif diff --git a/DataFormats/Detectors/CPV/include/DataFormatsCPV/Cluster.h b/DataFormats/Detectors/CPV/include/DataFormatsCPV/Cluster.h index 1aa9908db17dc..433d64353970c 100644 --- a/DataFormats/Detectors/CPV/include/DataFormatsCPV/Cluster.h +++ b/DataFormats/Detectors/CPV/include/DataFormatsCPV/Cluster.h @@ -21,11 +21,27 @@ class Geometry; /// \class Cluster /// \brief Contains CPV cluster parameters +constexpr float kMinX = -72.32; // Minimal coordinate in X direction +constexpr float kStepX = 0.0025; // digitization step in X direction +constexpr float kMinZ = -63.3; // Minimal coordinate in Z direction +constexpr float kStepZ = 0.002; // digitization step in Z direction +constexpr float kStepE = 1.; // Amplitude digitization step + class Cluster { + union CluStatus { + uint8_t mBits; + struct { + uint8_t multiplicity : 5; // Pad multiplicty, bits 0-4 + uint8_t module : 2; // module number, bits 5-6 + uint8_t unfolded : 1; // unfolded bit, bit 7 + }; + }; + public: Cluster() = default; + Cluster(char mult, char mod, char exMax, float x, float z, float e) : mMulDigit(mult), mModule(mod), mNExMax(exMax), mLocalPosX(x), mLocalPosZ(z), mEnergy(e) {} Cluster(const Cluster& clu) = default; ~Cluster() = default; @@ -42,13 +58,6 @@ class Cluster void setEnergy(float e) { mEnergy = e; } float getEnergy() const { return mEnergy; } - void getPosition(float& posX, float& posY, float& posZ) const - { - posX = mPosX; - posX = mPosY; - posZ = mPosZ; - } - void getLocalPosition(float& posX, float& posZ) const { posX = mLocalPosX; @@ -58,24 +67,51 @@ class Cluster // 0: was no unfolging, -1: unfolding failed char getModule() const { return mModule; } // CPV module of a current cluster - int getLabel() const { return mLabel; } //Index in MCContainer entry - void setLabel(int l) { mLabel = l; } - // 0: was no unfolging, -1: unfolding failed void setNExMax(char nmax = 1) { mNExMax = nmax; } char getNExMax() const { return mNExMax; } // Number of maxima found in cluster in unfolding: // 0: was no unfolging, -1: unfolding failed + // raw access for CTF encoding + uint16_t getPackedPosX() const { return uint16_t((mLocalPosX - kMinX) / kStepX); } + void setPackedPosX(uint16_t v) { mLocalPosX = kMinX + kStepX * v; } + + uint16_t getPackedPosZ() const { return uint16_t((mLocalPosZ - kMinZ) / kStepZ); } + void setPackedPosZ(uint16_t v) { mLocalPosZ = kMinZ + kStepZ * v; } + + uint8_t getPackedEnergy() const { return uint8_t(std::min(255, int(mEnergy / kStepE))); } + void setPackedEnergy(uint16_t v) { mEnergy = v * kStepE; } + + uint8_t getPackedClusterStatus() const + { + CluStatus s = {0}; + s.multiplicity = mMulDigit; + s.module = mModule; + s.unfolded = mNExMax > 1; + return s.mBits; + } + void setPackedClusterStatus(uint8_t v) + { + CluStatus s = {v}; + mMulDigit = s.multiplicity; + mModule = s.module; + mNExMax = s.unfolded ? 1 : 2; + } + + void setPacked(uint16_t posX, uint16_t posZ, uint8_t en, uint8_t status) + { + setPackedPosX(posX); + setPackedPosZ(posZ); + setPackedEnergy(en); + setPackedClusterStatus(status); + } + protected: char mMulDigit = 0; ///< Digit nultiplicity char mModule = 0; ///< Module number char mNExMax = -1; ///< number of (Ex-)maxima before unfolding - int mLabel = -1; ///< Ref to entry in MCTruthContainer with list of labels float mLocalPosX = 0.; ///< Center of gravity position in local module coordunates (phi direction) float mLocalPosZ = 0.; ///< Center of gravity position in local module coordunates (z direction) - float mPosX = 0.; ///< Center of gravity position in global coordinates - float mPosY = 0.; ///< Center of gravity position in global coordinates - float mPosZ = 0.; ///< Center of gravity position in global coordinates float mEnergy = 0.; ///< full energy of a cluster ClassDefNV(Cluster, 1); diff --git a/DataFormats/Detectors/CPV/include/DataFormatsCPV/Digit.h b/DataFormats/Detectors/CPV/include/DataFormatsCPV/Digit.h index 15eaf170e521b..8c5f83ea6c964 100644 --- a/DataFormats/Detectors/CPV/include/DataFormatsCPV/Digit.h +++ b/DataFormats/Detectors/CPV/include/DataFormatsCPV/Digit.h @@ -36,7 +36,7 @@ class Digit : public DigitBase /// \brief Main Digit constructor /// \param cell absId of a cell, amplitude energy deposited in a cell, time time measured in cell, label label of a /// particle in case of MC \return constructed Digit - Digit(short cell, float amplitude, int label); + Digit(unsigned short cell, float amplitude, int label); /// \brief Digit constructor from Hit /// \param CPV Hit @@ -93,8 +93,8 @@ class Digit : public DigitBase Digit& operator+=(const Digit& other); // /// \brief Absolute sell id - short getAbsId() const { return mAbsId; } - void setAbsId(short cellId) { mAbsId = cellId; } + unsigned short getAbsId() const { return mAbsId; } + void setAbsId(unsigned short cellId) { mAbsId = cellId; } /// \brief Energy deposited in a cell float getAmplitude() const { return mAmplitude; } @@ -109,9 +109,9 @@ class Digit : public DigitBase private: // friend class boost::serialization::access; - short mAbsId = 0; ///< pad index (absolute pad ID) - int mLabel = -1; ///< Index of the corresponding entry/entries in the MC label array - float mAmplitude = 0; ///< Amplitude + unsigned short mAbsId = 0; ///< pad index (absolute pad ID) + int mLabel = -1; ///< Index of the corresponding entry/entries in the MC label array + float mAmplitude = 0; ///< Amplitude ClassDefNV(Digit, 2); }; diff --git a/DataFormats/Detectors/CPV/include/DataFormatsCPV/RawFormats.h b/DataFormats/Detectors/CPV/include/DataFormatsCPV/RawFormats.h new file mode 100644 index 0000000000000..b1d3b4677d30d --- /dev/null +++ b/DataFormats/Detectors/CPV/include/DataFormatsCPV/RawFormats.h @@ -0,0 +1,63 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef ALICEO2_CPV_RAWFORMATS_H +#define ALICEO2_CPV_RAWFORMATS_H + +namespace o2 +{ + +namespace cpv +{ + +union PadWord { + uint32_t mDataWord; + struct { + uint32_t charge : 11; ///< Bits 0 - 10 : charge + uint32_t address : 6; ///< Bits 12 - 17 : address (0..47) + uint32_t dilogic : 4; ///< Bits 18 - 21 : dilogic (1..10) + uint32_t row : 6; ///< Bits 22 - 26 : raw (1..24) + uint32_t zero : 1; ///< Bits 27 - 27 : zeroed so we can distinguish it from the EoE + }; +}; + +union EoEWord { + uint32_t mDataWord; + struct { + uint32_t nword : 7; ///< Bits 0 - 6 : word counter (0...47) + uint32_t en : 11; ///< Bits 7 - 17 : event number -- not used + uint32_t dilogic : 4; ///< Bits 18 - 21 : dilogic (1..10) + uint32_t row : 6; ///< Bits 22 - 26 : raw (1..24) + uint32_t checkbit : 1; ///< Bits 27 - 27 : bit 27 is always 1 by definition of EoE + }; +}; + +union SegMarkerWord { + uint32_t mDataWord; + struct { + uint32_t row : 8; ///< Bits 0 - 7 : segment 0,1,2 charge + uint32_t nwords : 12; ///< Bits 8 - 19 : number of words in the segment + uint32_t marker : 12; ///< Bits 20 - 31: ab0 the segment marker word + }; +}; + +union RowMarkerWord { + uint32_t mDataWord; + struct { + uint32_t marker : 16; ///< Bits 0,15); //the marker word + uint32_t nwords : 16; ///< Bits 16 - 31 : number of words written after row marker (digits and EoE) + }; +}; + +} // namespace cpv + +} // namespace o2 + +#endif diff --git a/DataFormats/Detectors/CPV/src/CTF.cxx b/DataFormats/Detectors/CPV/src/CTF.cxx new file mode 100644 index 0000000000000..cd082fe17df8e --- /dev/null +++ b/DataFormats/Detectors/CPV/src/CTF.cxx @@ -0,0 +1,15 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include +#include +#include "DataFormatsCPV/CTF.h" + +using namespace o2::cpv; diff --git a/DataFormats/Detectors/CPV/src/DataFormatsCPVLinkDef.h b/DataFormats/Detectors/CPV/src/DataFormatsCPVLinkDef.h index 7b08dd67ba51f..6313759502591 100644 --- a/DataFormats/Detectors/CPV/src/DataFormatsCPVLinkDef.h +++ b/DataFormats/Detectors/CPV/src/DataFormatsCPVLinkDef.h @@ -22,4 +22,8 @@ #pragma link C++ class std::vector < o2::cpv::Cluster> + ; #pragma link C++ class std::vector < o2::cpv::TriggerRecord> + ; +#pragma link C++ struct o2::cpv::CTFHeader + ; +#pragma link C++ struct o2::cpv::CTF + ; +#pragma link C++ class o2::ctf::EncodedBlocks < o2::cpv::CTFHeader, 7, uint32_t> + ; + #endif diff --git a/DataFormats/Detectors/CPV/src/Digit.cxx b/DataFormats/Detectors/CPV/src/Digit.cxx index a2e211b167fda..b0968d45d6bb9 100644 --- a/DataFormats/Detectors/CPV/src/Digit.cxx +++ b/DataFormats/Detectors/CPV/src/Digit.cxx @@ -17,7 +17,7 @@ using namespace o2::cpv; ClassImp(Digit); -Digit::Digit(short absId, float amplitude, int label) +Digit::Digit(unsigned short absId, float amplitude, int label) : DigitBase(0), mAmplitude(amplitude), mAbsId(absId), mLabel(label) { } diff --git a/Detectors/CPV/base/CMakeLists.txt b/Detectors/CPV/base/CMakeLists.txt index e3ad172022a44..40286d5f9f733 100644 --- a/Detectors/CPV/base/CMakeLists.txt +++ b/Detectors/CPV/base/CMakeLists.txt @@ -9,9 +9,14 @@ # submit itself to any jurisdiction. o2_add_library(CPVBase - SOURCES src/Geometry.cxx src/Hit.cxx src/CPVSimParams.cxx + SOURCES src/Geometry.cxx + src/Hit.cxx + src/CPVSimParams.cxx + src/RCUTrailer.cxx PUBLIC_LINK_LIBRARIES O2::SimulationDataFormat) o2_target_root_dictionary(CPVBase HEADERS include/CPVBase/Geometry.h - include/CPVBase/Hit.h include/CPVBase/CPVSimParams.h) + include/CPVBase/Hit.h + include/CPVBase/CPVSimParams.h + include/CPVBase/RCUTrailer.h) diff --git a/Detectors/CPV/base/include/CPVBase/CPVSimParams.h b/Detectors/CPV/base/include/CPVBase/CPVSimParams.h index 501902c0ff518..bc827cb64a5f0 100644 --- a/Detectors/CPV/base/include/CPVBase/CPVSimParams.h +++ b/Detectors/CPV/base/include/CPVBase/CPVSimParams.h @@ -39,14 +39,14 @@ struct CPVSimParams : public o2::conf::ConfigurableParamHelper { //Parameters used in electronic noise calculation and thresholds (Digitizer) bool mApplyDigitization = true; ///< if energy digitization should be applied - float mZSthreshold = 0.005; ///< Zero Suppression threshold + float mZSthreshold = 0.01; ///< Zero Suppression threshold float mADCWidth = 0.005; ///< Widht of ADC channel used for energy digitization - float mNoise = 0.03; ///< charge noise in one pad + float mNoise = 0.01; ///< charge noise in one pad float mCoeffToNanoSecond = 1.e+9; ///< Conversion for time units float mSortingDelta = 0.1; ///< used in sorting clusters inverse sorting band in cm //Parameters used in clusterization - float mDigitMinEnergy = 0.005; ///< Minimal amplitude of a digit to be used in cluster + float mDigitMinEnergy = 0.01; ///< Minimal amplitude of a digit to be used in cluster float mClusteringThreshold = 0.050; ///< Seed digit minimal amplitude float mUnfogingEAccuracy = 1.e-3; ///< Accuracy of energy calculation in unfoding prosedure (GeV) float mUnfogingXZAccuracy = 1.e-1; ///< Accuracy of position calculation in unfolding procedure (cm) diff --git a/Detectors/CPV/base/include/CPVBase/Geometry.h b/Detectors/CPV/base/include/CPVBase/Geometry.h index 922d1d5064097..99df0860473ef 100644 --- a/Detectors/CPV/base/include/CPVBase/Geometry.h +++ b/Detectors/CPV/base/include/CPVBase/Geometry.h @@ -28,6 +28,22 @@ class Geometry static constexpr short kNumberOfCPVPadsZ = 60; static constexpr float kCPVPadSizePhi = 1.13; static constexpr float kCPVPadSizeZ = 2.1093; + //for hwaddress + static constexpr short kNPAD = 48; + static constexpr short kNDilogic = 10; + static constexpr short kNRow = 16; + static constexpr short kNDDL = 4; + + /// Available numbering schems: + /// relative pad coordinates + /// relId[3]={Module, phi col, z row} where Module=1..3, phi col=0..127, z row=0..59 + /// Absolute pad coordunate + /// absId=0..128*60*3=23040 + /// Raw addresses: + /// DDL corresponds to one module: ddl=Module-1 + /// each module consist of 16 columns of width 8 pads: row=0..15 + /// Each column consists of 10 dilogics (in z direction) dilogic=0...9 + /// Ecah dilogic contains 8*6 pads: hwaddress=0...48 /// /// Default constructor. @@ -55,7 +71,7 @@ class Geometry // = 1 are neighbour // = 2 are not neighbour but do not continue searching // =-1 are not neighbour, continue searching, but do not look before d2 next time - static int areNeighbours(short absId1, short absId2); + static short areNeighbours(unsigned short absId1, unsigned short absId2); /// /// \return AbsId index of the CPV cell @@ -64,14 +80,17 @@ class Geometry /// \param strip: strip number // \param cell: cell in strip number /// - static short relToAbsId(char moduleNumber, int iphi, int iz); - static bool absToRelNumbering(short absId, short* relid); - static char absIdToModule(short absId); - static void absIdToRelPosInModule(short absId, float& x, float& z); - static bool relToAbsNumbering(const short* relId, short& absId); + static unsigned short relToAbsId(short moduleNumber, short iphi, short iz); + static bool absToRelNumbering(unsigned short absId, short* relid); + static short absIdToModule(unsigned short absId); + static void absIdToRelPosInModule(unsigned short absId, float& x, float& z); + static bool relToAbsNumbering(const short* relId, unsigned short& absId); + + static void hwaddressToAbsId(short ddl, short row, short dilogic, short hw, unsigned short& absId); + static void absIdToHWaddress(unsigned short absId, short& ddl, short& row, short& dilogic, short& hw); - static int getTotalNPads() { return kNumberOfCPVPadsPhi * kNumberOfCPVPadsZ * 3; } - static bool IsPadExists(short absId) + static unsigned short getTotalNPads() { return kNumberOfCPVPadsPhi * kNumberOfCPVPadsZ * 4; } + static bool IsPadExists(unsigned short absId) { return absId > 0 && absId <= getTotalNPads(); } // TODO: evaluate from real geometry diff --git a/Detectors/CPV/base/include/CPVBase/RCUTrailer.h b/Detectors/CPV/base/include/CPVBase/RCUTrailer.h new file mode 100644 index 0000000000000..d6f8967c108e9 --- /dev/null +++ b/Detectors/CPV/base/include/CPVBase/RCUTrailer.h @@ -0,0 +1,186 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +#ifndef ALICEO2_CPV_RCUTRAILER_H +#define ALICEO2_CPV_RCUTRAILER_H + +#include +#include +#include +#include +#include +#include "Rtypes.h" + +namespace o2 +{ + +namespace cpv +{ + +/// \class RCUTrailer +/// \brief Information stored in the RCU trailer +/// \ingroup CPVbase +/// +/// The RCU trailer can be found at the end of +/// the payload and contains general information +/// sent by the SRU. +class RCUTrailer +{ + public: + /// \class Error + /// \brief Error handling of the + class Error : public std::exception + { + public: + /// \enum ErrorType_t + /// \brief Error codes for different error types + enum class ErrorType_t { + DECODING_INVALID, ///< Invalid words during decoding + SIZE_INVALID, ///< Invalid trailer size + SAMPLINGFREQ_INVALID, ///< Invalid sampling frequency + L1PHASE_INVALID ///< Invalid L1 phase + }; + + /// \brief Constructor + /// \param errtype Code of the error type + /// \param message corresponding error message + /// + /// Initializing the error with error code and message. + /// To be called when the exception is raised. + Error(ErrorType_t errtype, const char* message) : mErrorType(errtype), mErrorMessage(message) {} + + /// \brief Destructor + ~Error() noexcept override = default; + + /// \brief Access to the error message + /// \return Error message related to the exception type + const char* what() const noexcept override { return mErrorMessage.data(); } + + /// \brief Access to error code + /// \return Error code of the exception type + ErrorType_t getErrorType() const noexcept { return mErrorType; } + + private: + ErrorType_t mErrorType; ///< Type of the error + std::string mErrorMessage; ///< Error Message + }; + + /// \brief Constructor + RCUTrailer() = default; + + /// \brief destructor + ~RCUTrailer() = default; + + /// \brief Reset the RCU trailer + /// + /// Setting all values to 0 + void reset(); + + /// \brief Prints the contents of the RCU trailer data + /// \param stream stream the trailer has to be put on + void printStream(std::ostream& stream) const; + + /// \brief Decode RCU trailer from the 32-bit words in the raw buffer + /// \param buffer Raw buffer from which to read the trailer + /// + /// Read the RCU trailer according to the RCU formware version + /// specified in CDH. + void constructFromRawPayload(const gsl::span payload); + + unsigned int getFECErrorsA() const { return mFECERRA; } + unsigned int getFECErrorsB() const { return mFECERRB; } + unsigned short getErrorsG2() const { return mERRREG2; } + unsigned int getErrorsG3() const { return mERRREG3; } + unsigned short getActiveFECsA() const { return mActiveFECsA; } + unsigned short getActiveFECsB() const { return mActiveFECsB; } + unsigned int getAltroCFGReg1() const { return mAltroCFG1; } + unsigned int getAltroCFGReg2() const { return mAltroCFG2; } + int getRCUID() const { return mRCUId; } + unsigned int getTrailerSize() const { return mTrailerSize; } + unsigned int getPayloadSize() const { return mPayloadSize; } + unsigned char getFirmwareVersion() const { return mFirmwareVersion; } + + unsigned short getNumberOfChannelAddressMismatch() const { return (mERRREG3 & 0xFFF); } + unsigned short getNumberOfChannelLengthMismatch() const { return ((mERRREG3 >> 12) & 0x1FFF); } + unsigned char getBaselineCorrection() const { return mAltroCFG1 & 0xF; } + bool getPolarity() const { return (mAltroCFG1 >> 4) & 0x1; } + unsigned char getNumberOfPresamples() const { return (mAltroCFG1 >> 5) & 0x3; } + unsigned char getNumberOfPostsamples() const { return (mAltroCFG1 >> 7) & 0xF; } + bool hasSecondBaselineCorr() const { return (mAltroCFG1 >> 11) & 0x1; } + unsigned char getGlitchFilter() const { return (mAltroCFG1 >> 12) & 0x3; } + unsigned char getNumberOfNonZeroSuppressedPostsamples() const { return (mAltroCFG1 >> 14) & 0x7; } + unsigned char getNumberOfNonZeroSuppressedPresamples() const { return (mAltroCFG1 >> 17) & 0x3; } + bool hasZeroSuppression() const { return (mAltroCFG1 >> 19) & 0x1; } + bool getNumberOfAltroBuffers() const { return (mAltroCFG2 >> 24) & 0x1; } + unsigned char getNumberOfPretriggerSamples() const { return (mAltroCFG2 >> 20) & 0xF; } + unsigned short getNumberOfSamplesPerChannel() const { return (mAltroCFG2 >> 10) & 0x3FF; } + bool isSparseReadout() const { return (mAltroCFG2 >> 9) & 0x1; } + + /// \brief Access to the sampling time + /// \return Sampling time in seconds. + /// \throw Error if the RCU trailer was not properly initializied + double getTimeSample() const; + + /// \brief set time sample + /// \param timesample Time sample (in ns) + void setTimeSample(double timesample); + + /// \brief Access to the L1 phase + /// \return L1 phase w.r.t to the LHC clock + double getL1Phase() const; + + /// \brief Set the L1 phase + /// \param l1phase L1 phase (in ns) + void setL1Phase(double l1phase); + + void setFECErrorsA(unsigned int value) { mFECERRA = value; } + void setFECErrorsB(unsigned int value) { mFECERRB = value; } + void setErrorsG2(unsigned short value) { mERRREG2 = value; } + void setErrorsG3(unsigned int value) { mERRREG3 = value; } + void setActiveFECsA(unsigned short value) { mActiveFECsA = value; } + void setActiveFECsB(unsigned short value) { mActiveFECsB = value; } + void setAltroCFGReg1(unsigned int value) { mAltroCFG1 = value; } + void setAltroCFGReg2(unsigned int value) { mAltroCFG2 = value; } + void setFirmwareVersion(unsigned char version) { mFirmwareVersion = version; } + void setPayloadSize(unsigned int size) { mPayloadSize = size; } + + /// \brief checlks whether the RCU trailer is initialzied + /// \return True if the trailer is initialized, false otherwise + bool isInitialized() const { return mIsInitialized; } + + std::vector encode() const; + + static RCUTrailer constructFromPayloadWords(const gsl::span payloadwords); + static RCUTrailer constructFromPayload(const gsl::span payload); + + private: + int mRCUId = -1; ///< current RCU identifier + unsigned char mFirmwareVersion = 0; ///< RCU firmware version + unsigned int mTrailerSize = 0; ///< Size of the trailer (in number of 32 bit words) + unsigned int mPayloadSize = 0; ///< Size of the payload (in nunber of 32 bit words) + unsigned int mFECERRA = 0; ///< contains errors related to ALTROBUS transactions + unsigned int mFECERRB = 0; ///< contains errors related to ALTROBUS transactions + unsigned short mERRREG2 = 0; ///< contains errors related to ALTROBUS transactions or trailer of ALTRO channel block + unsigned int mERRREG3 = 0; ///< contains number of altro channels skipped due to an address mismatch + unsigned short mActiveFECsA = 0; ///< bit pattern of active FECs in branch A + unsigned short mActiveFECsB = 0; ///< bit pattern of active FECs in branch B + unsigned int mAltroCFG1 = 0; ///< ALTROCFG1 register + unsigned int mAltroCFG2 = 0; ///< ALTROCFG2 and ALTROIF register + bool mIsInitialized = false; ///< Flag whether RCU trailer is initialized for the given raw event + + ClassDefNV(RCUTrailer, 1); +}; + +std::ostream& operator<<(std::ostream& stream, const RCUTrailer& trailer); + +} // namespace cpv + +} // namespace o2 + +#endif \ No newline at end of file diff --git a/Detectors/CPV/base/src/Geometry.cxx b/Detectors/CPV/base/src/Geometry.cxx index f80c0387183a8..f9f27f57ffdb9 100644 --- a/Detectors/CPV/base/src/Geometry.cxx +++ b/Detectors/CPV/base/src/Geometry.cxx @@ -9,39 +9,40 @@ // or submit itself to any jurisdiction. #include "CPVBase/Geometry.h" +#include "FairLogger.h" using namespace o2::cpv; ClassImp(Geometry); -short Geometry::relToAbsId(char moduleNumber, int iphi, int iz) +unsigned short Geometry::relToAbsId(short moduleNumber, short iphi, short iz) { //converts module number, phi and z coordunates to absId - return kNumberOfCPVPadsPhi * kNumberOfCPVPadsZ * (moduleNumber - 1) + kNumberOfCPVPadsZ * (iz - 1) + iphi; + return kNumberOfCPVPadsPhi * kNumberOfCPVPadsZ * (moduleNumber - 1) + kNumberOfCPVPadsZ * iz + iphi; } -bool Geometry::absToRelNumbering(short absId, short* relid) +bool Geometry::absToRelNumbering(unsigned short absId, short* relid) { // Converts the absolute numbering into the following array // relid[0] = CPV Module number 1:fNModules // relid[1] = Column number inside a CPV module (Phi coordinate) // relid[2] = Row number inside a CPV module (Z coordinate) - short nCPV = kNumberOfCPVPadsPhi * kNumberOfCPVPadsZ; - relid[0] = (absId - 1) / nCPV + 1; + const short nCPV = kNumberOfCPVPadsPhi * kNumberOfCPVPadsZ; + relid[0] = absId / nCPV + 1; absId -= (relid[0] - 1) * nCPV; - relid[2] = absId / kNumberOfCPVPadsZ + 1; - relid[1] = absId - (relid[2] - 1) * kNumberOfCPVPadsZ; + relid[1] = absId / kNumberOfCPVPadsZ; + relid[2] = absId % kNumberOfCPVPadsZ; return true; } -char Geometry::absIdToModule(short absId) +short Geometry::absIdToModule(unsigned short absId) { - return 1 + (absId - 1) / (kNumberOfCPVPadsPhi * kNumberOfCPVPadsZ); + return 1 + absId / (kNumberOfCPVPadsPhi * kNumberOfCPVPadsZ); } -int Geometry::areNeighbours(short absId1, short absId2) +short Geometry::areNeighbours(unsigned short absId1, unsigned short absId2) { // Gives the neighbourness of two digits = 0 are not neighbour but continue searching @@ -80,23 +81,69 @@ int Geometry::areNeighbours(short absId1, short absId2) } return 0; } -void Geometry::absIdToRelPosInModule(short absId, float& x, float& z) +void Geometry::absIdToRelPosInModule(unsigned short absId, float& x, float& z) { //Calculate from absId of a cell its position in module short relid[3]; absToRelNumbering(absId, relid); - x = (relid[1] - kNumberOfCPVPadsPhi / 2 - 0.5) * kCPVPadSizePhi; - z = (relid[2] - kNumberOfCPVPadsZ / 2 - 0.5) * kCPVPadSizeZ; + x = (relid[1] - kNumberOfCPVPadsPhi / 2 + 0.5) * kCPVPadSizePhi; + z = (relid[2] - kNumberOfCPVPadsZ / 2 + 0.5) * kCPVPadSizeZ; } -bool Geometry::relToAbsNumbering(const short* relId, short& absId) +bool Geometry::relToAbsNumbering(const short* relId, unsigned short& absId) { absId = (relId[0] - 1) * kNumberOfCPVPadsPhi * kNumberOfCPVPadsZ + // the offset of PHOS modules - (relId[2] - 1) * kNumberOfCPVPadsZ + // the offset along phi - relId[1]; // the offset along z + relId[1] * kNumberOfCPVPadsZ + // the offset along phi + relId[2]; // the offset along z return true; } +void Geometry::hwaddressToAbsId(short ddl, short row, short dilog, short hw, unsigned short& absId) +{ + + short relid[3] = {short(ddl + 1), short(8 * row + hw % 8), short(6 * dilog + hw / 8)}; + + relToAbsNumbering(relid, absId); +} + +void Geometry::absIdToHWaddress(unsigned short absId, short& ddl, short& row, short& dilogic, short& hw) +{ + // Convert absId to hw address + // Arguments: w32,ddl,row,dilogic,address where to write the results + + short relid[3]; + absToRelNumbering(absId, relid); + + ddl = relid[0] - 1; // DDL# 0..2 + row = relid[1] / 8; // row# 0..16 + dilogic = relid[2] / 6; // Dilogic# 0..10 + hw = relid[1] % 8 + 8 * (relid[2] % 6); // Address 0..47 + + if (hw < 0 || hw > kNPAD) { + LOG(ERROR) << "Wrong hw address: hw=" << hw << " > kNPAD=" << kNPAD; + hw = 0; + dilogic = 0; + row = 0; + ddl = 0; + return; + } + if (dilogic < 0 || dilogic > kNDilogic) { + LOG(ERROR) << "Wrong dilogic address: dilogic=" << dilogic << " > kNDilogic=" << kNDilogic; + hw = 0; + dilogic = 0; + row = 0; + ddl = 0; + return; + } + if (row < 0 || row > kNRow) { + LOG(ERROR) << "Wrong row address: row=" << row << " > kNRow=" << kNRow; + hw = 0; + dilogic = 0; + row = 0; + ddl = 0; + return; + } +} diff --git a/Detectors/CPV/base/src/RCUTrailer.cxx b/Detectors/CPV/base/src/RCUTrailer.cxx new file mode 100644 index 0000000000000..a7c8e075a2065 --- /dev/null +++ b/Detectors/CPV/base/src/RCUTrailer.cxx @@ -0,0 +1,249 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +#include +#include +#include +#include +#include "CommonConstants/LHCConstants.h" +#include "CPVBase/RCUTrailer.h" + +using namespace o2::cpv; + +void RCUTrailer::reset() +{ + mRCUId = -1; + mFirmwareVersion = 0; + mTrailerSize = 0; + mPayloadSize = 0; + mFECERRA = 0; + mFECERRB = 0; + mERRREG2 = 0; + mERRREG3 = 0; + mActiveFECsA = 0; + mActiveFECsB = 0; + mAltroCFG1 = 0; + mAltroCFG2 = 0; + mIsInitialized = false; +} + +void RCUTrailer::constructFromRawPayload(const gsl::span payloadwords) +{ + + // Code below assumes uint32_t span instead of current char!!!!!! + // reset(); + // int index = payloadwords.size(); + // auto word = payloadwords[--index]; + // if ((word >> 30) != 3) { + // throw Error(Error::ErrorType_t::DECODING_INVALID, "Last RCU trailer word not found!"); + // } + // mFirmwareVersion = (word >> 16) & 0xFF; + + // mRCUId = (int)((word >> 7) & 0x1FF); + // int trailerSize = (word & 0x7F); + + // if (trailerSize < 2) { + // throw Error(Error::ErrorType_t::SIZE_INVALID, fmt::format("Invalid trailer size found (%d bytes) !", trailerSize * 4).data()); + // } + // mTrailerSize = trailerSize; + + // trailerSize -= 2; // Cut first and last trailer words as they are handled separately + // for (; trailerSize > 0; trailerSize--) { + // word = payloadwords[--index]; + // if ((word >> 30) != 2) { + // std::cerr << "Missing RCU trailer identifier pattern!\n"; + // continue; + // } + // int parCode = (word >> 26) & 0xF; + // int parData = word & 0x3FFFFFF; + // switch (parCode) { + // case 1: + // // ERR_REG1 + // mFECERRA = ((parData >> 13) & 0x1FFF) << 7; + // mFECERRB = ((parData & 0x1FFF)) << 7; + // break; + // case 2: + // // ERR_REG2 + // mERRREG2 = parData & 0x1FF; + // break; + // case 3: + // // ERR_REG3 + // mERRREG3 = parData & 0x1FFFFFF; + // break; + // case 4: + // // FEC_RO_A + // mActiveFECsA = parData & 0xFFFF; + // break; + // case 5: + // // FEC_RO_B + // mActiveFECsB = parData & 0xFFFF; + // break; + // case 6: + // // RDO_CFG1 + // mAltroCFG1 = parData & 0xFFFFF; + // break; + // case 7: + // // RDO_CFG2 + // mAltroCFG2 = parData & 0x1FFFFFF; + // break; + // default: + // std::cerr << "Undefined parameter code " << parCode << ", ignore it !\n"; + // break; + // } + // } + // mPayloadSize = payloadwords[--index] & 0x3FFFFFF; + mIsInitialized = true; +} + +double RCUTrailer::getTimeSample() const +{ + unsigned char fq = (mAltroCFG2 >> 5) & 0xF; + double tSample; + switch (fq) { + case 0: + // 20 MHz + tSample = 2.0; + break; + case 1: + // 10 Mhz + tSample = 4.0; + break; + case 2: + // 5 MHz + tSample = 8.; + break; + default: + throw Error(Error::ErrorType_t::SAMPLINGFREQ_INVALID, fmt::format("Invalid sampling frequency value %d !", int(fq)).data()); + } + + return tSample * o2::constants::lhc::LHCBunchSpacingNS * 1.e-9; +} + +void RCUTrailer::setTimeSample(double timesample) +{ + int fq = 0; + if (std::abs(timesample - 50) < DBL_EPSILON) { + fq = 0; + } else if (std::abs(timesample - 100) < DBL_EPSILON) { + fq = 1; + } else if (std::abs(timesample - 200) < DBL_EPSILON) { + fq = 2; + } else { + throw Error(Error::ErrorType_t::SAMPLINGFREQ_INVALID, fmt::format("invalid time sample: %f", timesample).data()); + } + mAltroCFG2 = (mAltroCFG2 & 0x1F) | fq << 5; +} + +double RCUTrailer::getL1Phase() const +{ + double tSample = getTimeSample(), + phase = ((double)(mAltroCFG2 & 0x1F)) * o2::constants::lhc::LHCBunchSpacingNS * 1.e-9; + if (phase >= tSample) { + throw Error(Error::ErrorType_t::L1PHASE_INVALID, fmt::format("Invalid L1 trigger phase (%e s (phase) >= %e s (sampling time)) !", phase, tSample).data()); + } + return phase; +} + +void RCUTrailer::setL1Phase(double l1phase) +{ + int phase = l1phase / 25.; + mAltroCFG2 = (mAltroCFG2 & 0x1E0) | phase; +} + +std::vector RCUTrailer::encode() const +{ + std::vector encoded; + encoded.emplace_back(mPayloadSize | 2 << 30); + encoded.emplace_back(mAltroCFG2 | 7 << 26 | 2 << 30); + encoded.emplace_back(mAltroCFG1 | 6 << 26 | 2 << 30); + encoded.emplace_back(mActiveFECsB | 5 << 26 | 2 << 30); + encoded.emplace_back(mActiveFECsA | 4 << 26 | 2 << 30); + encoded.emplace_back(mERRREG3 | 3 << 26 | 2 << 30); + encoded.emplace_back(mERRREG2 | 2 << 26 | 2 << 30); + encoded.emplace_back(mFECERRB >> 7 | (mFECERRA >> 7) << 13 | 1 << 26 | 2 << 30); + + uint32_t lasttrailerword = 3 << 30 | mFirmwareVersion << 16 | mRCUId << 7 | (encoded.size() + 1); + encoded.emplace_back(lasttrailerword); + + return encoded; +} + +void RCUTrailer::printStream(std::ostream& stream) const +{ + std::vector errors; + double timesample = -1., l1phase = -1.; + try { + timesample = getTimeSample(); + } catch (Error& e) { + errors.push_back(e.what()); + } + try { + l1phase = getL1Phase(); + } catch (Error& e) { + errors.push_back(e.what()); + } + + stream << "RCU trailer (Format version 2):\n" + << "==================================================\n" + << "RCU ID: " << mRCUId << "\n" + << "Firmware version: " << int(mFirmwareVersion) << "\n" + << "Trailer size: " << mTrailerSize << "\n" + << "Payload size: " << mPayloadSize << "\n" + << "FECERRA: 0x" << std::hex << mFECERRA << "\n" + << "FECERRB: 0x" << std::hex << mFECERRB << "\n" + << "ERRREG2: 0x" << std::hex << mERRREG2 << "\n" + << "#channels skipped due to address mismatch: " << std::dec << getNumberOfChannelAddressMismatch() << "\n" + << "#channels skipped due to bad block length: " << std::dec << getNumberOfChannelLengthMismatch() << "\n" + << "Active FECs (branch A): 0x" << std::hex << mActiveFECsA << "\n" + << "Active FECs (branch B): 0x" << std::hex << mActiveFECsB << "\n" + << "Baseline corr: 0x" << std::hex << int(getBaselineCorrection()) << "\n" + << "Number of presamples: " << std::dec << int(getNumberOfPresamples()) << "\n" + << "Number of postsamples: " << std::dec << int(getNumberOfPostsamples()) << "\n" + << "Second baseline corr: " << (hasSecondBaselineCorr() ? "yes" : "no") << "\n" + << "GlitchFilter: " << std::dec << int(getGlitchFilter()) << "\n" + << "Number of non-ZS postsamples: " << std::dec << int(getNumberOfNonZeroSuppressedPostsamples()) << "\n" + << "Number of non-ZS presamples: " << std::dec << int(getNumberOfNonZeroSuppressedPresamples()) << "\n" + << "Number of ALTRO buffers: " << std::dec << getNumberOfAltroBuffers() << "\n" + << "Number of pretrigger samples: " << std::dec << int(getNumberOfPretriggerSamples()) << "\n" + << "Number of samples per channel: " << std::dec << getNumberOfSamplesPerChannel() << "\n" + << "Sparse readout: " << (isSparseReadout() ? "yes" : "no") << "\n" + << "AltroCFG1: 0x" << std::hex << mAltroCFG1 << "\n" + << "AltroCFG2: 0x" << std::hex << mAltroCFG2 << "\n" + << "Sampling time: " << std::scientific << timesample << " s\n" + << "L1 Phase: " << std::scientific << l1phase << " s\n" + << std::dec << std::fixed; + if (errors.size()) { + stream << "Errors: \n" + << "-------------------------------------------------\n"; + for (const auto& e : errors) { + stream << e << "\n"; + } + } + stream << "==================================================\n"; +} + +RCUTrailer RCUTrailer::constructFromPayloadWords(const gsl::span payloadwords) +{ + RCUTrailer result; + auto tmp = gsl::span(reinterpret_cast(payloadwords.data()), payloadwords.size() * sizeof(uint32_t)); + result.constructFromRawPayload(tmp); + return result; +} +RCUTrailer RCUTrailer::constructFromPayload(const gsl::span payloadwords) +{ + RCUTrailer result; + result.constructFromRawPayload(payloadwords); + return result; +} + +std::ostream& o2::cpv::operator<<(std::ostream& stream, const o2::cpv::RCUTrailer& trailer) +{ + trailer.printStream(stream); + return stream; +} diff --git a/Detectors/CPV/calib/include/CPVCalib/BadChannelMap.h b/Detectors/CPV/calib/include/CPVCalib/BadChannelMap.h index 54f69a0d0e596..d2ba63f23346f 100644 --- a/Detectors/CPV/calib/include/CPVCalib/BadChannelMap.h +++ b/Detectors/CPV/calib/include/CPVCalib/BadChannelMap.h @@ -59,7 +59,7 @@ class BadChannelMap BadChannelMap() = default; /// \brief Constructur used to build test bad map - BadChannelMap(int test); + BadChannelMap(short test); /// \brief Destructor ~BadChannelMap() = default; @@ -93,20 +93,20 @@ class BadChannelMap /// Only bad or warm cells are added to the container. In case /// the mask type is GOOD_CELL, the entry is removed from the /// container if present before, otherwise the cell is ignored. - void addBadChannel(short channelID) { mBadCells.set(channelID); } //set bit to true + void addBadChannel(unsigned short channelID) { mBadCells.set(channelID); } //set bit to true /// \brief Mark channel as good /// \param channelID Absolute ID of the channel /// /// Setting channel as good. - void setChannelGood(short channelID) { mBadCells.set(channelID, false); } + void setChannelGood(unsigned short channelID) { mBadCells.set(channelID, false); } /// \brief Get the status of a certain cell /// \param channelID channel for which to obtain the channel status /// \return true if good channel /// /// Provide the mask status of a cell. - bool isChannelGood(short channelID) const { return !mBadCells.test(channelID); } + bool isChannelGood(unsigned short channelID) const { return !mBadCells.test(channelID); } /// \brief Convert map into 2D histogram representation /// \param mod Module number @@ -117,7 +117,7 @@ class BadChannelMap /// - 0: GOOD_CELL /// - 1: BAD_CELL /// Attention: It is responsibility of user to create/delete histogram - void getHistogramRepresentation(char mod, TH2* h) const; + void getHistogramRepresentation(short mod, TH2* h) const; /// \brief Print bad channels on a given stream /// \param stream Stream on which the bad channel map is printed on @@ -131,8 +131,8 @@ class BadChannelMap void PrintStream(std::ostream& stream) const; private: - static constexpr short NCHANNELS = 28673; ///< Number of channels starting from 1 (4*128*56+1 - std::bitset mBadCells; ///< Container for bad cells, 1 means bad sell + static constexpr unsigned short NCHANNELS = 30720; ///< Number of channels in modules 1-4 starting from 0 (4*128*60) + std::bitset mBadCells; ///< Container for bad cells, 1 means bad sell ClassDefNV(BadChannelMap, 1); }; diff --git a/Detectors/CPV/calib/include/CPVCalib/CalibParams.h b/Detectors/CPV/calib/include/CPVCalib/CalibParams.h index aa9f6ff27e898..dc598c18c65ab 100644 --- a/Detectors/CPV/calib/include/CPVCalib/CalibParams.h +++ b/Detectors/CPV/calib/include/CPVCalib/CalibParams.h @@ -36,7 +36,7 @@ class CalibParams CalibParams() = default; /// \brief Constructor for tests - CalibParams(int test); + CalibParams(short test); /// \brief Destructor ~CalibParams() = default; @@ -44,22 +44,22 @@ class CalibParams /// \brief Get High Gain energy calibration coefficients /// \param cellID Absolute ID of cell /// \return high gain energy calibration coefficient of the cell - float getGain(short cellID) const { return mGainCalib[cellID]; } + float getGain(unsigned short cellID) const { return mGainCalib[cellID]; } /// \brief Set High Gain energy calibration coefficient /// \param cellID Absolute ID of cell /// \param c is the calibration coefficient - void setGain(short cellID, float c) { mGainCalib[cellID] = c; } + void setGain(unsigned short cellID, float c) { mGainCalib[cellID] = c; } /// \brief Set High Gain energy calibration coefficients for one module in the form of 2D histogram /// \param 2D(64,56) histogram with calibration coefficients /// \param module number /// \return Is successful - bool setGain(TH2* h, char module); + bool setGain(TH2* h, short module); private: - static constexpr short NCHANNELS = 28673; ///< Number of channels starting from 1 - std::array mGainCalib; ///< Container for the gain calibration coefficients + static constexpr unsigned short NCHANNELS = 30720; ///< Number of channels starting from 1 + std::array mGainCalib; ///< Container for the gain calibration coefficients ClassDefNV(CalibParams, 1); }; diff --git a/Detectors/CPV/calib/src/BadChannelMap.cxx b/Detectors/CPV/calib/src/BadChannelMap.cxx index 92bf6f77f2cb1..0e545574b16bc 100644 --- a/Detectors/CPV/calib/src/BadChannelMap.cxx +++ b/Detectors/CPV/calib/src/BadChannelMap.cxx @@ -19,13 +19,13 @@ using namespace o2::cpv; -BadChannelMap::BadChannelMap(int /*dummy*/) +BadChannelMap::BadChannelMap(short /*dummy*/) { //Mark few channels as bad for test peurposes for (short i = 0; i < 60; i++) { //module 2 - short channelID = 3584 + i * 57; + unsigned short channelID = 3584 + i * 57; mBadCells.set(channelID); channelID = 3640 + i * 55; mBadCells.set(channelID); @@ -33,7 +33,7 @@ BadChannelMap::BadChannelMap(int /*dummy*/) for (short i = 0; i < 16; i++) { //module 3 - int channelID = 8972 + i * 57; + unsigned short channelID = 8972 + i * 57; mBadCells.set(channelID); channelID = 8092 + i * 57; mBadCells.set(channelID); @@ -44,7 +44,7 @@ BadChannelMap::BadChannelMap(int /*dummy*/) } } -void BadChannelMap::getHistogramRepresentation(char module, TH2* h) const +void BadChannelMap::getHistogramRepresentation(short module, TH2* h) const { if (!h) { LOG(ERROR) << "provide histogram to be filled"; @@ -60,7 +60,7 @@ void BadChannelMap::getHistogramRepresentation(char module, TH2* h) const h->Reset(); short relid[3] = {module, 1, 1}; - short absId; + unsigned short absId; for (short ix = 1; ix <= MAXX; ix++) { relid[1] = ix; for (short iz = 1; iz <= MAXZ; iz++) { diff --git a/Detectors/CPV/calib/src/CalibParams.cxx b/Detectors/CPV/calib/src/CalibParams.cxx index f7de1a259f88a..c2ee2487e5fca 100644 --- a/Detectors/CPV/calib/src/CalibParams.cxx +++ b/Detectors/CPV/calib/src/CalibParams.cxx @@ -19,13 +19,13 @@ using namespace o2::cpv; -CalibParams::CalibParams(int /*dummy*/) +CalibParams::CalibParams(short /*dummy*/) { //produce reasonable objest for test purposes - mGainCalib.fill(0.005); + mGainCalib.fill(0.01); } -bool CalibParams::setGain(TH2* h, char module) +bool CalibParams::setGain(TH2* h, short module) { const short MAXX = 128, MAXZ = 56; @@ -40,7 +40,7 @@ bool CalibParams::setGain(TH2* h, char module) } short relid[3] = {module, 1, 1}; - short absId; + unsigned short absId; for (short ix = 1; ix <= MAXX; ix++) { relid[1] = ix; for (short iz = 1; iz <= MAXZ; iz++) { diff --git a/Detectors/CPV/reconstruction/CMakeLists.txt b/Detectors/CPV/reconstruction/CMakeLists.txt index a7c66754ad5f4..ba63c292f58d8 100644 --- a/Detectors/CPV/reconstruction/CMakeLists.txt +++ b/Detectors/CPV/reconstruction/CMakeLists.txt @@ -11,11 +11,20 @@ o2_add_library(CPVReconstruction SOURCES src/Clusterer.cxx src/FullCluster.cxx + src/RawDecoder.cxx + src/RawReaderMemory.cxx + src/CTFCoder.cxx + src/CTFHelper.cxx PUBLIC_LINK_LIBRARIES O2::CPVBase O2::CPVCalib O2::DataFormatsCPV - AliceO2::InfoLogger) + O2::DetectorsRaw + AliceO2::InfoLogger + O2::rANS + ms_gsl::ms_gsl) o2_target_root_dictionary(CPVReconstruction HEADERS include/CPVReconstruction/Clusterer.h - include/CPVReconstruction/FullCluster.h) + include/CPVReconstruction/FullCluster.h + include/CPVReconstruction/RawReaderMemory.h + include/CPVReconstruction/RawDecoder.h) diff --git a/Detectors/CPV/reconstruction/include/CPVReconstruction/CTFCoder.h b/Detectors/CPV/reconstruction/include/CPVReconstruction/CTFCoder.h new file mode 100644 index 0000000000000..79b9a0f228289 --- /dev/null +++ b/Detectors/CPV/reconstruction/include/CPVReconstruction/CTFCoder.h @@ -0,0 +1,152 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file CTFCoder.h +/// \author ruben.shahoyan@cern.ch +/// \brief class for entropy encoding/decoding of CPV data + +#ifndef O2_CPV_CTFCODER_H +#define O2_CPV_CTFCODER_H + +#include +#include +#include +#include +#include "DataFormatsCPV/CTF.h" +#include "DetectorsCommonDataFormats/DetID.h" +#include "DetectorsBase/CTFCoderBase.h" +#include "rANS/rans.h" +#include "CPVReconstruction/CTFHelper.h" + +class TTree; + +namespace o2 +{ +namespace cpv +{ + +class CTFCoder : public o2::ctf::CTFCoderBase +{ + public: + CTFCoder() : o2::ctf::CTFCoderBase(CTF::getNBlocks(), o2::detectors::DetID::CPV) {} + ~CTFCoder() = default; + + /// entropy-encode data to buffer with CTF + template + void encode(VEC& buff, const gsl::span& trigData, const gsl::span& cluData); + + /// entropy decode data from buffer with CTF + template + void decode(const CTF::base& ec, VTRG& trigVec, VCLUSTER& cluVec); + + void createCoders(const std::string& dictPath, o2::ctf::CTFCoderBase::OpType op); + + private: + void appendToTree(TTree& tree, CTF& ec); + void readFromTree(TTree& tree, int entry, std::vector& trigVec, std::vector& cluVec); +}; + +/// entropy-encode clusters to buffer with CTF +template +void CTFCoder::encode(VEC& buff, const gsl::span& trigData, const gsl::span& cluData) +{ + using MD = o2::ctf::Metadata::OptStore; + // what to do which each field: see o2::ctd::Metadata explanation + constexpr MD optField[CTF::getNBlocks()] = { + MD::EENCODE, // BLC_bcIncTrig + MD::EENCODE, // BLC_orbitIncTrig + MD::EENCODE, // BLC_entriesTrig + MD::EENCODE, // BLC_posX + MD::EENCODE, // BLC_posZ + MD::EENCODE, // BLC_energy + MD::EENCODE // BLC_status + }; + + CTFHelper helper(trigData, cluData); + + // book output size with some margin + auto szIni = sizeof(CTFHeader) + helper.getSize() / 4; // will be autoexpanded if needed + buff.resize(szIni); + + auto ec = CTF::create(buff); + using ECB = CTF::base; + + ec->setHeader(helper.createHeader()); + ec->getANSHeader().majorVersion = 0; + ec->getANSHeader().minorVersion = 1; + // at every encoding the buffer might be autoexpanded, so we don't work with fixed pointer ec +#define ENCODECPV(beg, end, slot, bits) CTF::get(buff.data())->encode(beg, end, int(slot), bits, optField[int(slot)], &buff, mCoders[int(slot)].get()); + // clang-format off + ENCODECPV(helper.begin_bcIncTrig(), helper.end_bcIncTrig(), CTF::BLC_bcIncTrig, 0); + ENCODECPV(helper.begin_orbitIncTrig(), helper.end_orbitIncTrig(), CTF::BLC_orbitIncTrig, 0); + ENCODECPV(helper.begin_entriesTrig(), helper.end_entriesTrig(), CTF::BLC_entriesTrig, 0); + + ENCODECPV(helper.begin_posX(), helper.end_posX(), CTF::BLC_posX, 0); + ENCODECPV(helper.begin_posZ(), helper.end_posZ(), CTF::BLC_posZ, 0); + ENCODECPV(helper.begin_energy(), helper.end_energy(), CTF::BLC_energy, 0); + ENCODECPV(helper.begin_status(), helper.end_status(), CTF::BLC_status, 0); + // clang-format on + CTF::get(buff.data())->print(getPrefix()); +} + +/// decode entropy-encoded clusters to standard compact clusters +template +void CTFCoder::decode(const CTF::base& ec, VTRG& trigVec, VCLUSTER& cluVec) +{ + auto header = ec.getHeader(); + ec.print(getPrefix()); + std::vector bcInc, entries, posX, posZ; + std::vector orbitInc; + std::vector energy, status; + +#define DECODECPV(part, slot) ec.decode(part, int(slot), mCoders[int(slot)].get()) + // clang-format off + DECODECPV(bcInc, CTF::BLC_bcIncTrig); + DECODECPV(orbitInc, CTF::BLC_orbitIncTrig); + DECODECPV(entries, CTF::BLC_entriesTrig); + DECODECPV(posX, CTF::BLC_posX); + DECODECPV(posZ, CTF::BLC_posZ); + DECODECPV(energy, CTF::BLC_energy); + DECODECPV(status, CTF::BLC_status); + // clang-format on + // + trigVec.clear(); + cluVec.clear(); + trigVec.reserve(header.nTriggers); + status.reserve(header.nClusters); + + uint32_t firstEntry = 0, cluCount = 0; + o2::InteractionRecord ir(header.firstBC, header.firstOrbit); + + Cluster clu; + for (uint32_t itrig = 0; itrig < header.nTriggers; itrig++) { + // restore TrigRecord + if (orbitInc[itrig]) { // non-0 increment => new orbit + ir.bc = bcInc[itrig]; // bcInc has absolute meaning + ir.orbit += orbitInc[itrig]; + } else { + ir.bc += bcInc[itrig]; + } + + firstEntry = cluVec.size(); + for (uint16_t ic = 0; ic < entries[itrig]; ic++) { + clu.setPacked(posX[cluCount], posZ[cluCount], energy[cluCount], status[cluCount]); + cluVec.emplace_back(clu); + cluCount++; + } + trigVec.emplace_back(ir, firstEntry, entries[itrig]); + } + assert(cluCount == header.nClusters); +} + +} // namespace cpv +} // namespace o2 + +#endif // O2_CPV_CTFCODER_H diff --git a/Detectors/CPV/reconstruction/include/CPVReconstruction/CTFHelper.h b/Detectors/CPV/reconstruction/include/CPVReconstruction/CTFHelper.h new file mode 100644 index 0000000000000..32652e5610e31 --- /dev/null +++ b/Detectors/CPV/reconstruction/include/CPVReconstruction/CTFHelper.h @@ -0,0 +1,193 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file CTFHelper.h +/// \author ruben.shahoyan@cern.ch +/// \brief Helper for CPV CTF creation + +#ifndef O2_CPV_CTF_HELPER_H +#define O2_CPV_CTF_HELPER_H + +#include "DataFormatsCPV/CTF.h" +#include + +namespace o2 +{ +namespace cpv +{ + +class CTFHelper +{ + + public: + CTFHelper(const gsl::span& trgData, const gsl::span& cluData) + : mTrigData(trgData), mCluData(cluData) {} + + CTFHeader createHeader() + { + CTFHeader h{uint32_t(mTrigData.size()), uint32_t(mCluData.size()), 0, 0}; + if (mTrigData.size()) { + h.firstOrbit = mTrigData[0].getBCData().orbit; + h.firstBC = mTrigData[0].getBCData().bc; + } + return h; + } + + size_t getSize() const { return mTrigData.size() * sizeof(TriggerRecord) + mCluData.size() * sizeof(Cluster); } + + //>>> =========================== ITERATORS ======================================== + + template + class _Iter + { + public: + using difference_type = int64_t; + using value_type = T; + using pointer = const T*; + using reference = const T&; + using iterator_category = std::random_access_iterator_tag; + + _Iter(const gsl::span& data, bool end = false) : mData(data), mIndex(end ? data.size() : 0){}; + _Iter() = default; + + const I& operator++() + { + ++mIndex; + return (I&)(*this); + } + + const I& operator--() + { + mIndex--; + return (I&)(*this); + } + + difference_type operator-(const I& other) const { return mIndex - other.mIndex; } + + difference_type operator-(size_t idx) const { return mIndex - idx; } + + const I& operator-(size_t idx) + { + mIndex -= idx; + return (I&)(*this); + } + + bool operator!=(const I& other) const { return mIndex != other.mIndex; } + bool operator==(const I& other) const { return mIndex == other.mIndex; } + bool operator>(const I& other) const { return mIndex > other.mIndex; } + bool operator<(const I& other) const { return mIndex < other.mIndex; } + + protected: + gsl::span mData{}; + size_t mIndex = 0; + }; + + //_______________________________________________ + // BC difference wrt previous if in the same orbit, otherwise the abs.value. + // For the very 1st entry return 0 (diff wrt 1st BC in the CTF header) + class Iter_bcIncTrig : public _Iter + { + public: + using _Iter::_Iter; + value_type operator*() const + { + if (mIndex) { + if (mData[mIndex].getBCData().orbit == mData[mIndex - 1].getBCData().orbit) { + return mData[mIndex].getBCData().bc - mData[mIndex - 1].getBCData().bc; + } else { + return mData[mIndex].getBCData().bc; + } + } + return 0; + } + }; + + //_______________________________________________ + // Orbit difference wrt previous. For the very 1st entry return 0 (diff wrt 1st BC in the CTF header) + class Iter_orbitIncTrig : public _Iter + { + public: + using _Iter::_Iter; + value_type operator*() const { return mIndex ? mData[mIndex].getBCData().orbit - mData[mIndex - 1].getBCData().orbit : 0; } + }; + + //_______________________________________________ + // Number of cells for trigger + class Iter_entriesTrig : public _Iter + { + public: + using _Iter::_Iter; + value_type operator*() const { return mData[mIndex].getNumberOfObjects(); } + }; + + //_______________________________________________ + class Iter_posX : public _Iter + { + public: + using _Iter::_Iter; + value_type operator*() const { return mData[mIndex].getPackedPosX(); } + }; + + //_______________________________________________ + class Iter_posZ : public _Iter + { + public: + using _Iter::_Iter; + value_type operator*() const { return mData[mIndex].getPackedPosZ(); } + }; + + //_______________________________________________ + class Iter_energy : public _Iter + { + public: + using _Iter::_Iter; + value_type operator*() const { return mData[mIndex].getPackedEnergy(); } + }; + + //_______________________________________________ + class Iter_status : public _Iter + { + public: + using _Iter::_Iter; + value_type operator*() const { return mData[mIndex].getPackedClusterStatus(); } + }; + + //<<< =========================== ITERATORS ======================================== + + Iter_bcIncTrig begin_bcIncTrig() const { return Iter_bcIncTrig(mTrigData, false); } + Iter_bcIncTrig end_bcIncTrig() const { return Iter_bcIncTrig(mTrigData, true); } + + Iter_orbitIncTrig begin_orbitIncTrig() const { return Iter_orbitIncTrig(mTrigData, false); } + Iter_orbitIncTrig end_orbitIncTrig() const { return Iter_orbitIncTrig(mTrigData, true); } + + Iter_entriesTrig begin_entriesTrig() const { return Iter_entriesTrig(mTrigData, false); } + Iter_entriesTrig end_entriesTrig() const { return Iter_entriesTrig(mTrigData, true); } + + Iter_posX begin_posX() const { return Iter_posX(mCluData, false); } + Iter_posX end_posX() const { return Iter_posX(mCluData, true); } + + Iter_posZ begin_posZ() const { return Iter_posZ(mCluData, false); } + Iter_posZ end_posZ() const { return Iter_posZ(mCluData, true); } + + Iter_energy begin_energy() const { return Iter_energy(mCluData, false); } + Iter_energy end_energy() const { return Iter_energy(mCluData, true); } + + Iter_status begin_status() const { return Iter_status(mCluData, false); } + Iter_status end_status() const { return Iter_status(mCluData, true); } + + private: + const gsl::span mTrigData; + const gsl::span mCluData; +}; + +} // namespace cpv +} // namespace o2 + +#endif diff --git a/Detectors/CPV/reconstruction/include/CPVReconstruction/Clusterer.h b/Detectors/CPV/reconstruction/include/CPVReconstruction/Clusterer.h index cb0269312ef69..591d5f75d13c4 100644 --- a/Detectors/CPV/reconstruction/include/CPVReconstruction/Clusterer.h +++ b/Detectors/CPV/reconstruction/include/CPVReconstruction/Clusterer.h @@ -33,36 +33,29 @@ class Clusterer void initialize(); void process(gsl::span digits, gsl::span dtr, - const o2::dataformats::MCTruthContainer* dmc, - std::vector* clusters, std::vector* rigRec, + const o2::dataformats::MCTruthContainer& dmc, + std::vector* clusters, std::vector* trigRec, o2::dataformats::MCTruthContainer* cluMC); void makeClusters(gsl::span digits); void evalCluProperties(gsl::span digits, std::vector* clusters, - const o2::dataformats::MCTruthContainer* dmc, + const o2::dataformats::MCTruthContainer& dmc, o2::dataformats::MCTruthContainer* cluMC); float responseShape(float dx, float dz); // Parameterization of EM shower + void propagateMC(bool toRun = true) { mRunMC = toRun; } void makeUnfoldings(gsl::span digits); // Find and unfold clusters with few local maxima void unfoldOneCluster(FullCluster& iniClu, char nMax, gsl::span digitId, gsl::span digits); - protected: - //Calibrate Amplitude - inline float calibrate(float amp, short absId) { return amp * mCalibParams->getGain(absId); } - //Test Bad map - inline bool isBadChannel(short absId) { return (!mBadMap->isChannelGood(absId)); } - protected: static constexpr short NLMMax = 10; ///< maximal number of local maxima in cluster - const CalibParams* mCalibParams = nullptr; //! Calibration coefficients - const BadChannelMap* mBadMap = nullptr; //! Calibration coefficients - - std::vector mClusters; ///< internal vector of clusters + bool mRunMC = false; ///< Process MC info int mFirstDigitInEvent; ///< Range of digits from one event int mLastDigitInEvent; ///< Range of digits from one event - std::vector mDigits; ///< vector of trancient digits for cell processing + std::vector mClusters; ///< internal vector of clusters + std::vector mDigits; ///< vector of transient digits for cell processing std::vector> meInClusters = std::vector>(10, std::vector(NLMMax)); std::vector> mfij = std::vector>(10, std::vector(NLMMax)); diff --git a/Detectors/CPV/reconstruction/include/CPVReconstruction/RawDecoder.h b/Detectors/CPV/reconstruction/include/CPVReconstruction/RawDecoder.h new file mode 100644 index 0000000000000..9adfd45848a4d --- /dev/null +++ b/Detectors/CPV/reconstruction/include/CPVReconstruction/RawDecoder.h @@ -0,0 +1,121 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +#ifndef ALICEO2_CPV_RAWDECODER_H +#define ALICEO2_CPV_RAWDECODER_H + +#include +#include +#include +#include "CPVBase/RCUTrailer.h" +#include "DataFormatsCPV/Digit.h" +#include "CPVReconstruction/RawReaderMemory.h" +class Digits; + +namespace o2 +{ +namespace cpv +{ + +class RawDecoderError +{ + public: + RawDecoderError() = default; //Constructors for vector::emplace_back methods + RawDecoderError(short l, short r, short d, short p, RawErrorType_t e) : Ddl(l), Row(r), Dilogic(d), Pad(p), errortype(e) {} + RawDecoderError(const RawDecoderError& e) = default; + ~RawDecoderError() = default; + + short Ddl; + short Row; + short Dilogic; + short Pad; + RawErrorType_t errortype; + ClassDefNV(RawDecoderError, 1); +}; + +union AddressCharge { + uint32_t mDataWord; + struct { + uint32_t Address : 18; ///< Bits 0 - 17 : Address + uint32_t Charge : 14; ///< Bits 18 - 32 : charge + }; +}; + +/// \class RawDecoder +/// \brief Decoder of the ALTRO data in the raw page +/// \ingroup CPVreconstruction +/// \author Dmitri Peresunko +/// \since Dec, 2020 +/// +/// This is a base class for reading raw data digits. + +class RawDecoder +{ + public: + /// \brief Constructor + /// \param reader Raw reader instance to be decoded + RawDecoder(RawReaderMemory& reader); + + /// \brief Destructor + ~RawDecoder() = default; + + /// \brief Decode the ALTRO stream + /// \throw RawDecoderError if the RCUTrailer or ALTRO payload cannot be decoded + /// + /// Decoding and checking the RCUTtrailer and + /// all channels and bunches in the ALTRO stream. + /// After successfull decoding the Decoder can provide + /// a reference to the RCU trailer and a vector + /// with the decoded chanenels, each containing + /// its bunches. + RawErrorType_t decode(); + + /// \brief Get reference to the RCU trailer object + /// \return const reference to the RCU trailer + const RCUTrailer& getRCUTrailer() const; + + /// \brief Get the reference to the digits container + /// \return Reference to the digits container + const std::vector& getDigits() const; + + /// \brief Get the reference to the list of decoding errors + /// \return Reference to the list of decoding errors + const std::vector& getErrors() const { return mErrors; } + + protected: + /// \brief Read RCU trailer for the current event in the raw buffer + RawErrorType_t readRCUTrailer(); + + /// \brief Read channels for the current event in the raw buffer + RawErrorType_t readChannels(); + + private: + /// \brief run checks on the RCU trailer + /// \throw Error if the RCU trailer has inconsistencies + /// + /// Performing various consistency checks on the RCU trailer + /// In case of failure an exception is thrown. + void checkRCUTrailer(); + + void addDigit(uint32_t padWord, short ddl); + + RawReaderMemory& mRawReader; ///< underlying raw reader + RCUTrailer mRCUTrailer; ///< RCU trailer + std::vector mDigits; ///< vector of channels in the raw stream + std::vector mErrors; ///< vector of decoding errors + bool mChannelsInitialized = false; ///< check whether the channels are initialized + + ClassDefNV(RawDecoder, 1); +}; + +} // namespace cpv + +} // namespace o2 + +#endif diff --git a/Detectors/CPV/reconstruction/include/CPVReconstruction/RawReaderMemory.h b/Detectors/CPV/reconstruction/include/CPVReconstruction/RawReaderMemory.h new file mode 100644 index 0000000000000..5492fc12086a3 --- /dev/null +++ b/Detectors/CPV/reconstruction/include/CPVReconstruction/RawReaderMemory.h @@ -0,0 +1,123 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +#ifndef ALICEO2_CPV_RAWREADERMEMORY_H +#define ALICEO2_CPV_RAWREADERMEMORY_H + +#include +#include + +#include "CPVBase/RCUTrailer.h" +#include "Headers/RAWDataHeader.h" +#include "Headers/RDHAny.h" + +namespace o2 +{ + +namespace cpv +{ + +enum RawErrorType_t { + kOK, ///< NoError + kNO_PAYLOAD, ///< No payload per ddl + kHEADER_DECODING, + kPAGE_NOTFOUND, + kPAYLOAD_DECODING, + kHEADER_INVALID, + kRCU_TRAILER_ERROR, ///< RCU trailer cannot be decoded or invalid + kRCU_VERSION_ERROR, ///< RCU trailer version not matching with the version in the raw header + kRCU_TRAILER_SIZE_ERROR, ///< RCU trailer size length + kSEGMENT_HEADER_ERROR, + kROW_HEADER_ERROR, + kEOE_HEADER_ERROR, + kPADERROR, + kPadAddress +}; + +/// \class RawReaderMemory +/// \brief Reader for raw data produced by the Readout application in in-memory format +/// \ingroup CPVreconstruction +/// \author Dmitri Peresunko after Markus Fasel +/// \since Sept. 25, 2020 +/// +/// +class RawReaderMemory +{ + public: + /// \brief Constructor + RawReaderMemory(const gsl::span rawmemory); + + /// \brief Destructor + ~RawReaderMemory() = default; + + /// \brief set new raw memory chunk + /// \param rawmemory New raw memory chunk + void setRawMemory(const gsl::span rawmemory); + + /// \brief Read next payload from the stream + /// + /// Read the next pages until the stop bit is found. + RawErrorType_t next(); + + /// \brief Read the next page from the stream (single DMA page) + /// \param resetPayload If true the raw payload is reset + /// \throw Error if the page cannot be read or header or payload cannot be deocded + /// + /// Function reading a single DMA page from the stream. It is called + /// inside the next() function for reading payload from multiple DMA + /// pages. As the function cannot handle payload from multiple pages + /// it should not be called directly by the user. + RawErrorType_t nextPage(); + + /// \brief access to the raw header of the current page + /// \return Raw header of the current page + const o2::header::RDHAny& getRawHeader() const { return mRawHeader; } + + /// \brief access to the full raw payload (single or multiple DMA pages) + /// \return Raw Payload of the data until the stop bit is received. + const std::vector& getPayload() const { return mRawPayload; } + + /// \brief Return size of the payload + /// \return size of the payload + int getPayloadSize() const { return mRawPayload.size(); } + + /// \brief get the size of the file in bytes + /// \return size of the file in byte + int getFileSize() const noexcept { return mRawMemoryBuffer.size(); } + + /// \brief check if more pages are available in the raw file + /// \return true if there is a next page + bool hasNext() const { return mCurrentPosition < mRawMemoryBuffer.size(); } + + protected: + /// \brief Initialize the raw stream + /// + /// Rewind stream to the first entry + void init(); + + o2::header::RDHAny decodeRawHeader(const void* headerwords); + + private: + gsl::span mRawMemoryBuffer; ///< Memory block with multiple DMA pages + o2::header::RDHAny mRawHeader; ///< Raw header + std::vector mRawPayload; ///< Raw payload (can consist of multiple pages) + RCUTrailer mCurrentTrailer; ///< RCU trailer + uint64_t mTrailerPayloadWords = 0; ///< Payload words in common trailer + int mCurrentPosition = 0; ///< Current page in file + bool mRawHeaderInitialized = false; ///< RDH for current page initialized + bool mPayloadInitialized = false; ///< Payload for current page initialized + + ClassDefNV(RawReaderMemory, 1); +}; + +} // namespace cpv + +} // namespace o2 + +#endif diff --git a/Detectors/CPV/reconstruction/src/CPVReconstructionLinkDef.h b/Detectors/CPV/reconstruction/src/CPVReconstructionLinkDef.h index 98a55d8446614..76d1bc00c7ae6 100644 --- a/Detectors/CPV/reconstruction/src/CPVReconstructionLinkDef.h +++ b/Detectors/CPV/reconstruction/src/CPVReconstructionLinkDef.h @@ -16,5 +16,7 @@ #pragma link C++ class o2::cpv::Clusterer + ; #pragma link C++ class o2::cpv::FullCluster + ; +#pragma link C++ class o2::cpv::RawReaderMemory + ; +#pragma link C++ class o2::cpv::RawDecoder + ; #endif diff --git a/Detectors/CPV/reconstruction/src/CTFCoder.cxx b/Detectors/CPV/reconstruction/src/CTFCoder.cxx new file mode 100644 index 0000000000000..df9d4f6292ee3 --- /dev/null +++ b/Detectors/CPV/reconstruction/src/CTFCoder.cxx @@ -0,0 +1,76 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file CTFCoder.cxx +/// \author ruben.shahoyan@cern.ch +/// \brief class for entropy encoding/decoding of CPV data + +#include "CPVReconstruction/CTFCoder.h" +#include "CommonUtils/StringUtils.h" +#include + +using namespace o2::cpv; + +///___________________________________________________________________________________ +// Register encoded data in the tree (Fill is not called, will be done by caller) +void CTFCoder::appendToTree(TTree& tree, CTF& ec) +{ + ec.appendToTree(tree, mDet.getName()); +} + +///___________________________________________________________________________________ +// extract and decode data from the tree +void CTFCoder::readFromTree(TTree& tree, int entry, std::vector& trigVec, std::vector& cluVec) +{ + assert(entry >= 0 && entry < tree.GetEntries()); + CTF ec; + ec.readFromTree(tree, mDet.getName(), entry); + decode(ec, trigVec, cluVec); +} + +///________________________________ +void CTFCoder::createCoders(const std::string& dictPath, o2::ctf::CTFCoderBase::OpType op) +{ + bool mayFail = true; // RS FIXME if the dictionary file is not there, do not produce exception + auto buff = readDictionaryFromFile(dictPath, mayFail); + if (!buff.size()) { + if (mayFail) { + return; + } + throw std::runtime_error("Failed to create CTF dictionaty"); + } + const auto* ctf = CTF::get(buff.data()); + + auto getFreq = [ctf](CTF::Slots slot) -> o2::rans::FrequencyTable { + o2::rans::FrequencyTable ft; + auto bl = ctf->getBlock(slot); + auto md = ctf->getMetadata(slot); + ft.addFrequencies(bl.getDict(), bl.getDict() + bl.getNDict(), md.min, md.max); + return std::move(ft); + }; + auto getProbBits = [ctf](CTF::Slots slot) -> int { + return ctf->getMetadata(slot).probabilityBits; + }; + + // just to get types + uint16_t bcInc = 0, entries = 0, cluPosX = 0, cluPosZ = 0; + uint32_t orbitInc = 0; + uint8_t energy = 0, status = 0; +#define MAKECODER(part, slot) createCoder(op, getFreq(slot), getProbBits(slot), int(slot)) + // clang-format off + MAKECODER(bcInc, CTF::BLC_bcIncTrig); + MAKECODER(orbitInc, CTF::BLC_orbitIncTrig); + MAKECODER(entries, CTF::BLC_entriesTrig); + MAKECODER(cluPosX, CTF::BLC_posX); + MAKECODER(cluPosZ, CTF::BLC_posZ); + MAKECODER(energy, CTF::BLC_energy); + MAKECODER(status, CTF::BLC_status); + // clang-format on +} diff --git a/Detectors/CPV/reconstruction/src/CTFHelper.cxx b/Detectors/CPV/reconstruction/src/CTFHelper.cxx new file mode 100644 index 0000000000000..9954f5a8d0a1b --- /dev/null +++ b/Detectors/CPV/reconstruction/src/CTFHelper.cxx @@ -0,0 +1,15 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file CTFHelper.cxx +/// \author ruben.shahoyan@cern.ch +/// \brief Helper for CPV CTF creation + +#include "CPVReconstruction/CTFHelper.h" diff --git a/Detectors/CPV/reconstruction/src/Clusterer.cxx b/Detectors/CPV/reconstruction/src/Clusterer.cxx index 5af836e70d79e..edd427f29d4da 100644 --- a/Detectors/CPV/reconstruction/src/Clusterer.cxx +++ b/Detectors/CPV/reconstruction/src/Clusterer.cxx @@ -32,19 +32,21 @@ void Clusterer::initialize() mFirstDigitInEvent = 0; mLastDigitInEvent = -1; } + //____________________________________________________________________________ void Clusterer::process(gsl::span digits, gsl::span dtr, - const o2::dataformats::MCTruthContainer* dmc, + const o2::dataformats::MCTruthContainer& dmc, std::vector* clusters, std::vector* trigRec, o2::dataformats::MCTruthContainer* cluMC) { clusters->clear(); //final out list of clusters trigRec->clear(); - if (cluMC) { + if (mRunMC) { cluMC->clear(); } for (const auto& tr : dtr) { + mFirstDigitInEvent = tr.getFirstEntry(); mLastDigitInEvent = mFirstDigitInEvent + tr.getNumberOfObjects(); int indexStart = clusters->size(); @@ -52,36 +54,14 @@ void Clusterer::process(gsl::span digits, gsl::span metadata; // do we want to store any meta data? - ccdb.init("http://ccdb-test.cern.ch:8080"); // or http://localhost:8080 for a local installation - long bcTime = 1; //TODO!!! Convert BC time to time o2::InteractionRecord bcTime = digitsTR.front().getBCData() ; - mBadMap = ccdb.retrieveFromTFileAny("CPV/BadMap", metadata, bcTime); - mCalibParams = ccdb.retrieveFromTFileAny("CPV/Calib", metadata, bcTime); - if (!mBadMap) { - LOG(FATAL) << "[CPVClusterer - run] can not get Bad Map"; - } - if (!mCalibParams) { - LOG(FATAL) << "[CPVClusterer - run] can not get CalibParams"; - } - } - } - // Collect digits to clusters makeClusters(digits); - // Unfold overlapped clusters - // Split clusters with several local maxima if necessary - if (o2::cpv::CPVSimParams::Instance().mUnfoldClusters) { - makeUnfoldings(digits); - } + // // Unfold overlapped clusters + // // Split clusters with several local maxima if necessary + // if (o2::cpv::CPVSimParams::Instance().mUnfoldClusters) { + // makeUnfoldings(digits); + // } // Calculate properties of collected clusters (Local position, energy, disp etc.) evalCluProperties(digits, clusters, dmc, cluMC); @@ -97,59 +77,51 @@ void Clusterer::makeClusters(gsl::span digits) // A cluster is defined as a list of neighbour digits // Mark all digits as unused yet - const int maxNDigits = 12546; // There is no digits more than in CPV modules ;) - bool digitsUsed[maxNDigits]; - memset(digitsUsed, 0, sizeof(bool) * maxNDigits); + const int maxNDigits = 23040; // There is no digits more than in CPV modules ;) + std::bitset digitsUsed; ///< Container for bad cells, 1 means bad sell + digitsUsed.reset(); int iFirst = mFirstDigitInEvent; // first index of digit which potentially can be a part of cluster for (int i = iFirst; i < mLastDigitInEvent; i++) { - if (digitsUsed[i - mFirstDigitInEvent]) { + if (digitsUsed.test(i - mFirstDigitInEvent)) { continue; } const Digit& digitSeed = digits[i]; - float digitSeedEnergy = calibrate(digitSeed.getAmplitude(), digitSeed.getAbsId()); - if (isBadChannel(digitSeed.getAbsId())) { - digitSeedEnergy = 0.; - } + float digitSeedEnergy = digitSeed.getAmplitude(); //already calibrated digits if (digitSeedEnergy < o2::cpv::CPVSimParams::Instance().mDigitMinEnergy) { continue; } // is this digit so energetic that start cluster? - FullCluster* clu = nullptr; - int iDigitInCluster = 0; - if (digitSeedEnergy > o2::cpv::CPVSimParams::Instance().mClusteringThreshold) { - // start new cluster - mClusters.emplace_back(digitSeed.getAbsId(), digitSeedEnergy, digitSeed.getLabel()); - clu = &(mClusters.back()); - - digitsUsed[i - mFirstDigitInEvent] = true; - iDigitInCluster = 1; - } else { + if (digitSeedEnergy < o2::cpv::CPVSimParams::Instance().mClusteringThreshold) { continue; } + // start new cluster + mClusters.emplace_back(digitSeed.getAbsId(), digitSeedEnergy, digitSeed.getLabel()); + FullCluster& clu = mClusters.back(); + digitsUsed.set(i - mFirstDigitInEvent, true); + int iDigitInCluster = 1; + // Now scan remaining digits in list to find neigbours of our seed int index = 0; while (index < iDigitInCluster) { // scan over digits already in cluster - short digitSeedAbsId = clu->getDigitAbsId(index); + short digitSeedAbsId = clu.getDigitAbsId(index); index++; - for (Int_t j = iFirst; j < mLastDigitInEvent; j++) { - if (digitsUsed[j - mFirstDigitInEvent]) { + bool runLoop = true; + for (Int_t j = iFirst; runLoop && (j < mLastDigitInEvent); j++) { + if (digitsUsed.test(j - mFirstDigitInEvent)) { continue; // look through remaining digits } - const Digit* digitN = &(digits[j]); - float digitNEnergy = calibrate(digitN->getAmplitude(), digitN->getAbsId()); - if (isBadChannel(digitN->getAbsId())) { //remove digit - digitNEnergy = 0.; - } + const Digit& digitN = digits[j]; + float digitNEnergy = digitN.getAmplitude(); //Already calibrated digits! if (digitNEnergy < o2::cpv::CPVSimParams::Instance().mDigitMinEnergy) { continue; } // call (digit,digitN) in THAT oder !!!!! - Int_t ineb = Geometry::areNeighbours(digitSeedAbsId, digitN->getAbsId()); + Int_t ineb = Geometry::areNeighbours(digitSeedAbsId, digitN.getAbsId()); switch (ineb) { case -1: // too early (e.g. previous module), do not look before j at subsequent passes iFirst = j; @@ -157,12 +129,13 @@ void Clusterer::makeClusters(gsl::span digits) case 0: // not a neighbour break; case 1: // are neighbours - clu->addDigit(digitN->getAbsId(), digitNEnergy, digitN->getLabel()); + clu.addDigit(digitN.getAbsId(), digitNEnergy, digitN.getLabel()); iDigitInCluster++; - digitsUsed[j - mFirstDigitInEvent] = true; + digitsUsed.set(j - mFirstDigitInEvent, true); break; case 2: // too far from each other default: + runLoop = false; break; } // switch } @@ -340,7 +313,7 @@ void Clusterer::unfoldOneCluster(FullCluster& iniClu, char nMax, gsl::span //____________________________________________________________________________ void Clusterer::evalCluProperties(gsl::span digits, std::vector* clusters, - const o2::dataformats::MCTruthContainer* dmc, + const o2::dataformats::MCTruthContainer& dmc, o2::dataformats::MCTruthContainer* cluMC) { @@ -349,9 +322,10 @@ void Clusterer::evalCluProperties(gsl::span digits, std::vectorgetIndexedSize(); } + auto clu = mClusters.begin(); while (clu != mClusters.end()) { @@ -370,7 +344,7 @@ void Clusterer::evalCluProperties(gsl::span digits, std::vectorgetEnergy() > 1.e-4) { //Non-empty cluster clusters->emplace_back(*clu); - if (cluMC) { //Handle labels + if (mRunMC) { //Handle labels //Calculate list of primaries //loop over entries in digit MCTruthContainer const std::vector* vl = clu->getElementList(); @@ -381,7 +355,7 @@ void Clusterer::evalCluProperties(gsl::span digits, std::vector spDigList = dmc->getLabels(i); + gsl::span spDigList = dmc.getLabels(i); gsl::span spCluList = cluMC->getLabels(labelIndex); //get updated list auto digL = spDigList.begin(); while (digL != spDigList.end()) { @@ -401,7 +375,6 @@ void Clusterer::evalCluProperties(gsl::span digits, std::vectorback().setLabel(labelIndex); labelIndex++; } // Work with MC } diff --git a/Detectors/CPV/reconstruction/src/RawDecoder.cxx b/Detectors/CPV/reconstruction/src/RawDecoder.cxx new file mode 100644 index 0000000000000..18937c0e8ea31 --- /dev/null +++ b/Detectors/CPV/reconstruction/src/RawDecoder.cxx @@ -0,0 +1,171 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +#include +#include "CPVReconstruction/RawReaderMemory.h" +#include "CPVReconstruction/RawDecoder.h" +#include "DataFormatsCPV/RawFormats.h" +#include "InfoLogger/InfoLogger.hxx" +#include "DetectorsRaw/RDHUtils.h" +#include "CPVBase/Geometry.h" + +using namespace o2::cpv; + +RawDecoder::RawDecoder(RawReaderMemory& reader) : mRawReader(reader), + mRCUTrailer(), + mChannelsInitialized(false) +{ +} + +RawErrorType_t RawDecoder::decode() +{ + + auto& header = mRawReader.getRawHeader(); + short ddl = o2::raw::RDHUtils::getFEEID(header); + mDigits.clear(); + + auto payloadwords = mRawReader.getPayload(); + if (payloadwords.size() == 0) { + mErrors.emplace_back(ddl, 0, 0, 0, kNO_PAYLOAD); //add error + LOG(ERROR) << "Empty payload for DDL=" << ddl; + return kNO_PAYLOAD; + } + + // if(readRCUTrailer()!=kOK){ + // LOG(ERROR) << "can not read RCU trailer for DDL " << ddl ; + // return kRCU_TRAILER_ERROR; + // } + + return readChannels(); +} + +RawErrorType_t RawDecoder::readRCUTrailer() +{ + gsl::span payload(reinterpret_cast(mRawReader.getPayload().data()), mRawReader.getPayload().size() * sizeof(uint32_t)); + mRCUTrailer.constructFromRawPayload(payload); + return kOK; +} + +RawErrorType_t RawDecoder::readChannels() +{ + mChannelsInitialized = false; + auto& header = mRawReader.getRawHeader(); + short ddl = o2::raw::RDHUtils::getFEEID(header); //Current fee/ddl + + auto& payloadwords = mRawReader.getPayload(); + //start reading from the end + auto currentWord = payloadwords.rbegin(); + while (currentWord != payloadwords.rend()) { + SegMarkerWord sw = {*currentWord++}; //first get value, then increment + if (sw.marker != 2736) { //error + mErrors.emplace_back(ddl, 17, 2, 0, kSEGMENT_HEADER_ERROR); //add error for non-existing row + //try adding this as padWord + addDigit(sw.mDataWord, ddl); + continue; + } + short nSegWords = sw.nwords; + short currentRow = sw.row; + short nEoE = 0; + while (nSegWords > 0 && currentWord != payloadwords.rend()) { + EoEWord ew = {*currentWord++}; + nSegWords--; + if (ew.checkbit != 1) { //error + LOG(ERROR) << " error EoE, ddl" << ddl << " row " << currentRow; + mErrors.emplace_back(ddl, currentRow, 11, 0, kEOE_HEADER_ERROR); //add error + //try adding this as padWord + addDigit(ew.mDataWord, ddl); + continue; + } + nEoE++; + short nEoEwords = ew.nword; + short currentDilogic = ew.dilogic; + if (ew.row != currentRow) { + LOG(ERROR) << "Row in EoE=" << ew.row << " != expected row " << currentRow; + mErrors.emplace_back(ddl, currentRow, currentDilogic, 0, kEOE_HEADER_ERROR); //add error + //try adding this as padWord + addDigit(ew.mDataWord, ddl); + continue; + } + if (currentDilogic < 0 || currentDilogic > 10) { + LOG(ERROR) << "wrong Dilogic in EoE=" << currentDilogic; + mErrors.emplace_back(ddl, currentRow, currentDilogic, 0, kEOE_HEADER_ERROR); //add error + //try adding this as padWord + addDigit(ew.mDataWord, ddl); + continue; + } + while (nEoEwords > 0 && currentWord != payloadwords.rend()) { + PadWord pad = {*currentWord++}; + nEoEwords--; + nSegWords--; + if (pad.zero != 0) { + LOG(ERROR) << "bad pad, word=" << pad.mDataWord; + mErrors.emplace_back(ddl, currentRow, currentDilogic, 49, kPADERROR); //add error and skip word + continue; + } + //check paw/pad indexes + if (pad.row != currentRow || pad.dilogic != currentDilogic) { + LOG(ERROR) << "RawPad " << pad.row << " != currentRow=" << currentRow << "dilogicPad=" << pad.dilogic << "!= currentDilogic=" << currentDilogic; + mErrors.emplace_back(ddl, short(pad.row), short(pad.dilogic), short(pad.address), kPadAddress); //add error and skip word + //do not skip, try adding using info from pad + } + addDigit(pad.mDataWord, ddl); + } //pads in EoE + if (nEoE % 10 == 0) { // kNDilogic = 10; ///< Number of dilogic per row + if (currentWord != payloadwords.rend()) { //Read row HEader + RowMarkerWord rw = {*currentWord++}; + nSegWords--; + currentRow--; + if (rw.marker != 13992) { + LOG(ERROR) << "Error in row marker:" << rw.marker << "row header word=" << rw.mDataWord; + mErrors.emplace_back(ddl, currentRow, 11, 0, kPadAddress); //add error and skip word + //try adding digit assuming this is pad word + addDigit(rw.mDataWord, ddl); + } + } + } + } // in Segment + } + mChannelsInitialized = true; + return kOK; +} + +const RCUTrailer& RawDecoder::getRCUTrailer() const +{ + if (!mRCUTrailer.isInitialized()) { + LOG(ERROR) << "RCU trailer not initialized"; + } + return mRCUTrailer; +} + +const std::vector& RawDecoder::getDigits() const +{ + if (!mChannelsInitialized) { + LOG(ERROR) << "Channels not initialized"; + } + return mDigits; +} + +void RawDecoder::addDigit(uint32_t w, short ddl) +{ + + PadWord pad = {w}; + if (pad.zero != 0) { + return; + } + short rowPad = pad.row; + short dilogicPad = pad.dilogic; + short hw = pad.address; + unsigned short absId; + o2::cpv::Geometry::hwaddressToAbsId(ddl, rowPad, dilogicPad, hw, absId); + + AddressCharge ac = {0}; + ac.Address = absId; + ac.Charge = pad.charge; + mDigits.push_back(ac.mDataWord); +} \ No newline at end of file diff --git a/Detectors/CPV/reconstruction/src/RawReaderMemory.cxx b/Detectors/CPV/reconstruction/src/RawReaderMemory.cxx new file mode 100644 index 0000000000000..701130be7ff27 --- /dev/null +++ b/Detectors/CPV/reconstruction/src/RawReaderMemory.cxx @@ -0,0 +1,142 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include +#include +#include "FairLogger.h" +#include "CPVReconstruction/RawReaderMemory.h" +#include "DetectorsRaw/RDHUtils.h" + +using namespace o2::cpv; + +using RDHDecoder = o2::raw::RDHUtils; + +RawReaderMemory::RawReaderMemory(gsl::span rawmemory) : mRawMemoryBuffer(rawmemory) +{ + init(); +} + +void RawReaderMemory::setRawMemory(const gsl::span rawmemory) +{ + mRawMemoryBuffer = rawmemory; + init(); +} + +o2::header::RDHAny RawReaderMemory::decodeRawHeader(const void* payloadwords) +{ + auto headerversion = RDHDecoder::getVersion(payloadwords); + if (headerversion == 4) { + return o2::header::RDHAny(*reinterpret_cast(payloadwords)); + } else if (headerversion == 5) { + return o2::header::RDHAny(*reinterpret_cast(payloadwords)); + } else if (headerversion == 6) { + return o2::header::RDHAny(*reinterpret_cast(payloadwords)); + } + LOG(ERROR) << "Unknown RDH version"; + return o2::header::RDHAny(*reinterpret_cast(payloadwords)); + ; +} + +void RawReaderMemory::init() +{ + mCurrentPosition = 0; + mRawHeaderInitialized = false; + mPayloadInitialized = false; +} + +RawErrorType_t RawReaderMemory::next() +{ + mRawPayload.clear(); + mCurrentTrailer.reset(); + bool isDataTerminated = false; + do { + RawErrorType_t e = nextPage(); + if (e != RawErrorType_t::kOK) { + return e; + } + if (hasNext()) { + auto nextheader = decodeRawHeader(mRawMemoryBuffer.data() + mCurrentPosition); + // check continuing payload based on the bc/orbit ID + auto currentbc = RDHDecoder::getTriggerBC(mRawHeader), + nextbc = RDHDecoder::getTriggerBC(nextheader); + auto currentorbit = RDHDecoder::getTriggerOrbit(mRawHeader), + nextorbit = RDHDecoder::getTriggerOrbit(nextheader); + if (currentbc != nextbc || currentorbit != nextorbit) { + isDataTerminated = true; + } else { + auto nextpagecounter = RDHDecoder::getPageCounter(nextheader); + if (nextpagecounter == 0) { + isDataTerminated = true; + } else { + isDataTerminated = false; + } + } + } else { + isDataTerminated = true; + } + // Check if the data continues + } while (!isDataTerminated); + try { + mCurrentTrailer.constructFromPayloadWords(mRawPayload); + } catch (...) { + return RawErrorType_t::kHEADER_DECODING; + } + return RawErrorType_t::kOK; +} + +RawErrorType_t RawReaderMemory::nextPage() +{ + if (!hasNext()) { + return RawErrorType_t::kPAGE_NOTFOUND; + } + mRawHeaderInitialized = false; + mPayloadInitialized = false; + + // Read RDH header + try { + mRawHeader = decodeRawHeader(mRawMemoryBuffer.data() + mCurrentPosition); + while (RDHDecoder::getOffsetToNext(mRawHeader) == RDHDecoder::getHeaderSize(mRawHeader) && + mCurrentPosition < mRawMemoryBuffer.size()) { + // No Payload - jump to next rawheader + // This will eventually move, depending on whether for events without payload in the SRU we send the RCU trailer + mCurrentPosition += RDHDecoder::getHeaderSize(mRawHeader); + mRawHeader = decodeRawHeader(mRawMemoryBuffer.data() + mCurrentPosition); + } + mRawHeaderInitialized = true; + } catch (...) { + return RawErrorType_t::kHEADER_DECODING; + } + if (mCurrentPosition + RDHDecoder::getMemorySize(mRawHeader) > mRawMemoryBuffer.size()) { + // Payload incomplete + return RawErrorType_t::kPAYLOAD_DECODING; + } + + auto tmp = reinterpret_cast(mRawMemoryBuffer.data()); + int start = (mCurrentPosition + RDHDecoder::getHeaderSize(mRawHeader)) / sizeof(uint32_t); + int end = start + (RDHDecoder::getMemorySize(mRawHeader) - RDHDecoder::getHeaderSize(mRawHeader)) / sizeof(uint32_t); + for (auto iword = start; iword < end; iword++) { + mRawPayload.push_back(tmp[iword]); + } + + mCurrentPosition += RDHDecoder::getOffsetToNext(mRawHeader); /// Assume fixed 8 kB page size + /* + mCurrentTrailer.setPayloadSize(mCurrentTrailer.getPayloadSize() + trailer.getPayloadSize()); + tralersize = trailer.getTrailerSize(); + } + + gsl::span payloadWithoutTrailer(mRawBuffer.getDataWords().data(), mRawBuffer.getDataWords().size() - tralersize); + + mRawPayload.appendPayloadWords(payloadWithoutTrailer); + mRawPayload.increasePageCount(); + } + mCurrentPosition += RDHDecoder::getOffsetToNext(mRawHeader); /// Assume fixed 8 kB page size +*/ + return RawErrorType_t::kOK; +} diff --git a/Detectors/CPV/simulation/CMakeLists.txt b/Detectors/CPV/simulation/CMakeLists.txt index 5d96c06ae79e0..dd2b803aa3f4d 100644 --- a/Detectors/CPV/simulation/CMakeLists.txt +++ b/Detectors/CPV/simulation/CMakeLists.txt @@ -9,11 +9,27 @@ # submit itself to any jurisdiction. o2_add_library(CPVSimulation - SOURCES src/Detector.cxx src/GeometryParams.cxx - src/Digitizer.cxx - PUBLIC_LINK_LIBRARIES O2::DetectorsBase O2::CPVBase O2::DataFormatsCPV O2::CPVCalib O2::CCDB) + SOURCES src/Detector.cxx + src/GeometryParams.cxx + src/Digitizer.cxx + src/RawWriter.cxx + PUBLIC_LINK_LIBRARIES O2::DetectorsBase + O2::DataFormatsCPV + O2::CPVBase + O2::CPVCalib + O2::CCDB + O2::SimConfig + O2::SimulationDataFormat + O2::Headers + O2::DetectorsRaw) o2_target_root_dictionary(CPVSimulation HEADERS include/CPVSimulation/Detector.h include/CPVSimulation/GeometryParams.h - include/CPVSimulation/Digitizer.h) + include/CPVSimulation/Digitizer.h + include/CPVSimulation/RawWriter.h) +o2_add_executable(digi2raw + COMPONENT_NAME cpv + PUBLIC_LINK_LIBRARIES O2::CPVSimulation + SOURCES src/RawCreator.cxx) + diff --git a/Detectors/CPV/simulation/include/CPVSimulation/RawWriter.h b/Detectors/CPV/simulation/include/CPVSimulation/RawWriter.h new file mode 100644 index 0000000000000..6d990fb4c375c --- /dev/null +++ b/Detectors/CPV/simulation/include/CPVSimulation/RawWriter.h @@ -0,0 +1,91 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef ALICEO2_CPV_RAWWRITER_H +#define ALICEO2_CPV_RAWWRITER_H + +#include + +#include +#include +#include +#include +#include +#include + +#include "Rtypes.h" + +#include "DetectorsRaw/RawFileWriter.h" +#include "DataFormatsCPV/Digit.h" +#include "DataFormatsCPV/TriggerRecord.h" +#include "CPVCalib/CalibParams.h" + +namespace o2 +{ + +namespace cpv +{ + +static constexpr short kNDDL = 4; ///< Total number of DDLs +static constexpr short kNPAD = 48; ///< Nuber of pads per dilogic +static constexpr short kNDilogic = 10; ///< Number of dilogic per row +static constexpr short kNRow = 16; ///< number of rows per dddl + +struct padCharge { + short charge; + short pad; + padCharge() : charge(0), pad(0) {} + padCharge(short a, short b) : charge(a), + pad(b) + { + } //for std::vector::emplace_back functionality +}; + +class RawWriter +{ + public: + enum class FileFor_t { + kFullDet, + kLink + }; + RawWriter() = default; + RawWriter(const char* outputdir) { setOutputLocation(outputdir); } + ~RawWriter() = default; + + o2::raw::RawFileWriter& getWriter() const { return *mRawWriter; } + + void setOutputLocation(const char* outputdir) { mOutputLocation = outputdir; } + void setFileFor(FileFor_t filefor) { mFileFor = filefor; } + + void init(); + void digitsToRaw(gsl::span digits, gsl::span triggers); + bool processTrigger(const gsl::span digitsbranch, const o2::cpv::TriggerRecord& trg); + + int carryOverMethod(const header::RDHAny* rdh, const gsl::span data, + const char* ptr, int maxSize, int splitID, + std::vector& trailer, std::vector& header) const; + + private: + std::vector mPadCharge[kNDDL][kNRow][kNDilogic]; ///< list of signals per event + FileFor_t mFileFor = FileFor_t::kFullDet; ///< Granularity of the output files + std::string mOutputLocation = "./"; ///< Rawfile name + std::unique_ptr mCalibParams; ///< CPV calibration + std::vector mPayload; ///< Payload to be written + gsl::span mDigits; ///< Digits input vector - must be in digitized format including the time response + std::unique_ptr mRawWriter; ///< Raw writer + + ClassDefNV(RawWriter, 1); +}; + +} // namespace cpv + +} // namespace o2 + +#endif diff --git a/Detectors/CPV/simulation/src/CPVSimulationLinkDef.h b/Detectors/CPV/simulation/src/CPVSimulationLinkDef.h index 1c9ff59953133..a2e865e038991 100644 --- a/Detectors/CPV/simulation/src/CPVSimulationLinkDef.h +++ b/Detectors/CPV/simulation/src/CPVSimulationLinkDef.h @@ -18,5 +18,6 @@ #pragma link C++ class o2::cpv::GeometryParams + ; #pragma link C++ class o2::base::DetImpl < o2::cpv::Detector> + ; #pragma link C++ class o2::cpv::Digitizer + ; +#pragma link C++ class o2::cpv::RawWriter + ; #endif diff --git a/Detectors/CPV/simulation/src/Detector.cxx b/Detectors/CPV/simulation/src/Detector.cxx index a6df5ae4806f6..3a857759abd98 100644 --- a/Detectors/CPV/simulation/src/Detector.cxx +++ b/Detectors/CPV/simulation/src/Detector.cxx @@ -249,15 +249,15 @@ Bool_t Detector::ProcessHits(FairVolume* v) int ixcell = (int)xcell; float zc = zcell - izcell - 0.5; float xc = xcell - ixcell - 0.5; - for (int iz = 1; iz <= cpvparam.mNgamz; iz++) { + for (int iz = 0; iz < cpvparam.mNgamz; iz++) { int kzg = izcell + iz - nz3; - if (kzg <= 0 || kzg > cpvparam.mnCellZ) { + if (kzg < 0 || kzg >= cpvparam.mnCellZ) { continue; } float zg = (float)(iz - nz3) - zc; - for (int ix = 1; ix <= cpvparam.mNgamx; ix++) { + for (int ix = 0; ix < cpvparam.mNgamx; ix++) { int kxg = ixcell + ix - nx3; - if (kxg <= 0 || kxg > cpvparam.mnCellX) { + if (kxg < 0 || kxg >= cpvparam.mnCellX) { continue; } float xg = (float)(ix - nx3) - xc; diff --git a/Detectors/CPV/simulation/src/RawCreator.cxx b/Detectors/CPV/simulation/src/RawCreator.cxx new file mode 100644 index 0000000000000..89a8352f3193b --- /dev/null +++ b/Detectors/CPV/simulation/src/RawCreator.cxx @@ -0,0 +1,108 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include +#include +#include +#include "Framework/Logger.h" + +#include + +#include +#include +#include +#include + +#include "CommonUtils/ConfigurableParam.h" +#include "CommonUtils/StringUtils.h" +#include "DataFormatsCPV/Digit.h" +#include "DataFormatsCPV/TriggerRecord.h" +#include "CPVBase/Geometry.h" +#include "CPVSimulation/RawWriter.h" + +namespace bpo = boost::program_options; + +int main(int argc, const char** argv) +{ + bpo::variables_map vm; + bpo::options_description opt_general("Usage:\n " + std::string(argv[0]) + + " \n" + " Tool will encode cpv raw data from input file\n" + "Commands / Options"); + bpo::options_description opt_hidden(""); + bpo::options_description opt_all; + bpo::positional_options_description opt_pos; + + try { + auto add_option = opt_general.add_options(); + add_option("help,h", "Print this help message"); + add_option("verbose,v", bpo::value()->default_value(0), "Select verbosity level [0 = no output]"); + add_option("input-file,i", bpo::value()->default_value("cpvdigits.root"), "Specifies digit input file."); + add_option("file-for,f", bpo::value()->default_value("all"), "single file per: all,link"); + add_option("output-dir,o", bpo::value()->default_value("./"), "output directory for raw data"); + add_option("debug,d", bpo::value()->default_value(0), "Select debug output level [0 = no debug output]"); + add_option("configKeyValues", bpo::value()->default_value(""), "comma-separated configKeyValues"); + + opt_all.add(opt_general).add(opt_hidden); + bpo::store(bpo::command_line_parser(argc, argv).options(opt_all).positional(opt_pos).run(), vm); + + if (vm.count("help") || argc == 1) { + std::cout << opt_general << std::endl; + exit(0); + } + + } catch (bpo::error& e) { + std::cerr << "ERROR: " << e.what() << std::endl + << std::endl; + std::cerr << opt_general << std::endl; + exit(1); + } catch (std::exception& e) { + std::cerr << e.what() << ", application will now exit" << std::endl; + exit(2); + } + + o2::conf::ConfigurableParam::updateFromString(vm["configKeyValues"].as()); + + auto digitfilename = vm["input-file"].as(), + outputdir = vm["output-dir"].as(), + filefor = vm["file-for"].as(); + + // if needed, create output directory + if (gSystem->AccessPathName(outputdir.c_str())) { + if (gSystem->mkdir(outputdir.c_str(), kTRUE)) { + LOG(FATAL) << "could not create output directory " << outputdir; + } else { + LOG(INFO) << "created output directory " << outputdir; + } + } + + std::unique_ptr digitfile(TFile::Open(digitfilename.data(), "READ")); + auto treereader = std::make_unique(static_cast(digitfile->Get("o2sim"))); + TTreeReaderValue> digitbranch(*treereader, "CPVDigit"); + TTreeReaderValue> triggerbranch(*treereader, "CPVDigitTrigRecords"); + + o2::cpv::RawWriter::FileFor_t granularity = o2::cpv::RawWriter::FileFor_t::kFullDet; + if (filefor == "all") { + granularity = o2::cpv::RawWriter::FileFor_t::kFullDet; + } else if (filefor == "link") { + granularity = o2::cpv::RawWriter::FileFor_t::kLink; + } + + o2::cpv::RawWriter rawwriter; + rawwriter.setOutputLocation(outputdir.data()); + rawwriter.setFileFor(granularity); + rawwriter.init(); + + // Loop over all entries in the tree, where each tree entry corresponds to a time frame + for (auto en : *treereader) { + rawwriter.digitsToRaw(*digitbranch, *triggerbranch); + } + rawwriter.getWriter().writeConfFile("CPV", "RAWDATA", o2::utils::concat_string(outputdir, "/CPVraw.cfg")); +} diff --git a/Detectors/CPV/simulation/src/RawWriter.cxx b/Detectors/CPV/simulation/src/RawWriter.cxx new file mode 100644 index 0000000000000..1bcc660004ff3 --- /dev/null +++ b/Detectors/CPV/simulation/src/RawWriter.cxx @@ -0,0 +1,169 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "FairLogger.h" + +#include +#include +#include +#include "DataFormatsCPV/RawFormats.h" +#include "CPVSimulation/RawWriter.h" +#include "CPVBase/CPVSimParams.h" +#include "CPVBase/RCUTrailer.h" +#include "CPVBase/Geometry.h" +#include "CCDB/CcdbApi.h" + +using namespace o2::cpv; + +void RawWriter::init() +{ + mRawWriter = std::make_unique(o2::header::gDataOriginCPV, false); + mRawWriter->setCarryOverCallBack(this); + mRawWriter->setApplyCarryOverToLastPage(true); + + // Set output file and register link + std::string rawfilename = mOutputLocation; + rawfilename += "/cpv.raw"; + + for (int ddl = 0; ddl < kNDDL; ddl++) { + short crorc = 0, link = ddl; + mRawWriter->registerLink(ddl, crorc, link, 0, rawfilename.data()); + } +} + +void RawWriter::digitsToRaw(gsl::span digitsbranch, gsl::span triggerbranch) +{ + if (!mCalibParams) { + if (o2::cpv::CPVSimParams::Instance().mCCDBPath.compare("localtest") == 0) { + mCalibParams = std::make_unique(1); // test default calibration + LOG(INFO) << "[RawWriter] No reading calibration from ccdb requested, set default"; + } else { + LOG(INFO) << "[RawWriter] getting calibration object from ccdb"; + o2::ccdb::CcdbApi ccdb; + std::map metadata; + ccdb.init("http://ccdb-test.cern.ch:8080"); // or http://localhost:8080 for a local installation + auto tr = triggerbranch.begin(); + double eventTime = -1; + // if(tr!=triggerbranch.end()){ + // eventTime = (*tr).getBCData().getTimeNS() ; + // } + //add copy constructor if necessary + // mCalibParams = std::make_unique(ccdb.retrieveFromTFileAny("CPV/Calib", metadata, eventTime)); + if (!mCalibParams) { + LOG(FATAL) << "[RawWriter] can not get calibration object from ccdb"; + } + } + } + + for (auto trg : triggerbranch) { + processTrigger(digitsbranch, trg); + } +} + +bool RawWriter::processTrigger(const gsl::span digitsbranch, const o2::cpv::TriggerRecord& trg) +{ + + //Array used to sort properly digits + for (int i = kNDDL; i--;) { + for (int j = kNRow; j--;) { + for (int k = kNDilogic; k--;) { + mPadCharge[i][j][k].clear(); + } + } + } + + for (auto& dig : gsl::span(digitsbranch.data() + trg.getFirstEntry(), trg.getNumberOfObjects())) { + short absId = dig.getAbsId(); + short ddl, dilogic, row, hwAddr; + o2::cpv::Geometry::absIdToHWaddress(absId, ddl, row, dilogic, hwAddr); + + //Convert Amp to ADC counts + short charge = dig.getAmplitude() / mCalibParams->getGain(absId); + if (charge > 2047) { + charge = 2047; + } + mPadCharge[ddl][row][dilogic].emplace_back(charge, hwAddr); + } + + //Do through DLLs and fill raw words in proper order + for (int ddl = 0; ddl < kNDDL; ddl++) { + mPayload.clear(); + //write empty header, later will be updated ? + + int nwInSegment = 0; + int posRowMarker = 0; + for (int row = 0; row < kNRow; row++) { + //reserve place for row header + mPayload.emplace_back(uint32_t(0)); + posRowMarker = mPayload.size() - 1; + nwInSegment++; + int nwRow = 0; + for (Int_t dilogic = 0; dilogic < kNDilogic; dilogic++) { + int nPad = 0; + for (padCharge& pc : mPadCharge[ddl][row][dilogic]) { + PadWord currentword = {0}; + currentword.charge = pc.charge; + currentword.address = pc.pad; + currentword.dilogic = dilogic; + currentword.row = row; + mPayload.push_back(currentword.mDataWord); + nwInSegment++; + nPad++; + nwRow++; + } + EoEWord we = {0}; + we.nword = nPad; + we.dilogic = dilogic; + we.row = row; + we.checkbit = 1; + mPayload.push_back(we.mDataWord); + nwInSegment++; + nwRow++; + } + if (row % 8 == 7) { + SegMarkerWord w = {0}; + w.row = row; + w.nwords = nwInSegment; + w.marker = 2736; + mPayload.push_back(w.mDataWord); + nwInSegment = 0; + nwRow++; + } + //Now we know number of words, update rawMarker + RowMarkerWord wr = {0}; + wr.marker = 13992; + wr.nwords = nwRow - 1; + mPayload[posRowMarker] = wr.mDataWord; + } + + // register output data + LOG(DEBUG1) << "Adding payload with size " << mPayload.size() << " (" << mPayload.size() / 4 << " ALTRO words)"; + + short crorc = 0, link = ddl; + mRawWriter->addData(ddl, crorc, link, 0, trg.getBCData(), gsl::span(reinterpret_cast(mPayload.data()), mPayload.size() * sizeof(uint32_t))); + } + return true; +} +int RawWriter::carryOverMethod(const header::RDHAny* rdh, const gsl::span data, + const char* ptr, int maxSize, int splitID, + std::vector& trailer, std::vector& header) const +{ + + constexpr int phosTrailerSize = 36; + int offs = ptr - &data[0]; // offset wrt the head of the payload + assert(offs >= 0 && size_t(offs + maxSize) <= data.size()); // make sure ptr and end of the suggested block are within the payload + int leftBefore = data.size() - offs; // payload left before this splitting + int leftAfter = leftBefore - maxSize; // what would be left after the suggested splitting + int actualSize = maxSize; + if (leftAfter && leftAfter <= phosTrailerSize) { // avoid splitting the trailer or writing only it. + actualSize -= (phosTrailerSize - leftAfter) + 4; // (as we work with int, not char in decoding) + } + return actualSize; +} diff --git a/Detectors/CPV/workflow/CMakeLists.txt b/Detectors/CPV/workflow/CMakeLists.txt index ec8736c8cc89b..8b6a4e6657690 100644 --- a/Detectors/CPV/workflow/CMakeLists.txt +++ b/Detectors/CPV/workflow/CMakeLists.txt @@ -13,10 +13,24 @@ o2_add_library(CPVWorkflow src/PublisherSpec.cxx src/ClusterizerSpec.cxx src/DigitsPrinterSpec.cxx - PUBLIC_LINK_LIBRARIES O2::Framework O2::DataFormatsCPV - O2::DPLUtils O2::CPVBase O2::CPVCalib O2::CPVSimulation O2::CPVReconstruction O2::Algorithm) + src/RawToDigitConverterSpec.cxx + src/EntropyEncoderSpec.cxx + src/EntropyDecoderSpec.cxx + PUBLIC_LINK_LIBRARIES O2::Framework + O2::DataFormatsCPV + O2::DPLUtils + O2::CPVBase + O2::CPVCalib + O2::CPVSimulation + O2::CPVReconstruction + O2::Algorithm) o2_add_executable(reco-workflow COMPONENT_NAME cpv SOURCES src/cpv-reco-workflow.cxx PUBLIC_LINK_LIBRARIES O2::CPVWorkflow) + +o2_add_executable(entropy-encoder-workflow + COMPONENT_NAME cpv + SOURCES src/entropy-encoder-workflow.cxx + PUBLIC_LINK_LIBRARIES O2::CPVWorkflow) diff --git a/Detectors/CPV/workflow/include/CPVWorkflow/EntropyDecoderSpec.h b/Detectors/CPV/workflow/include/CPVWorkflow/EntropyDecoderSpec.h new file mode 100644 index 0000000000000..7f23f832667d0 --- /dev/null +++ b/Detectors/CPV/workflow/include/CPVWorkflow/EntropyDecoderSpec.h @@ -0,0 +1,47 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// @file EntropyDecoderSpec.h +/// @brief Convert CTF (EncodedBlocks) to CPV digit/channels strean + +#ifndef O2_CPV_ENTROPYDECODER_SPEC +#define O2_CPV_ENTROPYDECODER_SPEC + +#include "Framework/DataProcessorSpec.h" +#include "Framework/Task.h" +#include "CPVReconstruction/CTFCoder.h" +#include + +namespace o2 +{ +namespace cpv +{ + +class EntropyDecoderSpec : public o2::framework::Task +{ + public: + EntropyDecoderSpec(); + ~EntropyDecoderSpec() override = default; + void run(o2::framework::ProcessingContext& pc) final; + void init(o2::framework::InitContext& ic) final; + void endOfStream(o2::framework::EndOfStreamContext& ec) final; + + private: + o2::cpv::CTFCoder mCTFCoder; + TStopwatch mTimer; +}; + +/// create a processor spec +framework::DataProcessorSpec getEntropyDecoderSpec(); + +} // namespace cpv +} // namespace o2 + +#endif diff --git a/Detectors/CPV/workflow/include/CPVWorkflow/EntropyEncoderSpec.h b/Detectors/CPV/workflow/include/CPVWorkflow/EntropyEncoderSpec.h new file mode 100644 index 0000000000000..f7543b856e77f --- /dev/null +++ b/Detectors/CPV/workflow/include/CPVWorkflow/EntropyEncoderSpec.h @@ -0,0 +1,47 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// @file EntropyEncoderSpec.h +/// @brief Convert CPV data to CTF (EncodedBlocks) + +#ifndef O2_CPV_ENTROPYENCODER_SPEC +#define O2_CPV_ENTROPYENCODER_SPEC + +#include "Framework/DataProcessorSpec.h" +#include "Framework/Task.h" +#include +#include "CPVReconstruction/CTFCoder.h" + +namespace o2 +{ +namespace cpv +{ + +class EntropyEncoderSpec : public o2::framework::Task +{ + public: + EntropyEncoderSpec(); + ~EntropyEncoderSpec() override = default; + void run(o2::framework::ProcessingContext& pc) final; + void init(o2::framework::InitContext& ic) final; + void endOfStream(o2::framework::EndOfStreamContext& ec) final; + + private: + o2::cpv::CTFCoder mCTFCoder; + TStopwatch mTimer; +}; + +/// create a processor spec +framework::DataProcessorSpec getEntropyEncoderSpec(); + +} // namespace cpv +} // namespace o2 + +#endif diff --git a/Detectors/CPV/workflow/include/CPVWorkflow/RawToDigitConverterSpec.h b/Detectors/CPV/workflow/include/CPVWorkflow/RawToDigitConverterSpec.h new file mode 100644 index 0000000000000..78764982c9ebe --- /dev/null +++ b/Detectors/CPV/workflow/include/CPVWorkflow/RawToDigitConverterSpec.h @@ -0,0 +1,81 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include + +#include "Framework/DataProcessorSpec.h" +#include "Framework/Task.h" +#include "DataFormatsCPV/Digit.h" +#include "DataFormatsCPV/TriggerRecord.h" +#include "CPVCalib/CalibParams.h" +#include "CPVCalib/BadChannelMap.h" +#include "CPVReconstruction/RawDecoder.h" + +namespace o2 +{ + +namespace cpv +{ + +namespace reco_workflow +{ + +/// \class RawToDigitConverterSpec +/// \brief Coverter task for Raw data to CPV cells +/// \author Dmitri Peresunko NRC KI +/// \since Sept., 2020 +/// +class RawToDigitConverterSpec : public framework::Task +{ + public: + /// \brief Constructor + /// \param propagateMC If true the MCTruthContainer is propagated to the output + RawToDigitConverterSpec() : framework::Task(){}; + + /// \brief Destructor + ~RawToDigitConverterSpec() override = default; + + /// \brief Initializing the RawToDigitConverterSpec + /// \param ctx Init context + void init(framework::InitContext& ctx) final; + + /// \brief Run conversion of raw data to cells + /// \param ctx Processing context + /// + /// The following branches are linked: + /// Input RawData: {"ROUT", "RAWDATA", 0, Lifetime::Timeframe} + /// Output cells: {"CPV", "CELLS", 0, Lifetime::Timeframe} + /// Output cells trigger record: {"CPV", "CELLSTR", 0, Lifetime::Timeframe} + /// Output HW errors: {"CPV", "RAWHWERRORS", 0, Lifetime::Timeframe} + void run(framework::ProcessingContext& ctx) final; + + protected: + /// \brief simple check of HW address + char CheckHWAddress(short ddl, short hwAddress, short& fee); + + private: + int mDDL = 15; + std::unique_ptr mCalibParams; ///< CPV calibration + std::unique_ptr mBadMap; ///< BadMap + std::vector mOutputDigits; ///< Container with output cells + std::vector mOutputTriggerRecords; ///< Container with output cells + std::vector mOutputHWErrors; ///< Errors occured in reading data +}; + +/// \brief Creating DataProcessorSpec for the CPV Cell Converter Spec +/// +/// Refer to RawToDigitConverterSpec::run for input and output specs +o2::framework::DataProcessorSpec getRawToDigitConverterSpec(); + +} // namespace reco_workflow + +} // namespace cpv + +} // namespace o2 diff --git a/Detectors/CPV/workflow/src/ClusterizerSpec.cxx b/Detectors/CPV/workflow/src/ClusterizerSpec.cxx index 88d79e6bbeca9..7a6ab4cde247f 100644 --- a/Detectors/CPV/workflow/src/ClusterizerSpec.cxx +++ b/Detectors/CPV/workflow/src/ClusterizerSpec.cxx @@ -23,41 +23,37 @@ void ClusterizerSpec::init(framework::InitContext& ctx) // Initialize clusterizer and link geometry mClusterizer.initialize(); + mClusterizer.propagateMC(mPropagateMC); } void ClusterizerSpec::run(framework::ProcessingContext& ctx) { - if (ctx.inputs().isValid("digits")) { - LOG(DEBUG) << "CPVClusterizer - run on digits called"; + LOG(INFO) << "Start run "; + LOG(DEBUG) << "CPVClusterizer - run on digits called"; + auto digits = ctx.inputs().get>("digits"); + // auto digitsTR = ctx.inputs().get>("digitTriggerRecords"); //TODO:: Why span does not work??? + // auto digits = ctx.inputs().get>("digits"); + auto digitsTR = ctx.inputs().get>("digitTriggerRecords"); - auto dataref = ctx.inputs().get("digits"); - auto const* cpvheader = o2::framework::DataRefUtils::getHeader(dataref); - if (!cpvheader->mHasPayload) { - LOG(DEBUG) << "[CPVClusterizer - run] No more digits" << std::endl; - ctx.services().get().readyToQuit(framework::QuitRequest::Me); - return; - } + // printf("CluSpec: digits=%d, TR=%d \n",digits.size(),digitsTR.size()) ; - // auto digits = ctx.inputs().get>("digits"); - auto digits = ctx.inputs().get>("digits"); - auto digitsTR = ctx.inputs().get>("digitTriggerRecords"); - - LOG(DEBUG) << "[CPVClusterizer - run] Received " << digitsTR.size() << " TR, running clusterizer ..."; - auto truthcont = ctx.inputs().get*>("digitsmctr"); - mClusterizer.process(digits, digitsTR, truthcont.get(), &mOutputClusters, &mOutputClusterTrigRecs, &mOutputTruthCont); // Find clusters on digits (pass by ref) + LOG(DEBUG) << "[CPVClusterizer - run] Received " << digitsTR.size() << " TR, running clusterizer ..."; + std::unique_ptr> truthcont; + if (mPropagateMC) { + truthcont = ctx.inputs().get*>("digitsmctr"); } - LOG(DEBUG) << "[CPVClusterizer - run] Writing " << mOutputClusters.size() << " clusters, " << mOutputClusterTrigRecs.size() << "TR and " << mOutputTruthCont.getIndexedSize() << " Labels"; + mClusterizer.process(digits, digitsTR, *truthcont, &mOutputClusters, &mOutputClusterTrigRecs, &mOutputTruthCont); // Find clusters on digits (pass by ref) + ctx.outputs().snapshot(o2::framework::Output{"CPV", "CLUSTERS", 0, o2::framework::Lifetime::Timeframe}, mOutputClusters); ctx.outputs().snapshot(o2::framework::Output{"CPV", "CLUSTERTRIGRECS", 0, o2::framework::Lifetime::Timeframe}, mOutputClusterTrigRecs); if (mPropagateMC) { ctx.outputs().snapshot(o2::framework::Output{"CPV", "CLUSTERTRUEMC", 0, o2::framework::Lifetime::Timeframe}, mOutputTruthCont); } - LOG(INFO) << "Finished "; + LOG(INFO) << "Finished, wrote " << mOutputClusters.size() << " clusters, " << mOutputClusterTrigRecs.size() << "TR and " << mOutputTruthCont.getIndexedSize() << " Labels"; ctx.services().get().endOfStream(); ctx.services().get().readyToQuit(framework::QuitRequest::Me); } - o2::framework::DataProcessorSpec o2::cpv::reco_workflow::getClusterizerSpec(bool propagateMC) { std::vector inputs; diff --git a/Detectors/CPV/workflow/src/EntropyDecoderSpec.cxx b/Detectors/CPV/workflow/src/EntropyDecoderSpec.cxx new file mode 100644 index 0000000000000..1b696185f7dbd --- /dev/null +++ b/Detectors/CPV/workflow/src/EntropyDecoderSpec.cxx @@ -0,0 +1,79 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// @file EntropyDecoderSpec.cxx + +#include + +#include "Framework/ControlService.h" +#include "Framework/ConfigParamRegistry.h" +#include "CPVWorkflow/EntropyDecoderSpec.h" + +using namespace o2::framework; + +namespace o2 +{ +namespace cpv +{ + +EntropyDecoderSpec::EntropyDecoderSpec() +{ + mTimer.Stop(); + mTimer.Reset(); +} + +void EntropyDecoderSpec::init(o2::framework::InitContext& ic) +{ + std::string dictPath = ic.options().get("cpv-ctf-dictionary"); + if (!dictPath.empty() && dictPath != "none") { + mCTFCoder.createCoders(dictPath, o2::ctf::CTFCoderBase::OpType::Decoder); + } +} + +void EntropyDecoderSpec::run(ProcessingContext& pc) +{ + auto cput = mTimer.CpuTime(); + mTimer.Start(false); + + auto buff = pc.inputs().get>("ctf"); + + auto& triggers = pc.outputs().make>(OutputRef{"triggers"}); + auto& clusters = pc.outputs().make>(OutputRef{"clusters"}); + + // since the buff is const, we cannot use EncodedBlocks::relocate directly, instead we wrap its data to another flat object + const auto ctfImage = o2::cpv::CTF::getImage(buff.data()); + mCTFCoder.decode(ctfImage, triggers, clusters); + + mTimer.Stop(); + LOG(INFO) << "Decoded " << clusters.size() << " CPV clusters in " << triggers.size() << " triggers in " << mTimer.CpuTime() - cput << " s"; +} + +void EntropyDecoderSpec::endOfStream(EndOfStreamContext& ec) +{ + LOGF(INFO, "CPV Entropy Decoding total timing: Cpu: %.3e Real: %.3e s in %d slots", + mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); +} + +DataProcessorSpec getEntropyDecoderSpec() +{ + std::vector outputs{ + OutputSpec{{"triggers"}, "CPV", "CLUSTERTRIGRECS", 0, Lifetime::Timeframe}, + OutputSpec{{"clusters"}, "CPV", "CLUSTERS", 0, Lifetime::Timeframe}}; + + return DataProcessorSpec{ + "cpv-entropy-decoder", + Inputs{InputSpec{"ctf", "CPV", "CTFDATA", 0, Lifetime::Timeframe}}, + outputs, + AlgorithmSpec{adaptFromTask()}, + Options{{"cpv-ctf-dictionary", VariantType::String, "ctf_dictionary.root", {"File of CTF decoding dictionary"}}}}; +} + +} // namespace cpv +} // namespace o2 diff --git a/Detectors/CPV/workflow/src/EntropyEncoderSpec.cxx b/Detectors/CPV/workflow/src/EntropyEncoderSpec.cxx new file mode 100644 index 0000000000000..95588b1f45e77 --- /dev/null +++ b/Detectors/CPV/workflow/src/EntropyEncoderSpec.cxx @@ -0,0 +1,79 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// @file EntropyEncoderSpec.cxx + +#include + +#include "Framework/ControlService.h" +#include "Framework/ConfigParamRegistry.h" +#include "CPVWorkflow/EntropyEncoderSpec.h" +#include "DetectorsCommonDataFormats/DetID.h" + +using namespace o2::framework; + +namespace o2 +{ +namespace cpv +{ + +EntropyEncoderSpec::EntropyEncoderSpec() +{ + mTimer.Stop(); + mTimer.Reset(); +} + +void EntropyEncoderSpec::init(o2::framework::InitContext& ic) +{ + std::string dictPath = ic.options().get("cpv-ctf-dictionary"); + if (!dictPath.empty() && dictPath != "none") { + mCTFCoder.createCoders(dictPath, o2::ctf::CTFCoderBase::OpType::Encoder); + } +} + +void EntropyEncoderSpec::run(ProcessingContext& pc) +{ + auto cput = mTimer.CpuTime(); + mTimer.Start(false); + auto triggers = pc.inputs().get>("triggers"); + auto clusters = pc.inputs().get>("clusters"); + + auto& buffer = pc.outputs().make>(Output{"CPV", "CTFDATA", 0, Lifetime::Timeframe}); + mCTFCoder.encode(buffer, triggers, clusters); + auto eeb = CTF::get(buffer.data()); // cast to container pointer + eeb->compactify(); // eliminate unnecessary padding + buffer.resize(eeb->size()); // shrink buffer to strictly necessary size + // eeb->print(); + mTimer.Stop(); + LOG(INFO) << "Created encoded data of size " << eeb->size() << " for CPV in " << mTimer.CpuTime() - cput << " s"; +} + +void EntropyEncoderSpec::endOfStream(EndOfStreamContext& ec) +{ + LOGF(INFO, "CPV Entropy Encoding total timing: Cpu: %.3e Real: %.3e s in %d slots", + mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); +} + +DataProcessorSpec getEntropyEncoderSpec() +{ + std::vector inputs; + inputs.emplace_back("triggers", "CPV", "CLUSTERTRIGRECS", 0, Lifetime::Timeframe); + inputs.emplace_back("clusters", "CPV", "CLUSTERS", 0, Lifetime::Timeframe); + + return DataProcessorSpec{ + "cpv-entropy-encoder", + inputs, + Outputs{{"CPV", "CTFDATA", 0, Lifetime::Timeframe}}, + AlgorithmSpec{adaptFromTask()}, + Options{{"cpv-ctf-dictionary", VariantType::String, "ctf_dictionary.root", {"File of CTF encoding dictionary"}}}}; +} + +} // namespace cpv +} // namespace o2 diff --git a/Detectors/CPV/workflow/src/RawToDigitConverterSpec.cxx b/Detectors/CPV/workflow/src/RawToDigitConverterSpec.cxx new file mode 100644 index 0000000000000..219e19ccb6248 --- /dev/null +++ b/Detectors/CPV/workflow/src/RawToDigitConverterSpec.cxx @@ -0,0 +1,212 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +#include +#include "FairLogger.h" +#include "CommonDataFormat/InteractionRecord.h" +#include "Framework/InputRecordWalker.h" +#include "Framework/ConfigParamRegistry.h" +#include "Framework/ControlService.h" +#include "Framework/WorkflowSpec.h" +#include "DataFormatsCPV/CPVBlockHeader.h" +#include "DataFormatsCPV/TriggerRecord.h" +#include "DetectorsRaw/RDHUtils.h" +#include "CPVReconstruction/RawDecoder.h" +#include "CPVWorkflow/RawToDigitConverterSpec.h" +#include "CCDB/CcdbApi.h" +#include "CPVBase/CPVSimParams.h" +#include "CPVBase/Geometry.h" + +using namespace o2::cpv::reco_workflow; + +void RawToDigitConverterSpec::init(framework::InitContext& ctx) +{ + mDDL = ctx.options().get("DDL"); + LOG(DEBUG) << "Initialize converter "; +} + +void RawToDigitConverterSpec::run(framework::ProcessingContext& ctx) +{ + // Cache digits from bunch crossings as the component reads timeframes from many links consecutively + std::map>> digitBuffer; // Internal digit buffer + int firstEntry = 0; + mOutputHWErrors.clear(); + + if (!mCalibParams) { + if (o2::cpv::CPVSimParams::Instance().mCCDBPath.compare("localtest") == 0) { + mCalibParams = std::make_unique(1); // test default calibration + LOG(INFO) << "No reading calibration from ccdb requested, set default"; + } else { + LOG(INFO) << "Getting calibration object from ccdb"; + //TODO: configuring ccdb address from config file, readign proper calibration/BadMap and updateing if necessary + o2::ccdb::CcdbApi ccdb; + std::map metadata; + ccdb.init("http://ccdb-test.cern.ch:8080"); // or http://localhost:8080 for a local installation + // auto tr = triggerbranch.begin(); + // double eventTime = -1; + // if(tr!=triggerbranch.end()){ + // eventTime = (*tr).getBCData().getTimeNS() ; + // } + // mCalibParams = ccdb.retrieveFromTFileAny("CPV/Calib", metadata, eventTime); + // if (!mCalibParams) { + // LOG(FATAL) << "Can not get calibration object from ccdb"; + // } + } + } + + if (!mBadMap) { + if (o2::cpv::CPVSimParams::Instance().mCCDBPath.compare("localtest") == 0) { + mBadMap = std::make_unique(1); // test default calibration + LOG(INFO) << "No reading bad map from ccdb requested, set default"; + } else { + LOG(INFO) << "Getting bad map object from ccdb"; + o2::ccdb::CcdbApi ccdb; + std::map metadata; + ccdb.init("http://ccdb-test.cern.ch:8080"); // or http://localhost:8080 for a local installation + // auto tr = triggerbranch.begin(); + // double eventTime = -1; + // if(tr!=triggerbranch.end()){ + // eventTime = (*tr).getBCData().getTimeNS() ; + // } + // mBadMap = ccdb.retrieveFromTFileAny("CPV/BadMap", metadata, eventTime); + // if (!mBadMap) { + // LOG(FATAL) << "Can not get bad map object from ccdb"; + // } + } + } + + for (const auto& rawData : framework::InputRecordWalker(ctx.inputs())) { + + // enum RawErrorType_t { + // kOK, ///< NoError + // kNO_PAYLOAD, ///< No payload per ddl + // kHEADER_DECODING, + // kPAGE_NOTFOUND, + // kPAYLOAD_DECODING, + // kHEADER_INVALID, + // kRCU_TRAILER_ERROR, ///< RCU trailer cannot be decoded or invalid + // kRCU_VERSION_ERROR, ///< RCU trailer version not matching with the version in the raw header + // kRCU_TRAILER_SIZE_ERROR, ///< RCU trailer size length + // kSEGMENT_HEADER_ERROR, + // kROW_HEADER_ERROR, + // kEOE_HEADER_ERROR, + // kPADERROR, + // kPadAddress + // }; + + o2::cpv::RawReaderMemory rawreader(o2::framework::DataRefUtils::as(rawData)); + // loop over all the DMA pages + while (rawreader.hasNext()) { + try { + rawreader.next(); + } catch (RawErrorType_t e) { + LOG(ERROR) << "Raw decoding error " << (int)e; + //add error list + mOutputHWErrors.emplace_back(5, 0, 0, 0, e); //Put general errors to non-existing DDL5 + //if problem in header, abandon this page + if (e == RawErrorType_t::kPAGE_NOTFOUND || + e == RawErrorType_t::kHEADER_DECODING || + e == RawErrorType_t::kHEADER_INVALID) { + break; + } + //if problem in payload, try to continue + continue; + } + auto& header = rawreader.getRawHeader(); + auto triggerBC = o2::raw::RDHUtils::getTriggerBC(header); + auto triggerOrbit = o2::raw::RDHUtils::getTriggerOrbit(header); + auto ddl = o2::raw::RDHUtils::getFEEID(header); + ddl -= mDDL; + + o2::InteractionRecord currentIR(triggerBC, triggerOrbit); + std::shared_ptr> currentDigitContainer; + auto found = digitBuffer.find(currentIR); + if (found == digitBuffer.end()) { + currentDigitContainer = std::make_shared>(); + digitBuffer[currentIR] = currentDigitContainer; + } else { + currentDigitContainer = found->second; + } + // + if (ddl > o2::cpv::Geometry::kNDDL) { //only 4 correct DDLs + LOG(ERROR) << "DDL=" << ddl; + mOutputHWErrors.emplace_back(6, ddl, 0, 0, kHEADER_INVALID); //Add non-existing DDL as DDL 5 + continue; //skip STU ddl + } + // use the altro decoder to decode the raw data, and extract the RCU trailer + o2::cpv::RawDecoder decoder(rawreader); + RawErrorType_t err = decoder.decode(); + + if (err != kOK) { + //TODO handle severe errors + //TODO: probably careful conversion of decoder errors to Fitter errors? + mOutputHWErrors.emplace_back(ddl, 1, 0, 0, err); //assign general header errors to non-existing FEE 16 + } + // Loop over all the channels + for (uint32_t adch : decoder.getDigits()) { + AddressCharge ac = {adch}; + unsigned short absId = ac.Address; + //test bad map + if (mBadMap->isChannelGood(absId)) { + if (ac.Charge > o2::cpv::CPVSimParams::Instance().mZSthreshold) { + float amp = mCalibParams->getGain(absId) * ac.Charge; + currentDigitContainer->emplace_back(absId, amp, -1); + } + } + } + //Check and send list of hwErrors + for (auto& er : decoder.getErrors()) { + mOutputHWErrors.push_back(er); + } + } //RawReader::hasNext + } + + // Loop over BCs, sort digits with increasing digit ID and write to output containers + mOutputDigits.clear(); + mOutputTriggerRecords.clear(); + for (auto [bc, digits] : digitBuffer) { + int prevDigitSize = mOutputDigits.size(); + if (digits->size()) { + // Sort digits according to digit ID + std::sort(digits->begin(), digits->end(), [](o2::cpv::Digit& lhs, o2::cpv::Digit& rhs) { return lhs.getAbsId() < rhs.getAbsId(); }); + + for (auto digit : *digits) { + mOutputDigits.push_back(digit); + } + } + + mOutputTriggerRecords.emplace_back(bc, prevDigitSize, mOutputDigits.size() - prevDigitSize); + } + digitBuffer.clear(); + + LOG(INFO) << "[CPVRawToDigitConverter - run] Writing " << mOutputDigits.size() << " digits ..."; + ctx.outputs().snapshot(o2::framework::Output{"CPV", "DIGITS", 0, o2::framework::Lifetime::Timeframe}, mOutputDigits); + ctx.outputs().snapshot(o2::framework::Output{"CPV", "DIGITTRIGREC", 0, o2::framework::Lifetime::Timeframe}, mOutputTriggerRecords); + ctx.outputs().snapshot(o2::framework::Output{"CPV", "RAWHWERRORS", 0, o2::framework::Lifetime::Timeframe}, mOutputHWErrors); +} + +o2::framework::DataProcessorSpec o2::cpv::reco_workflow::getRawToDigitConverterSpec() +{ + std::vector inputs; + inputs.emplace_back("RAWDATA", o2::framework::ConcreteDataTypeMatcher{"CPV", "RAWDATA"}, o2::framework::Lifetime::Timeframe); + + std::vector outputs; + outputs.emplace_back("CPV", "DIGITS", 0, o2::framework::Lifetime::Timeframe); + outputs.emplace_back("CPV", "DIGITTRIGREC", 0, o2::framework::Lifetime::Timeframe); + outputs.emplace_back("CPV", "RAWHWERRORS", 0, o2::framework::Lifetime::Timeframe); + + return o2::framework::DataProcessorSpec{"CPVRawToDigitConverterSpec", + inputs, // o2::framework::select("A:CPV/RAWDATA"), + outputs, + o2::framework::adaptFromTask(), + o2::framework::Options{ + {"pedestal", o2::framework::VariantType::String, "off", {"Analyze as pedestal run on/off"}}, + {"DDL", o2::framework::VariantType::String, "0", {"DDL id to read"}}, + }}; +} diff --git a/Detectors/CPV/workflow/src/RecoWorkflow.cxx b/Detectors/CPV/workflow/src/RecoWorkflow.cxx index 82bc452753350..f46a9614b5e37 100644 --- a/Detectors/CPV/workflow/src/RecoWorkflow.cxx +++ b/Detectors/CPV/workflow/src/RecoWorkflow.cxx @@ -24,8 +24,8 @@ #include "DataFormatsCPV/TriggerRecord.h" #include "CPVWorkflow/RecoWorkflow.h" #include "CPVWorkflow/ClusterizerSpec.h" -#include "CPVWorkflow/DigitsPrinterSpec.h" #include "CPVWorkflow/PublisherSpec.h" +#include "CPVWorkflow/RawToDigitConverterSpec.h" //#include "CPVWorkflow/RawWriterSpec.h" #include "Framework/DataSpecUtils.h" #include "SimulationDataFormat/MCTruthContainer.h" @@ -83,21 +83,29 @@ o2::framework::WorkflowSpec getWorkflow(bool propagateMC, // specs.emplace_back(o2::cpv::reco_workflow::getRawWriterSpec()); // } - if (inputType == InputType::Digits) { - specs.emplace_back(o2::cpv::getPublisherSpec(PublisherConf{ - "cpv-digit-reader", - "o2sim", - {"digitbranch", "CPVDigit", "Digit branch"}, - {"digittrigger", "CPVDigitTrigRecords", "TrigRecords branch"}, - {"mcbranch", "CPVDigitMCTruth", "MC label branch"}, - o2::framework::OutputSpec{"CPV", "DIGITS"}, - o2::framework::OutputSpec{"CPV", "DIGITTRIGREC"}, - o2::framework::OutputSpec{"CPV", "DIGITSMCTR"}}, - propagateMC)); - - if (enableDigitsPrinter) { - specs.emplace_back(o2::cpv::reco_workflow::getPhosDigitsPrinterSpec()); + if (inputType == InputType::Raw) { + //no explicit raw reader + + if (isEnabled(OutputType::Digits)) { + specs.emplace_back(o2::cpv::reco_workflow::getRawToDigitConverterSpec()); } + } + + if (inputType == InputType::Digits) { + // specs.emplace_back(o2::cpv::getPublisherSpec(PublisherConf{ + // "cpv-digit-reader", + // "o2sim", + // {"digitbranch", "CPVDigit", "Digit branch"}, + // {"digittrigger", "CPVDigitTrigRecords", "TrigRecords branch"}, + // {"mcbranch", "CPVDigitMCTruth", "MC label branch"}, + // o2::framework::OutputSpec{"CPV", "DIGITS"}, + // o2::framework::OutputSpec{"CPV", "DIGITTRIGREC"}, + // o2::framework::OutputSpec{"CPV", "DIGITSMCTR"}}, + // propagateMC)); + + // if (enableDigitsPrinter) { + // specs.emplace_back(o2::cpv::reco_workflow::getDigitsPrinterSpec()); + // } if (isEnabled(OutputType::Clusters)) { // add clusterizer @@ -110,104 +118,56 @@ o2::framework::WorkflowSpec getWorkflow(bool propagateMC, // } } - // check if the process is ready to quit - // this is decided upon the meta information in the CPV block header, the operation is set - // value kNoPayload in case of no data or no operation - // see also PublisherSpec.cxx - // in this workflow, the EOD is sent after the last real data, and all inputs will receive EOD, - // so it is enough to check on the first occurence - // FIXME: this will be changed once DPL can propagate control events like EOD - auto checkReady = [](o2::framework::DataRef const& ref) { - auto const* cpvheader = o2::framework::DataRefUtils::getHeader(ref); - // sector number -1 indicates end-of-data - if (cpvheader != nullptr) { - // indicate normal processing if not ready and skip if ready - if (!cpvheader->mHasPayload) { - return std::make_tuple(o2::framework::MakeRootTreeWriterSpec::TerminationCondition::Action::SkipProcessing, true); - } - } - return std::make_tuple(o2::framework::MakeRootTreeWriterSpec::TerminationCondition::Action::DoProcessing, false); - }; - - // auto makeWriterSpec = [propagateMC, checkReady](const char* processName, - // const char* defaultFileName, - // const char* defaultTreeName, - // bool createMCMap, - // auto&& databranch, - // auto&& datatrbranch, - // auto&& mcbranch=nullptr, - // auto&& mcmapbranch=nullptr) { - // // depending on the MC propagation flag, the RootTreeWriter spec is created with two - // // or one branch definition - // if (propagateMC) { - // if(createMCMap){ - // return std::move(o2::framework::MakeRootTreeWriterSpec(processName, defaultFileName, defaultTreeName, - // o2::framework::MakeRootTreeWriterSpec::TerminationCondition{checkReady}, - // std::move(databranch), - // std::move(datatrbranch), - // std::move(mcbranch), - // std::move(mcmapbranch))); + // // check if the process is ready to quit + // // this is decided upon the meta information in the CPV block header, the operation is set + // // value kNoPayload in case of no data or no operation + // // see also PublisherSpec.cxx + // // in this workflow, the EOD is sent after the last real data, and all inputs will receive EOD, + // // so it is enough to check on the first occurence + // // FIXME: this will be changed once DPL can propagate control events like EOD + // auto checkReady = [](o2::framework::DataRef const& ref) { + // auto const* cpvheader = o2::framework::DataRefUtils::getHeader(ref); + // // sector number -1 indicates end-of-data + // if (cpvheader != nullptr) { + // // indicate normal processing if not ready and skip if ready + // if (!cpvheader->mHasPayload) { + // return std::make_tuple(o2::framework::MakeRootTreeWriterSpec::TerminationCondition::Action::SkipProcessing, true); // } - // else{ - // return std::move(o2::framework::MakeRootTreeWriterSpec(processName, defaultFileName, defaultTreeName, - // o2::framework::MakeRootTreeWriterSpec::TerminationCondition{checkReady}, - // std::move(databranch), - // std::move(datatrbranch), - // std::move(mcbranch))); - // } - // } - // else{ - // return std::move(o2::framework::MakeRootTreeWriterSpec(processName, defaultFileName, defaultTreeName, - // o2::framework::MakeRootTreeWriterSpec::TerminationCondition{checkReady}, - // std::move(databranch), - // std::move(datatrbranch))); // } + // return std::make_tuple(o2::framework::MakeRootTreeWriterSpec::TerminationCondition::Action::DoProcessing, false); // }; - // if (isEnabled(OutputType::Raw)) { - // using RawOutputType = std::vector; - // specs.push_back(makeWriterSpec("cpv-raw-writer", - // inputType == InputType::Digits ? "cpv-raw.root" : "cpvrawcells.root", - // "o2sim", - // BranchDefinition{o2::framework::InputSpec{"data", "CPV", "RAW", 0}, - // "CPVRaw", - // "raw-branch-name"}, - // BranchDefinition{o2::framework::InputSpec{"mc", "CPV", "RAWMCTR", 0}, - // "CPVRawMCTruth", - // "rawmc-branch-name"})()); - // } + // if (isEnabled(OutputType::Digits)) { + // using DigitOutputType = std::vector; + // using DTROutputType = std::vector; - if (isEnabled(OutputType::Digits)) { - using DigitOutputType = std::vector; - using DTROutputType = std::vector; - - specs.emplace_back(o2::framework::MakeRootTreeWriterSpec("cpv-digits-writer", "cpvdigits.root", "o2sim", - -1, - o2::framework::MakeRootTreeWriterSpec::TerminationCondition{checkReady}, - BranchDefinition{o2::framework::InputSpec{"data", "CPV", "DIGITS", 0}, - "CPVDigit", - "digit-branch-name"}, - BranchDefinition{o2::framework::InputSpec{"data", "CPV", "DIGITTRIGREC", 0}, - "CPVDigTR", - "digittr-branch-name"}, - BranchDefinition{o2::framework::InputSpec{"mc", "CPV", "DIGITSMCTR", 0}, - "CPVDigitMCTruth", - "digitmc-branch-name"})()); - } + // specs.emplace_back(o2::framework::MakeRootTreeWriterSpec("cpv-digits-writer", "cpvdigits.root", "o2sim", + // -1, + // o2::framework::MakeRootTreeWriterSpec::TerminationCondition{checkReady}, + // BranchDefinition{o2::framework::InputSpec{"data", "CPV", "DIGITS", 0}, + // "CPVDigit", + // "digit-branch-name"}, + // BranchDefinition{o2::framework::InputSpec{"data", "CPV", "DIGITTRIGREC", 0}, + // "CPVDigTR", + // "digittr-branch-name"}, + // BranchDefinition{o2::framework::InputSpec{"mc", "CPV", "DIGITSMCTR", 0}, + // "CPVDigitMCTruth", + // "digitmc-branch-name"})()); + // } - if (isEnabled(OutputType::Clusters)) { - specs.emplace_back(o2::framework::MakeRootTreeWriterSpec("cpv-clusters-writer", "cpvclusters.root", "o2sim", -1, - o2::framework::MakeRootTreeWriterSpec::TerminationCondition{checkReady}, - BranchDefinition>{o2::framework::InputSpec{"data", "CPV", "CLUSTERS", 0}, - "CPVCluster", - "cluster-branch-name"}, - BranchDefinition>{o2::framework::InputSpec{"datatr", "CPV", "CLUSTERTRIGRECS", 0}, - "CPVClusTR", - "clustertr-branch-name"}, - BranchDefinition>{o2::framework::InputSpec{"mc", "CPV", "CLUSTERTRUEMC", 0}, - "CPVClusMC", - "clustermc-branch-name"})()); - } + // if (isEnabled(OutputType::Clusters)) { + // specs.emplace_back(o2::framework::MakeRootTreeWriterSpec("cpv-clusters-writer", "cpvclusters.root", "o2sim", -1, + // o2::framework::MakeRootTreeWriterSpec::TerminationCondition{checkReady}, + // BranchDefinition>{o2::framework::InputSpec{"data", "CPV", "CLUSTERS", 0}, + // "CPVCluster", + // "cluster-branch-name"}, + // BranchDefinition>{o2::framework::InputSpec{"datatr", "CPV", "CLUSTERTRIGRECS", 0}, + // "CPVClusTR", + // "clustertr-branch-name"}, + // BranchDefinition>{o2::framework::InputSpec{"mc", "CPV", "CLUSTERTRUEMC", 0}, + // "CPVClusMC", + // "clustermc-branch-name"})()); + // } return std::move(specs); } diff --git a/Detectors/CPV/workflow/src/entropy-encoder-workflow.cxx b/Detectors/CPV/workflow/src/entropy-encoder-workflow.cxx new file mode 100644 index 0000000000000..5c43f8af68991 --- /dev/null +++ b/Detectors/CPV/workflow/src/entropy-encoder-workflow.cxx @@ -0,0 +1,39 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "CPVWorkflow/EntropyEncoderSpec.h" +#include "CommonUtils/ConfigurableParam.h" +#include "Framework/ConfigParamSpec.h" + +using namespace o2::framework; + +// ------------------------------------------------------------------ + +// we need to add workflow options before including Framework/runDataProcessing +void customize(std::vector& workflowOptions) +{ + // option allowing to set parameters + std::vector options{ConfigParamSpec{"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}}; + + std::swap(workflowOptions, options); +} + +// ------------------------------------------------------------------ + +#include "Framework/runDataProcessing.h" + +WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) +{ + WorkflowSpec wf; + // Update the (declared) parameters if changed from the command line + o2::conf::ConfigurableParam::updateFromString(cfgc.options().get("configKeyValues")); + wf.emplace_back(o2::cpv::getEntropyEncoderSpec()); + return wf; +} diff --git a/Detectors/CTF/CMakeLists.txt b/Detectors/CTF/CMakeLists.txt index 4daff377c664c..5565515a769fe 100644 --- a/Detectors/CTF/CMakeLists.txt +++ b/Detectors/CTF/CMakeLists.txt @@ -76,8 +76,16 @@ o2_add_test(emcal o2_add_test(phos PUBLIC_LINK_LIBRARIES O2::CTFWorkflow - O2::DataFormatsEMCAL + O2::DataFormatsPHOS O2::PHOSReconstruction SOURCES test/test_ctf_io_phos.cxx COMPONENT_NAME ctf LABELS ctf) + +o2_add_test(cpv + PUBLIC_LINK_LIBRARIES O2::CTFWorkflow + O2::DataFormatsCPV + O2::CPVReconstruction + SOURCES test/test_ctf_io_cpv.cxx + COMPONENT_NAME ctf + LABELS ctf) diff --git a/Detectors/CTF/test/test_ctf_io_cpv.cxx b/Detectors/CTF/test/test_ctf_io_cpv.cxx new file mode 100644 index 0000000000000..b60131e60c851 --- /dev/null +++ b/Detectors/CTF/test/test_ctf_io_cpv.cxx @@ -0,0 +1,127 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#define BOOST_TEST_MODULE Test CPVCTFIO +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK +#include +#include "DetectorsCommonDataFormats/NameConf.h" +#include "CPVReconstruction/CTFCoder.h" +#include "DataFormatsCPV/CTF.h" +#include "Framework/Logger.h" +#include +#include +#include +#include +#include + +using namespace o2::cpv; + +BOOST_AUTO_TEST_CASE(CTFTest) +{ + std::vector triggers; + std::vector clusters; + TStopwatch sw; + sw.Start(); + o2::InteractionRecord ir(0, 0); + Cluster clu; + for (int irof = 0; irof < 1000; irof++) { + ir += 1 + gRandom->Integer(200); + + auto start = clusters.size(); + int n = 1 + gRandom->Poisson(100); + for (int i = n; i--;) { + char mult = gRandom->Integer(30); + char mod = 1 + gRandom->Integer(3); + char exMax = gRandom->Integer(3); + float x = 72.3 * 2. * (gRandom->Rndm() - 0.5); + float z = 63.3 * 2. * (gRandom->Rndm() - 0.5); + float e = 254. * gRandom->Rndm(); + clusters.emplace_back(mult, mod, exMax, x, z, e); + } + triggers.emplace_back(ir, start, clusters.size() - start); + } + + sw.Start(); + std::vector vec; + { + CTFCoder coder; + coder.encode(vec, triggers, clusters); // compress + } + sw.Stop(); + LOG(INFO) << "Compressed in " << sw.CpuTime() << " s"; + + // writing + { + sw.Start(); + auto* ctfImage = o2::cpv::CTF::get(vec.data()); + TFile flOut("test_ctf_cpv.root", "recreate"); + TTree ctfTree(std::string(o2::base::NameConf::CTFTREENAME).c_str(), "O2 CTF tree"); + ctfImage->print(); + ctfImage->appendToTree(ctfTree, "CPV"); + ctfTree.Write(); + sw.Stop(); + LOG(INFO) << "Wrote to tree in " << sw.CpuTime() << " s"; + } + + // reading + vec.clear(); + LOG(INFO) << "Start reading from tree "; + { + sw.Start(); + TFile flIn("test_ctf_cpv.root"); + std::unique_ptr tree((TTree*)flIn.Get(std::string(o2::base::NameConf::CTFTREENAME).c_str())); + BOOST_CHECK(tree); + o2::cpv::CTF::readFromTree(vec, *(tree.get()), "CPV"); + sw.Stop(); + LOG(INFO) << "Read back from tree in " << sw.CpuTime() << " s"; + } + + std::vector triggersD; + std::vector clustersD; + + sw.Start(); + const auto ctfImage = o2::cpv::CTF::getImage(vec.data()); + { + CTFCoder coder; + coder.decode(ctfImage, triggersD, clustersD); // decompress + } + sw.Stop(); + LOG(INFO) << "Decompressed in " << sw.CpuTime() << " s"; + + BOOST_CHECK(triggersD.size() == triggers.size()); + BOOST_CHECK(clustersD.size() == clusters.size()); + LOG(INFO) << " BOOST_CHECK triggersD.size() " << triggersD.size() << " triggers.size() " << triggers.size() + << " BOOST_CHECK(clustersD.size() " << clustersD.size() << " clusters.size()) " << clusters.size(); + + for (size_t i = 0; i < triggers.size(); i++) { + const auto& dor = triggers[i]; + const auto& ddc = triggersD[i]; + LOG(DEBUG) << " Orig.TriggerRecord " << i << " " << dor.getBCData() << " " << dor.getFirstEntry() << " " << dor.getNumberOfObjects(); + LOG(DEBUG) << " Deco.TriggerRecord " << i << " " << ddc.getBCData() << " " << ddc.getFirstEntry() << " " << ddc.getNumberOfObjects(); + + BOOST_CHECK(dor.getBCData() == ddc.getBCData()); + BOOST_CHECK(dor.getNumberOfObjects() == ddc.getNumberOfObjects()); + BOOST_CHECK(dor.getFirstEntry() == dor.getFirstEntry()); + } + + for (size_t i = 0; i < clusters.size(); i++) { + const auto& cor = clusters[i]; + const auto& cdc = clustersD[i]; + BOOST_CHECK(cor.getMultiplicity() == cdc.getMultiplicity()); + BOOST_CHECK(cor.getModule() == cdc.getModule()); + BOOST_CHECK(TMath::Abs(cor.getEnergy() - cdc.getEnergy()) < 1.); + float xCor, zCor, xCdc, zCdc; + cor.getLocalPosition(xCor, zCor); + cdc.getLocalPosition(xCdc, zCdc); + BOOST_CHECK(TMath::Abs(xCor - xCdc) < 0.004); + BOOST_CHECK(TMath::Abs(zCor - zCdc) < 0.004); + } +} diff --git a/Detectors/CTF/workflow/CMakeLists.txt b/Detectors/CTF/workflow/CMakeLists.txt index 1bb7896c3dd2c..efc519308a0ac 100644 --- a/Detectors/CTF/workflow/CMakeLists.txt +++ b/Detectors/CTF/workflow/CMakeLists.txt @@ -20,6 +20,8 @@ o2_add_library(CTFWorkflow O2::DataFormatsFV0 O2::DataFormatsFDD O2::DataFormatsMID + O2::DataFormatsPHOS + O2::DataFormatsCPV O2::DataFormatsParameters O2::ITSMFTWorkflow O2::TPCWorkflow @@ -30,6 +32,7 @@ o2_add_library(CTFWorkflow O2::MIDWorkflow O2::EMCALWorkflow O2::PHOSWorkflow + O2::CPVWorkflow O2::Algorithm O2::CommonUtils) diff --git a/Detectors/CTF/workflow/src/CTFReaderSpec.cxx b/Detectors/CTF/workflow/src/CTFReaderSpec.cxx index 1021128026b1f..740c335622808 100644 --- a/Detectors/CTF/workflow/src/CTFReaderSpec.cxx +++ b/Detectors/CTF/workflow/src/CTFReaderSpec.cxx @@ -32,6 +32,7 @@ #include "DataFormatsMID/CTF.h" #include "DataFormatsEMCAL/CTF.h" #include "DataFormatsPHOS/CTF.h" +#include "DataFormatsCPV/CTF.h" #include "Algorithm/RangeTokenizer.h" using namespace o2::framework; @@ -181,6 +182,13 @@ void CTFReaderSpec::run(ProcessingContext& pc) setFirstTFOrbit(det.getName()); } + det = DetID::CPV; + if (detsTF[det]) { + auto& bufVec = pc.outputs().make>({det.getName()}, sizeof(o2::cpv::CTF)); + o2::cpv::CTF::readFromTree(bufVec, *(tree.get()), det.getName()); + setFirstTFOrbit(det.getName()); + } + mTimer.Stop(); LOG(INFO) << "Read CTF " << inputFile << " in " << mTimer.CpuTime() - cput << " s"; diff --git a/Detectors/CTF/workflow/src/CTFWriterSpec.cxx b/Detectors/CTF/workflow/src/CTFWriterSpec.cxx index fefaa596bb84b..dafa122b38d41 100644 --- a/Detectors/CTF/workflow/src/CTFWriterSpec.cxx +++ b/Detectors/CTF/workflow/src/CTFWriterSpec.cxx @@ -29,6 +29,7 @@ #include "DataFormatsMID/CTF.h" #include "DataFormatsEMCAL/CTF.h" #include "DataFormatsPHOS/CTF.h" +#include "DataFormatsCPV/CTF.h" using namespace o2::framework; @@ -106,6 +107,7 @@ void CTFWriterSpec::run(ProcessingContext& pc) processDet(pc, DetID::MID, header, treeOut.get()); processDet(pc, DetID::EMC, header, treeOut.get()); processDet(pc, DetID::PHS, header, treeOut.get()); + processDet(pc, DetID::CPV, header, treeOut.get()); mTimer.Stop(); @@ -177,6 +179,7 @@ void CTFWriterSpec::storeDictionaries() storeDictionary(DetID::MID, header); storeDictionary(DetID::EMC, header); storeDictionary(DetID::PHS, header); + storeDictionary(DetID::CPV, header); // close remnants if (mDictTreeOut) { closeDictionaryTreeAndFile(header); diff --git a/Detectors/CTF/workflow/src/ctf-reader-workflow.cxx b/Detectors/CTF/workflow/src/ctf-reader-workflow.cxx index b99922a7d02ac..14c552470b370 100644 --- a/Detectors/CTF/workflow/src/ctf-reader-workflow.cxx +++ b/Detectors/CTF/workflow/src/ctf-reader-workflow.cxx @@ -29,6 +29,7 @@ #include "MIDWorkflow/EntropyDecoderSpec.h" #include "EMCALWorkflow/EntropyDecoderSpec.h" #include "PHOSWorkflow/EntropyDecoderSpec.h" +#include "CPVWorkflow/EntropyDecoderSpec.h" using namespace o2::framework; using DetID = o2::detectors::DetID; @@ -100,6 +101,9 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) if (dets[DetID::PHS]) { specs.push_back(o2::phos::getEntropyDecoderSpec()); } + if (dets[DetID::CPV]) { + specs.push_back(o2::cpv::getEntropyDecoderSpec()); + } return std::move(specs); } diff --git a/Detectors/PHOS/base/src/RCUTrailer.cxx b/Detectors/PHOS/base/src/RCUTrailer.cxx index 5fbef81c6c62f..6295023ecbf53 100644 --- a/Detectors/PHOS/base/src/RCUTrailer.cxx +++ b/Detectors/PHOS/base/src/RCUTrailer.cxx @@ -11,6 +11,7 @@ #include #include #include +#include "FairLogger.h" #include "CommonConstants/LHCConstants.h" #include "PHOSBase/RCUTrailer.h" @@ -60,7 +61,7 @@ void RCUTrailer::constructFromRawPayload(const gsl::span payload for (; trailerSize > 0; trailerSize--) { word = payloadwords[--index]; if ((word >> 30) != 2) { - std::cerr << "Missing RCU trailer identifier pattern!\n"; + LOG(ERROR) << "Missing RCU trailer identifier pattern!"; continue; } int parCode = (word >> 26) & 0xF; @@ -96,7 +97,7 @@ void RCUTrailer::constructFromRawPayload(const gsl::span payload mAltroCFG2 = parData & 0x1FFFFFF; break; default: - std::cerr << "Undefined parameter code " << parCode << ", ignore it !\n"; + LOG(ERROR) << "Undefined parameter code " << parCode << ", ignore it !"; break; } } diff --git a/Detectors/PHOS/reconstruction/src/AltroDecoder.cxx b/Detectors/PHOS/reconstruction/src/AltroDecoder.cxx index 181d90107d010..5547ececcd2f0 100644 --- a/Detectors/PHOS/reconstruction/src/AltroDecoder.cxx +++ b/Detectors/PHOS/reconstruction/src/AltroDecoder.cxx @@ -80,6 +80,10 @@ void AltroDecoder::readChannels() auto& currentchannel = mChannels.back(); /// decode all words for channel int numberofwords = (currentchannel.getPayloadSize() + 2) / 3; + if (numberofwords > payloadend - currentpos) { + LOG(ERROR) << "Channel payload " << numberofwords << " larger than left in total " << payloadend - currentpos; + continue; + } std::vector bunchwords; for (int iword = 0; iword < numberofwords; iword++) { currentword = buffer[currentpos++]; @@ -109,7 +113,8 @@ void AltroDecoder::readChannels() const std::vector& AltroDecoder::getChannels() const { - if (!mChannelsInitialized) + if (!mChannelsInitialized) { throw AltroDecoderError::ErrorType_t::CHANNEL_ERROR; // "Channels not initizalized"); + } return mChannels; } diff --git a/Steer/DigitizerWorkflow/src/SimpleDigitizerWorkflow.cxx b/Steer/DigitizerWorkflow/src/SimpleDigitizerWorkflow.cxx index 4bde26c686859..27b67d7d7f2e6 100644 --- a/Steer/DigitizerWorkflow/src/SimpleDigitizerWorkflow.cxx +++ b/Steer/DigitizerWorkflow/src/SimpleDigitizerWorkflow.cxx @@ -576,7 +576,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) // the CPV part if (isEnabled(o2::detectors::DetID::CPV)) { detList.emplace_back(o2::detectors::DetID::CPV); - // connect the PHOS digitization + // connect the CPV digitization specs.emplace_back(o2::cpv::getCPVDigitizerSpec(fanoutsize++, mctruth)); // add PHOS writer specs.emplace_back(o2::cpv::getCPVDigitWriterSpec(mctruth));