From 9f33d25a9ac9d4808eff155ae2d2c86f918860cb Mon Sep 17 00:00:00 2001 From: Felix Schlepper Date: Fri, 16 Oct 2026 12:11:37 +0200 Subject: [PATCH] ALICE3: add FT3 clusters, reader and writer Signed-off-by: Felix Schlepper --- .../common/reconstruction/CMakeLists.txt | 1 + .../include/TRKReconstruction/Clusterer.h | 8 + .../common/reconstruction/src/Clusterer.cxx | 3 +- .../TRKFT3/common/workflow/CMakeLists.txt | 1 + .../include/TRKWorkflow/ClusterReaderSpec.h | 69 +++++++ .../include/TRKWorkflow/ClustererSpec.h | 12 +- .../include/TRKWorkflow/DigitReaderSpec.h | 16 +- .../include/TRKWorkflow/RecoWorkflow.h | 5 +- .../common/workflow/src/ClusterReaderSpec.cxx | 175 ++++++++++++++++++ .../common/workflow/src/ClustererSpec.cxx | 120 +++++++----- .../common/workflow/src/DigitReaderSpec.cxx | 50 +++-- .../common/workflow/src/RecoWorkflow.cxx | 26 ++- .../common/workflow/src/trk-reco-workflow.cxx | 10 +- .../src/TRKDigitizerSpec.cxx | 4 +- 14 files changed, 428 insertions(+), 72 deletions(-) create mode 100644 Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/ClusterReaderSpec.h create mode 100644 Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/ClusterReaderSpec.cxx diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/CMakeLists.txt index fab32edc7c819..b2f8a954888fc 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/CMakeLists.txt @@ -23,6 +23,7 @@ o2_add_library(TRKReconstruction O2::DataFormatsTRKFT3 O2::SimulationDataFormat O2::TRKBase + O2::FT3Base nlohmann_json::nlohmann_json ${actsTarget}) diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/include/TRKReconstruction/Clusterer.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/include/TRKReconstruction/Clusterer.h index 0e709476d09fd..53378d25b4f0b 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/include/TRKReconstruction/Clusterer.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/include/TRKReconstruction/Clusterer.h @@ -34,8 +34,14 @@ #include #include #include +#include #include +namespace o2::ft3 +{ +class GeometryTGeo; +} + namespace o2::trk { @@ -56,6 +62,8 @@ class Clusterer using ClusterTruth = o2::dataformats::MCTruthContainer; using ConstDigitTruth = o2::dataformats::ConstMCTruthContainerView; using Label = o2::MCCompLabel; + /// The geometry that gives a chip's sub-detector and layer. + using GeometryTGeo = std::conditional_t; //---------------------------------------------- struct BBox { diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/src/Clusterer.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/src/Clusterer.cxx index e197304c2b03c..03e7e5285a31f 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/src/Clusterer.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/src/Clusterer.cxx @@ -13,6 +13,7 @@ /// \brief Implementation of the TRK cluster finder #include "TRKReconstruction/Clusterer.h" +#include "FT3Base/GeometryTGeo.h" #include "TRKBase/GeometryTGeo.h" #include "TRKBase/SegmentationChip.h" @@ -82,7 +83,7 @@ void Clusterer::process(gsl::span digits, mThread = std::make_unique(this); } - auto* geom = o2::trk::GeometryTGeo::Instance(); + auto* geom = GeometryTGeo::Instance(); for (size_t iROF = 0; iROF < digitROFs.size(); ++iROF) { const auto& inROF = digitROFs[iROF]; diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/CMakeLists.txt index f437b53715149..a743e51a0a122 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/CMakeLists.txt @@ -15,6 +15,7 @@ o2_add_library(TRKWorkflow src/DigitWriterSpec.cxx src/ClustererSpec.cxx src/ClusterWriterSpec.cxx + src/ClusterReaderSpec.cxx src/RecoWorkflow.cxx PUBLIC_LINK_LIBRARIES O2::Framework O2::GPUWorkflow diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/ClusterReaderSpec.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/ClusterReaderSpec.h new file mode 100644 index 0000000000000..126efc84b7b8d --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/ClusterReaderSpec.h @@ -0,0 +1,69 @@ +// 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. + +/// @file ClusterReaderSpec.h +/// @brief Reads the TRK or FT3 clusters the cluster writer stored, one stream per layer + +#ifndef O2_TRK_CLUSTERREADER +#define O2_TRK_CLUSTERREADER + +#include +#include +#include + +#include "TFile.h" +#include "TTree.h" +#include "DataFormatsTRKFT3/Cluster.h" +#include "DataFormatsTRKFT3/ROFRecord.h" +#include "SimulationDataFormat/MCCompLabel.h" +#include "SimulationDataFormat/MCTruthContainer.h" +#include "Framework/DataProcessorSpec.h" +#include "Framework/Task.h" +#include "TRKBase/AlmiraParam.h" + +namespace o2::trk +{ + +template +class ClusterReader : public framework::Task +{ + public: + explicit ClusterReader(bool useMC); + void init(framework::InitContext& ic) final; + void run(framework::ProcessingContext& pc) final; + + static constexpr int NLayers = DetID == o2::detectors::DetID::TRK ? static_cast(o2::trk::AlmiraParam::kNLayers) : o2::trk::constants::MLOTDisks::nLayers; + + private: + void connectTree(const std::string& filename); + template + void setBranchAddress(const std::string& base, int layer, Ptr& address); + + std::vector>*> mClusters; + std::vector*> mPatterns; + std::vector*> mROFs; + std::vector*> mLabels; + + std::unique_ptr mFile; + std::unique_ptr mTree; + bool mUseMC = true; +}; + +using TRKClusterReader = ClusterReader; +using FT3ClusterReader = ClusterReader; + +/// The reader of \a defname, by default the cluster writer's file. +framework::DataProcessorSpec getTRKClusterReaderSpec(bool useMC = true, std::string defname = "o2clus_trk.root"); +framework::DataProcessorSpec getFT3ClusterReaderSpec(bool useMC = true, std::string defname = "o2clus_ft3.root"); + +} // namespace o2::trk + +#endif diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/ClustererSpec.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/ClustererSpec.h index 0166aa14462a8..0354133c27392 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/ClustererSpec.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/ClustererSpec.h @@ -12,9 +12,9 @@ #ifndef O2_TRK_CLUSTERERDPL #define O2_TRK_CLUSTERERDPL +#include "DetectorsCommonDataFormats/DetID.h" #include "Framework/DataProcessorSpec.h" #include "Framework/Task.h" -#include "TRKBase/AlmiraParam.h" #include "TRKReconstruction/Clusterer.h" #ifdef O2_WITH_ACTS #include "TRKReconstruction/ClustererACTS.h" @@ -23,6 +23,8 @@ namespace o2::trk { +/// Clusterises the digits of TRK or FT3, layer by layer. +template class ClustererDPL : public o2::framework::Task { public: @@ -31,17 +33,19 @@ class ClustererDPL : public o2::framework::Task void run(o2::framework::ProcessingContext& pc) final; private: - static constexpr int mLayers = o2::trk::AlmiraParam::kNLayers; bool mUseMC = true; int mNThreads = 1; - o2::trk::TRKClusterer mClusterer; + Clusterer mClusterer; #ifdef O2_WITH_ACTS - bool mUseACTS = false; + bool mUseACTS = false; // TRK only o2::trk::ClustererACTS mClustererACTS; #endif }; +/// The clusterer of TRK's digits. o2::framework::DataProcessorSpec getClustererSpec(bool useMC); +/// The clusterer of FT3's digits. +o2::framework::DataProcessorSpec getFT3ClustererSpec(bool useMC); } // namespace o2::trk diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/DigitReaderSpec.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/DigitReaderSpec.h index de04358b227eb..e3a8843661ecd 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/DigitReaderSpec.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/DigitReaderSpec.h @@ -24,7 +24,6 @@ #include "Framework/Task.h" #include "Headers/DataHeader.h" #include "DetectorsCommonDataFormats/DetID.h" -#include "TRKBase/AlmiraParam.h" using namespace o2::framework; @@ -48,7 +47,7 @@ class DigitReader : public Task void setBranchAddress(const std::string& base, Ptr& addr, int layer = -1); std::string getBranchName(const std::string& base, int index) const; - static constexpr int mLayers = o2::trk::AlmiraParam::kNLayers; + int mLayers = 0; ///< TRK's AlmiraParam entries or FT3's discs std::vector*> mDigits{nullptr}; std::vector mCalib, *mCalibPtr = &mCalib; @@ -84,9 +83,20 @@ class TRKDigitReader : public DigitReader } }; +class FT3DigitReader : public DigitReader +{ + public: + FT3DigitReader(bool useMC = true, bool useCalib = false) + : DigitReader(o2::detectors::DetID::FT3, useMC, useCalib) + { + mOrigin = o2::header::gDataOriginFT3; + } +}; + /// create a processor spec -/// read ITS/MFT Digit data from a root file +/// read TRK/FT3 Digit data from a root file framework::DataProcessorSpec getTRKDigitReaderSpec(bool useMC = true, bool useCalib = false, std::string defname = "trkdigits.root"); +framework::DataProcessorSpec getFT3DigitReaderSpec(bool useMC = true, bool useCalib = false, std::string defname = "ft3digits.root"); } // namespace trk } // namespace o2 diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/RecoWorkflow.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/RecoWorkflow.h index 863c5deae7241..c61e5b556c966 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/RecoWorkflow.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/RecoWorkflow.h @@ -12,6 +12,7 @@ #ifndef O2_TRK_RECOWORKFLOW_H #define O2_TRK_RECOWORKFLOW_H +#include "DetectorsCommonDataFormats/DetID.h" #include "Framework/WorkflowSpec.h" namespace o2::trk @@ -19,10 +20,12 @@ namespace o2::trk namespace reco_workflow { +/// The digit readers, clusterers and cluster writers of \a detectors, TRK and/or FT3. o2::framework::WorkflowSpec getWorkflow(bool useMC, bool upstreamDigits = false, bool upstreamClusters = false, - bool disableRootOutput = false); + bool disableRootOutput = false, + o2::detectors::DetID::mask_t detectors = o2::detectors::DetID::getMask(o2::detectors::DetID::TRK)); } } // namespace o2::trk diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/ClusterReaderSpec.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/ClusterReaderSpec.cxx new file mode 100644 index 0000000000000..185a44787e176 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/ClusterReaderSpec.cxx @@ -0,0 +1,175 @@ +// Copyright 2019-2026 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. + +/// @file ClusterReaderSpec.cxx + +#include "TRKWorkflow/ClusterReaderSpec.h" +#include "DetectorsCommonDataFormats/DetID.h" +#include "Framework/ConfigParamRegistry.h" +#include "Framework/ControlService.h" +#include "Framework/Logger.h" + +#include +#include +#include +#include +#include + +using namespace o2::framework; + +namespace o2::trk +{ + +namespace +{ +template +std::string detName() +{ + return o2::detectors::DetID(DetID).getName(); +} + +template +std::string lowerName() +{ + auto name = detName(); + std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) { return std::tolower(c); }); + return name; +} + +template +std::string branchName(const std::string& base, int layer) +{ + return std::format("{}{}_{}", detName(), base, layer); +} +} // namespace + +template +ClusterReader::ClusterReader(bool useMC) + : mClusters(NLayers, nullptr), mPatterns(NLayers, nullptr), mROFs(NLayers, nullptr), mLabels(NLayers, nullptr), mUseMC(useMC) +{ +} + +template +void ClusterReader::init(InitContext& ic) +{ + connectTree(ic.options().get((lowerName() + "-cluster-infile").c_str())); +} + +template +void ClusterReader::run(ProcessingContext& pc) +{ + constexpr auto origin = DetID == o2::detectors::DetID::TRK ? o2::header::gDataOriginTRK : o2::header::gDataOriginFT3; + const auto entry = mTree->GetReadEntry() + 1; + // A timeframe without a collision has no entry: publish empty streams, so + // that the consumers still see the timeframe (as the digit reader does). + const bool noEntry = entry >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << detName() << "ClusterReader: no entry to read, publishing empty output"; + } else { + mTree->GetEntry(entry); + } + static const std::vector> noClusters; + static const std::vector noPatterns; + static const std::vector noROFs; + static const o2::dataformats::MCTruthContainer noLabels; + for (int layer = 0; layer < NLayers; ++layer) { + const auto subspec = static_cast(layer); + const auto& clusters = noEntry ? noClusters : *mClusters[layer]; + const auto& rofs = noEntry ? noROFs : *mROFs[layer]; + LOG(info) << detName() << "ClusterReader on layer " << layer << " pushes " << clusters.size() << " clusters in " << rofs.size() + << " RO frames at entry " << entry; + pc.outputs().snapshot(Output{origin, "COMPCLUSTERS", subspec}, clusters); + pc.outputs().snapshot(Output{origin, "PATTERNS", subspec}, noEntry ? noPatterns : *mPatterns[layer]); + pc.outputs().snapshot(Output{origin, "CLUSTERSROF", subspec}, rofs); + if (mUseMC) { + pc.outputs().snapshot(Output{origin, "CLUSTERSMCTR", subspec}, noEntry ? noLabels : *mLabels[layer]); + } + } + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + pc.services().get().endOfStream(); + pc.services().get().readyToQuit(QuitRequest::Me); + } +} + +template +void ClusterReader::connectTree(const std::string& filename) +{ + mTree.reset(); + mFile.reset(TFile::Open(filename.c_str())); + if (!mFile || mFile->IsZombie()) { + throw std::runtime_error(detName() + "ClusterReader: cannot open " + filename); + } + mTree.reset(mFile->Get("o2sim")); + if (!mTree) { + throw std::runtime_error(detName() + "ClusterReader: no tree o2sim in " + filename); + } + for (int layer = 0; layer < NLayers; ++layer) { + setBranchAddress("ClusterComp", layer, mClusters[layer]); + setBranchAddress("ClusterPatt", layer, mPatterns[layer]); + setBranchAddress("ClustersROF", layer, mROFs[layer]); + if (mUseMC) { + setBranchAddress("ClusterMCTruth", layer, mLabels[layer]); + } + } + LOG(info) << detName() << "ClusterReader: " << filename << " with " << mTree->GetEntries() << " entries"; +} + +template +template +void ClusterReader::setBranchAddress(const std::string& base, int layer, Ptr& address) +{ + const auto name = branchName(base, layer); + if (!mTree->GetBranch(name.c_str())) { + throw std::runtime_error(detName() + "ClusterReader: no branch " + name + (base == "ClusterMCTruth" ? " (MC labels requested)" : "")); + } + if (const Int_t status = mTree->SetBranchAddress(name.c_str(), &address); status != 0) { + throw std::runtime_error(std::format("{}ClusterReader: cannot set the address of {} ({})", detName(), name, status)); + } +} + +namespace +{ +template +DataProcessorSpec getClusterReaderSpecT(bool useMC, std::string defname) +{ + constexpr auto origin = DetID == o2::detectors::DetID::TRK ? o2::header::gDataOriginTRK : o2::header::gDataOriginFT3; + std::vector outputs; + for (int layer = 0; layer < ClusterReader::NLayers; ++layer) { + outputs.emplace_back(origin, "COMPCLUSTERS", layer, Lifetime::Timeframe); + outputs.emplace_back(origin, "PATTERNS", layer, Lifetime::Timeframe); + outputs.emplace_back(origin, "CLUSTERSROF", layer, Lifetime::Timeframe); + if (useMC) { + outputs.emplace_back(origin, "CLUSTERSMCTR", layer, Lifetime::Timeframe); + } + } + return DataProcessorSpec{ + lowerName() + "-cluster-reader", + Inputs{}, + outputs, + AlgorithmSpec{adaptFromTask>(useMC)}, + Options{{lowerName() + "-cluster-infile", VariantType::String, std::move(defname), {"Name of the input cluster file"}}}}; +} +} // namespace + +DataProcessorSpec getTRKClusterReaderSpec(bool useMC, std::string defname) +{ + return getClusterReaderSpecT(useMC, std::move(defname)); +} + +DataProcessorSpec getFT3ClusterReaderSpec(bool useMC, std::string defname) +{ + return getClusterReaderSpecT(useMC, std::move(defname)); +} + +template class ClusterReader; +template class ClusterReader; + +} // namespace o2::trk diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/ClustererSpec.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/ClustererSpec.cxx index 8aaa2e07cd58a..03bf4c8b02a28 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/ClustererSpec.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/ClustererSpec.cxx @@ -10,6 +10,7 @@ // or submit itself to any jurisdiction. #include "TRKWorkflow/ClustererSpec.h" +#include "TRKBase/AlmiraParam.h" #include "DetectorsBase/GeometryManager.h" #include "DataFormatsTRKFT3/Cluster.h" #include "DataFormatsTRKFT3/ROFRecord.h" @@ -17,25 +18,35 @@ #include "Framework/Logger.h" #include "SimulationDataFormat/ConstMCTruthContainer.h" +#include +#include #include +#include namespace o2::trk { -void ClustererDPL::init(o2::framework::InitContext& ic) +template +void ClustererDPL::init(o2::framework::InitContext& ic) { mNThreads = std::max(1, ic.options().get("nthreads")); #ifdef O2_WITH_ACTS - mUseACTS = ic.options().get("useACTS"); + if constexpr (DetID == o2::detectors::DetID::TRK) { + mUseACTS = ic.options().get("useACTS"); + } #endif } -void ClustererDPL::run(o2::framework::ProcessingContext& pc) +template +void ClustererDPL::run(o2::framework::ProcessingContext& pc) { o2::base::GeometryManager::loadGeometry(); // default prefix -> o2sim_geometry[-aligned].root + constexpr auto Origin = DetID == o2::detectors::DetID::TRK ? o2::header::gDataOriginTRK : o2::header::gDataOriginFT3; + const auto detName = Origin.template as(); uint64_t totalClusters = 0; - for (int iLayer = 0; iLayer < mLayers; ++iLayer) { + constexpr int NLayers = DetID == o2::detectors::DetID::TRK ? static_cast(o2::trk::AlmiraParam::kNLayers) : o2::trk::constants::MLOTDisks::nLayers; + for (int iLayer = 0; iLayer < NLayers; ++iLayer) { auto digits = pc.inputs().get>(std::format("digits_{}", iLayer)); auto rofs = pc.inputs().get>(std::format("ROframes_{}", iLayer)); @@ -45,7 +56,7 @@ void ClustererDPL::run(o2::framework::ProcessingContext& pc) } o2::dataformats::ConstMCTruthContainerView labels(labelbuffer); - std::vector clusters; + std::vector> clusters; std::vector patterns; std::vector clusterROFs; std::unique_ptr> clusterLabels; @@ -53,20 +64,24 @@ void ClustererDPL::run(o2::framework::ProcessingContext& pc) clusterLabels = std::make_unique>(); } + bool clustered = false; #ifdef O2_WITH_ACTS - if (mUseACTS) { - LOG(info) << "Running TRKClusterer with ACTS on layer " << iLayer; - mClustererACTS.process(digits, - rofs, - clusters, - patterns, - clusterROFs, - mUseMC ? &labels : nullptr, - clusterLabels.get()); - } else + if constexpr (DetID == o2::detectors::DetID::TRK) { + if (mUseACTS) { + LOG(info) << "Running TRKClusterer with ACTS on layer " << iLayer; + mClustererACTS.process(digits, + rofs, + clusters, + patterns, + clusterROFs, + mUseMC ? &labels : nullptr, + clusterLabels.get()); + clustered = true; + } + } #endif - { - LOG(info) << "Running TRKClusterer on layer " << iLayer; + if (!clustered) { + LOG(info) << "Running " << detName << "Clusterer on layer " << iLayer; mClusterer.process(digits, rofs, clusters, @@ -77,52 +92,73 @@ void ClustererDPL::run(o2::framework::ProcessingContext& pc) } const auto subspec = static_cast(iLayer); - pc.outputs().snapshot(o2::framework::Output{"TRK", "COMPCLUSTERS", subspec}, clusters); - pc.outputs().snapshot(o2::framework::Output{"TRK", "PATTERNS", subspec}, patterns); - pc.outputs().snapshot(o2::framework::Output{"TRK", "CLUSTERSROF", subspec}, clusterROFs); + pc.outputs().snapshot(o2::framework::Output{Origin, "COMPCLUSTERS", subspec}, clusters); + pc.outputs().snapshot(o2::framework::Output{Origin, "PATTERNS", subspec}, patterns); + pc.outputs().snapshot(o2::framework::Output{Origin, "CLUSTERSROF", subspec}, clusterROFs); if (mUseMC) { - pc.outputs().snapshot(o2::framework::Output{"TRK", "CLUSTERSMCTR", subspec}, *clusterLabels); + pc.outputs().snapshot(o2::framework::Output{Origin, "CLUSTERSMCTR", subspec}, *clusterLabels); } totalClusters += clusters.size(); - LOGP(info, "TRKClusterer layer {} pushed {} clusters in {} ROFs", iLayer, clusters.size(), clusterROFs.size()); + LOGP(info, "{}Clusterer layer {} pushed {} clusters in {} ROFs", detName, iLayer, clusters.size(), clusterROFs.size()); } - LOGP(info, "TRKClusterer produced {} clusters", totalClusters); + LOGP(info, "{}Clusterer produced {} clusters", detName, totalClusters); } -o2::framework::DataProcessorSpec getClustererSpec(bool useMC) +namespace { - static constexpr int nLayers = o2::trk::AlmiraParam::kNLayers; +template +o2::framework::DataProcessorSpec getClustererSpecT(bool useMC) +{ + constexpr auto origin = DetID == o2::detectors::DetID::TRK ? o2::header::gDataOriginTRK : o2::header::gDataOriginFT3; std::vector inputs; - for (int iLayer = 0; iLayer < nLayers; ++iLayer) { - inputs.emplace_back(std::format("digits_{}", iLayer), "TRK", "DIGITS", iLayer, o2::framework::Lifetime::Timeframe); - inputs.emplace_back(std::format("ROframes_{}", iLayer), "TRK", "DIGITSROF", iLayer, o2::framework::Lifetime::Timeframe); + constexpr int NLayers = DetID == o2::detectors::DetID::TRK ? static_cast(o2::trk::AlmiraParam::kNLayers) : o2::trk::constants::MLOTDisks::nLayers; + for (int iLayer = 0; iLayer < NLayers; ++iLayer) { + inputs.emplace_back(std::format("digits_{}", iLayer), origin, "DIGITS", iLayer, o2::framework::Lifetime::Timeframe); + inputs.emplace_back(std::format("ROframes_{}", iLayer), origin, "DIGITSROF", iLayer, o2::framework::Lifetime::Timeframe); if (useMC) { - inputs.emplace_back(std::format("labels_{}", iLayer), "TRK", "DIGITSMCTR", iLayer, o2::framework::Lifetime::Timeframe); + inputs.emplace_back(std::format("labels_{}", iLayer), origin, "DIGITSMCTR", iLayer, o2::framework::Lifetime::Timeframe); } } std::vector outputs; - for (int iLayer = 0; iLayer < nLayers; ++iLayer) { - outputs.emplace_back("TRK", "COMPCLUSTERS", iLayer, o2::framework::Lifetime::Timeframe); - outputs.emplace_back("TRK", "PATTERNS", iLayer, o2::framework::Lifetime::Timeframe); - outputs.emplace_back("TRK", "CLUSTERSROF", iLayer, o2::framework::Lifetime::Timeframe); + for (int iLayer = 0; iLayer < NLayers; ++iLayer) { + outputs.emplace_back(origin, "COMPCLUSTERS", iLayer, o2::framework::Lifetime::Timeframe); + outputs.emplace_back(origin, "PATTERNS", iLayer, o2::framework::Lifetime::Timeframe); + outputs.emplace_back(origin, "CLUSTERSROF", iLayer, o2::framework::Lifetime::Timeframe); if (useMC) { - outputs.emplace_back("TRK", "CLUSTERSMCTR", iLayer, o2::framework::Lifetime::Timeframe); + outputs.emplace_back(origin, "CLUSTERSMCTR", iLayer, o2::framework::Lifetime::Timeframe); } } + o2::framework::Options options{{"nthreads", o2::framework::VariantType::Int, 1, {"Number of clustering threads"}}}; +#ifdef O2_WITH_ACTS + if constexpr (DetID == o2::detectors::DetID::TRK) { + options.push_back({"useACTS", o2::framework::VariantType::Bool, false, {"Use ACTS for clustering"}}); + } +#endif + auto name = origin.template as() + "-clusterer"; + std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) { return std::tolower(c); }); return o2::framework::DataProcessorSpec{ - "trk-clusterer", + name, inputs, outputs, - o2::framework::AlgorithmSpec{o2::framework::adaptFromTask(useMC)}, - o2::framework::Options{{"nthreads", o2::framework::VariantType::Int, 1, {"Number of clustering threads"}} -#ifdef O2_WITH_ACTS - , - {"useACTS", o2::framework::VariantType::Bool, false, {"Use ACTS for clustering"}} -#endif - }}; + o2::framework::AlgorithmSpec{o2::framework::adaptFromTask>(useMC)}, + options}; } +} // namespace + +o2::framework::DataProcessorSpec getClustererSpec(bool useMC) +{ + return getClustererSpecT(useMC); +} + +o2::framework::DataProcessorSpec getFT3ClustererSpec(bool useMC) +{ + return getClustererSpecT(useMC); +} + +template class ClustererDPL; +template class ClustererDPL; } // namespace o2::trk diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/DigitReaderSpec.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/DigitReaderSpec.cxx index 16bb1941fe785..507a584372745 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/DigitReaderSpec.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/DigitReaderSpec.cxx @@ -17,11 +17,15 @@ #include "Framework/ConfigParamRegistry.h" #include "Framework/Logger.h" #include "TRKWorkflow/DigitReaderSpec.h" +#include "TRKBase/AlmiraParam.h" #include "SimulationDataFormat/MCCompLabel.h" #include "SimulationDataFormat/ConstMCTruthContainer.h" #include "SimulationDataFormat/MCTruthContainer.h" #include "SimulationDataFormat/IOMCTruthContainerView.h" +#include #include +#include +#include using namespace o2::framework; using namespace o2::itsmft; @@ -33,7 +37,8 @@ namespace trk DigitReader::DigitReader(o2::detectors::DetID id, bool useMC, bool useCalib) { - assert(id == o2::detectors::DetID::TRK); + assert(id == o2::detectors::DetID::TRK || id == o2::detectors::DetID::FT3); + mLayers = id == o2::detectors::DetID::TRK ? static_cast(o2::trk::AlmiraParam::kNLayers) : o2::trk::constants::MLOTDisks::nLayers; mDetNameLC = mDetName = id.getName(); mDigTreeName = "o2sim"; @@ -154,28 +159,45 @@ void DigitReader::setBranchAddress(const std::string& base, Ptr& addr, int layer } } -DataProcessorSpec getTRKDigitReaderSpec(bool useMC, bool useCalib, std::string defname) +namespace +{ +template +DataProcessorSpec getDigitReaderSpecT(bool useMC, bool useCalib, std::string defname) { - static constexpr int nLayers = o2::trk::AlmiraParam::kNLayers; + constexpr auto origin = DetID == o2::detectors::DetID::TRK ? o2::header::gDataOriginTRK : o2::header::gDataOriginFT3; std::vector outputSpec; - for (int iLayer = 0; iLayer < nLayers; ++iLayer) { - outputSpec.emplace_back("TRK", "DIGITS", iLayer, Lifetime::Timeframe); - outputSpec.emplace_back("TRK", "DIGITSROF", iLayer, Lifetime::Timeframe); + constexpr int NLayers = DetID == o2::detectors::DetID::TRK ? static_cast(o2::trk::AlmiraParam::kNLayers) : o2::trk::constants::MLOTDisks::nLayers; + for (int iLayer = 0; iLayer < NLayers; ++iLayer) { + outputSpec.emplace_back(origin, "DIGITS", iLayer, Lifetime::Timeframe); + outputSpec.emplace_back(origin, "DIGITSROF", iLayer, Lifetime::Timeframe); if (useMC) { - outputSpec.emplace_back("TRK", "DIGITSMCTR", iLayer, Lifetime::Timeframe); + outputSpec.emplace_back(origin, "DIGITSMCTR", iLayer, Lifetime::Timeframe); } } if (useCalib) { - outputSpec.emplace_back("TRK", "GBTCALIB", 0, Lifetime::Timeframe); + outputSpec.emplace_back(origin, "GBTCALIB", 0, Lifetime::Timeframe); } + std::string detNameLC = o2::detectors::DetID(DetID).getName(); + std::transform(detNameLC.begin(), detNameLC.end(), detNameLC.begin(), ::tolower); return DataProcessorSpec{ - "trk-digit-reader", - Inputs{}, - outputSpec, - AlgorithmSpec{adaptFromTask(useMC, useCalib)}, - Options{ - {"trk-digit-infile", VariantType::String, defname, {"Name of the input digit file"}}}}; + .name = detNameLC + "-digit-reader", + .inputs = Inputs{}, + .outputs = outputSpec, + .algorithm = AlgorithmSpec{adaptFromTask(useMC, useCalib)}, + .options = Options{ + {detNameLC + "-digit-infile", VariantType::String, defname, {"Name of the input digit file"}}}}; +} +} // namespace + +DataProcessorSpec getTRKDigitReaderSpec(bool useMC, bool useCalib, std::string defname) +{ + return getDigitReaderSpecT(useMC, useCalib, std::move(defname)); +} + +DataProcessorSpec getFT3DigitReaderSpec(bool useMC, bool useCalib, std::string defname) +{ + return getDigitReaderSpecT(useMC, useCalib, std::move(defname)); } } // namespace trk diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/RecoWorkflow.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/RecoWorkflow.cxx index 02895f42ac094..a215469650277 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/RecoWorkflow.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/RecoWorkflow.cxx @@ -23,19 +23,37 @@ namespace o2::trk::reco_workflow framework::WorkflowSpec getWorkflow(bool useMC, bool upstreamDigits, bool upstreamClusters, - bool disableRootOutput) + bool disableRootOutput, + o2::detectors::DetID::mask_t detectors) { framework::WorkflowSpec specs; + const bool trk = detectors[o2::detectors::DetID::TRK]; + const bool ft3 = detectors[o2::detectors::DetID::FT3]; if (!(upstreamDigits || upstreamClusters)) { - specs.emplace_back(o2::trk::getTRKDigitReaderSpec(useMC, false, "trkdigits.root")); + if (trk) { + specs.emplace_back(o2::trk::getTRKDigitReaderSpec(useMC, false, "trkdigits.root")); + } + if (ft3) { + specs.emplace_back(o2::trk::getFT3DigitReaderSpec(useMC, false, "ft3digits.root")); + } } if (!upstreamClusters) { - specs.emplace_back(o2::trk::getClustererSpec(useMC)); + if (trk) { + specs.emplace_back(o2::trk::getClustererSpec(useMC)); + } + if (ft3) { + specs.emplace_back(o2::trk::getFT3ClustererSpec(useMC)); + } } if (!disableRootOutput) { - specs.emplace_back(o2::trk::getClusterWriterSpec(useMC)); + if (trk) { + specs.emplace_back(o2::trk::getTRKClusterWriterSpec(useMC)); + } + if (ft3) { + specs.emplace_back(o2::trk::getFT3ClusterWriterSpec(useMC)); + } } return specs; diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/trk-reco-workflow.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/trk-reco-workflow.cxx index bd1d5acc9b9a7..c709fc72b5f22 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/trk-reco-workflow.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/trk-reco-workflow.cxx @@ -16,6 +16,7 @@ #include "Framework/ConfigContext.h" #include "Framework/CompletionPolicyHelpers.h" +#include #include using namespace o2::framework; @@ -28,7 +29,7 @@ void customize(std::vector& policies) void customize(std::vector& policies) { // ordered policies for the writers - policies.push_back(CompletionPolicyHelpers::consumeWhenAllOrdered(".*(?:TRK|trk).*[W,w]riter.*")); + policies.push_back(CompletionPolicyHelpers::consumeWhenAllOrdered(".*(?:TRK|trk|FT3|ft3).*[W,w]riter.*")); } void customize(std::vector& workflowOptions) @@ -39,6 +40,7 @@ void customize(std::vector& workflowOptions) {"clusters-from-upstream", VariantType::Bool, false, {"clusters will be provided from upstream, skip clusterizer"}}, {"disable-root-output", VariantType::Bool, false, {"do not write output root files"}}, {"disable-mc", VariantType::Bool, false, {"disable MC propagation even if available"}}, + {"detectors", VariantType::String, "TRK", {"comma-separated list of the detectors to process: TRK, FT3"}}, {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}}; std::swap(workflowOptions, options); } @@ -53,10 +55,14 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) auto extDigits = configcontext.options().get("digits-from-upstream"); auto extClusters = configcontext.options().get("clusters-from-upstream"); auto disableRootOutput = configcontext.options().get("disable-root-output"); + const auto detectors = o2::detectors::DetID::getMask(configcontext.options().get("detectors")); + if ((detectors & ~(o2::detectors::DetID::getMask(o2::detectors::DetID::TRK) | o2::detectors::DetID::getMask(o2::detectors::DetID::FT3))).any() || detectors.none()) { + throw std::invalid_argument("--detectors takes TRK and/or FT3"); + } o2::conf::ConfigurableParam::updateFromString(configcontext.options().get("configKeyValues")); // write the configuration used for the reco workflow o2::conf::ConfigurableParam::writeINI("o2itsrecoflow_configuration.ini"); - return o2::trk::reco_workflow::getWorkflow(useMC, extDigits, extClusters, disableRootOutput); + return o2::trk::reco_workflow::getWorkflow(useMC, extDigits, extClusters, disableRootOutput, detectors); } diff --git a/Steer/DigitizerWorkflow/src/TRKDigitizerSpec.cxx b/Steer/DigitizerWorkflow/src/TRKDigitizerSpec.cxx index bf95164cecfd4..6a0efa0554edf 100644 --- a/Steer/DigitizerWorkflow/src/TRKDigitizerSpec.cxx +++ b/Steer/DigitizerWorkflow/src/TRKDigitizerSpec.cxx @@ -286,8 +286,10 @@ class TRKFT3DPLDigitizerTask : BaseDPLDigitizer mLabels.resize(mLayers); mLabelsAccum.resize(mLayers); + // FT3's discs take the AlmiraParam entries after TRK's barrel layers, which it reserves for them. + constexpr int firstParLayer = N == o2::detectors::DetID::TRK ? 0 : o2::trk::AlmiraParam::getNLayers() - o2::trk::constants::MLOTDisks::nLayers; for (int iLayer = 0; iLayer < mLayers; ++iLayer) { - const int parLayer = std::min(iLayer, o2::trk::AlmiraParam::getNLayers() - 1); + const int parLayer = std::min(firstParLayer + iLayer, o2::trk::AlmiraParam::getNLayers() - 1); const auto roFrameLengthInBC = aopt.getROFLengthInBC(parLayer); const auto frameNS = roFrameLengthInBC * o2::constants::lhc::LHCBunchSpacingNS; digipar.setROFrameLengthInBC(roFrameLengthInBC, iLayer);