From d08d21a1bd2d0badc98f10863efe557fad9b4d28 Mon Sep 17 00:00:00 2001 From: Markus Fasel Date: Fri, 16 Jun 2023 11:32:48 +0200 Subject: [PATCH] [EMCAL-916, EMCAL-537, EMCAL-550, EMCAL-911] Integrate TRU decoding into sync reco - EMCAL-916: Add decoding of TRU data (FastORs and patch index) - EMCAL-911: Add handling of TRU data in RecoContainer - Provide helper classes for TRU- and FastOR decoding including the calculation of the L0timesum - EMCAL-537: Provide data structure for patches, TRUs and Timesums - EMCAL-550: Integration into RecoWorkflow: Output channels and option to disable trigger reconstruction --- DataFormats/Detectors/EMCAL/CMakeLists.txt | 1 + .../DataFormatsEMCAL/CompressedTriggerData.h | 68 ++++ .../EMCAL/src/CompressedTriggerData.cxx | 31 ++ .../base/include/EMCALBase/TriggerMappingV2.h | 1 + Detectors/EMCAL/base/src/TriggerMappingV2.cxx | 2 +- Detectors/EMCAL/reconstruction/CMakeLists.txt | 4 + .../EMCALReconstruction/FastORTimeSeries.h | 83 +++++ .../EMCALReconstruction/RecoContainer.h | 78 +++- .../EMCALReconstruction/TRUDataHandler.h | 185 ++++++++++ .../src/EMCALReconstructionLinkDef.h | 2 + .../reconstruction/src/FastORTimeSeries.cxx | 44 +++ .../reconstruction/src/RecoContainer.cxx | 41 ++- .../reconstruction/src/TRUDataHandler.cxx | 67 ++++ .../EMCALWorkflow/RawToCellConverterSpec.h | 109 +++++- .../include/EMCALWorkflow/RecoWorkflow.h | 4 +- .../workflow/src/RawToCellConverterSpec.cxx | 337 ++++++++++++++---- Detectors/EMCAL/workflow/src/RecoWorkflow.cxx | 5 +- .../EMCAL/workflow/src/emc-reco-workflow.cxx | 4 +- 18 files changed, 978 insertions(+), 88 deletions(-) create mode 100644 DataFormats/Detectors/EMCAL/include/DataFormatsEMCAL/CompressedTriggerData.h create mode 100644 DataFormats/Detectors/EMCAL/src/CompressedTriggerData.cxx create mode 100644 Detectors/EMCAL/reconstruction/include/EMCALReconstruction/FastORTimeSeries.h create mode 100644 Detectors/EMCAL/reconstruction/include/EMCALReconstruction/TRUDataHandler.h create mode 100644 Detectors/EMCAL/reconstruction/src/FastORTimeSeries.cxx create mode 100644 Detectors/EMCAL/reconstruction/src/TRUDataHandler.cxx diff --git a/DataFormats/Detectors/EMCAL/CMakeLists.txt b/DataFormats/Detectors/EMCAL/CMakeLists.txt index 80b00474465b8..9c93bae30ddf6 100644 --- a/DataFormats/Detectors/EMCAL/CMakeLists.txt +++ b/DataFormats/Detectors/EMCAL/CMakeLists.txt @@ -22,6 +22,7 @@ o2_add_library(DataFormatsEMCAL src/ErrorTypeFEE.cxx src/CellLabel.cxx src/ClusterLabel.cxx + src/CompressedTriggerData.cxx PUBLIC_LINK_LIBRARIES O2::CommonDataFormat O2::Headers O2::MathUtils diff --git a/DataFormats/Detectors/EMCAL/include/DataFormatsEMCAL/CompressedTriggerData.h b/DataFormats/Detectors/EMCAL/include/DataFormatsEMCAL/CompressedTriggerData.h new file mode 100644 index 0000000000000..5fbf2187ab5dd --- /dev/null +++ b/DataFormats/Detectors/EMCAL/include/DataFormatsEMCAL/CompressedTriggerData.h @@ -0,0 +1,68 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// 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_EMCAL_COMPRESSEDTRIGGERDATA_H +#define ALICEO2_EMCAL_COMPRESSEDTRIGGERDATA_H + +#include +#include + +namespace o2::emcal +{ + +/// \struct CompressedTRU +/// \brief Compressed reconstructed TRU information +/// \ingroup EMCALDataFormat +struct CompressedTRU { + uint8_t mTRUIndex; ///< TRU index + uint8_t mTriggerTime; ///< Trigger time of the TRU + bool mFired; ///< Fired status of the TRU + uint8_t mNumberOfPatches; ///< Number of patches found for the TRU +}; + +/// \struct CompressedTriggerPatch +/// \brief Compressed reconstructed L0 trigger patch information +/// \ingroup EMCALDataFormat +struct CompressedTriggerPatch { + uint8_t mTRUIndex; ///< Index of the TRU where the trigger patch has been found + uint8_t mPatchIndexInTRU; ///< Index of the trigger patch in the TRU + uint8_t mTime; ///< Reconstructed time of the trigger patch + uint16_t mADC; ///< ADC sum of the trigger patch +}; + +/// \struct CompressedL0TimeSum +/// \brief Compressed L0 timesum information +/// \ingroup EMCALDataFormat +struct CompressedL0TimeSum { + uint16_t mIndex; ///< Absolute ID of the FastOR + uint16_t mTimesum; ///< ADC value of the time-sum (4-integral) +}; + +/// \brief Output stream operator of the CompressedTRU +/// \param stream Stream to write to +/// \param tru TRU data to be streamed +/// \return Stream after writing +std::ostream& operator<<(std::ostream& stream, const CompressedTRU& tru); + +/// \brief Output stream operator of the CompressedTriggerPatch +/// \param stream Stream to write to +/// \param patch Trigger patch to be streamed +/// \return Stream after writing +std::ostream& operator<<(std::ostream& stream, const CompressedTriggerPatch& patch); + +/// \brief Output stream operator of the CompressedL0TimeSum +/// \param stream Stream to write to +/// \param timesum FastOR L0 timesum to be streamed +/// \return Stream after writing +std::ostream& operator<<(std::ostream& stream, const CompressedL0TimeSum& timesum); + +} // namespace o2::emcal + +#endif // ALICEO2_EMCAL_COMPRESSEDTRIGGERDATA_H \ No newline at end of file diff --git a/DataFormats/Detectors/EMCAL/src/CompressedTriggerData.cxx b/DataFormats/Detectors/EMCAL/src/CompressedTriggerData.cxx new file mode 100644 index 0000000000000..e60b58c958d03 --- /dev/null +++ b/DataFormats/Detectors/EMCAL/src/CompressedTriggerData.cxx @@ -0,0 +1,31 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// 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 "DataFormatsEMCAL/CompressedTriggerData.h" + +std::ostream& o2::emcal::operator<<(std::ostream& stream, const o2::emcal::CompressedTRU& tru) +{ + stream << "TRU " << tru.mTRUIndex << ": Fired " << (tru.mFired ? "yes" : "no") << ", time " << (tru.mFired ? std::to_string(static_cast(tru.mTriggerTime)) : "Undefined") << ", number of patches " << tru.mNumberOfPatches; + return stream; +} + +std::ostream& o2::emcal::operator<<(std::ostream& stream, const o2::emcal::CompressedTriggerPatch& patch) +{ + stream << "Patch " << patch.mPatchIndexInTRU << " in TRU " << patch.mTRUIndex << ": Time " << patch.mTime << ", ADC " << patch.mADC; + return stream; +} + +std::ostream& o2::emcal::operator<<(std::ostream& stream, const o2::emcal::CompressedL0TimeSum& timesum) +{ + stream << "FastOR " << timesum.mIndex << ": " << timesum.mTimesum << " ADC counts"; + return stream; +} \ No newline at end of file diff --git a/Detectors/EMCAL/base/include/EMCALBase/TriggerMappingV2.h b/Detectors/EMCAL/base/include/EMCALBase/TriggerMappingV2.h index 3b56c1794d3e4..24d8c1686f005 100644 --- a/Detectors/EMCAL/base/include/EMCALBase/TriggerMappingV2.h +++ b/Detectors/EMCAL/base/include/EMCALBase/TriggerMappingV2.h @@ -49,6 +49,7 @@ class TriggerMappingV2 static constexpr unsigned int FASTORSPHI = (5 * FASTORSPHISM) + (1 * FASTORSPHISM / 3) /*EMCAL*/ + (3 * FASTORSPHISM) + (1 * FASTORSPHISM / 3) /*DCAL */; ///< Number of FastOR/EMCALs in Phi static constexpr unsigned int ALLFASTORS = FASTORSETA * FASTORSPHI; ///< Number of FastOR/EMCALs + static constexpr unsigned int PATCHESINTRU = 77; //******************************************** // Index types diff --git a/Detectors/EMCAL/base/src/TriggerMappingV2.cxx b/Detectors/EMCAL/base/src/TriggerMappingV2.cxx index 5d5ee46dc6f2f..e60d7b31e5305 100644 --- a/Detectors/EMCAL/base/src/TriggerMappingV2.cxx +++ b/Detectors/EMCAL/base/src/TriggerMappingV2.cxx @@ -390,7 +390,7 @@ TriggerMappingV2::IndexTRU TriggerMappingV2::getTRUIndexFromOnlineHardareAddree( unsigned short branch = (hardwareAddress >> 11) & 0x1; // 0/1 - IndexTRU truIndex = ((ddlID << 1) | branch) - 1; // 0..2 + IndexTRU truIndex = (((ddlID % 2) << 1) | branch) - 1; // 0..2 truIndex = (supermoduleID % 2) ? 2 - truIndex : truIndex; diff --git a/Detectors/EMCAL/reconstruction/CMakeLists.txt b/Detectors/EMCAL/reconstruction/CMakeLists.txt index 952db0cf5ba01..983bef1aeba40 100644 --- a/Detectors/EMCAL/reconstruction/CMakeLists.txt +++ b/Detectors/EMCAL/reconstruction/CMakeLists.txt @@ -16,6 +16,7 @@ o2_add_library(EMCALReconstruction src/AltroDecoder.cxx src/Bunch.cxx src/Channel.cxx + src/FastORTimeSeries.cxx src/RecoParam.cxx src/RawDecodingError.cxx src/STUDecoderError.cxx @@ -32,6 +33,7 @@ o2_add_library(EMCALReconstruction src/CTFCoder.cxx src/CTFHelper.cxx src/StuDecoder.cxx + src/TRUDataHandler.cxx PUBLIC_LINK_LIBRARIES O2::Headers AliceO2::InfoLogger O2::DataFormatsEMCAL @@ -49,6 +51,7 @@ o2_target_root_dictionary( include/EMCALReconstruction/RawPayload.h include/EMCALReconstruction/Bunch.h include/EMCALReconstruction/Channel.h + include/EMCALReconstruction/FastORTimeSeries.h include/EMCALReconstruction/CaloFitResults.h include/EMCALReconstruction/CaloRawFitter.h include/EMCALReconstruction/CaloRawFitterStandard.h @@ -59,6 +62,7 @@ o2_target_root_dictionary( include/EMCALReconstruction/DigitReader.h include/EMCALReconstruction/RecoParam.h include/EMCALReconstruction/StuDecoder.h + include/EMCALReconstruction/TRUDataHandler.h ) o2_add_executable(rawreader-file diff --git a/Detectors/EMCAL/reconstruction/include/EMCALReconstruction/FastORTimeSeries.h b/Detectors/EMCAL/reconstruction/include/EMCALReconstruction/FastORTimeSeries.h new file mode 100644 index 0000000000000..b6dac4a3b3d0c --- /dev/null +++ b/Detectors/EMCAL/reconstruction/include/EMCALReconstruction/FastORTimeSeries.h @@ -0,0 +1,83 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// 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_EMCAL_FASTORTIMESERIES_H +#define ALICEO2_EMCAL_FASTORTIMESERIES_H + +#include +#include +#include "Rtypes.h" + +namespace o2::emcal +{ + +/// \class FastORTimeSeries +/// \brief Container for FastOR time series +/// \author Markus Fasel , Oak Ridge National Laboratory +/// \ingroup EMCALReconstruction +/// \since April 19, 2024 +/// +/// Time series are encoded in bunches in the raw data, which are usually time-reversed. +/// The FastORTimeSeries handles the time series of all bunches in the readout window, +/// in proper future-direction time order, correcting the time-reversal from the Fake-ALTRO. +/// Consequently the ADC samples are expected in time-reversed format. The function +/// calculateL1TimeSum calculates the timesum of the timeseries as 4-integral with respect to +/// a given L0 time, which is expected at the end of the time integration range. +class FastORTimeSeries +{ + public: + /// @brief Dummy constructor + FastORTimeSeries() = default; + + /// \brief Construcor + /// \param maxsamples Maximum number of time samples + /// \param timesamples Time-reversed raw ADC samples + /// \param starttime Start time + FastORTimeSeries(int maxsamples, const gsl::span timesamples, uint8_t starttime) + { + setSize(maxsamples); + fillReversed(timesamples, starttime); + } + + /// \brief Destructor + ~FastORTimeSeries() = default; + + void setTimeSamples(const gsl::span timesamples, uint8_t starttime) { fillReversed(timesamples, starttime); } + + /// \brief Calculate L0 timesum (4-integral of the ADC series) with respect to a given L0 time + /// \param l0time L0 time (end of the time series) + /// \return Timesum of the time series + uint16_t calculateL1TimeSum(uint8_t l0time) const; + + /// \brief Access raw ADC values (in forward time order) + /// \return ADC values of the time series in forward time order + const gsl::span getADCs() const { return mTimeSamples; } + + /// \brief Clear ADC samples in the time series + void clear(); + + private: + /// \brief Set the container size for the ADC samples + /// \param maxsamples Max. amount of samples to be handled + void setSize(int maxsamples); + + /// \brief Fill the internal time samples in proper time order + /// \param timesamples Time-reversed time samples + /// \param starttime Start time + void fillReversed(const gsl::span timesamples, uint8_t starttime); + + std::vector mTimeSamples; ///< Raw ADC time samples (in forward time order) + + ClassDef(FastORTimeSeries, 1); +}; + +} // namespace o2::emcal + +#endif \ No newline at end of file diff --git a/Detectors/EMCAL/reconstruction/include/EMCALReconstruction/RecoContainer.h b/Detectors/EMCAL/reconstruction/include/EMCALReconstruction/RecoContainer.h index c1a9fc151a1d5..e02cafd1c3769 100644 --- a/Detectors/EMCAL/reconstruction/include/EMCALReconstruction/RecoContainer.h +++ b/Detectors/EMCAL/reconstruction/include/EMCALReconstruction/RecoContainer.h @@ -14,8 +14,10 @@ /// \author Markus Fasel , Oak Ridge National Laboratory /// \since May 30, 2023 +#include #include #include +#include #include #include #include @@ -24,6 +26,9 @@ #include #include #include +#include +#include +#include namespace o2::emcal { @@ -53,6 +58,36 @@ struct RecCellInfo { class EventContainer { public: + /// \class TRUIndexException + /// \brief Handler for access of TRU data with invalid TRU index + /// \ingroup EMCALReconstruction + class TRUIndexException final : public std::exception + { + public: + /// \brief Constructor + /// \param index TRU index raising the exception + TRUIndexException(std::size_t index); + + /// \brief Destructor + ~TRUIndexException() noexcept final = default; + + /// \brief Get the error message of the exception + /// \return Error message + const char* what() const noexcept final { return mMessage.data(); } + + /// \brief Get the TRU index raising the exception + /// \return TRU index + std::size_t getIndex() const { return mIndex; } + + /// \brief Print error message on stream + /// \param stream Stream to print on + void printStream(std::ostream& stream) const; + + private: + std::size_t mIndex; ///< TRU index raising the exception + std::string mMessage; ///< Buffer for error message + }; + /// \brief Constructor EventContainer() = default; @@ -95,6 +130,22 @@ class EventContainer /// \return Number of LEDMONs int getNumberOfLEDMONs() const { return mLEDMons.size(); } + /// \brief Read and write access TRU data of a given TRU + /// \param truIndex Index of the TRU + /// \return TRU data handler for the TRU + /// \throw TRUIndexException in case the TRU index is invalid (>= 52) + TRUDataHandler& getTRUData(std::size_t truIndex); + + /// \brief Read-only access TRU data of a given TRU + /// \param truIndex Index of the TRU + /// \return TRU data handler for the TRU + /// \throw TRUIndexException in case the TRU index is invalid (>= 52) + const TRUDataHandler& readTRUData(std::size_t truIndex) const; + + /// \brief Access to container with FastOR time series + /// \return Container with time series + const std::unordered_map& getTimeSeriesContainer() const { return mL0FastORs; } + /// \brief Add cell information to the event container /// \param tower Tower ID /// \param energy Cell energy @@ -129,6 +180,16 @@ class EventContainer setCellCommon(tower, energy, time, celltype, true, hwaddress, ddlID, doMergeHGLG); } + /// \brief Add bunch of time series to the container + /// \param fastORAbsID Absolute ID of the FastOR + /// \param starttime Start time of the bunch + /// \param timesamples Time samples of the bunch in time-reversed format + /// + /// In case a TimeSeries is already present for the given FastOR abs. ID in the container + /// the bunch is added to this, otherwise a new TimeSeries is added with the ADCs of the + /// bunch. + void setFastOR(uint16_t fastORAbsID, uint8_t starttime, const gsl::span timesamples); + /// \brief Sort Cells / LEDMONs in container according to tower / module ID /// \param isLEDmon Switch between Cell and LEDMON void sortCells(bool isLEDmon); @@ -148,21 +209,26 @@ class EventContainer /// \return True if the energy is in the saturation region, false otherwise bool isCellSaturated(double energy) const; - o2::InteractionRecord mInteractionRecord; - uint64_t mTriggerBits = 0; ///< Trigger bits of the event - std::vector mCells; ///< Container of cells in event - std::vector mLEDMons; ///< Container of LEDMONs in event + /// \brief Initialize the TRU handlers + void initTRUs(); + + o2::InteractionRecord mInteractionRecord; ///< Interaction record of the event + uint64_t mTriggerBits = 0; ///< Trigger bits of the event + std::vector mCells; ///< Container of cells in event + std::vector mLEDMons; ///< Container of LEDMONs in event + std::array mTRUData; ///< TRU status + std::unordered_map mL0FastORs; ///< L0 FastOR time series }; /// \class RecoContainer -/// \brief Handler for cells in +/// \brief Handler for cells/LEDMONS/Trigger data in timeframes /// \ingroup EMCALReconstruction class RecoContainer { public: /// \class InteractionNotFoundException /// \brief Handling of access to trigger interaction record not present in container - class InteractionNotFoundException : public std::exception + class InteractionNotFoundException final : public std::exception { public: /// \brief Constructor diff --git a/Detectors/EMCAL/reconstruction/include/EMCALReconstruction/TRUDataHandler.h b/Detectors/EMCAL/reconstruction/include/EMCALReconstruction/TRUDataHandler.h new file mode 100644 index 0000000000000..419e0dc773e36 --- /dev/null +++ b/Detectors/EMCAL/reconstruction/include/EMCALReconstruction/TRUDataHandler.h @@ -0,0 +1,185 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// 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_EMCAL_TRUDataHandler_H +#define ALICEO2_EMCAL_TRUDataHandler_H + +#include +#include +#include +#include +#include +#include + +#include "Rtypes.h" + +#include "EMCALBase/TriggerMappingV2.h" + +namespace o2::emcal +{ + +/// \class TRUDataHandler +/// \brief Helper class to handle decoded TRU data during the reconstruction +/// \author Markus Fasel , Oak Ridge National Laboratory +/// \ingroup EMCALReconstruction +/// \since April 19, 2024 +/// +/// The decoded TRU data contains the following information +/// - Index of the TRU +/// - Trigger time of the TRU +/// - Fired or not +/// - Index of fired patches with time the patch has fired +/// The information is decoded in columns 96 to 105 of the FakeALTRO data. The +/// class does not handle FastOR timesums (colums 0-96), they are handled by a +/// separate class FastORTimeSeries. +class TRUDataHandler +{ + public: + /// \class PatchIndexException + /// \brief Handler of errors related to invalid trigger patch IDs + /// \ingroup EMCALReconstruction + class PatchIndexException final : public std::exception + { + public: + /// \brief Constructor + /// \param index Patch index raising the exception + PatchIndexException(int8_t index); + + /// \brief Destructor + ~PatchIndexException() noexcept final = default; + + /// \brief Get patch index raising the exception + /// \return Patch index + int8_t getIndex() const { return mIndex; } + + /// \brief Access Error message + /// \return Error message + const char* what() const noexcept final + { + return mMessage.data(); + } + + /// \brief Print error on output stream + /// \param stream Stream to be printed to + void printStream(std::ostream& stream) const; + + private: + int8_t mIndex = -1; ///< Patch index rainsing the exception + std::string mMessage; ///< Buffer for error message + }; + + /// \brief Constructor + TRUDataHandler(); + + /// \brief Destructor + ~TRUDataHandler() = default; + + /// \brief Reset handler + void reset(); + + /// \brief Set reconstructed trigger patch + /// \param index Index of the trigger patch in the TRU + /// \param time Decoded time of the patch + /// \throw PatchIndexException in case the patch index is invalid (>= 77) + void setPatch(unsigned int index, unsigned int time) + { + checkPatchIndex(index); + mPatchTimes[index] = time; + } + + /// \brief Mark TRU as fired (containing at least one patch above threshold) + /// \param fired + void setFired(bool fired) { mL0Fired = fired; } + + /// \brief Set the L0 time of the TRU + /// \param l0time L0 time of the TRU + void setL0time(int l0time) { mL0Time = l0time; } + + /// \brief Set the index of the TRU (in global STU indexing scheme) + /// \param index Index of the TRU + void setTRUIndex(int index) { mTRUIndex = index; } + + /// \brief Check whether the TRU was fired (at least one patch above threshold) + /// \return True if the TRU was fired, false otherwise + bool isFired() const { return mL0Fired; } + + int8_t getL0time() const { return mL0Time; } + + /// \brief Check whehther the patch at the given index has fired + /// \param index Index of the patch + /// \return True if the patch has fired, false otherwise + /// \throw PatchIndexException in case the patch index is invalid (>= 77) + bool hasPatch(unsigned int index) const + { + checkPatchIndex(index); + return mPatchTimes[index] < UCHAR_MAX; + } + + /// \brief Get the trigger time of the trigger patch at a given index + /// \param index Index of the trigger patch + /// \return Reconstructed patch time (UCHAR_MAX in case the patch has not fired) + /// \throw PatchIndexException in case the patch index is invalid (>= 77) + uint8_t getPatchTime(unsigned int index) const + { + checkPatchIndex(index); + return mPatchTimes[index]; + } + + /// \brief Check whether the TRU has any patch fired + /// \return True if at least one fired patch was found, false otherwise + bool hasAnyPatch() const + { + for (int ipatch = 0; ipatch < mPatchTimes.size(); ipatch++) { + if (hasPatch(ipatch)) { + return true; + } + } + return false; + } + + /// \brief Get the index of the TRU in global (STU) index schemes + /// \return Index of the TRU + int getTRUIndex() const { return mTRUIndex; } + + /// \brief Print TRU information to an output stream + /// \param stream Stream to print on + void printStream(std::ostream& stream) const; + + private: + /// \brief Check whether the patch index is valid + /// \throw PatchIndexException in case the patch index is invalid (>= 77) + void checkPatchIndex(unsigned int index) const + { + if (index >= mPatchTimes.size()) { + throw PatchIndexException(index); + } + } + + std::array mPatchTimes; ///< Patch times: In case the patch time is smaller than UCHAR_MAX then the patch has fired + bool mL0Fired = false; ///< TRU has fired + int8_t mL0Time = -1; ///< L0 time of the TRU + int8_t mTRUIndex = -1; ///< Index of the TRU + ClassDefNV(TRUDataHandler, 1); +}; + +/// \brief Output stream operator for the TRU data handler +/// \param stream Stream to print on +/// \param data TRU data to be streamed +/// \return Stream after printing +std::ostream& operator<<(std::ostream& stream, const TRUDataHandler& data); + +/// \brief Output stream operator of the PatchIndexException +/// \param stream Stream to print on +/// \param error Error to be streamed +/// \return Stream after printing +std::ostream& operator<<(std::ostream& stream, const TRUDataHandler::PatchIndexException& error); + +} // namespace o2::emcal +#endif diff --git a/Detectors/EMCAL/reconstruction/src/EMCALReconstructionLinkDef.h b/Detectors/EMCAL/reconstruction/src/EMCALReconstructionLinkDef.h index 9482db17c0386..ee39958db5add 100644 --- a/Detectors/EMCAL/reconstruction/src/EMCALReconstructionLinkDef.h +++ b/Detectors/EMCAL/reconstruction/src/EMCALReconstructionLinkDef.h @@ -25,6 +25,8 @@ #pragma link C++ class o2::emcal::CaloRawFitterStandard + ; #pragma link C++ class o2::emcal::CaloRawFitterGamma2 + ; #pragma link C++ class o2::emcal::StuDecoder + ; +#pragma link C++ class o2::emcal::FastORTimeSeries + ; +#pragma link C++ class o2::emcal::TRUDataHandler + ; #pragma link C++ class o2::emcal::RecoParam + ; #pragma link C++ class o2::conf::ConfigurableParamHelper < o2::emcal::RecoParam> + ; diff --git a/Detectors/EMCAL/reconstruction/src/FastORTimeSeries.cxx b/Detectors/EMCAL/reconstruction/src/FastORTimeSeries.cxx new file mode 100644 index 0000000000000..d23d982047f2b --- /dev/null +++ b/Detectors/EMCAL/reconstruction/src/FastORTimeSeries.cxx @@ -0,0 +1,44 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// 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 "EMCALReconstruction/FastORTimeSeries.h" + +using namespace o2::emcal; + +void FastORTimeSeries::fillReversed(const gsl::span samples, uint8_t starttime) +{ + + for (std::size_t isample = 0; isample < samples.size(); isample++) { + mTimeSamples[starttime - isample] = samples[isample]; + } +} + +uint16_t FastORTimeSeries::calculateL1TimeSum(uint8_t l0time) const +{ + uint16_t timesum = 0; + int firstbin = l0time - 4; // Include sample before the L0 time + for (int isample = firstbin; isample < firstbin + 4; isample++) { + timesum += mTimeSamples[isample]; + } + return timesum; +} + +void FastORTimeSeries::setSize(int maxsamples) +{ + mTimeSamples.resize(maxsamples); +} + +void FastORTimeSeries::clear() +{ + std::fill(mTimeSamples.begin(), mTimeSamples.end(), 0); +} \ No newline at end of file diff --git a/Detectors/EMCAL/reconstruction/src/RecoContainer.cxx b/Detectors/EMCAL/reconstruction/src/RecoContainer.cxx index 4ed49e5876c93..062887287f86b 100644 --- a/Detectors/EMCAL/reconstruction/src/RecoContainer.cxx +++ b/Detectors/EMCAL/reconstruction/src/RecoContainer.cxx @@ -16,7 +16,15 @@ using namespace o2::emcal; -EventContainer::EventContainer(const o2::InteractionRecord& currentIR) : mInteractionRecord(currentIR) {} +EventContainer::EventContainer(const o2::InteractionRecord& currentIR) : mInteractionRecord(currentIR) { initTRUs(); } + +void EventContainer::initTRUs() +{ + for (auto index = 0; index < mTRUData.size(); index++) { + mTRUData[index].setTRUIndex(index); + mTRUData[index].setL0time(INT8_MAX); + } +} void EventContainer::setCellCommon(int tower, double energy, double time, ChannelType_t celltype, bool isLEDmon, int hwaddress, int ddlID, bool doMergeHGLG) { @@ -83,6 +91,16 @@ void EventContainer::setCellCommon(int tower, double energy, double time, Channe } } +void EventContainer::setFastOR(uint16_t fastORAbsID, uint8_t starttime, const gsl::span timesamples) +{ + auto found = mL0FastORs.find(fastORAbsID); + if (found != mL0FastORs.end()) { + found->second.setTimeSamples(timesamples, starttime); + } else { + mL0FastORs[fastORAbsID] = FastORTimeSeries(14, timesamples, starttime); + } +} + void EventContainer::sortCells(bool isLEDmon) { auto& dataContainer = isLEDmon ? mLEDMons : mCells; @@ -94,6 +112,27 @@ bool EventContainer::isCellSaturated(double energy) const return energy / o2::emcal::constants::EMCAL_ADCENERGY > o2::emcal::constants::OVERFLOWCUT; } +TRUDataHandler& EventContainer::getTRUData(std::size_t index) +{ + if (index >= mTRUData.size()) { + throw TRUIndexException(index); + } + return mTRUData[index]; +} + +const TRUDataHandler& EventContainer::readTRUData(std::size_t index) const +{ + if (index >= mTRUData.size()) { + throw TRUIndexException(index); + } + return mTRUData[index]; +} + +EventContainer::TRUIndexException::TRUIndexException(std::size_t index) : mIndex(index), mMessage() +{ + mMessage = "Invalid TRU index " + std::to_string(index); +} + EventContainer& RecoContainer::getEventContainer(const o2::InteractionRecord& currentIR) { auto found = mEvents.find(currentIR); diff --git a/Detectors/EMCAL/reconstruction/src/TRUDataHandler.cxx b/Detectors/EMCAL/reconstruction/src/TRUDataHandler.cxx new file mode 100644 index 0000000000000..d6d9bd3535d5c --- /dev/null +++ b/Detectors/EMCAL/reconstruction/src/TRUDataHandler.cxx @@ -0,0 +1,67 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// 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 "EMCALReconstruction/TRUDataHandler.h" + +using namespace o2::emcal; + +TRUDataHandler::TRUDataHandler() +{ + reset(); +} + +void TRUDataHandler::reset() +{ + mTRUIndex = -1; + mL0Fired = false; + mL0Time = -1; + std::fill(mPatchTimes.begin(), mPatchTimes.end(), UCHAR_MAX); +} + +void TRUDataHandler::printStream(std::ostream& stream) const +{ + std::string patchstring; + for (auto index = 0; index < mPatchTimes.size(); index++) { + if (hasPatch(index)) { + if (patchstring.length()) { + patchstring += ", "; + } + patchstring += std::to_string(index); + } + } + if (!patchstring.length()) { + patchstring = "-"; + } + stream << "TRU: " << static_cast(mTRUIndex) << ", time " << static_cast(mL0Time) << ", fired: " << (mL0Fired ? "yes" : "no") << ", patches: " << patchstring; +} + +TRUDataHandler::PatchIndexException::PatchIndexException(int8_t index) : mIndex(index), mMessage() +{ + mMessage = "Invalid patch index " + std::to_string(index); +} + +void TRUDataHandler::PatchIndexException::printStream(std::ostream& stream) const +{ + stream << what(); +} + +std::ostream& o2::emcal::operator<<(std::ostream& stream, const TRUDataHandler& data) +{ + data.printStream(stream); + return stream; +} + +std::ostream& o2::emcal::operator<<(std::ostream& stream, const TRUDataHandler::PatchIndexException& error) +{ + error.printStream(stream); + return stream; +} \ No newline at end of file diff --git a/Detectors/EMCAL/workflow/include/EMCALWorkflow/RawToCellConverterSpec.h b/Detectors/EMCAL/workflow/include/EMCALWorkflow/RawToCellConverterSpec.h index a90a0f1718397..a418858c0b578 100644 --- a/Detectors/EMCAL/workflow/include/EMCALWorkflow/RawToCellConverterSpec.h +++ b/Detectors/EMCAL/workflow/include/EMCALWorkflow/RawToCellConverterSpec.h @@ -20,10 +20,12 @@ #include "Framework/DataProcessorSpec.h" #include "Framework/Task.h" #include "DataFormatsEMCAL/Cell.h" +#include "DataFormatsEMCAL/CompressedTriggerData.h" #include "DataFormatsEMCAL/TriggerRecord.h" #include "Headers/DataHeader.h" #include "EMCALBase/Geometry.h" #include "EMCALBase/Mapper.h" +#include "EMCALBase/TriggerMappingV2.h" #include "EMCALReconstruction/CaloRawFitter.h" #include "EMCALReconstruction/RawReaderMemory.h" #include "EMCALReconstruction/RecoContainer.h" @@ -37,6 +39,7 @@ namespace emcal { class AltroDecoderError; +class Channel; class MinorAltroDecodingError; class RawDecodingError; @@ -54,9 +57,9 @@ class RawToCellConverterSpec : public framework::Task /// \brief Constructor /// \param subspecification Output subspecification for parallel running on multiple nodes /// \param hasDecodingErrors Option to swich on/off creating raw decoding error objects for later monitoring - /// \param loadRecoParamsFromCCDB Option to load the RecoParams from the CCDB + /// \param hasTriggerReconstruction Perform trigger reconstruction and add trigger-related outputs /// \param calibhandler Calibration object handler - RawToCellConverterSpec(int subspecification, bool hasDecodingErrors, std::shared_ptr calibhandler) : framework::Task(), mSubspecification(subspecification), mCreateRawDataErrors(hasDecodingErrors), mCalibHandler(calibhandler){}; + RawToCellConverterSpec(int subspecification, bool hasDecodingErrors, bool hasTriggerReconstruction, std::shared_ptr calibhandler) : framework::Task(), mSubspecification(subspecification), mCreateRawDataErrors(hasDecodingErrors), mDoTriggerReconstruction(hasTriggerReconstruction), mCalibHandler(calibhandler){}; /// \brief Destructor ~RawToCellConverterSpec() override; @@ -88,7 +91,12 @@ class RawToCellConverterSpec : public framework::Task mMaxErrorMessages = maxMessages; } - void setNoiseThreshold(int thresold) { mNoiseThreshold = thresold; } + /// \brief Set noise threshold for gain type errors + /// \param threshold Noise threshold + void setNoiseThreshold(int threshold) { mNoiseThreshold = threshold; } + + /// \brief Get the noise threshold for gain type errors + /// \return Noise threshold int getNoiseThreshold() const { return mNoiseThreshold; } /// \brief Set ID of the subspecification @@ -170,6 +178,32 @@ class RawToCellConverterSpec : public framework::Task int mRowShifted = -1; /// << shifted row of the module (cell-case) }; + /// \struct CellTimeCorrection + /// \brief Correction for cell time + struct CellTimeCorrection { + double mTimeShift; ///< Constant time shift + int mBcMod4; ///< BC-dependent shift + + /// \brief Get the corrected cell time + /// \param rawtime Raw time from fit + /// \return Corrected time + /// + /// The time is corrected for an average shift and the BC phase + double getCorrectedTime(double rawtime) const { return rawtime - mTimeShift - 25. * mBcMod4; } + }; + + /// \struct LocalPosition + /// \brief Position in the supermodule coordinate system + struct LocalPosition { + uint16_t mSupermoduleID; ///< Supermodule ID + uint16_t mFeeID; ///< FEE ID + uint8_t mColumn; ///< Column in supermodule + uint8_t mRow; ///< Row in supermodule + }; + + using TRUContainer = std::vector; + using PatchContainer = std::vector; + /// \brief Check if the timeframe is empty /// \param ctx Processing context of timeframe /// \return True if the timeframe is empty, false otherwise @@ -193,14 +227,12 @@ class RawToCellConverterSpec : public framework::Task int bookEventCells(const gsl::span& cells, bool isLELDMON); /// \brief Send data to output channels - /// \param cells Container with output cells for timeframe - /// \param triggers Container with trigger records for timeframe - /// \param decodingErrors Container with decoding errors for timeframe + /// \param ctx target processing context /// /// Send data to all output channels for the given subspecification. The subspecification /// is determined on the fly in the run method and therefore used as parameter. Consumers /// must use wildcard subspecification via ConcreteDataTypeMatcher. - void sendData(framework::ProcessingContext& ctx, const std::vector& cells, const std::vector& triggers, const std::vector& decodingErrors) const; + void sendData(framework::ProcessingContext& ctx) const; /// \brief Get absolute Cell ID from column/row in supermodule /// \param supermoduleID Index of the supermodule @@ -217,6 +249,54 @@ class RawToCellConverterSpec : public framework::Task /// \throw ModuleIndexException in case of invalid module indices int geLEDMONAbsID(int supermoduleID, int module); + /// \brief Add FEE channel to the current evnet + /// \param currentEvent Event to add the channel to + /// \param currentchannel Current FEE channel + /// \param timeCorrector Handler for correction of the time + /// \param position Channel coordinates + /// \param chantype Channel type (High Gain, Low Gain, LEDMON) + /// + /// Performing a raw fit of the bunches in the channel to extract energy and time, and + /// adding them to the container for FEE data of the given event. + void addFEEChannelToEvent(o2::emcal::EventContainer& currentEvent, const o2::emcal::Channel& currentchannel, const CellTimeCorrection& timeCorrector, const LocalPosition& position, ChannelType_t chantype); + + /// \brief Add TRU channel to the event + /// \param currentEvent Event to add the channel to + /// \param currentchannel Current TRU channel + /// \param position Channel coordinates + /// + /// TRU channels are encoded in colums: + /// - 0-95: FastOR timeseries (time-reversed) + /// - 96-105: bitmap with fired patches and TRU header + /// The TRU index is taken from the hardware address, while the FastOR index is taken from the + /// column number. The TRU and patch times are taken from the time sample in which the corresponding + /// bit is found. + void addTRUChannelToEvent(o2::emcal::EventContainer& currentEvent, const o2::emcal::Channel& currentchannel, const LocalPosition& position); + + /// @brief Build L0 patches from FastOR time series and TRU data of the current event + /// @param currentevent Current event to process + /// @return Compressed patches + /// + /// Only reconstruct patches which were decoded as fired from the raw data. The patch energy and time + /// are calculated from the corresponding FastOR time series as the energy and time with the largest + /// patch energy extracted from all possible time integrals (see reconstructTriggerPatch) + std::tuple buildL0Patches(const EventContainer& currentevent) const; + + /// @brief Build L0 timesums with respect to a given L0 time + /// @param currentevent Current event with FastOR time series + /// @param l0time L0 time of the event + /// @return Container with time series + std::vector buildL0Timesums(const o2::emcal::EventContainer& currentevent, uint8_t l0time) const; + + /// \brief Reconstruct trigger patch energy and time from its FastOR time series + /// \param fastors FastORs contributing to the patch (only present) + /// \return Tuple with 0 - patch ADC, 1 - patch time + /// + /// For all possible combinations reconstruct the 4-time integral of the patches from + /// its contributing FastORs. The patch is reconstructed when the ADC reached its + /// maximum. The patch time is the start time of the 4-integral + std::tuple reconstructTriggerPatch(const gsl::span fastors) const; + void handleAddressError(const Mapper::AddressNotFoundException& error, int ddlID, int hwaddress); void handleAltroError(const o2::emcal::AltroDecoderError& altroerror, int ddlID); @@ -249,24 +329,33 @@ class RawToCellConverterSpec : public framework::Task bool mPrintTrailer = false; ///< Print RCU trailer bool mDisablePedestalEvaluation = false; ///< Disable pedestal evaluation independent of settings in the RCU trailer bool mCreateRawDataErrors = false; ///< Create raw data error objects for monitoring + bool mDoTriggerReconstruction = false; ///< Do trigger reconstruction std::chrono::time_point mReferenceTime; ///< Reference time for muting messages Geometry* mGeometry = nullptr; ///! mCalibHandler; ///< Handler for calibration objects std::unique_ptr mMapper = nullptr; ///! mTriggerMapping; ///! mRawFitter; ///! mOutputCells; ///< Container with output cells - std::vector mOutputTriggerRecords; ///< Container with output cells + std::vector mOutputTriggerRecords; ///< Container with output trigger records for cells std::vector mOutputDecoderErrors; ///< Container with decoder errors + std::vector mOutputTRUs; ///< Compressed output TRU information + std::vector mOutputTRUTriggerRecords; ///< Container with trigger records for TRU data + std::vector mOutputPatches; ///< Compressed trigger patch information + std::vector mOutputPatchTriggerRecords; ///< Container with trigger records for Patch data + std::vector mOutputTimesums; ///< Compressed L0 timesum information + std::vector mOutputTimesumTriggerRecords; ///< Trigger records for L0 timesum }; /// \brief Creating DataProcessorSpec for the EMCAL Cell Converter Spec /// \param askDISTSTF Include input spec FLP/DISTSUBTIMEFRAME -/// \param loadRecoParamsFromCCDB Obtain reco params from the CCDB +/// \param disableDecodingErrors Obtain reco params from the CCDB +/// \param disableTriggerReconstruction Do not run trigger reconstruction /// \param subspecification Subspecification used in the output spec /// /// Refer to RawToCellConverterSpec::run for input and output specs -framework::DataProcessorSpec getRawToCellConverterSpec(bool askDISTSTF, bool disableDecodingError, int subspecification); +framework::DataProcessorSpec getRawToCellConverterSpec(bool askDISTSTF, bool disableDecodingError, bool disableTriggerReconstruction, int subspecification); } // namespace reco_workflow diff --git a/Detectors/EMCAL/workflow/include/EMCALWorkflow/RecoWorkflow.h b/Detectors/EMCAL/workflow/include/EMCALWorkflow/RecoWorkflow.h index 1cc0544d3c61d..909e356297095 100644 --- a/Detectors/EMCAL/workflow/include/EMCALWorkflow/RecoWorkflow.h +++ b/Detectors/EMCAL/workflow/include/EMCALWorkflow/RecoWorkflow.h @@ -55,6 +55,7 @@ enum struct OutputType { Digits, ///< EMCAL digits /// \param disableRootInput Disable reading from ROOT file (raw mode) /// \param disableRootOutput Disable writing ROOT files (sync reco) /// \param disableDecodingErrors Diable streaming raw decoding errors (async reco) +/// \param disableTriggerReconstruction Disable trigger reconstrction /// \return EMCAL reconstruction workflow for the configuration provided /// \ingroup EMCALwokflow framework::WorkflowSpec getWorkflow(bool propagateMC = true, @@ -66,7 +67,8 @@ framework::WorkflowSpec getWorkflow(bool propagateMC = true, std::string const& cfgOutput = "clusters", bool disableRootInput = false, bool disableRootOutput = false, - bool disableDecodingErrors = false); + bool disableDecodingErrors = false, + bool disableTriggerReconstruction = false); } // namespace reco_workflow } // namespace emcal diff --git a/Detectors/EMCAL/workflow/src/RawToCellConverterSpec.cxx b/Detectors/EMCAL/workflow/src/RawToCellConverterSpec.cxx index 35fb95e010040..4e90aef881415 100644 --- a/Detectors/EMCAL/workflow/src/RawToCellConverterSpec.cxx +++ b/Detectors/EMCAL/workflow/src/RawToCellConverterSpec.cxx @@ -12,6 +12,7 @@ #include #include #include +#include #include @@ -75,6 +76,10 @@ void RawToCellConverterSpec::init(framework::InitContext& ctx) LOG(error) << "Failed to initialize mapper"; } + if (!mTriggerMapping) { + mTriggerMapping = std::make_unique(mGeometry); + } + auto fitmethod = ctx.options().get("fitmethod"); if (fitmethod == "standard") { LOG(info) << "Using standard raw fitter"; @@ -131,9 +136,15 @@ void RawToCellConverterSpec::run(framework::ProcessingContext& ctx) mOutputCells.clear(); mOutputTriggerRecords.clear(); mOutputDecoderErrors.clear(); + mOutputTRUs.clear(); + mOutputTRUTriggerRecords.clear(); + mOutputPatches.clear(); + mOutputPatchTriggerRecords.clear(); + mOutputTimesums.clear(); + mOutputTimesumTriggerRecords.clear(); if (isLostTimeframe(ctx)) { - sendData(ctx, mOutputCells, mOutputTriggerRecords, mOutputDecoderErrors); + sendData(ctx); return; } @@ -216,6 +227,7 @@ void RawToCellConverterSpec::run(framework::ProcessingContext& ctx) if (!currentEvent.getTriggerBits()) { currentEvent.setTriggerBits(triggerbits); } + CellTimeCorrection timeCorrector{timeshift, bcmod4}; if (feeID >= 40) { continue; // skip STU ddl @@ -268,68 +280,33 @@ void RawToCellConverterSpec::run(framework::ProcessingContext& ctx) // Loop over all the channels int nBunchesNotOK = 0; for (auto& chan : decoder.getChannels()) { - int iRow, iCol; - ChannelType_t chantype; try { - iRow = map.getRow(chan.getHardwareAddress()); - iCol = map.getColumn(chan.getHardwareAddress()); - chantype = map.getChannelType(chan.getHardwareAddress()); + auto iRow = map.getRow(chan.getHardwareAddress()); + auto iCol = map.getColumn(chan.getHardwareAddress()); + auto chantype = map.getChannelType(chan.getHardwareAddress()); + LocalPosition channelPosition{iSM, feeID, iCol, iRow}; + switch (chantype) { + case o2::emcal::ChannelType_t::HIGH_GAIN: + case o2::emcal::ChannelType_t::LOW_GAIN: + addFEEChannelToEvent(currentEvent, chan, timeCorrector, channelPosition, chantype); + break; + case o2::emcal::ChannelType_t::LEDMON: + // Drop LEDMON reconstruction in case of physics triggers + if (triggerbits & o2::trigger::Cal) { + addFEEChannelToEvent(currentEvent, chan, timeCorrector, channelPosition, chantype); + } + break; + case o2::emcal::ChannelType_t::TRU: + addTRUChannelToEvent(currentEvent, chan, channelPosition); + break; + default: + LOG(error) << "Unknown channel type for HW address " << chan.getHardwareAddress(); + break; + } } catch (Mapper::AddressNotFoundException& ex) { handleAddressError(ex, feeID, chan.getHardwareAddress()); continue; } - - if (!(chantype == o2::emcal::ChannelType_t::HIGH_GAIN || chantype == o2::emcal::ChannelType_t::LOW_GAIN || chantype == o2::emcal::ChannelType_t::LEDMON)) { - continue; - } - - // Drop LEDMON reconstruction in case of physics triggers - if (chantype == o2::emcal::ChannelType_t::LEDMON && !(triggerbits & o2::trigger::Cal)) { - continue; - } - - int CellID = -1; - bool isLowGain = false; - try { - if (chantype == o2::emcal::ChannelType_t::HIGH_GAIN || chantype == o2::emcal::ChannelType_t::LOW_GAIN) { - // high- / low-gain cell - CellID = getCellAbsID(iSM, iCol, iRow); - isLowGain = chantype == o2::emcal::ChannelType_t::LOW_GAIN; - } else { - CellID = geLEDMONAbsID(iSM, iCol); // Module index encoded in colum for LEDMONs - isLowGain = iRow == 0; // For LEDMONs gain type is encoded in the row (0 - low gain, 1 - high gain) - } - } catch (ModuleIndexException& e) { - handleGeometryError(e, iSM, CellID, chan.getHardwareAddress(), chantype); - continue; - } - - // define the conatiner for the fit results, and perform the raw fitting using the stadnard raw fitter - CaloFitResults fitResults; - try { - fitResults = mRawFitter->evaluate(chan.getBunches()); - // Prevent negative entries - we should no longer get here as the raw fit usually will end in an error state - if (fitResults.getAmp() < 0) { - fitResults.setAmp(0.); - } - if (fitResults.getTime() < 0) { - fitResults.setTime(0.); - } - // apply correction for bc mod 4 - double celltime = fitResults.getTime() - timeshift - 25 * bcmod4; - double amp = fitResults.getAmp() * o2::emcal::constants::EMCAL_ADCENERGY; - if (isLowGain) { - amp *= o2::emcal::constants::EMCAL_HGLGFACTOR; - } - if (chantype == o2::emcal::ChannelType_t::LEDMON) { - // Mark LEDMONs as HIGH_GAIN/LOW_GAIN for gain type merging - will be flagged as LEDMON later when pushing to the output container - currentEvent.setLEDMONCell(CellID, amp, celltime, isLowGain ? o2::emcal::ChannelType_t::LOW_GAIN : o2::emcal::ChannelType_t::HIGH_GAIN, chan.getHardwareAddress(), feeID, mMergeLGHG); - } else { - currentEvent.setCell(CellID, amp, celltime, chantype, chan.getHardwareAddress(), feeID, mMergeLGHG); - } - } catch (CaloRawFitter::RawFitterError_t& fiterror) { - handleFitError(fiterror, feeID, CellID, chan.getHardwareAddress()); - } } } catch (o2::emcal::MappingHandler::DDLInvalid& ddlerror) { // Unable to catch mapping @@ -400,10 +377,31 @@ void RawToCellConverterSpec::run(framework::ProcessingContext& ctx) } LOG(debug) << "Next event [Orbit " << interaction.orbit << ", BC (" << interaction.bc << "]: Accepted " << ncellsEvent << " cells and " << nLEDMONsEvent << " LEDMONS"; mOutputTriggerRecords.emplace_back(interaction, currentevent.getTriggerBits(), eventstart, ncellsEvent + nLEDMONsEvent); + + // Add trigger data + if (mDoTriggerReconstruction) { + auto [trus, patches] = buildL0Patches(currentevent); + LOG(debug) << "Found " << patches.size() << " L0 patches from " << trus.size() << " TRUs"; + auto trusstart = mOutputTRUs.size(); + std::copy(trus.begin(), trus.end(), std::back_inserter(mOutputTRUs)); + mOutputTRUTriggerRecords.emplace_back(interaction, currentevent.getTriggerBits(), trusstart, trus.size()); + auto patchesstart = mOutputPatches.size(); + std::copy(patches.begin(), patches.end(), std::back_inserter(mOutputPatches)); + mOutputPatchTriggerRecords.emplace_back(interaction, currentevent.getTriggerBits(), patchesstart, patches.size()); + // For L0 timesums use fixed time, across TRUs and triggers, determined from the patch time QC + // average found to be - will be made configurable + auto timesumsstart = mOutputTimesums.size(); + auto timesums = buildL0Timesums(currentevent, 8); + std::copy(timesums.begin(), timesums.end(), std::back_inserter(mOutputTimesums)); + mOutputTimesumTriggerRecords.emplace_back(interaction, currentevent.getTriggerBits(), timesumsstart, timesums.size()); + } } LOG(info) << "[EMCALRawToCellConverter - run] Writing " << mOutputCells.size() << " cells from " << mOutputTriggerRecords.size() << " events ..."; - sendData(ctx, mOutputCells, mOutputTriggerRecords, mOutputDecoderErrors); + if (mDoTriggerReconstruction) { + LOG(info) << "[EMCALRawToCellConverter - run] Writing " << mOutputTRUs.size() << " TRU infos and " << mOutputPatches.size() << " trigger patches and " << mOutputTimesums.size() << " timesums from " << mOutputTRUTriggerRecords.size() << " events ..."; + } + sendData(ctx); } void RawToCellConverterSpec::finaliseCCDB(o2::framework::ConcreteDataMatcher& matcher, void* obj) @@ -449,6 +447,197 @@ bool RawToCellConverterSpec::isLostTimeframe(framework::ProcessingContext& ctx) return false; } +void RawToCellConverterSpec::addFEEChannelToEvent(o2::emcal::EventContainer& currentEvent, const o2::emcal::Channel& currentchannel, const CellTimeCorrection& timeCorrector, const LocalPosition& position, ChannelType_t chantype) +{ + int CellID = -1; + bool isLowGain = false; + try { + if (chantype == o2::emcal::ChannelType_t::HIGH_GAIN || chantype == o2::emcal::ChannelType_t::LOW_GAIN) { + // high- / low-gain cell + CellID = getCellAbsID(position.mSupermoduleID, position.mColumn, position.mRow); + isLowGain = chantype == o2::emcal::ChannelType_t::LOW_GAIN; + } else { + CellID = geLEDMONAbsID(position.mSupermoduleID, position.mColumn); // Module index encoded in colum for LEDMONs + isLowGain = position.mRow == 0; // For LEDMONs gain type is encoded in the row (0 - low gain, 1 - high gain) + } + } catch (ModuleIndexException& e) { + handleGeometryError(e, position.mSupermoduleID, CellID, currentchannel.getHardwareAddress(), chantype); + return; + } + + // define the conatiner for the fit results, and perform the raw fitting using the stadnard raw fitter + CaloFitResults fitResults; + try { + fitResults = mRawFitter->evaluate(currentchannel.getBunches()); + // Prevent negative entries - we should no longer get here as the raw fit usually will end in an error state + if (fitResults.getAmp() < 0) { + fitResults.setAmp(0.); + } + if (fitResults.getTime() < 0) { + fitResults.setTime(0.); + } + // apply correction for bc mod 4 + double celltime = timeCorrector.getCorrectedTime(fitResults.getTime()); + double amp = fitResults.getAmp() * o2::emcal::constants::EMCAL_ADCENERGY; + if (isLowGain) { + amp *= o2::emcal::constants::EMCAL_HGLGFACTOR; + } + if (chantype == o2::emcal::ChannelType_t::LEDMON) { + // Mark LEDMONs as HIGH_GAIN/LOW_GAIN for gain type merging - will be flagged as LEDMON later when pushing to the output container + currentEvent.setLEDMONCell(CellID, amp, celltime, isLowGain ? o2::emcal::ChannelType_t::LOW_GAIN : o2::emcal::ChannelType_t::HIGH_GAIN, currentchannel.getHardwareAddress(), position.mFeeID, mMergeLGHG); + } else { + currentEvent.setCell(CellID, amp, celltime, chantype, currentchannel.getHardwareAddress(), position.mFeeID, mMergeLGHG); + } + } catch (CaloRawFitter::RawFitterError_t& fiterror) { + handleFitError(fiterror, position.mFeeID, CellID, currentchannel.getHardwareAddress()); + } +} + +void RawToCellConverterSpec::addTRUChannelToEvent(o2::emcal::EventContainer& currentEvent, const o2::emcal::Channel& currentchannel, const LocalPosition& position) +{ + auto tru = mTriggerMapping->getTRUIndexFromOnlineHardareAddree(currentchannel.getHardwareAddress(), position.mFeeID, position.mSupermoduleID); + if (position.mColumn >= 96 && position.mColumn <= 105) { + auto& trudata = currentEvent.getTRUData(tru); + // Trigger patch information encoded columns 95-105 + for (auto& bunch : currentchannel.getBunches()) { + LOG(debug) << "Found bunch of length " << static_cast(bunch.getBunchLength()) << " with start time " << static_cast(bunch.getStartTime()) << " (column " << static_cast(position.mColumn) << ")"; + auto l0time = bunch.getStartTime(); + int isample = 0; + for (auto& adc : bunch.getADC()) { + // patch word might be in any of the samples, need to check all of them + // in case of colum 105 the first 6 bits are the patch word, the remaining 4 bits are the header word + if (adc == 0) { + isample++; + continue; + } + if (position.mColumn == 105) { + std::bitset<6> patchBits(adc & 0x3F); + std::bitset<4> headerbits((adc >> 6) & 0xF); + for (auto localindex = 0; localindex < patchBits.size(); localindex++) { + if (patchBits.test(localindex)) { + auto globalindex = (position.mColumn - 96) * 10 + localindex; + LOG(debug) << "Found patch with index " << globalindex << " in sample " << isample; + // std::cout << "Found patch with index " << globalindex << " in sample " << isample << " (" << (bunch.getStartTime() - isample) << ")" << std::endl; + trudata.setPatch(globalindex, bunch.getStartTime() - isample); + } + } + if (headerbits.test(2)) { + LOG(debug) << "TRU " << tru << ": Found TRU fired (" << tru << ") in sample " << isample; + // std::cout << "TRU " << tru << ": Found TRU fired (" << tru << ") in sample " << isample << " (" << (bunch.getStartTime() - isample) << ")" << std::endl; + trudata.setFired(true); + trudata.setL0time(bunch.getStartTime() - isample); + } + } else { + std::bitset<10> patchBits(adc & 0x3FF); + for (auto localindex = 0; localindex < patchBits.size(); localindex++) { + if (patchBits.test(localindex)) { + auto globalindex = (position.mColumn - 96) * 10 + localindex; + LOG(debug) << "TRU " << tru << ": Found patch with index " << globalindex << " in sample " << isample; + // std::cout << "TRU " << tru << ": Found patch with index " << globalindex << " in sample " << isample << " (" << (bunch.getStartTime() - isample) << ")" << std::endl; + trudata.setPatch(globalindex, bunch.getStartTime() - isample); + } + } + } + isample++; + } + } + } else { + auto absFastOR = mTriggerMapping->getAbsFastORIndexFromIndexInTRU(tru, position.mColumn); + for (auto& bunch : currentchannel.getBunches()) { + // FastOR data reversed internally (positive in time direction) + // -> Start time marks the first timebin, consequently it must be also reversed. + // std::cout << "Adding non-reversed FastOR time series for FastOR " << absFastOR << " (TRU " << tru << ", index " << static_cast(position.mColumn) << ") with start time " << static_cast(bunch.getStartTime()) << " (reversed " << bunch.getStartTime() + 1 - bunch.getADC().size() << "): "; + // for (auto adc : bunch.getADC()) { + // std::cout << adc << ", "; + //} + // std::cout << std::endl; + currentEvent.setFastOR(absFastOR, bunch.getStartTime(), bunch.getADC()); + } + } +} + +std::tuple RawToCellConverterSpec::buildL0Patches(const EventContainer& currentevent) const +{ + LOG(debug) << "Reconstructing patches for Orbit " << currentevent.getInteractionRecord().orbit << ", BC " << currentevent.getInteractionRecord().bc; + TRUContainer eventTRUs; + PatchContainer eventPatches; + auto& fastOrs = currentevent.getTimeSeriesContainer(); + std::set foundFastOrs; + for (auto fastor : fastOrs) { + foundFastOrs.insert(fastor.first); + } + for (std::size_t itru = 0; itru < TriggerMappingV2::ALLTRUS; itru++) { + auto& currenttru = currentevent.readTRUData(itru); + if (!currenttru.hasAnyPatch()) { + continue; + } + auto l0time = currenttru.getL0time(); + LOG(debug) << "Found patches in TRU " << itru << ", fired: " << (currenttru.isFired() ? "yes" : "no") << ", L0 time " << static_cast(l0time); + uint8_t npatches = 0; + for (auto ipatch = 0; ipatch < o2::emcal::TriggerMappingV2::PATCHESINTRU; ipatch++) { + if (currenttru.hasPatch(ipatch)) { + auto patchtime = currenttru.getPatchTime(ipatch); + LOG(debug) << "Found patch " << ipatch << " in TRU " << itru << " with time " << static_cast(patchtime); + auto fastorStart = mTriggerMapping->getAbsFastORIndexFromIndexInTRU(itru, ipatch); + auto fastORs = mTriggerMapping->getFastORIndexFromL0Index(itru, ipatch, 4); + std::array fastors; + std::fill(fastors.begin(), fastors.end(), nullptr); + int indexFastorInTRU = 0; + for (auto fastor : fastORs) { + auto [truID, fastorTRU] = mTriggerMapping->getTRUFromAbsFastORIndex(fastor); + // std::cout << "Patch has abs FastOR " << fastor << " -> " << fastorTRU << " (in TRU)" << std::endl; + auto timeseriesFound = fastOrs.find(fastor); + if (timeseriesFound != fastOrs.end()) { + LOG(debug) << "Adding FastOR (" << indexFastorInTRU << ") with index " << fastor << " to patch"; + fastors[indexFastorInTRU] = &(timeseriesFound->second); + indexFastorInTRU++; + } + } + auto [patchADC, recpatchtime] = reconstructTriggerPatch(fastors); + // Correct for bit shift 12->10 bits due to ALTRO format + patchADC = patchADC << 2; + LOG(debug) << "Reconstructed patch at index " << ipatch << " with peak time " << static_cast(recpatchtime) << " (time sample " << static_cast(patchtime) << ") and energy " << patchADC; + eventPatches.push_back({static_cast(itru), static_cast(ipatch), patchtime, patchADC}); + } + } + eventTRUs.push_back({static_cast(itru), static_cast(l0time), currenttru.isFired(), npatches}); + } + return std::make_tuple(eventTRUs, eventPatches); +} + +std::vector RawToCellConverterSpec::buildL0Timesums(const o2::emcal::EventContainer& currentevent, uint8_t l0time) const +{ + std::vector timesums; + for (const auto& [fastorID, timeseries] : currentevent.getTimeSeriesContainer()) { + timesums.push_back({fastorID, static_cast(timeseries.calculateL1TimeSum(l0time) << 2)}); + } + return timesums; +} + +std::tuple RawToCellConverterSpec::reconstructTriggerPatch(const gsl::span fastors) const +{ + constexpr size_t INTEGRATE_SAMPLES = 4, + MAX_SAMPLES = 12; + double maxpatchenergy = 0; + uint8_t foundtime = 0; + for (size_t itime = 0; itime < MAX_SAMPLES - INTEGRATE_SAMPLES; ++itime) { + double currenttimesum = 0; + for (size_t isample = 0; isample < INTEGRATE_SAMPLES; ++isample) { + for (auto ifastor = 0; ifastor < fastors.size(); ++ifastor) { + if (fastors[ifastor]) { + currenttimesum += fastors[ifastor]->getADCs()[itime + isample]; + } + } + } + if (currenttimesum > maxpatchenergy) { + maxpatchenergy = currenttimesum; + foundtime = itime; + } + } + + return std::make_tuple(maxpatchenergy, foundtime); +} + int RawToCellConverterSpec::bookEventCells(const gsl::span& cells, bool isLELDMON) { double noiseThresholLGnoHG = RecoParam::Instance().getNoiseThresholdLGnoHG(); @@ -719,14 +908,22 @@ void RawToCellConverterSpec::handleMinorPageError(const RawReaderMemory::MinorEr } } -void RawToCellConverterSpec::sendData(framework::ProcessingContext& ctx, const std::vector& cells, const std::vector& triggers, const std::vector& decodingErrors) const +void RawToCellConverterSpec::sendData(framework::ProcessingContext& ctx) const { constexpr auto originEMC = o2::header::gDataOriginEMC; - ctx.outputs().snapshot(framework::Output{originEMC, "CELLS", mSubspecification}, cells); - ctx.outputs().snapshot(framework::Output{originEMC, "CELLSTRGR", mSubspecification}, triggers); + ctx.outputs().snapshot(framework::Output{originEMC, "CELLS", mSubspecification}, mOutputCells); + ctx.outputs().snapshot(framework::Output{originEMC, "CELLSTRGR", mSubspecification}, mOutputTriggerRecords); if (mCreateRawDataErrors) { - LOG(debug) << "Sending " << decodingErrors.size() << " decoding errors"; - ctx.outputs().snapshot(framework::Output{originEMC, "DECODERERR", mSubspecification}, decodingErrors); + LOG(debug) << "Sending " << mOutputDecoderErrors.size() << " decoding errors"; + ctx.outputs().snapshot(framework::Output{originEMC, "DECODERERR", mSubspecification}, mOutputDecoderErrors); + } + if (mDoTriggerReconstruction) { + ctx.outputs().snapshot(framework::Output{originEMC, "TRUS", mSubspecification}, mOutputTRUs); + ctx.outputs().snapshot(framework::Output{originEMC, "TRUSTRGR", mSubspecification}, mOutputTRUTriggerRecords); + ctx.outputs().snapshot(framework::Output{originEMC, "PATCHES", mSubspecification}, mOutputPatches); + ctx.outputs().snapshot(framework::Output{originEMC, "PATCHESTRGR", mSubspecification}, mOutputPatchTriggerRecords); + ctx.outputs().snapshot(framework::Output{originEMC, "FASTORS", mSubspecification}, mOutputTimesums); + ctx.outputs().snapshot(framework::Output{originEMC, "FASTORSTRGR", mSubspecification}, mOutputTimesumTriggerRecords); } } @@ -739,7 +936,7 @@ RawToCellConverterSpec::ModuleIndexException::ModuleIndexException(int moduleInd RawToCellConverterSpec::ModuleIndexException::ModuleIndexException(int moduleIndex) : mModuleType(ModuleType_t::LEDMON_MODULE), mIndex(moduleIndex) {} -o2::framework::DataProcessorSpec o2::emcal::reco_workflow::getRawToCellConverterSpec(bool askDISTSTF, bool disableDecodingErrors, int subspecification) +o2::framework::DataProcessorSpec o2::emcal::reco_workflow::getRawToCellConverterSpec(bool askDISTSTF, bool disableDecodingErrors, bool disableTriggerReconstruction, int subspecification) { constexpr auto originEMC = o2::header::gDataOriginEMC; std::vector outputs; @@ -749,6 +946,14 @@ o2::framework::DataProcessorSpec o2::emcal::reco_workflow::getRawToCellConverter if (!disableDecodingErrors) { outputs.emplace_back(originEMC, "DECODERERR", subspecification, o2::framework::Lifetime::Timeframe); } + if (!disableTriggerReconstruction) { + outputs.emplace_back(originEMC, "TRUS", subspecification, o2::framework::Lifetime::Timeframe); + outputs.emplace_back(originEMC, "TRUSTRGR", subspecification, o2::framework::Lifetime::Timeframe); + outputs.emplace_back(originEMC, "PATCHES", subspecification, o2::framework::Lifetime::Timeframe); + outputs.emplace_back(originEMC, "PATCHESTRGR", subspecification, o2::framework::Lifetime::Timeframe); + outputs.emplace_back(originEMC, "FASTORS", subspecification, o2::framework::Lifetime::Timeframe); + outputs.emplace_back(originEMC, "FASTORSTRGR", subspecification, o2::framework::Lifetime::Timeframe); + } std::vector inputs{{"stf", o2::framework::ConcreteDataTypeMatcher{originEMC, o2::header::gDataDescriptionRawData}, o2::framework::Lifetime::Timeframe}}; if (askDISTSTF) { @@ -764,7 +969,7 @@ o2::framework::DataProcessorSpec o2::emcal::reco_workflow::getRawToCellConverter "EMCALRawToCellConverterSpec", inputs, outputs, - o2::framework::adaptFromTask(subspecification, !disableDecodingErrors, calibhandler), + o2::framework::adaptFromTask(subspecification, !disableDecodingErrors, !disableTriggerReconstruction, calibhandler), o2::framework::Options{ {"fitmethod", o2::framework::VariantType::String, "gamma2", {"Fit method (standard or gamma2)"}}, {"maxmessage", o2::framework::VariantType::Int, 100, {"Max. amout of error messages to be displayed"}}, diff --git a/Detectors/EMCAL/workflow/src/RecoWorkflow.cxx b/Detectors/EMCAL/workflow/src/RecoWorkflow.cxx index db6e95f17f643..28e0deb3ae0b3 100644 --- a/Detectors/EMCAL/workflow/src/RecoWorkflow.cxx +++ b/Detectors/EMCAL/workflow/src/RecoWorkflow.cxx @@ -53,7 +53,8 @@ o2::framework::WorkflowSpec getWorkflow(bool propagateMC, std::string const& cfgOutput, bool disableRootInput, bool disableRootOutput, - bool disableDecodingErrors) + bool disableDecodingErrors, + bool disableTriggerReconstruction) { const std::unordered_map InputMap{ @@ -173,7 +174,7 @@ o2::framework::WorkflowSpec getWorkflow(bool propagateMC, specs.emplace_back(o2::emcal::reco_workflow::getCellConverterSpec(propagateMC, subspecificationIn, subspecificationOut)); } else if (inputType == InputType::Raw) { // raw data will come from upstream - specs.emplace_back(o2::emcal::reco_workflow::getRawToCellConverterSpec(askDISTSTF, disableDecodingErrors, subspecificationOut)); + specs.emplace_back(o2::emcal::reco_workflow::getRawToCellConverterSpec(askDISTSTF, disableDecodingErrors, disableTriggerReconstruction, subspecificationOut)); } } diff --git a/Detectors/EMCAL/workflow/src/emc-reco-workflow.cxx b/Detectors/EMCAL/workflow/src/emc-reco-workflow.cxx index ceb34695eb0e9..4e31a26ee4b5c 100644 --- a/Detectors/EMCAL/workflow/src/emc-reco-workflow.cxx +++ b/Detectors/EMCAL/workflow/src/emc-reco-workflow.cxx @@ -53,6 +53,7 @@ void customize(std::vector& workflowOptions) {"configKeyValues", o2::framework::VariantType::String, "", {"Semicolon separated key=value strings"}}, {"disable-mc", o2::framework::VariantType::Bool, false, {"disable sending of MC information"}}, {"disable-decoding-errors", o2::framework::VariantType::Bool, false, {"disable propagating decoding errors"}}, + {"disable-trigger-reconstruction", o2::framework::VariantType::Bool, false, {"disable reconstruction of trigger data"}}, {"ignore-dist-stf", o2::framework::VariantType::Bool, false, {"do not subscribe to FLP/DISTSUBTIMEFRAME/0 message (no lost TF recovery)"}}, {"subspecificationIn", o2::framework::VariantType::Int, 0, {"Subspecification for input in case the workflow runs in parallel on multiple nodes (i.e. different FLPs)"}}, {"subspecificationOut", o2::framework::VariantType::Int, 0, {"Subspecification for output in case the workflow runs in parallel on multiple nodes (i.e. different FLPs)"}}}; @@ -86,7 +87,8 @@ o2::framework::WorkflowSpec defineDataProcessing(o2::framework::ConfigContext co cfgc.options().get("output-type"), cfgc.options().get("disable-root-input"), cfgc.options().get("disable-root-output"), - cfgc.options().get("disable-decoding-errors")); + cfgc.options().get("disable-decoding-errors"), + cfgc.options().get("disable-trigger-reconstruction")); // configure dpl timer to inject correct firstTForbit: start from the 1st orbit of TF containing 1st sampled orbit o2::raw::HBFUtilsInitializer hbfIni(cfgc, wf);