diff --git a/Detectors/EMCAL/workflow/CMakeLists.txt b/Detectors/EMCAL/workflow/CMakeLists.txt index d567ba10760a3..5e95770e2b294 100644 --- a/Detectors/EMCAL/workflow/CMakeLists.txt +++ b/Detectors/EMCAL/workflow/CMakeLists.txt @@ -22,6 +22,7 @@ o2_add_library(EMCALWorkflow src/EntropyEncoderSpec.cxx src/EntropyDecoderSpec.cxx src/StandaloneAODProducerSpec.cxx + src/EventBuilderSpec.cxx PUBLIC_LINK_LIBRARIES O2::Framework O2::DataFormatsEMCAL O2::EMCALSimulation O2::Steer O2::DPLUtils O2::EMCALBase O2::EMCALReconstruction O2::Algorithm O2::MathUtils) @@ -30,6 +31,16 @@ o2_add_executable(reco-workflow SOURCES src/emc-reco-workflow.cxx PUBLIC_LINK_LIBRARIES O2::EMCALWorkflow) +o2_add_executable(multi-rawfitter-workflow + COMPONENT_NAME emcal + SOURCES src/multi-rawfitter-workflow.cxx + PUBLIC_LINK_LIBRARIES O2::EMCALWorkflow) + +o2_add_executable(event-builder-workflow + COMPONENT_NAME emcal + SOURCES src/event-builder-workflow.cxx + PUBLIC_LINK_LIBRARIES O2::EMCALWorkflow) + o2_add_executable(entropy-encoder-workflow COMPONENT_NAME emcal SOURCES src/entropy-encoder-workflow.cxx diff --git a/Detectors/EMCAL/workflow/include/EMCALWorkflow/EventBuilderSpec.h b/Detectors/EMCAL/workflow/include/EMCALWorkflow/EventBuilderSpec.h new file mode 100644 index 0000000000000..a4284465d6ae4 --- /dev/null +++ b/Detectors/EMCAL/workflow/include/EMCALWorkflow/EventBuilderSpec.h @@ -0,0 +1,76 @@ +// 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 + +#include "CommonDataFormat/RangeReference.h" +#include "CommonDataFormat/InteractionRecord.h" +#include "Framework/DataProcessorSpec.h" +#include "Framework/Task.h" +#include "Headers/DataHeader.h" + +namespace o2 +{ + +namespace emcal +{ +class TriggerRecord; + +namespace reco_workflow +{ + +/// \class EventBuilderSpec +/// \brief Class combining cell data from different subtimeframes into single cell range +/// \ingroup EMCALEMCALworkflow +/// \author Markus Fasel , Oak Ridge National Laboratory +/// \since Aug 9, 2021 +/// +/// The data processor combines cells from the same trigger in different subtimeframes +/// into a single cell container. Cells within the event range are ordered according +/// to the tower ID. New trigger record objects indicate the combined. +class EventBuilderSpec : public framework::Task +{ + public: + /// \brief Constructor + EventBuilderSpec() = default; + + /// \brief Destructor + ~EventBuilderSpec() override = default; + + void init(framework::InitContext& ctx) final; + void run(framework::ProcessingContext& ctx) final; + + private: + struct RangeSubspec { + header::DataHeader::SubSpecificationType mSpecification; + dataformats::RangeReference mDataRage; + }; + + struct RangeCollection { + InteractionRecord mInteractionRecord; + uint32_t mTriggerType; + std::vector mRangesSubtimeframe; + + bool operator==(const RangeCollection& other) const { return mInteractionRecord == other.mInteractionRecord; } + bool operator<(const RangeCollection& other) const { return mInteractionRecord < other.mInteractionRecord; } + }; + std::set connectRangesFromSubtimeframes(const std::unordered_map>& triggerrecords) const; +}; + +o2::framework::DataProcessorSpec getEventBuilderSpec(); + +} // namespace reco_workflow + +} // namespace emcal + +} // namespace o2 diff --git a/Detectors/EMCAL/workflow/include/EMCALWorkflow/RawToCellConverterSpec.h b/Detectors/EMCAL/workflow/include/EMCALWorkflow/RawToCellConverterSpec.h index 1fef5ecea12fc..ede0bc15cedc9 100644 --- a/Detectors/EMCAL/workflow/include/EMCALWorkflow/RawToCellConverterSpec.h +++ b/Detectors/EMCAL/workflow/include/EMCALWorkflow/RawToCellConverterSpec.h @@ -16,6 +16,7 @@ #include "Framework/Task.h" #include "DataFormatsEMCAL/Cell.h" #include "DataFormatsEMCAL/TriggerRecord.h" +#include "DataFormatsEMCAL/ErrorTypeFEE.h" #include "Headers/DataHeader.h" #include "EMCALBase/Geometry.h" #include "EMCALBase/Mapper.h" @@ -43,6 +44,11 @@ class RawToCellConverterSpec : public framework::Task /// \param hasDecodingErrors Option to swich on/off creating raw decoding error objects for later monitoring RawToCellConverterSpec(int subspecification, bool hasDecodingErrors) : framework::Task(), mSubspecification(subspecification), mCreateRawDataErrors(hasDecodingErrors){}; + /// \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 + RawToCellConverterSpec(int subspecification, bool hasDecodingErrors, bool requireSubspecLinks) : framework::Task(), mSubspecification(subspecification), mCreateRawDataErrors(hasDecodingErrors), mRequireSubspecLinks(requireSubspecLinks){}; + /// \brief Destructor ~RawToCellConverterSpec() override; @@ -105,6 +111,14 @@ class RawToCellConverterSpec : public framework::Task }; bool isLostTimeframe(framework::ProcessingContext& ctx) const; + /// \brief Get FEEID links for a certain subspecification + /// \param subspec Subspecification (FLP ID) + /// \return FEEID links + /// + /// Accepted links for subspec 0 (EMCAL FLP): 0 - 23 + /// Accepted links for subspec 1 (DCAL FLP): 24 - 39 + std::vector getLinksForSubspec(int subspec) const; + /// \brief Send data to output channels /// \param cells Container with output cells for timeframe /// \param triggers Container with trigger records for timeframe @@ -124,6 +138,7 @@ 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 mRequireSubspecLinks = false; ///< Process only FEEIDs for a certain subspecification (FLP) std::chrono::time_point mReferenceTime; ///< Reference time for muting messages Geometry* mGeometry = nullptr; ///! mMapper = nullptr; ///! +#include +#include +#include + +#include "DataFormatsEMCAL/Cell.h" +#include "DataFormatsEMCAL/TriggerRecord.h" +#include "Framework/DataDescriptorMatcher.h" +#include "Framework/DataDescriptorQueryBuilder.h" +#include "Framework/InputSpec.h" +#include "Framework/Logger.h" +#include "EMCALWorkflow/EventBuilderSpec.h" + +using namespace o2::emcal::reco_workflow; + +void EventBuilderSpec::init(framework::InitContext& ctx) +{ +} + +void EventBuilderSpec::run(framework::ProcessingContext& ctx) +{ + auto dataorigin = o2::header::gDataOriginEMC; + std::unordered_map> triggers; + std::unordered_map> cells; + + std::vector outputCells; + std::vector outputTriggers; + + auto posCells = ctx.inputs().getPos("inputcells"), + posTriggerRecords = ctx.inputs().getPos("inputtriggers"); + auto numSlotsCells = ctx.inputs().getNofParts(posCells), + numSlotsTriggerRecords = ctx.inputs().getNofParts(posTriggerRecords); + for (decltype(numSlotsCells) islot = 0; islot < numSlotsCells; islot++) { + auto celldata = ctx.inputs().getByPos(posCells, islot); + auto subspecification = framework::DataRefUtils::getHeader(celldata)->subSpecification; + // Discard message if it is a deadbeaf message (empty timeframe) + if (subspecification == 0xDEADBEEF) { + continue; + } + if (cells.find(subspecification) != cells.end()) { + LOG(error) << "Found multiple messages for cells for subspec " << subspecification << "!"; + } + cells[subspecification] = ctx.inputs().get>(celldata); + LOG(info) << "Received " << cells[subspecification].size() << " cells for subspecification " << subspecification; + } + for (decltype(numSlotsTriggerRecords) islot = 0; islot < numSlotsTriggerRecords; islot++) { + auto trgrecorddata = ctx.inputs().getByPos(posTriggerRecords, islot); + auto subspecification = framework::DataRefUtils::getHeader(trgrecorddata)->subSpecification; + // Discard message if it is a deadbeaf message (empty timeframe) + if (subspecification == 0xDEADBEEF) { + continue; + } + if (triggers.find(subspecification) != triggers.end()) { + LOG(error) << "Found multiple messages for trigger records for subspec " << subspecification << "!"; + } + triggers[subspecification] = ctx.inputs().get>(trgrecorddata); + LOG(info) << "Received " << triggers[subspecification].size() << " triggers for subspecification " << subspecification; + } + LOG(info) << "Received data from " << cells.size() << " subspecifications for cells and " << triggers.size() << " subspecifications for triggers"; + + for (auto interaction : connectRangesFromSubtimeframes(triggers)) { + int start = outputCells.size(), + ncellsEvent = 0; + for (auto [subspec, triggerrecord] : interaction.mRangesSubtimeframe) { + LOG(debug) << interaction.mInteractionRecord.bc << " / " << interaction.mInteractionRecord.orbit << ": Receiving " << triggerrecord.getEntries() << " digits for subspec " << subspec; + auto cellcont = cells.find(subspec); + if (cellcont != cells.end()) { + for (auto& cell : gsl::span(cellcont->second.data() + triggerrecord.getFirstEntry(), triggerrecord.getEntries())) { + outputCells.emplace_back(cell); + } + } else { + LOG(alarm) << "No cell container found for subspec " << subspec; + } + } + if (ncellsEvent) { + std::sort(outputCells.begin() + start, outputCells.begin() + start + ncellsEvent, [](const Cell& lhs, const Cell& rhs) { return lhs.getTower() < rhs.getTower(); }); + } + outputTriggers.emplace_back(interaction.mInteractionRecord, interaction.mTriggerType, start, ncellsEvent); + } + + LOG(info) << "Sending " << outputTriggers.size() << " trigger records and " << outputCells.size() << " cells"; + + ctx.outputs().snapshot(framework::Output{dataorigin, "CELLS", 0, framework::Lifetime::Timeframe}, outputCells); + ctx.outputs().snapshot(framework::Output{dataorigin, "CELLSTRGR", 0, framework::Lifetime::Timeframe}, outputTriggers); +} + +std::set EventBuilderSpec::connectRangesFromSubtimeframes(const std::unordered_map>& triggerrecords) const +{ + std::set events; + + // Search interaction records from all subevents + // build also a map with indices of the trigger record in gsl span for each subspecification + std::unordered_map> allInteractions; + for (auto& [subspecification, trgrec] : triggerrecords) { + for (decltype(trgrec.size()) trgPos = 0; trgPos < trgrec.size(); trgPos++) { + auto eventIR = trgrec[trgPos].getBCData(); + auto interactionPtr = allInteractions.find(eventIR); + if (interactionPtr == allInteractions.end()) { + allInteractions[eventIR] = {{subspecification, trgPos}}; + } else { + interactionPtr->second.insert({subspecification, trgPos}); + } + } + } + + // iterate over all subevents for all bunch crossings + for (const auto& [collisionID, subevents] : allInteractions) { + RangeCollection nextevent; + nextevent.mInteractionRecord = collisionID; + bool first = true; + for (auto& [subspecification, indexInSubevent] : subevents) { + auto& eventTR = triggerrecords.find(subspecification)->second[indexInSubevent]; + if (first) { + nextevent.mTriggerType = eventTR.getTriggerBits(); + first = false; + } + nextevent.mRangesSubtimeframe.push_back({subspecification, o2::dataformats::RangeReference(eventTR.getFirstEntry(), eventTR.getNumberOfObjects())}); + } + events.insert(nextevent); + } + return events; +} + +o2::framework::DataProcessorSpec o2::emcal::reco_workflow::getEventBuilderSpec() +{ + auto dataorigin = o2::header::gDataOriginEMC; + o2::framework::Inputs inputs; + + // Match all inputs from EMC/CELLS and EMC/CELLSTRGR except subspec 0 + using namespace o2::framework::data_matcher; + DataDescriptorMatcher cellInputMatcher = { + DataDescriptorMatcher::Op::And, + OriginValueMatcher{"EMC"}, + std::make_unique( + DataDescriptorMatcher::Op::And, + DescriptionValueMatcher{"CELLS"}, + std::make_unique( + DataDescriptorMatcher::Op::And, + std::make_unique( + DataDescriptorMatcher::Op::Not, + SubSpecificationTypeValueMatcher{0}), + std::make_unique( + DataDescriptorMatcher::Op::Just, + StartTimeValueMatcher(ContextRef{0}))))}; + + DataDescriptorMatcher triggerRecordInputMatcher = { + DataDescriptorMatcher::Op::And, + OriginValueMatcher{"EMC"}, + std::make_unique( + DataDescriptorMatcher::Op::And, + DescriptionValueMatcher{"CELLSTRGR"}, + std::make_unique( + DataDescriptorMatcher::Op::And, + std::make_unique( + DataDescriptorMatcher::Op::Not, + SubSpecificationTypeValueMatcher{0}), + std::make_unique( + DataDescriptorMatcher::Op::Just, + StartTimeValueMatcher(ContextRef{0}))))}; + + inputs.push_back({"inputcells", std::move(cellInputMatcher), o2::framework::Lifetime::Timeframe}); + inputs.push_back({"inputtriggers", std::move(triggerRecordInputMatcher), o2::framework::Lifetime::Timeframe}); + + o2::framework::Outputs outputs{ + {dataorigin, "CELLS", 0, o2::framework::Lifetime::Timeframe}, + {dataorigin, "CELLSTRGR", 0, o2::framework::Lifetime::Timeframe}}; + + return o2::framework::DataProcessorSpec{"EMCALEventBuilder", + inputs, + outputs, + o2::framework::adaptFromTask(), + o2::framework::Options{}}; +} \ No newline at end of file diff --git a/Detectors/EMCAL/workflow/src/RawToCellConverterSpec.cxx b/Detectors/EMCAL/workflow/src/RawToCellConverterSpec.cxx index 7f7278c1ce1dc..e7163c4a54284 100644 --- a/Detectors/EMCAL/workflow/src/RawToCellConverterSpec.cxx +++ b/Detectors/EMCAL/workflow/src/RawToCellConverterSpec.cxx @@ -102,6 +102,11 @@ void RawToCellConverterSpec::run(framework::ProcessingContext& ctx) constexpr auto originEMC = o2::header::gDataOriginEMC; constexpr auto descRaw = o2::header::gDataDescriptionRawData; + std::vector acceptLinks; + if (mRequireSubspecLinks) { + acceptLinks = getLinksForSubspec(mSubspecification); + } + // reset message counter after 10 minutes auto currenttime = std::chrono::system_clock::now(); std::chrono::duration interval = mReferenceTime - currenttime; @@ -163,6 +168,11 @@ void RawToCellConverterSpec::run(framework::ProcessingContext& ctx) auto feeID = raw::RDHUtils::getFEEID(header); auto triggerbits = raw::RDHUtils::getTriggerType(header); + // Skip links in case links for a certain subspecification are required + if (mRequireSubspecLinks && !std::any_of(acceptLinks.begin(), acceptLinks.end(), [&feeID](int link) { return link == feeID; })) { + continue; + } + o2::InteractionRecord currentIR(triggerBC, triggerOrbit); std::shared_ptr> currentCellContainer; auto found = cellBuffer.find(currentIR); @@ -572,6 +582,19 @@ void RawToCellConverterSpec::sendData(framework::ProcessingContext& ctx, const s } } +std::vector RawToCellConverterSpec::getLinksForSubspec(int subspec) const +{ + std::vector links; + if (subspec == 146) { + links = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23}; + } else if (subspec == 147) { + links = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + } else { + links = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + } + return links; +} + o2::framework::DataProcessorSpec o2::emcal::reco_workflow::getRawToCellConverterSpec(bool askDISTSTF, bool disableDecodingErrors, int subspecification) { constexpr auto originEMC = o2::header::gDataOriginEMC; @@ -599,3 +622,40 @@ o2::framework::DataProcessorSpec o2::emcal::reco_workflow::getRawToCellConverter {"no-mergeHGLG", o2::framework::VariantType::Bool, false, {"Do not merge HG and LG channels for same tower"}}, {"no-evalpedestal", o2::framework::VariantType::Bool, false, {"Disable pedestal evaluation"}}}}; } + +o2::framework::DataProcessorSpec o2::emcal::reco_workflow::getRawToCellConverterSpecFLP(bool askDISTSTF, bool disableDecodingErrors, int subspecification) +{ + constexpr auto originEMC = o2::header::gDataOriginEMC; + std::vector outputs; + + outputs.emplace_back(originEMC, "CELLS", subspecification, o2::framework::Lifetime::Timeframe); + outputs.emplace_back(originEMC, "CELLSTRGR", subspecification, o2::framework::Lifetime::Timeframe); + if (!disableDecodingErrors) { + outputs.emplace_back(originEMC, "DECODERERR", subspecification, o2::framework::Lifetime::Timeframe); + } + + std::vector inputs{{"stf", o2::framework::ConcreteDataTypeMatcher{originEMC, o2::header::gDataDescriptionRawData}, o2::framework::Lifetime::Optional}}; + if (askDISTSTF) { + inputs.emplace_back("stdDist", "FLP", "DISTSUBTIMEFRAME", 0, o2::framework::Lifetime::Timeframe); + } + + std::string namesubspec = "EMCALRawToCellConverterSpec"; + if (subspecification == 147) { + namesubspec += "DCAL"; + } else if (subspecification == 146) { + namesubspec += "EMCAL"; + } else { + LOG(error) << "Wrong subspecification for FLP"; + } + + return o2::framework::DataProcessorSpec{namesubspec, + inputs, + outputs, + o2::framework::adaptFromTask(subspecification, !disableDecodingErrors, true), + 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"}}, + {"printtrailer", o2::framework::VariantType::Bool, false, {"Print RCU trailer (for debugging)"}}, + {"no-mergeHGLG", o2::framework::VariantType::Bool, false, {"Do not merge HG and LG channels for same tower"}}, + {"no-evalpedestal", o2::framework::VariantType::Bool, false, {"Disable pedestal evaluation"}}}}; +} diff --git a/Detectors/EMCAL/workflow/src/event-builder-workflow.cxx b/Detectors/EMCAL/workflow/src/event-builder-workflow.cxx new file mode 100644 index 0000000000000..611700516d3d4 --- /dev/null +++ b/Detectors/EMCAL/workflow/src/event-builder-workflow.cxx @@ -0,0 +1,41 @@ +// 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 +#include "EMCALWorkflow/EventBuilderSpec.h" +#include "CommonUtils/ConfigurableParam.h" +#include "Framework/ConfigParamSpec.h" +#include "Framework/Logger.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{ + {"configKeyValues", o2::framework::VariantType::String, "", {"Semicolon separated key=value strings"}}, + {"subspecifications", VariantType::String, "", {"Comma-separated list of subspecifications"}}}; + + std::swap(workflowOptions, options); +} + +#include "Framework/runDataProcessing.h" + +WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) +{ + o2::conf::ConfigurableParam::updateFromString(cfgc.options().get("configKeyValues")); + WorkflowSpec wf; + wf.emplace_back(o2::emcal::reco_workflow::getEventBuilderSpec()); + return wf; +} diff --git a/Detectors/EMCAL/workflow/src/multi-rawfitter-workflow.cxx b/Detectors/EMCAL/workflow/src/multi-rawfitter-workflow.cxx new file mode 100644 index 0000000000000..5c389aa8c3f63 --- /dev/null +++ b/Detectors/EMCAL/workflow/src/multi-rawfitter-workflow.cxx @@ -0,0 +1,61 @@ +// 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 "Algorithm/RangeTokenizer.h" +#include "CommonUtils/ConfigurableParam.h" +#include "DetectorsRaw/HBFUtilsInitializer.h" +#include "Framework/CallbacksPolicy.h" +#include "Framework/CompletionPolicyHelpers.h" +#include "Framework/ConfigParamSpec.h" +#include "Framework/WorkflowSpec.h" + +#include "EMCALWorkflow/EventBuilderSpec.h" +#include "EMCALWorkflow/RawToCellConverterSpec.h" + +void customize(std::vector& policies) +{ + o2::raw::HBFUtilsInitializer::addNewTimeSliceCallback(policies); +} + +// add workflow options, note that customization needs to be declared before +// including Framework/runDataProcessing +void customize(std::vector& workflowOptions) +{ + std::vector options{ + {"configKeyValues", o2::framework::VariantType::String, "", {"Semicolon separated key=value strings"}}, + {"disable-decoding-errors", o2::framework::VariantType::Bool, false, {"disable propagating decoding errors"}}, + {"ignore-dist-stf", o2::framework::VariantType::Bool, false, {"do not subscribe to FLP/DISTSUBTIMEFRAME/0 message (no lost TF recovery)"}}}; + std::swap(workflowOptions, options); +} + +#include "Framework/runDataProcessing.h" // the main driver + +o2::framework::WorkflowSpec defineDataProcessing(o2::framework::ConfigContext const& cfgc) +{ + o2::conf::ConfigurableParam::updateFromString(cfgc.options().get("configKeyValues")); + + o2::framework::WorkflowSpec wf; + + bool askdiststf = !cfgc.options().get("ignore-dist-stf"); + bool disableDecodingErrors = cfgc.options().get("disable-decoding-errors"); + + // Run 2 raw to cell converters, each one mimicing a single FLP (0 - EMCAL, 1 - DCAL) + wf.emplace_back(o2::emcal::reco_workflow::getRawToCellConverterSpecFLP(askdiststf, disableDecodingErrors, 146)); + wf.emplace_back(o2::emcal::reco_workflow::getRawToCellConverterSpecFLP(askdiststf, disableDecodingErrors, 147)); + + // Run event builder reading cells from the two converters and combining to single events + wf.emplace_back(o2::emcal::reco_workflow::getEventBuilderSpec()); + + // configure dpl timer to inject correct firstTFOrbit: start from the 1st orbit of TF containing 1st sampled orbit + o2::raw::HBFUtilsInitializer hbfIni(cfgc, wf); + + return std::move(wf); +} \ No newline at end of file