diff --git a/Detectors/Upgrades/ALICE3/IOTOF/CMakeLists.txt b/Detectors/Upgrades/ALICE3/IOTOF/CMakeLists.txt index a9964670a99c1..56702ca159a61 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/IOTOF/CMakeLists.txt @@ -12,5 +12,6 @@ add_subdirectory(base) add_subdirectory(simulation) add_subdirectory(DataFormatsIOTOF) +add_subdirectory(reconstruction) add_subdirectory(workflow) add_subdirectory(macros) diff --git a/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/CMakeLists.txt b/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/CMakeLists.txt index 534e6217807c5..9e075aabb2cc0 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/CMakeLists.txt @@ -12,11 +12,11 @@ o2_add_library(DataFormatsIOTOF SOURCES src/Digit.cxx # SOURCES src/MCLabel.cxx - # SOURCES src/Cluster.cxx + SOURCES src/Cluster.cxx PUBLIC_LINK_LIBRARIES O2::DataFormatsITSMFT) o2_target_root_dictionary(DataFormatsIOTOF HEADERS include/DataFormatsIOTOF/Digit.h # HEADERS include/DataFormatsIOTOF/MCLabel.h - # HEADERS include/DataFormatsIOTOF/Cluster.h + HEADERS include/DataFormatsIOTOF/Cluster.h ) diff --git a/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/include/DataFormatsIOTOF/Cluster.h b/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/include/DataFormatsIOTOF/Cluster.h new file mode 100644 index 0000000000000..ad789c649c785 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/include/DataFormatsIOTOF/Cluster.h @@ -0,0 +1,36 @@ +// 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. + +#ifndef ALICEO2_DATAFORMATSIOTOF_CLUSTER_H +#define ALICEO2_DATAFORMATSIOTOF_CLUSTER_H + +#include +#include +#include + +namespace o2::iotof +{ + +struct Cluster { + uint16_t chipID = 0; + uint16_t row = 0; + uint16_t col = 0; + uint16_t size = 1; + double time = 0.0; + + std::string asString() const; + + ClassDefNV(Cluster, 1); +}; + +} // namespace o2::iotof + +#endif diff --git a/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/src/Cluster.cxx b/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/src/Cluster.cxx new file mode 100644 index 0000000000000..6b5a4948900e7 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/src/Cluster.cxx @@ -0,0 +1,27 @@ +// 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. + +#include "DataFormatsIOTOF/Cluster.h" +#include + +ClassImp(o2::iotof::Cluster); + +namespace o2::iotof +{ + +std::string Cluster::asString() const +{ + std::ostringstream stream; + stream << "chip=" << chipID << " row=" << row << " col=" << col << " size=" << size; + return stream.str(); +} + +} // namespace o2::iotof diff --git a/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/src/DataFormatsIOTOFLinkDef.h b/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/src/DataFormatsIOTOFLinkDef.h index 3f629289cf842..7e121273d3fab 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/src/DataFormatsIOTOFLinkDef.h +++ b/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/src/DataFormatsIOTOFLinkDef.h @@ -18,6 +18,9 @@ #pragma link C++ class o2::iotof::Digit + ; #pragma link C++ class std::vector < o2::iotof::Digit> + ; +#pragma link C++ class o2::iotof::Cluster + ; +#pragma link C++ class std::vector < o2::iotof::Cluster> + ; + #pragma link C++ class o2::iotof::McLabelRef + ; #pragma link C++ class std::vector < o2::iotof::McLabelRef> + ; diff --git a/Detectors/Upgrades/ALICE3/IOTOF/base/src/GeometryTGeo.cxx b/Detectors/Upgrades/ALICE3/IOTOF/base/src/GeometryTGeo.cxx index 7b4711d8d3272..905da9ddce754 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/base/src/GeometryTGeo.cxx +++ b/Detectors/Upgrades/ALICE3/IOTOF/base/src/GeometryTGeo.cxx @@ -284,7 +284,9 @@ void GeometryTGeo::Build(int loadTrans) mLastChipIndex[j] = numberOfChips - 1; } - LOG(info) << "numberOfChipsITOF = " << mNumberOfChipsIOTOF[0] << ", numberOfChipsOTOF = " << mNumberOfChipsIOTOF[1] << ", numberOfChips = " << numberOfChips << ", mNumberOfChipesPerStaveITOF" << mNumberOfChipsPerStaveIOTOF[0]; + LOG(info) << "TF3 geometry: numberOfChipsITOF = " << mNumberOfChipsIOTOF[0] << ", numberOfChipsOTOF = " + << mNumberOfChipsIOTOF[1] << ", numberOfChips = " << numberOfChips << ", mNumberOfChipesPerStaveITOF" + << mNumberOfChipsPerStaveIOTOF[0]; setSize(numberOfChips); defineSensors(); diff --git a/Detectors/Upgrades/ALICE3/IOTOF/macros/CMakeLists.txt b/Detectors/Upgrades/ALICE3/IOTOF/macros/CMakeLists.txt index f56668d55ab91..efca7b8de1a9f 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/macros/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/IOTOF/macros/CMakeLists.txt @@ -25,3 +25,6 @@ o2_add_test_root_macro(CheckDigitsIOTOF.C O2::DetectorsBase O2::Steer LABELS iotof COMPILE_ONLY) + +o2_add_test_root_macro(CheckClustersIOTOF.C + LABELS iotof COMPILE_ONLY) diff --git a/Detectors/Upgrades/ALICE3/IOTOF/macros/CheckClustersIOTOF.C b/Detectors/Upgrades/ALICE3/IOTOF/macros/CheckClustersIOTOF.C new file mode 100644 index 0000000000000..107e5a4d02bf8 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/IOTOF/macros/CheckClustersIOTOF.C @@ -0,0 +1,247 @@ +// 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 CheckClustersIOTOF.C +/// \brief Simple macro to create clusters from TF3 digits + +#if !defined(__CLING__) || defined(__ROOTCLING__) +#include +#include +#include +#include +#include +#include + +#include "IOTOFSimulation/Segmentation.h" +#include "IOTOFBase/IOTOFBaseParam.h" +#include "IOTOFBase/GeometryTGeo.h" +#include "DataFormatsIOTOF/Digit.h" +#include "DataFormatsIOTOF/Cluster.h" +#include "MathUtils/Utils.h" +#include "SimulationDataFormat/ConstMCTruthContainer.h" +#include "SimulationDataFormat/IOMCTruthContainerView.h" +#include "SimulationDataFormat/MCCompLabel.h" +#include "DetectorsBase/GeometryManager.h" + +#include "DataFormatsITSMFT/ROFRecord.h" + +#endif + +#define ENABLE_UPGRADES + +void CheckClustersIOTOF(std::string digiFilePath = "tf3digits.root", std::string clsFilePath = "tf3clusters.root", std::string inputGeomPath = "o2sim_geometry.root") +{ + gStyle->SetPalette(55); + + using namespace o2::base; + using namespace o2::iotof; + + using o2::iotof::Cluster; + using o2::iotof::Digit; + + o2::conf::ConfigurableParam::updateFromString("IOTOFBase.segmentedInnerTOF=true;IOTOFBase.segmentedOuterTOF=true;IOTOFBase.enableForwardTOF=false;IOTOFBase.enableBackwardTOF=false"); + + auto segGeom = o2::iotof::Segmentation::Instance(); + + // Geometry + o2::base::GeometryManager::loadGeometry(inputGeomPath); + auto* tofGeo = o2::iotof::GeometryTGeo::Instance(); + tofGeo->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); + + // Digits + TFile* digiFile = TFile::Open(digiFilePath.data()); + TTree* digiTree = (TTree*)digiFile->Get("o2sim"); + std::vector* digitsArray{nullptr}; + digiTree->SetBranchAddress("TF3Digit", &digitsArray); + std::vector* digiRofRecordsArr{nullptr}; + digiTree->SetBranchAddress("TF3DigitROF", &digiRofRecordsArr); + auto& digiRofArr = *digiRofRecordsArr; + o2::dataformats::IOMCTruthContainerView* digiLabelsArr{nullptr}; + digiTree->SetBranchAddress("TF3DigitMCTruth", &digiLabelsArr); + digiTree->GetEntry(0); + o2::dataformats::ConstMCTruthContainer digiLabels; + digiLabelsArr->copyandflatten(digiLabels); + + // Clusters + TFile* clsFile = TFile::Open(clsFilePath.data()); + TTree* clsTree = (TTree*)clsFile->Get("o2sim"); + std::vector* clsArray{nullptr}; + clsTree->SetBranchAddress("TF3ClusterComp", &clsArray); + std::vector* clsRofRecordsArr{nullptr}; + clsTree->SetBranchAddress("TF3ClusterROF", &clsRofRecordsArr); + auto& clsRofArr = *clsRofRecordsArr; + o2::dataformats::MCTruthContainer* clsLabels{nullptr}; + clsTree->SetBranchAddress("TF3ClusterMCTruth", &clsLabels); + clsTree->GetEntry(0); + + // Summary of entries in all branches + std::cout << std::endl; + std::cout << "---> Number of digits: " << digitsArray->size() << std::endl; + std::cout << "---> Number of digit ROFs: " << digiRofArr.size() << std::endl; + std::cout << "---> Number of clusters: " << clsArray->size() << std::endl; + std::cout << "---> Number of cluster ROFs: " << clsRofArr.size() << std::endl; + std::cout << "---> Number of digits with MC label: " << digiLabels.getNElements() << std::endl; + std::cout << "---> Number of digits with MC label: " << digiLabels.getIndexedSize() << std::endl; + std::cout << "---> Number of clusters with MC label: " << clsLabels->getNElements() << std::endl; + std::cout << "---> Number of clusters with MC label: " << clsLabels->getIndexedSize() << std::endl; + std::cout << std::endl; + + auto clsTuple = new TNtuple("clsTuple", "clsTuple", "chip_id:x:y:z:row:col:time"); + clsTuple->SetDirectory(nullptr); + + TH1F* histXCoordCls = new TH1F("histXCoordCls", "histXCoordCls", 8000, -100, 100); + TH1F* histYCoordCls = new TH1F("histYCoordCls", "histYCoordCls", 8000, -100, 100); + TH1F* histZCoordCls = new TH1F("histZCoordCls", "histZCoordCls", 28000, -400, 400); + TH1F* histXCoordDigit = new TH1F("histXCoordDigit", "histXCoordDigit", 8000, -100, 100); + TH1F* histYCoordDigit = new TH1F("histYCoordDigit", "histYCoordDigit", 8000, -100, 100); + TH1F* histZCoordDigit = new TH1F("histZCoordDigit", "histZCoordDigit", 28000, -400, 400); + TH1F* histXCoordRes = new TH1F("histXCoordRes", "histXCoordRes", 100, -0.05, 0.05); + TH1F* histYCoordRes = new TH1F("histYCoordRes", "histYCoordRes", 100, -0.05, 0.05); + TH1F* histZCoordRes = new TH1F("histZCoordRes", "histZCoordRes", 100, -0.05, 0.05); + TH1F* histTimeRes = new TH1F("histTimeRes", "histTimeRes", 100, -0.05, 0.05); + + // Load all digits upfront and build a lookup map + int nDigits = digiTree->GetEntries(); + std::unordered_map digitsLabels; + for (int iDigit = 0; iDigit < digitsArray->size(); ++iDigit) { + auto label = digiLabels.getLabels(iDigit)[0]; + if (!label.isValid()) { + continue; + } + digitsLabels.emplace(label, iDigit); + } + + // LOOP on : ROFRecord array + for (unsigned int iROF = 0; iROF < clsRofArr.size(); ++iROF) { + + const unsigned int rofIndex = clsRofArr[iROF].getFirstEntry(); + const unsigned int rofNEntries = clsRofArr[iROF].getNEntries(); + + // LOOP on : digits array + std::cout << "\n\n ----> Starting loop on digits for ROF " << iROF << " with index " << rofIndex << " and nEntries " << rofNEntries << std::endl; + for (unsigned int iDigit = rofIndex; iDigit < rofIndex + rofNEntries; iDigit++) { + if (iDigit % 10000 == 0) { + std::cout << "Reading digit " << iDigit << " / " << digitsArray->size() << std::endl; + } + + Int_t iRow = (*digitsArray)[iDigit].getRow(); + Int_t iCol = (*digitsArray)[iDigit].getColumn(); + Int_t iDetID = (*digitsArray)[iDigit].getChipIndex(); + Int_t chipID = (*digitsArray)[iDigit].getChipIndex(); + Int_t subDetID = tofGeo->getIOTOFLayer(iDetID); + + Float_t x{0.f}, y{0.f}, z{0.f}; + if (subDetID >= 0) { + segGeom->detectorToLocal(iRow, iCol, x, z, subDetID); + } + + o2::math_utils::Point3D localDigitCoord(x, y, z); // local Digit + + const auto globalDigitCoord = tofGeo->getMatrixL2G(chipID)(localDigitCoord); // convert to global + histXCoordDigit->Fill(globalDigitCoord.X()); + histYCoordDigit->Fill(globalDigitCoord.Y()); + histZCoordDigit->Fill(globalDigitCoord.Z()); + } // end loop on digits array + + // LOOP on : clusters array + std::cout << "\n\n ----> Starting loop on clusters for ROF " << iROF << " with index " << rofIndex << " and nEntries " << rofNEntries << std::endl; + for (unsigned int iCls = rofIndex; iCls < rofIndex + rofNEntries; iCls++) { + if (iCls % 10000 == 0) { + std::cout << "Reading cluster " << iCls << " / " << clsArray->size() << std::endl; + } + + Int_t iRow = (*clsArray)[iCls].row; + Int_t iCol = (*clsArray)[iCls].col; + Int_t chipID = (*clsArray)[iCls].chipID; + Int_t subDetID = tofGeo->getIOTOFLayer(chipID); + Float_t time = (*clsArray)[iCls].time; + + Float_t x = 0.f, y = 0.f, z = 0.f; + if (subDetID >= 0) { + segGeom->detectorToLocal(iRow, iCol, x, z, subDetID); + } + + o2::math_utils::Point3D localClsCoords(x, y, z); // local Digit + const auto globalClsCoords = tofGeo->getMatrixL2G(chipID)(localClsCoords); // convert to global + clsTuple->Fill((*clsArray)[iCls].chipID, + globalClsCoords.x(), + globalClsCoords.y(), + globalClsCoords.z(), + (*clsArray)[iCls].row, + (*clsArray)[iCls].col, + (*clsArray)[iCls].time); + histXCoordCls->Fill(globalClsCoords.x()); + histYCoordCls->Fill(globalClsCoords.y()); + histZCoordCls->Fill(globalClsCoords.z()); + + // Match to digit + auto digitLabelFromCls = (clsLabels->getLabels(iCls))[0]; + auto digitEntry = digitsLabels.find(digitLabelFromCls); + + if (digitEntry == digitsLabels.end()) { + LOG(error) << "No matching digit for cluster " << iCls << " with label " << digitLabelFromCls.getRawValue(); + continue; + } + + int iDigit = digitEntry->second; + Int_t iRowFromDigit = (*digitsArray)[iDigit].getRow(); + Int_t iColFromDigit = (*digitsArray)[iDigit].getColumn(); + Int_t iChipIDFromDigit = (*digitsArray)[iDigit].getChipIndex(); + Int_t iSubDetIDFromDigit = tofGeo->getIOTOFLayer(iChipIDFromDigit); + Float_t timeFromDigit = (*digitsArray)[iDigit].getTime(); + + float xFromDigit = 0.f, yFromDigit = 0.f, zFromDigit = 0.f; + if (iSubDetIDFromDigit >= 0) { + segGeom->detectorToLocal(iRowFromDigit, iColFromDigit, xFromDigit, zFromDigit, iSubDetIDFromDigit); + } + + o2::math_utils::Point3D localDigitCoordFromDigit(xFromDigit, yFromDigit, zFromDigit); // local Digit + const auto globalDigitCoordFromDigit = tofGeo->getMatrixL2G(iChipIDFromDigit)(localDigitCoordFromDigit); // convert to global + histXCoordRes->Fill(globalClsCoords.x() - globalDigitCoordFromDigit.X()); + histYCoordRes->Fill(globalClsCoords.y() - globalDigitCoordFromDigit.Y()); + histZCoordRes->Fill(globalClsCoords.z() - globalDigitCoordFromDigit.Z()); + histTimeRes->Fill(time - timeFromDigit); + } // end loop on clusters array + } // end loop on ROFRecords + + std::cout << "Cluster array size: " << clsTuple->GetEntries() << std::endl; + + // cluster maps in the xy and yz planes + auto canvXY = new TCanvas("canvXY", "", 1600, 800); + canvXY->Divide(2, 1); + canvXY->cd(1); + clsTuple->Draw("y:x>>h_y_vs_x_IOTOF(1000, -100, 100, 1000, -100, 100)", "", "colz"); + canvXY->cd(2); + clsTuple->Draw("y:z>>h_y_vs_z_IOTOF(1000, -400, 400, 1000, -100, 100)", "", "colz"); + canvXY->SaveAs("clusters_digits_y_vs_x_vs_z.pdf"); + + // z distributions + auto canvZ = new TCanvas("canvZ", "", 800, 800); + canvZ->cd(); + clsTuple->Draw("z>>h_z_IOTOF(500, -70, 70)", ""); + canvZ->SaveAs("clusters_digits_z.pdf"); + + TFile* outFile = new TFile("CheckClusters.root", "RECREATE"); + // Save all columns of the tuple as hists + clsTuple->Write(); + histXCoordCls->Write(); + histYCoordCls->Write(); + histZCoordCls->Write(); + histXCoordDigit->Write(); + histYCoordDigit->Write(); + histZCoordDigit->Write(); + histXCoordRes->Write(); + histYCoordRes->Write(); + histZCoordRes->Write(); + histTimeRes->Write(); + outFile->Write(); + outFile->Close(); +} diff --git a/Detectors/Upgrades/ALICE3/IOTOF/reconstruction/CMakeLists.txt b/Detectors/Upgrades/ALICE3/IOTOF/reconstruction/CMakeLists.txt new file mode 100644 index 0000000000000..9a887bff8127c --- /dev/null +++ b/Detectors/Upgrades/ALICE3/IOTOF/reconstruction/CMakeLists.txt @@ -0,0 +1,20 @@ +# 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. + +o2_add_library(IOTOFReconstruction + TARGETVARNAME targetName + SOURCES src/Clusterer.cxx + PUBLIC_LINK_LIBRARIES + Microsoft.GSL::GSL + O2::DataFormatsIOTOF + O2::IOTOFBase + O2::IOTOFSimulation + ) diff --git a/Detectors/Upgrades/ALICE3/IOTOF/reconstruction/include/IOTOFReconstruction/Clusterer.h b/Detectors/Upgrades/ALICE3/IOTOF/reconstruction/include/IOTOFReconstruction/Clusterer.h new file mode 100644 index 0000000000000..252ecf8917377 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/IOTOF/reconstruction/include/IOTOFReconstruction/Clusterer.h @@ -0,0 +1,94 @@ +// 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 Clusterer.h +/// \brief Definition of the IOTOF cluster finder + +#ifndef ALICEO2_IOTOF_CLUSTERER_H +#define ALICEO2_IOTOF_CLUSTERER_H + +#include "DataFormatsIOTOF/Digit.h" +#include "DataFormatsITSMFT/ROFRecord.h" +#include "DataFormatsIOTOF/Cluster.h" +#include "SimulationDataFormat/ConstMCTruthContainer.h" +#include "SimulationDataFormat/MCCompLabel.h" +#include "SimulationDataFormat/MCTruthContainer.h" +#include +#include +#include +#include +#include +#include + +namespace o2::iotof +{ + +class GeometryTGeo; + +class Clusterer +{ + public: + static constexpr int MaxLabels = 10; + + using Digit = o2::iotof::Digit; + using DigROFRecord = o2::itsmft::ROFRecord; + using DigMC2ROFRecord = o2::itsmft::MC2ROFRecord; + using ClusterTruth = o2::dataformats::MCTruthContainer; + using ConstDigitTruth = o2::dataformats::ConstMCTruthContainerView; + using Label = o2::MCCompLabel; + + //---------------------------------------------- + struct ClustererThread { + Clusterer* parent = nullptr; + // Column buffers data members in TRK, for now not needed in TF3 + + // Further struct members in TRK, for now not needed in TF3 + + std::array labelsBuff; ///< MC label buffer for one cluster + + // per-thread output (accumulated, then merged back by caller) + std::vector clusters; + std::vector patterns; + ClusterTruth labels; + + // Further reset column buffer in TRK, not included for now in TF3 + + void fetchMCLabels(uint32_t digID, const ConstDigitTruth* labelsDig, int& nfilled); + void finishChipSingleHitFast(gsl::span digits, uint32_t digitIdx, + const ConstDigitTruth* labelsDigPtr, ClusterTruth* labelsClusPtr); + void processChip(gsl::span digits, int chipFirst, int chipN, + std::vector* clustersOut, std::vector* patternsOut, + const ConstDigitTruth* labelsDigPtr, ClusterTruth* labelsClusPtr); + + explicit ClustererThread(Clusterer* par = nullptr) : parent(par) {} + ClustererThread(const ClustererThread&) = delete; + ClustererThread& operator=(const ClustererThread&) = delete; + }; + //---------------------------------------------- + + virtual void process(gsl::span digits, + gsl::span digitROFs, + std::vector& clusters, + std::vector& patterns, + std::vector& clusterROFs, + const ConstDigitTruth* digitLabels = nullptr, + ClusterTruth* clusterLabels = nullptr, + gsl::span digMC2ROFs = {}, + std::vector* clusterMC2ROFs = nullptr); + + protected: + std::unique_ptr mThread; + std::vector mSortIdx; ///< reusable per-ROF sort buffer +}; + +} // namespace o2::iotof + +#endif diff --git a/Detectors/Upgrades/ALICE3/IOTOF/reconstruction/src/Clusterer.cxx b/Detectors/Upgrades/ALICE3/IOTOF/reconstruction/src/Clusterer.cxx new file mode 100644 index 0000000000000..edb9f71ac7f04 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/IOTOF/reconstruction/src/Clusterer.cxx @@ -0,0 +1,206 @@ +// 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 Clusterer.cxx +/// \brief Implementation of the IOTOF cluster finder + +#include "Framework/Logger.h" + +#include "IOTOFReconstruction/Clusterer.h" + +#include +#include + +namespace o2::iotof +{ + +//__________________________________________________ +void Clusterer::process(gsl::span digits, + gsl::span digitROFs, + std::vector& clusters, + std::vector& patterns, + std::vector& clusterROFs, + const ConstDigitTruth* digitLabels, + ClusterTruth* clusterLabels, + gsl::span digMC2ROFs, + std::vector* clusterMC2ROFs) +{ + LOG(info) << "Running clusterizer on " << digitROFs.size() << " ROFs, total digits: " << digits.size(); + + if (!mThread) { + mThread = std::make_unique(this); + } + + for (size_t iROF = 0; iROF < digitROFs.size(); ++iROF) { + LOG(debug) << "Processing digit ROF " << iROF << "/" << digitROFs.size(); + const auto& inROF = digitROFs[iROF]; + const auto outFirst = static_cast(clusters.size()); + const int first = inROF.getFirstEntry(); + const int nEntries = inROF.getNEntries(); + + if (nEntries == 0) { + LOG(debug) << "Digit ROF " << iROF << " has no entries, skipping"; + clusterROFs.emplace_back(inROF.getBCData(), inROF.getROFrame(), outFirst, 0); + continue; + } + + // Sort digit indices within this ROF by (chipID, col, row) + // chip by chip, column by column (taken from TRK). + mSortIdx.resize(nEntries); + std::iota(mSortIdx.begin(), mSortIdx.end(), first); + std::sort(mSortIdx.begin(), mSortIdx.end(), [&digits](int a, int b) { + const auto& da = digits[a]; + const auto& db = digits[b]; + if (da.getChipIndex() != db.getChipIndex()) { + return da.getChipIndex() < db.getChipIndex(); + } + if (da.getColumn() != db.getColumn()) { + return da.getColumn() < db.getColumn(); + } + return da.getRow() < db.getRow(); + }); + LOG(debug) << "Found " << nEntries << " digits for ROF " << iROF; + + // Process blocks of chips with the same chipID + int sliceStart = 0; + while (sliceStart < nEntries) { + const int chipFirst = sliceStart; + const uint16_t chipID = digits[mSortIdx[sliceStart]].getChipIndex(); + while (sliceStart < nEntries && digits[mSortIdx[sliceStart]].getChipIndex() == chipID) { + ++sliceStart; + } + const int chipN = sliceStart - chipFirst; + + LOG(debug) << "Processing chip " << chipID << " with " << chipN << " digits, next chip start from index " << sliceStart; + mThread->processChip(digits, chipFirst, chipN, &clusters, &patterns, digitLabels, clusterLabels); + } + + LOG(debug) << "Finished processing digit ROF " << iROF << ", produced " << (clusters.size() - outFirst) << " clusters"; + clusterROFs.emplace_back(inROF.getBCData(), inROF.getROFrame(), + outFirst, static_cast(clusters.size()) - outFirst); + } + + LOG(info) << "Finished processing all digit ROFs, total clusters produced: " << clusters.size(); + if (clusterMC2ROFs && !digMC2ROFs.empty()) { + clusterMC2ROFs->reserve(clusterMC2ROFs->size() + digMC2ROFs.size()); + for (const auto& in : digMC2ROFs) { + clusterMC2ROFs->emplace_back(in.eventRecordID, in.rofRecordID, in.minROF, in.maxROF); + } + } +} + +//__________________________________________________ +void Clusterer::ClustererThread::processChip(gsl::span digits, + int chipFirst, int chipN, + std::vector* clustersOut, + std::vector* patternsOut, + const ConstDigitTruth* labelsDigPtr, + ClusterTruth* labelsClusPtr) +{ + // chipFirst and chipN are relative to mSortIdx (i.e. mSortIdx[chipFirst..chipFirst+chipN-1] + // are the global digit indices for this chip, already sorted by col then row). + // We use parent->mSortIdx to resolve the global index of each pixel. + const auto& sortIdx = parent->mSortIdx; + + // TRK has per-ROF readout, so multiple hits belonging to the same chip, i.e. chipN > 1, + // are handled with a preclusterer. TF3 still does not have per-ROF readout, so we + // use finishChipSingleHitFast on all hits for now. + for (auto i = 0; i < chipN; ++i) { + finishChipSingleHitFast(digits, sortIdx[chipFirst + i], labelsDigPtr, labelsClusPtr); + } + + // // TRK logic for per-ROF readout, not used for TF3 yet. + // if (chipN == 1) { + // LOG(debug) << "Processing single hit chip"; + // finishChipSingleHitFast(digits, sortIdx[chipFirst], labelsDigPtr, labelsClusPtr); + // } else { + // LOG(debug) << "Processing multi-hit chip with " << chipN << " hits"; + // // Call to initChip() + // // Call to updateChip() + // // Call to finishChip() + // // Code for preclusters needed + // } + + // Flush per-thread output into the caller's containers + if (!clusters.empty()) { + clustersOut->insert(clustersOut->end(), clusters.begin(), clusters.end()); + clusters.clear(); + } + if (!patterns.empty()) { + patternsOut->insert(patternsOut->end(), patterns.begin(), patterns.end()); + patterns.clear(); + } + if (labelsClusPtr && labels.getNElements()) { + labelsClusPtr->mergeAtBack(labels); + labels.clear(); + } +} + +//__________________________________________________ +void Clusterer::ClustererThread::finishChipSingleHitFast(gsl::span digits, + uint32_t digitIdx, + const ConstDigitTruth* labelsDigPtr, + ClusterTruth* labelsClusPtr) +{ + const auto& digit = digits[digitIdx]; + const uint16_t chipID = digit.getChipIndex(); + const uint16_t row = digit.getRow(); + const uint16_t col = digit.getColumn(); + const double time = digit.getTime(); + + if (labelsClusPtr) { + int nlab = 0; + fetchMCLabels(digitIdx, labelsDigPtr, nlab); + const auto cnt = static_cast(clusters.size()); + for (int i = nlab; i--;) { + labels.addElement(cnt, labelsBuff[i]); + } + } + + // 1×1 pattern: rowSpan=1, colSpan=1, one byte = 0x80 + patterns.emplace_back(1); + patterns.emplace_back(1); + patterns.emplace_back(0x80); + + Cluster cluster; + cluster.chipID = chipID; + cluster.row = row; + cluster.col = col; + cluster.size = 1; + cluster.time = time; + clusters.emplace_back(cluster); +} + +//__________________________________________________ +void Clusterer::ClustererThread::fetchMCLabels(uint32_t digID, const ConstDigitTruth* labelsDig, int& nfilled) +{ + if (nfilled >= MaxLabels) { + return; + } + if (!labelsDig || digID >= labelsDig->getIndexedSize()) { + return; + } + const auto& lbls = labelsDig->getLabels(digID); + for (int i = lbls.size(); i--;) { + int ic = nfilled; + for (; ic--;) { + if (labelsBuff[ic] == lbls[i]) { + return; // already present + } + } + labelsBuff[nfilled++] = lbls[i]; + if (nfilled >= MaxLabels) { + break; + } + } +} + +} // namespace o2::iotof diff --git a/Detectors/Upgrades/ALICE3/IOTOF/workflow/CMakeLists.txt b/Detectors/Upgrades/ALICE3/IOTOF/workflow/CMakeLists.txt index 73ae6edd15cf4..14a0e215587d8 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/workflow/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/IOTOF/workflow/CMakeLists.txt @@ -13,10 +13,13 @@ o2_add_library(IOTOFWorkflow TARGETVARNAME targetName SOURCES src/DigitReaderSpec.cxx src/DigitWriterSpec.cxx + src/ClustererSpec.cxx + src/ClusterWriterSpec.cxx src/RecoWorkflow.cxx PUBLIC_LINK_LIBRARIES O2::Framework O2::DataFormatsIOTOF O2::DataFormatsITSMFT + O2::IOTOFReconstruction O2::DPLUtils ) diff --git a/Detectors/Upgrades/ALICE3/IOTOF/workflow/include/IOTOFWorkflow/ClusterWriterSpec.h b/Detectors/Upgrades/ALICE3/IOTOF/workflow/include/IOTOFWorkflow/ClusterWriterSpec.h new file mode 100644 index 0000000000000..90b75b5dbd9c9 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/IOTOF/workflow/include/IOTOFWorkflow/ClusterWriterSpec.h @@ -0,0 +1,24 @@ +// 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 O2_IOTOF_CLUSTERWRITER +#define O2_IOTOF_CLUSTERWRITER + +#include "Framework/DataProcessorSpec.h" + +namespace o2::iotof +{ + +o2::framework::DataProcessorSpec getIOTOFClusterWriterSpec(bool useMC, bool dec); + +} // namespace o2::iotof + +#endif diff --git a/Detectors/Upgrades/ALICE3/IOTOF/workflow/include/IOTOFWorkflow/ClustererSpec.h b/Detectors/Upgrades/ALICE3/IOTOF/workflow/include/IOTOFWorkflow/ClustererSpec.h new file mode 100644 index 0000000000000..c735c35691a35 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/IOTOF/workflow/include/IOTOFWorkflow/ClustererSpec.h @@ -0,0 +1,40 @@ +// 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. + +#ifndef O2_IOTOF_CLUSTERERDPL +#define O2_IOTOF_CLUSTERERDPL + +#include "Framework/DataProcessorSpec.h" +#include "Framework/Task.h" +#include "IOTOFReconstruction/Clusterer.h" + +namespace o2::iotof +{ + +class ClustererDPL : public o2::framework::Task +{ + public: + ClustererDPL(bool useMC) : mUseMC(useMC) {} + void init(o2::framework::InitContext& ic) final; + void run(o2::framework::ProcessingContext& pc) final; + + private: + static constexpr int mLayers = 2; + bool mUseMC = true; + int mNThreads = 1; + o2::iotof::Clusterer mClusterer; +}; + +o2::framework::DataProcessorSpec getIOTOFClustererSpec(bool useMC); + +} // namespace o2::iotof + +#endif diff --git a/Detectors/Upgrades/ALICE3/IOTOF/workflow/src/ClusterWriterSpec.cxx b/Detectors/Upgrades/ALICE3/IOTOF/workflow/src/ClusterWriterSpec.cxx new file mode 100644 index 0000000000000..4d63190be5d4c --- /dev/null +++ b/Detectors/Upgrades/ALICE3/IOTOF/workflow/src/ClusterWriterSpec.cxx @@ -0,0 +1,73 @@ +// 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 ClusterWriterSpec.cxx + +#include +#include +#include +#include +#include + +#include "IOTOFWorkflow/ClusterWriterSpec.h" +#include "Framework/ConcreteDataMatcher.h" +#include "Framework/DataRef.h" +#include "DetectorsCommonDataFormats/DetID.h" +#include "DPLUtils/MakeRootTreeWriterSpec.h" +#include "DataFormatsIOTOF/Cluster.h" +#include "DataFormatsIOTOF/Digit.h" +#include "DataFormatsITSMFT/ROFRecord.h" +#include "SimulationDataFormat/MCCompLabel.h" +#include "SimulationDataFormat/ConstMCTruthContainer.h" +#include "SimulationDataFormat/IOMCTruthContainerView.h" + +using namespace o2::framework; + +namespace o2::iotof +{ + +template +using BranchDefinition = MakeRootTreeWriterSpec::BranchDefinition; +using ClustersType = std::vector; +using PatternsType = std::vector; +using ROFrameType = std::vector; +using LabelsType = o2::dataformats::MCTruthContainer; + +DataProcessorSpec getClusterWriterSpec(bool mctruth, bool dec, o2::header::DataOrigin detOrig, o2::detectors::DetID detId) +{ + std::string detStr = o2::detectors::DetID::getName(detId); + std::string detStrL = dec ? "o2_" : ""; // for decoded digits prepend by o2 + detStrL += detStr; + std::transform(detStrL.begin(), detStrL.end(), detStrL.begin(), ::tolower); + auto logger = [](std::vector const& inClusters) { + LOG(info) << "RECEIVED CLUSTERS SIZE " << inClusters.size(); + }; + + return MakeRootTreeWriterSpec((detStr + "ClusterWriter" + (dec ? "_dec" : "")).c_str(), + (detStrL + "clusters.root").c_str(), + MakeRootTreeWriterSpec::TreeAttributes{.name = "o2sim", .title = "Tree with TF3 clusters"}, + BranchDefinition{InputSpec{"tf3_compclus", detOrig, "COMPCLUSTERS", 0}, + (detStr + "ClusterComp").c_str(), + logger}, + BranchDefinition{InputSpec{"tf3_patterns", detOrig, "PATTERNS", 0}, + (detStr + "ClusterPatt").c_str()}, + BranchDefinition{InputSpec{"tf3_ROframes", detOrig, "CLUSTERSROF", 0}, + (detStr + "ClusterROF").c_str(), "cluster-rof-branch"}, + BranchDefinition{InputSpec{"tf3_labels", detOrig, "CLUSTERSMCTR", 0}, + (detStr + "ClusterMCTruth").c_str()})(); +} + +DataProcessorSpec getIOTOFClusterWriterSpec(bool mctruth, bool dec) +{ + return getClusterWriterSpec(mctruth, dec, o2::header::gDataOriginTF3, o2::detectors::DetID::TF3); +} + +} // namespace o2::iotof diff --git a/Detectors/Upgrades/ALICE3/IOTOF/workflow/src/ClustererSpec.cxx b/Detectors/Upgrades/ALICE3/IOTOF/workflow/src/ClustererSpec.cxx new file mode 100644 index 0000000000000..79d823914727a --- /dev/null +++ b/Detectors/Upgrades/ALICE3/IOTOF/workflow/src/ClustererSpec.cxx @@ -0,0 +1,115 @@ +// 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. + +#include "IOTOFWorkflow/ClustererSpec.h" +#include "DetectorsBase/GeometryManager.h" +#include "DataFormatsIOTOF/Cluster.h" +#include "DataFormatsIOTOF/Digit.h" +#include "DataFormatsITSMFT/ROFRecord.h" +#include "Framework/ConfigParamRegistry.h" +#include "Framework/Logger.h" +#include "SimulationDataFormat/ConstMCTruthContainer.h" + +#include + +using namespace o2::framework; + +namespace o2::iotof +{ + +void ClustererDPL::init(o2::framework::InitContext& ic) +{ + mNThreads = std::max(1, ic.options().get("nthreads")); +} + +void ClustererDPL::run(o2::framework::ProcessingContext& pc) +{ + LOG(info) << "Start running with " << mNThreads << " threads"; + o2::base::GeometryManager::loadGeometry("o2sim_geometry.root", false, true); + + uint64_t totalClusters = 0; + + // Loop on layers to be added here, for now only one layer is processed + int iLayer = 0; + auto digits = pc.inputs().get>(std::format("digits_{}", iLayer)); + auto rofs = pc.inputs().get>(std::format("ROframes_{}", iLayer)); + + LOG(debug) << "Got " << digits.size() << " digits and " << rofs.size() << " ROFs for layer " << iLayer; + gsl::span labelbuffer; + if (mUseMC) { + labelbuffer = pc.inputs().get>(std::format("labels_{}", iLayer)); + LOG(debug) << "Got " << labelbuffer.size() << " bytes of MC labels for layer " << iLayer; + } + o2::dataformats::ConstMCTruthContainerView labels(labelbuffer); + + std::vector clusters; + std::vector patterns; + std::vector clusterROFs; + std::unique_ptr> clusterLabels; + if (mUseMC) { + clusterLabels = std::make_unique>(); + } + + LOG(info) << "Running IOTOF Clusterer for layer " << iLayer; + mClusterer.process(digits, + rofs, + clusters, + patterns, + clusterROFs, + mUseMC ? &labels : nullptr, + clusterLabels.get()); + LOG(info) << "Clusterization produced " << clusters.size() << " clusters for layer " << iLayer; + const auto subspec = static_cast(iLayer); + pc.outputs().snapshot(o2::framework::Output{"TF3", "COMPCLUSTERS", subspec}, clusters); + pc.outputs().snapshot(o2::framework::Output{"TF3", "PATTERNS", subspec}, patterns); + pc.outputs().snapshot(o2::framework::Output{"TF3", "CLUSTERSROF", subspec}, clusterROFs); + if (mUseMC) { + pc.outputs().snapshot(o2::framework::Output{"TF3", "CLUSTERSMCTR", subspec}, *clusterLabels); + } + totalClusters += clusters.size(); + LOGP(info, "Pushed {} clusters in {} ROFs for layer {}", clusters.size(), clusterROFs.size(), iLayer); + LOGP(info, "Pushed {} MC labels for layer {}", mUseMC ? clusterLabels->getNElements() : 0, iLayer); +} + +o2::framework::DataProcessorSpec getClustererSpec(bool useMC) +{ + static constexpr int nLayers = 2; + std::vector inputs; + // Currently TF3 digits (unlike TRK) are not separated by layer, eventually per-layer reading here + int iLayer = 0; + inputs.emplace_back(std::format("digits_{}", iLayer), "TF3", "DIGITS", iLayer, o2::framework::Lifetime::Timeframe); + inputs.emplace_back(std::format("ROframes_{}", iLayer), "TF3", "DIGITSROF", iLayer, o2::framework::Lifetime::Timeframe); + if (useMC) { + inputs.emplace_back(std::format("labels_{}", iLayer), "TF3", "DIGITSMCTR", iLayer, o2::framework::Lifetime::Timeframe); + } + + std::vector outputs; + outputs.emplace_back("TF3", "COMPCLUSTERS", iLayer, o2::framework::Lifetime::Timeframe); + outputs.emplace_back("TF3", "PATTERNS", iLayer, o2::framework::Lifetime::Timeframe); + outputs.emplace_back("TF3", "CLUSTERSROF", iLayer, o2::framework::Lifetime::Timeframe); + if (useMC) { + outputs.emplace_back("TF3", "CLUSTERSMCTR", iLayer, o2::framework::Lifetime::Timeframe); + } + + return o2::framework::DataProcessorSpec{ + "iotof-clusterer", + inputs, + outputs, + o2::framework::AlgorithmSpec{o2::framework::adaptFromTask(useMC)}, + o2::framework::Options{{"nthreads", o2::framework::VariantType::Int, 1, {"Number of clustering threads"}}}}; +} + +DataProcessorSpec getIOTOFClustererSpec(bool mctruth) +{ + return getClustererSpec(mctruth); +} + +} // namespace o2::iotof diff --git a/Detectors/Upgrades/ALICE3/IOTOF/workflow/src/RecoWorkflow.cxx b/Detectors/Upgrades/ALICE3/IOTOF/workflow/src/RecoWorkflow.cxx index b9be173842d7d..70710ee5519f1 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/workflow/src/RecoWorkflow.cxx +++ b/Detectors/Upgrades/ALICE3/IOTOF/workflow/src/RecoWorkflow.cxx @@ -11,6 +11,8 @@ #include "IOTOFWorkflow/RecoWorkflow.h" #include "IOTOFWorkflow/DigitReaderSpec.h" +#include "IOTOFWorkflow/ClustererSpec.h" +#include "IOTOFWorkflow/ClusterWriterSpec.h" #include "Framework/CCDBParamSpec.h" #include @@ -30,11 +32,11 @@ framework::WorkflowSpec getWorkflow(bool useMC, specs.emplace_back(o2::iotof::getIOTOFDigitReaderSpec(useMC, false, "tf3digits.root")); } if (!upstreamClusters) { - // specs.emplace_back(o2::iotof::getClustererSpec(useMC)); + specs.emplace_back(o2::iotof::getIOTOFClustererSpec(useMC)); } if (!disableRootOutput) { - // specs.emplace_back(o2::iotof::getClusterWriterSpec(useMC)); + specs.emplace_back(o2::iotof::getIOTOFClusterWriterSpec(useMC, false)); } return specs; diff --git a/Detectors/Upgrades/ALICE3/IOTOF/workflow/src/iotof-reco-workflow.cxx b/Detectors/Upgrades/ALICE3/IOTOF/workflow/src/iotof-reco-workflow.cxx index 0988262efe538..dad3f1ce07874 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/workflow/src/iotof-reco-workflow.cxx +++ b/Detectors/Upgrades/ALICE3/IOTOF/workflow/src/iotof-reco-workflow.cxx @@ -22,8 +22,6 @@ #include "IOTOFWorkflow/RecoWorkflow.h" #include "CommonUtils/ConfigurableParam.h" -// #include "ITStracking/TrackingConfigParam.h" -// #include "ITStracking/Configuration.h" #include "Framework/CallbacksPolicy.h" #include "Framework/ConfigContext.h" @@ -77,7 +75,7 @@ o2::framework::WorkflowSpec defineDataProcessing(o2::framework::ConfigContext co o2::conf::ConfigurableParam::updateFromString(configcontext.options().get("configKeyValues")); // write the configuration used for the reco workflow - o2::conf::ConfigurableParam::writeINI("o2itsrecoflow_configuration.ini"); + o2::conf::ConfigurableParam::writeINI("o2tf3recoflow_configuration.ini"); return o2::iotof::reco_workflow::getWorkflow(useMC, /*hitRecoConfig,*/ extDigits, extClusters, disableRootOutput /*, useGpuWF, gpuDevice*/); }