diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt index 6f425a623a31eb885bd18164b07c7ce17d798d0b..2841c8f7e2f44a43f9e988a7d8d537694afe5e15 100644 --- a/algo/CMakeLists.txt +++ b/algo/CMakeLists.txt @@ -35,6 +35,7 @@ set(SRCS detectors/much/ReadoutConfig.cxx detectors/much/Unpack.cxx detectors/tof/HitFinder.cxx + detectors/tof/ClusterizerTof.cxx detectors/tof/ReadoutConfig.cxx detectors/tof/Unpack.cxx detectors/bmon/ReadoutConfig.cxx diff --git a/algo/detectors/tof/ClusterizerTof.cxx b/algo/detectors/tof/ClusterizerTof.cxx new file mode 100644 index 0000000000000000000000000000000000000000..7f12af2f7dc79036e9aec95ad723ed2703de136f --- /dev/null +++ b/algo/detectors/tof/ClusterizerTof.cxx @@ -0,0 +1,445 @@ +/* Copyright (C) 2022 Facility for Antiproton and Ion Research in Europe, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Dominik Smith [committer], Pierre-Alain Loizeau */ + +#include "ClusterizerTof.h" + +// TOF Classes and includes +#include "CbmTofDigi.h" + +// C++ Classes and includes +#include <algorithm> +#include <iomanip> +#include <iostream> +#include <map> + +namespace cbm::algo +{ + + ClusterizerTof::resultType ClusterizerTof::operator()(inputType digisIn) + { + //std::vector<inputType> input = calibrateDigis(digisIn); + std::vector<inputType> input = chanSortDigis(digisIn); + return buildClusters(input); + } + + std::vector<ClusterizerTof::inputType> ClusterizerTof::chanSortDigis(inputType& digisIn) + { + std::vector<inputType> result(fParams.fChanPar.size()); //[nbCh][nDigis] + + for (size_t iDigi = 0; iDigi < digisIn.size(); iDigi++) { + CbmTofDigi* pDigi = digisIn[iDigi].first; + const int32_t index = digisIn[iDigi].second; + const double chan = pDigi->GetChannel(); + result[chan].push_back(std::make_pair(new CbmTofDigi(*pDigi), index)); + } + return result; + } + + //This calibration cannot reproduce the non RPC-parallel version, as the digi indices + //will be invalidated whenever digis are discarded in the dead-time check. + std::vector<ClusterizerTof::inputType> ClusterizerTof::calibrateDigis(inputType& digisIn) + { + std::vector<inputType> result(fParams.fChanPar.size()); + const int32_t numClWalkBinX = fParams.numClWalkBinX; + const double TOTMax = fParams.TOTMax; + const double TOTMin = fParams.TOTMin; + + // channel deadtime map + std::map<int32_t, double> mChannelDeadTime; + + for (size_t iDigi = 0; iDigi < digisIn.size(); iDigi++) { + CbmTofDigi* pDigi = digisIn[iDigi].first; + const double chan = pDigi->GetChannel(); + const double side = pDigi->GetSide(); + const double type = pDigi->GetType(); + + if (fParams.swapChannelSides && 5 != type && 8 != type) { + pDigi->SetAddress(pDigi->GetSm(), pDigi->GetRpc(), chan, (0 == side) ? 1 : 0, type); + } + + // Check dead time + const int32_t addr = pDigi->GetAddress(); + auto it = mChannelDeadTime.find(addr); + if (it != mChannelDeadTime.end() && pDigi->GetTime() <= it->second) { + it->second = pDigi->GetTime() + fParams.channelDeadtime; + continue; + } + mChannelDeadTime[addr] = pDigi->GetTime() + fParams.channelDeadtime; + + // Create calibrated digi + CbmTofDigi* pCalDigi = new CbmTofDigi(*pDigi); + + // calibrate Digi Time + pCalDigi->SetTime(pCalDigi->GetTime() - fParams.fChanPar[chan].fvCPTOff[side]); + + // subtract Offset + double charge = pCalDigi->GetTot() - fParams.fChanPar[chan].fvCPTotOff[side]; + if (charge < 0.001) charge = 0.001; + + // calibrate Digi ToT + pCalDigi->SetTot(charge * fParams.fChanPar[chan].fvCPTotGain[side]); + + // walk correction + const double chargeBinSize = (TOTMax - TOTMin) / numClWalkBinX; + int32_t iWx = (int32_t)((pCalDigi->GetTot() - TOTMin) / chargeBinSize); + + if (0 > iWx) { iWx = 0; } + if (iWx >= numClWalkBinX) { iWx = numClWalkBinX - 1; } + + std::vector<double>& walk = fParams.fChanPar[chan].fvCPWalk[side]; + const double dDTot = (pCalDigi->GetTot() - TOTMin) / chargeBinSize - (double) iWx - 0.5; + double dWT = walk[iWx]; + + // linear interpolation to next bin //memory leak??? (D.Smith 10.8.23: Clarify this comment!) + if (dDTot > 0) { + if (iWx < numClWalkBinX - 1) { dWT += dDTot * (walk[iWx + 1] - walk[iWx]); } + } + else { + if (0 < iWx) { dWT -= dDTot * (walk[iWx - 1] - walk[iWx]); } + } + pCalDigi->SetTime(pCalDigi->GetTime() - dWT); // calibrate Digi Time + + const int32_t index = digisIn[iDigi].second; + result[chan].push_back(std::make_pair(pCalDigi, index)); + } + + // D.Smith 10.8.23: Sorting below might not be needed. Check with P.A. + /// Sort the buffers of hits due to the time offsets applied + for (size_t chan = 0; chan < fParams.fChanPar.size(); chan++) { + std::sort(result[chan].begin(), result[chan].end(), + [](const std::pair<CbmTofDigi*, int32_t>& a, const std::pair<CbmTofDigi*, int32_t>& b) -> bool { + return a.first->GetTime() < b.first->GetTime(); + }); + } + + return result; + } + + + ClusterizerTof::resultType ClusterizerTof::buildClusters(std::vector<inputType>& input) + { + // Hit variables + TofCluster cluster; + std::vector<TofCluster> clustersOut; + + // Reference cell of a cluster + TofCell* cell = nullptr; + + // Last Channel Temp variables + int32_t lastChan = -1; + double lastPosY = 0.0; + double lastTime = 0.0; + + const size_t numChan = fParams.fChanPar.size(); + + for (int32_t chan = 0; chan < numChan; chan++) { + if (fParams.fDeadStrips & (1 << chan)) { continue; } // skip over dead channels + + inputType& storDigi = input[chan]; + + while (1 < storDigi.size()) { + while (storDigi[0].first->GetSide() == storDigi[1].first->GetSide()) { + // Not one Digi of each end! + uint8_t offset = 0; + + /* D.Smith 14.8.23: This condition is never needed due to time-sorted input (check with PAL and VF) + // use digi that is closer to the last one + if (storDigi.size() > 2 && storDigi[2].first->GetSide() != storDigi[0].first->GetSide() + && storDigi[2].first->GetTime() - storDigi[0].first->GetTime() + <= storDigi[2].first->GetTime() - storDigi[1].first->GetTime()) { + offset++; + } +*/ + storDigi.erase(storDigi.begin() + offset); + if (2 > storDigi.size()) { break; } + } // same condition side end + if (2 > storDigi.size()) { break; } + + // 2 Digis = both sides present + cell = &fParams.fChanPar[chan].cell; + CbmTofDigi* xDigiA = storDigi[0].first; + CbmTofDigi* xDigiB = storDigi[1].first; + + // use local coordinates, (0,0,0) is in the center of counter ? + ROOT::Math::XYZVector pos(((-(double) numChan / 2. + (double) chan) + 0.5) * cell->sizeX, 0., 0.); + + double timeDif = xDigiA->GetTime() - xDigiB->GetTime(); + + pos.SetY(fParams.fSigVel * timeDif * 0.5); // A is the top side, B is the bottom side + if (xDigiA->GetSide() != 1.) { pos.SetY(-pos.Y()); } // B is the bottom side, A is the top side + + while (storDigi.size() > 2 && std::abs(pos.Y()) > cell->sizeY * fParams.fPosYMaxScal) { + + CbmTofDigi* xDigiC = storDigi[2].first; + + double timeDifN = 0; + if (xDigiC->GetSide() == xDigiA->GetSide()) { timeDifN = xDigiC->GetTime() - xDigiB->GetTime(); } + else { + timeDifN = xDigiC->GetTime() - xDigiA->GetTime(); + } + + double posYN = fParams.fSigVel * timeDifN * 0.5; + if (xDigiC->GetSide() != 1.) { posYN *= -1.; } + + if (std::abs(posYN) >= std::abs(pos.Y())) { break; } + pos.SetY(posYN); + + if (xDigiC->GetSide() == xDigiA->GetSide()) { + xDigiA = xDigiC; + storDigi.erase(storDigi.begin()); + } + else { + xDigiB = xDigiC; + + //D.Smith 14.8.23: Shouldn't we erase the "+1" element here instead? + //Doesn't seem to have an effect on the result regardless. + //"+1" would be better for an iterator based implementation. + + //D.Smith 25.8.23: Actually, for some strange reason I don't fully understand, this + //erase operation doesn't matter at all, even though this branch is sometimes called. + //Perhaps this is data dependent? Erasing the zero element also works. This would + //be best for iterators. + storDigi.erase(storDigi.begin() + 2); + } + + } //while loop end + + if (std::abs(pos.Y()) > cell->sizeY * fParams.fPosYMaxScal) { // remove both digis + storDigi.erase(storDigi.begin()); + continue; + } + // The "Strip" time is the mean time between each end + double time = 0.5 * (xDigiA->GetTime() + xDigiB->GetTime()); + + // Weight is the total charge => sum of both ends ToT + double totSum = xDigiA->GetTot() + xDigiB->GetTot(); + + // Now check if a hit/cluster is already started + if (0 < cluster.numChan()) { + // a cluster is already started => check distance in space/time + // For simplicity, just check along strip direction for now + // and break cluster when a not fired strip is found + if (!(std::abs(time - lastTime) < fParams.fdMaxTimeDist && lastChan == chan - 1 + && std::abs(pos.Y() - lastPosY) < fParams.fdMaxSpaceDist)) { + + cluster.normalize(fParams.fTimeRes); + cluster.finalize(*cell, fParams); + clustersOut.push_back(cluster); + cluster.reset(); + } + } + else { + // first fired strip in this RPC + cluster.reset(); + } + cluster.add(pos, time, totSum, totSum, storDigi[0].second, storDigi[1].second); + + storDigi.erase(storDigi.begin()); + storDigi.erase(storDigi.begin()); + + lastChan = chan; + lastPosY = pos.Y(); + lastTime = time; + if (AddNextChan(input, lastChan, cluster, clustersOut)) { cluster.reset(); } + } // while( 1 < storDigi.size() ) + storDigi.clear(); //D.Smith 11.8.23: In rare cases, a single digi remains and is deleted here. + } // for( int32_t chan = 0; chan < iNbCh; chan++ ) + + // Now check if another hit/cluster is started + // and save it if it's the case. + if (0 < cluster.numChan()) { + cluster.normalize(fParams.fTimeRes); + cluster.finalize(*cell, fParams); + clustersOut.push_back(cluster); + } + //std::cout << "hits " << fiNbHits << std::endl; + return clustersOut; + } + + + bool ClusterizerTof::AddNextChan(std::vector<inputType>& input, int32_t lastChan, TofCluster& cluster, + std::vector<TofCluster>& clustersOut) + { + //D.Smith 25.8.23: Why are "C" digis (position "2") not considered here? + + //const int32_t iDetId = CbmTofAddress::GetUniqueAddress(iSm, iRpc, 0, 0, iSmType); + size_t numChan = fParams.fChanPar.size(); + int32_t chan = lastChan + 1; + inputType& storDigi = input[chan]; + + while (fParams.fDeadStrips & (1 << chan)) { + chan++; + if (chan >= numChan) { return false; } + } + + if (chan == numChan) { return false; } + if (0 == storDigi.size()) { return false; } + + bool addedHit = false; + for (size_t i1 = 0; i1 < storDigi.size() - 1; i1++) { + if (addedHit) { break; } + + size_t i2 = i1; + while (!addedHit && ++i2 < storDigi.size()) { + + const CbmTofDigi* xDigiA = storDigi[i1].first; + const CbmTofDigi* xDigiB = storDigi[i2].first; + + if (xDigiA->GetSide() == xDigiB->GetSide()) { continue; } + const double time = 0.5 * (xDigiA->GetTime() + xDigiB->GetTime()); + + if (std::abs(time - cluster.weightedTime / cluster.weightsSum) >= fParams.fdMaxTimeDist) { continue; } + + TofCell* cell = &fParams.fChanPar[chan].cell; + + const double timeDif = xDigiA->GetTime() - xDigiB->GetTime(); + double posY = fParams.fSigVel * timeDif * 0.5; + + if (1 != xDigiA->GetSide()) { posY *= -1.; } + + if (std::abs(posY - cluster.weightedPos.Y() / cluster.weightsSum) >= fParams.fdMaxSpaceDist) { continue; } + + // append digi pair to current cluster + const double posX = ((-(double) numChan / 2. + chan) + 0.5) * cell->sizeX; + const double totSum = xDigiA->GetTot() + xDigiB->GetTot(); + + ROOT::Math::XYZVector pos(posX, posY, 0.); + cluster.add(pos, time, totSum, totSum, storDigi[i1].second, storDigi[i2].second); + + // remove selected digis from pool + storDigi.erase(storDigi.begin() + i2); + storDigi.erase(storDigi.begin() + i1); + + if (AddNextChan(input, chan, cluster, clustersOut)) { + return true; // signal hit was already added + } + addedHit = true; + } + } + + TofCell cell = fParams.fChanPar[0].cell; //D.Smith 17.8.23: This is equivalent to using iDetId, see below + //D.Smith 10.8.23: Why pass iDetId here and not iChId? + cluster.normalize(fParams.fTimeRes); + cluster.finalize(cell, fParams); + clustersOut.push_back(cluster); + return true; + } + + + /* + ClusterizerTof::resultType ClusterizerTof::buildClusters(inputType& input) + { + // Intermediate storage variables + std::vector<std::vector<CbmTofDigi*>>& digisExp = input.first; //[nbCh][nDigis] + std::vector<std::vector<int32_t>>& digisInd = input.second; //[nbCh][nDigis] + + // Hit variables + TofCluster cluster; + std::vector<TofCluster> clustersOut; + + // Reference cell of a cluster + TofCell* trafoCell = nullptr; + int32_t iTrafoCell = -1; + + // Last Channel Temp variables + int32_t lastChan = -1; + double lastPosY = 0.0; + double lastTime = 0.0; + + //reset counter + numSameSide = 0; + + for (int32_t chan = 0; chan < fParams.fChanPar.size(); chan++) { + + std::vector<CbmTofDigi*>& storDigiExp = digisExp[chan]; + std::vector<int32_t>& storDigiInd = digisInd[chan]; + ClusterizerTofChanPar& chanPar = fParams.fChanPar[chan]; + + auto digiExpIt = storDigiExp.begin(); + auto digiIndIt = storDigiInd.begin(); + + while (1 < std::distance(digiExpIt, storDigiExp.end())) { + while ((*digiExpIt)->GetSide() == (*std::next(digiExpIt, 1))->GetSide()) { + // Not one Digi of each end! + numSameSide++; + digiExpIt++; + digiIndIt++; + if (2 > std::distance(digiExpIt, storDigiExp.end())) break; + } + if (2 > std::distance(digiExpIt, storDigiExp.end())) break; // 2 Digis = both sides present + + TofCell* channelInfo = &chanPar.cell; + CbmTofDigi* xDigiA = *digiExpIt; + CbmTofDigi* xDigiB = *std::next(digiExpIt, 1); + + // The "Strip" time is the mean time between each end + const double time = 0.5 * (xDigiA->GetTime() + xDigiB->GetTime()); + + // Weight is the total charge => sum of both ends ToT + const double totSum = xDigiA->GetTot() + xDigiB->GetTot(); + + if (nullptr == trafoCell) { + trafoCell = channelInfo; + iTrafoCell = chan; + } + + // switch to local coordinates, (0,0,0) is in the center of counter ? + // It is assumed that the cell size is the same for all channels within one rpc! + ROOT::Math::XYZVector pos(((double) (-iTrafoCell + chan)) * trafoCell->sizeX, 0., 0.); + + if (1 == xDigiA->GetSide()) { + // 0 is the top side, 1 is the bottom side + pos.SetY(fParams.CPSigPropSpeed * (xDigiA->GetTime() - xDigiB->GetTime()) / 2.0); + } + else { + // 0 is the bottom side, 1 is the top side + pos.SetY(fParams.CPSigPropSpeed * (xDigiB->GetTime() - xDigiA->GetTime()) / 2.0); + } + + if (channelInfo->sizeY / 2.0 < pos.Y() || -1 * channelInfo->sizeY / 2.0 > pos.Y()) { + // if outside of strip limits, the pair is bad => try to remove one end and check the next pair + // (if another possibility exist) + digiExpIt++; + digiIndIt++; + continue; + } // Pair leads to hit oustide of strip limits + + // Now check if a hit/cluster is already started + if (0 < cluster.numChan()) { + // a cluster is already started => check distance in space/time + // For simplicity, just check along strip direction for now + // and break cluster when a not fired strip is found + if (!(std::abs(time - lastTime) < fParams.maxTimeDist && lastChan == chan - 1 + && std::abs(pos.Y() - lastPosY) < fParams.maxSpaceDist)) { + // simple error scaling with TOT + // weightedTimeErr = TMath::Sqrt( weightedTimeErr ) * SysTimeRes / weightsSum; + // OR harcoded value: weightedTimeErr = SysTimeRes; + cluster.normalize(fParams.SysTimeRes); + + // Save Hit and start a new one + cluster.finalize(*trafoCell, iTrafoCell, fParams); + clustersOut.push_back(cluster); + cluster.reset(); + } + } + cluster.add(pos, time, totSum, totSum, *digiIndIt, *std::next(digiIndIt, 1)); + digiExpIt += 2; + digiIndIt += 2; + + lastChan = chan; + lastPosY = pos.Y(); + lastTime = time; + } // while (1 < storDigiExp.size()) + } // for( int32_t chan = 0; chan < iNbCh; chan++ ) + + // Save last cluster if it exists + if (0 < cluster.numChan()) { + cluster.normalize(fParams.SysTimeRes); + cluster.finalize(*trafoCell, iTrafoCell, fParams); + clustersOut.push_back(cluster); + } + return clustersOut; + } +*/ +} /* namespace cbm::algo */ diff --git a/algo/detectors/tof/ClusterizerTof.h b/algo/detectors/tof/ClusterizerTof.h new file mode 100644 index 0000000000000000000000000000000000000000..33f254da5c019cf974f2dc207c271723c1bcd78d --- /dev/null +++ b/algo/detectors/tof/ClusterizerTof.h @@ -0,0 +1,194 @@ +/* Copyright (C) 2022 Facility for Antiproton and Ion Research in Europe, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Dominik Smith [committer], Pierre-Alain Loizeau */ + +/* + This algo was based on CbmTofSimpClusterizer, which can be used only for simulation of the main setup and + is as the name implies a simplified solution. + A later step will be the replacement with a version based on CbmTofEventClusterizer, which is the version + currently maintained, based on what we learned from real data at mCBM. + This step will be required to apply the algo to real/online data and to prepare + our simulations for first CBM beam +*/ + +#ifndef CLUSTERIZERTOF_H +#define CLUSTERIZERTOF_H + +// TOF Classes and includes +class CbmTofDigi; + +// ROOT Classes and includes +#include "Math/Rotation3D.h" +#include "Math/Vector3Dfwd.h" + +// C++ Classes and includes +#include <memory> +#include <vector> + +#include <cmath> + +namespace cbm::algo +{ + struct TofCell { + double sizeX, sizeY; + ROOT::Math::XYZVector pos; + ROOT::Math::Rotation3D rotation; + }; + + struct ClusterizerTofChanPar { + std::vector<double> fvCPTOff; //[nbSide] + std::vector<double> fvCPTotGain; //[nbSide] + std::vector<double> fvCPTotOff; //[nbSide] + std::vector<std::vector<double>> fvCPWalk; //[nbSide][nbWalkBins] + int32_t address; //unique address + TofCell cell; + }; + + struct ClusterizerTofRpcPar { + uint32_t fDeadStrips; + double fPosYMaxScal; + double fdMaxTimeDist; + double fdMaxSpaceDist; + double fSigVel; + double fTimeRes; + int32_t numClWalkBinX; + double TOTMax; + double TOTMin; + bool swapChannelSides; + double channelDeadtime; + double fCPTOffYBinWidth; + double fCPTOffYRange; + std::vector<double> fCPTOffY; //[nBin] + std::vector<ClusterizerTofChanPar> fChanPar = {}; + }; + + struct TofCluster { + //temporary values + std::vector<int32_t> vDigiIndRef; + ROOT::Math::XYZVector weightedPos = ROOT::Math::XYZVector(0.0, 0.0, 0.0); + double weightedTime = 0.0; + double weightedTimeErr = 0.0; + double weightsSum = 0.0; + + //after finalization + ROOT::Math::XYZVector globalPos = ROOT::Math::XYZVector(0.0, 0.0, 0.0); + ROOT::Math::XYZVector globalErr = ROOT::Math::XYZVector(0.0, 0.0, 0.0); + int32_t channel = 0; + int32_t detId = 0; + + int32_t numChan() { return vDigiIndRef.size() / 2; } + + void reset() + { + vDigiIndRef.clear(); + weightedPos = ROOT::Math::XYZVector(0.0, 0.0, 0.0); + globalPos = ROOT::Math::XYZVector(0.0, 0.0, 0.0); + globalErr = ROOT::Math::XYZVector(0.5, 0.5, 0.5); //D.Smith 15.8.23: Set this back to zero eventually. + weightedTime = 0.0; + weightedTimeErr = 0.0; + weightsSum = 0.0; + channel = 0; + detId = 0; + } + + void add(ROOT::Math::XYZVector pos, double time, double timeErr, double weight, int32_t digiIndA, int32_t digiIndB) + { + vDigiIndRef.push_back(digiIndA); + vDigiIndRef.push_back(digiIndB); + weightedPos += pos * weight; + weightedTime += time * weight; + weightedTimeErr += timeErr * weight; + weightsSum += weight; + } + + void normalize(double timeErr) + { + // a/=b is translated to a := a*(1/b) in the ROOT::Math::XYZVector class, which has a different + // rounding behavior than a := (a/b). In rare cases this leads to 1.000... becoming 0.999... inside + // the floor() operation in the finalize() function of this class, and results in a different + // channel being associated with the cluster. To reproduce the output of the old hit finder, we + // divide element-wise instead. Perhaps floor() should be replaced by round(). + //////// weightedPos /= weightsSum; + + weightedPos.SetXYZ(weightedPos.X() / weightsSum, weightedPos.Y() / weightsSum, weightedPos.Z() / weightsSum); + weightedTime /= weightsSum; + weightedTimeErr = timeErr; + } + + void finalize(const TofCell& trafoCell, const ClusterizerTofRpcPar& par) + { + // prepare local->global trafo + ROOT::Math::Rotation3D rotMatrix = trafoCell.rotation; + + // get offset from weighted cluster position by rotation to master frame + ROOT::Math::XYZVector hitposOffset = rotMatrix(weightedPos); + + //get hit position by adding offset to cell coordinates + globalPos = trafoCell.pos + hitposOffset; + + // Simple errors, not properly done at all for now + // Right way of doing it should take into account the weight distribution + // and real system time resolution + ROOT::Math::XYZVector hitErrLocal(0.5, 0.5, 0.5); + + //store results + //globalErr = rotMatrix(hitErrLocal); + globalErr = hitErrLocal; + channel = par.fChanPar.size() / 2 + floor(weightedPos.X() / trafoCell.sizeX); + + // D.Smith 15.8.23: This channel correction doesn't seem to be needed + // if (channel < 0) channel = 0; + // if (channel > par.fChanPar.size() - 1) channel = par.fChanPar.size() - 1; + detId = par.fChanPar[channel].address; + + //Calibrate hit time if applicable + if (par.fCPTOffYRange == 0) { return; } + const double dBin = (weightedPos.Y() + par.fCPTOffYRange) / par.fCPTOffYBinWidth; + const int i1 = floor(dBin); + const int i2 = ceil(dBin); + weightedTime -= par.fCPTOffY[i1] + (dBin - i1) * (par.fCPTOffY[i2] - par.fCPTOffY[i1]); + } + }; + + class ClusterizerTof { + public: + typedef std::vector<TofCluster> resultType; + typedef std::vector<std::pair<CbmTofDigi*, int32_t>> inputType; + + /** + ** @brief Constructor. + **/ + ClusterizerTof() {}; + + /** + ** @brief Destructor. + **/ + ~ClusterizerTof() {}; + + /** + ** @brief Build clusters out of ToF Digis and store the resulting info in a TofHit. + **/ + resultType operator()(inputType digisIn); + + /** @brief Set the parameter container + ** @param params Vectorer to parameter container + **/ + void SetParams(std::unique_ptr<ClusterizerTofRpcPar> params) { fParams = *(std::move(params)); } + + private: + ClusterizerTofRpcPar fParams = {}; ///< Parameter container + + std::vector<inputType> calibrateDigis(inputType& digisIn); + std::vector<inputType> chanSortDigis(inputType& digisIn); + + resultType buildClusters(std::vector<inputType>& input); + + bool AddNextChan(std::vector<inputType>& input, int32_t iLastChan, TofCluster& cluster, + std::vector<TofCluster>& clustersOut); + + int32_t numSameSide; // Digis quality + }; + +} /* namespace cbm::algo */ + +#endif // CLUSTERIZERTOF_H diff --git a/reco/tasks/CMakeLists.txt b/reco/tasks/CMakeLists.txt index 4743544232b5b10e28bb4d63ff4421a13bdbc5a6..300bf6ed0f3ef5dbf2a93cc3d079302550ce083f 100644 --- a/reco/tasks/CMakeLists.txt +++ b/reco/tasks/CMakeLists.txt @@ -15,6 +15,7 @@ set(SRCS CbmTaskMakeRecoEvents.cxx CbmTaskTriggerDigi.cxx CbmTaskTofHitFinder.cxx + CbmTaskTofClusterizer.cxx CbmTaskUnpack.cxx CbmTaskUnpackXpu.cxx ) diff --git a/reco/tasks/CbmRecoTasksLinkDef.h b/reco/tasks/CbmRecoTasksLinkDef.h index 16e011359c06c17317bcf9b373304d58bec362f6..8548dc9c4608bf4df73d7b4026568537187bffc5 100644 --- a/reco/tasks/CbmRecoTasksLinkDef.h +++ b/reco/tasks/CbmRecoTasksLinkDef.h @@ -18,6 +18,7 @@ #pragma link C++ class CbmTaskEventsCloneInToOut + ; #pragma link C++ class CbmTaskMakeRecoEvents + ; #pragma link C++ class CbmTaskTofHitFinder + ; +#pragma link C++ class CbmTaskTofClusterizer + ; #pragma link C++ class CbmTaskTriggerDigi + ; #pragma link C++ class CbmTaskUnpack + ; #pragma link C++ class CbmTaskUnpackXpu + ; diff --git a/reco/tasks/CbmTaskTofClusterizer.cxx b/reco/tasks/CbmTaskTofClusterizer.cxx new file mode 100644 index 0000000000000000000000000000000000000000..7e90676aeb616eea7f229eb15efc6219098d6238 --- /dev/null +++ b/reco/tasks/CbmTaskTofClusterizer.cxx @@ -0,0 +1,1082 @@ +/* Copyright (C) 2022 Facility for Antiproton and Ion Research in Europe, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Dominik Smith [committer], Pierre-Alain Loizeau, Norbert Herrmann */ + +#include "CbmTaskTofClusterizer.h" + +// TOF Classes and includes +#include "CbmBmonDigi.h" // in cbmdata/bmon +#include "CbmDigiManager.h" +#include "CbmEvent.h" +#include "CbmMatch.h" +#include "CbmTofAddress.h" // in cbmdata/tof +#include "CbmTofCell.h" // in tof/TofData +#include "CbmTofCreateDigiPar.h" // in tof/TofTools +#include "CbmTofDetectorId_v12b.h" // in cbmdata/tof +#include "CbmTofDetectorId_v14a.h" // in cbmdata/tof +#include "CbmTofDetectorId_v21a.h" // in cbmdata/tof +#include "CbmTofDigi.h" // in cbmdata/tof +#include "CbmTofDigiBdfPar.h" // in tof/TofParam +#include "CbmTofDigiPar.h" // in tof/TofParam +#include "CbmTofGeoHandler.h" // in tof/TofTools +#include "CbmTofHit.h" // in cbmdata/tof +#include "CbmTsEventHeader.h" + +// FAIR classes and includes +#include "FairRootFileSink.h" +#include "FairRootManager.h" +#include "FairRunAna.h" +#include "FairRuntimeDb.h" +#include <Logger.h> + +// ROOT Classes and includes +#include "TClonesArray.h" +#include "TGeoManager.h" +#include "TGeoPhysicalNode.h" +#include "TH2.h" +#include "TProfile.h" +#include "TROOT.h" +#include "TStopwatch.h" +#include "TVector3.h" + +// C++ Classes and includes +#include <iomanip> +#include <vector> + +// Globals + + +/************************************************************************************/ +CbmTaskTofClusterizer::CbmTaskTofClusterizer() : CbmTaskTofClusterizer("TestbeamClusterizer", 0, 0) {} + +CbmTaskTofClusterizer::CbmTaskTofClusterizer(const char* name, int32_t verbose, bool writeDataInOut) + : FairTask(TString(name), verbose) + , fTofId(NULL) + , fDigiPar(NULL) + , fDigiBdfPar(NULL) + , fTsHeader(NULL) + , fDigiMan(nullptr) + , fEventsColl(nullptr) + , fbWriteHitsInOut(writeDataInOut) + , fbWriteDigisInOut(writeDataInOut) + , fTofHitsColl(NULL) + , fTofDigiMatchColl(NULL) + , fTofHitsCollOut(NULL) + , fTofDigiMatchCollOut(NULL) + , fiNbHits(0) + , fStorDigi() + , fvCPTOff() + , fvCPTotGain() + , fvCPTotOff() + , fvCPWalk() + , fvCPTOffY() + , fvCPTOffYBinWidth() + , fvCPTOffYRange() + , fvDeadStrips() + , fCalMode(0) + , fDutId(-1) + , fPosYMaxScal(1.) + , fTotMax(100.) + , fTotMin(0.) + , fTotOff(0.) + , fTotMean(0.) + , fMaxTimeDist(1.) + , fdChannelDeadtime(0.) + , fCalParFileName("") + , fdTOTMax(50.) + , fdTOTMin(0.) + , fdTTotMean(2.) + , fdMaxTimeDist(0.) + , fdMaxSpaceDist(0.) + , fdModifySigvel(1.0) + , fdEvent(0) + , fbSwapChannelSides(false) + , fiOutputTreeEntry(0) + , fiFileIndex(0) +{ +} + +CbmTaskTofClusterizer::~CbmTaskTofClusterizer() {} + +/************************************************************************************/ +// FairTasks inherited functions +InitStatus CbmTaskTofClusterizer::Init() +{ + LOG(info) << "CbmTaskTofClusterizer initializing... expect Digis in ns units! "; + if (false == RegisterInputs()) return kFATAL; + if (false == RegisterOutputs()) return kFATAL; + if (false == InitParameters()) return kFATAL; + if (false == InitCalibParameter()) return kFATAL; + if (false == InitAlgos()) return kFATAL; + return kSUCCESS; +} + +void CbmTaskTofClusterizer::SetParContainers() +{ + LOG(info) << "=> Get the digi parameters for tof"; + FairRunAna* ana = FairRunAna::Instance(); + FairRuntimeDb* rtdb = ana->GetRuntimeDb(); + + fDigiPar = (CbmTofDigiPar*) (rtdb->getContainer("CbmTofDigiPar")); + + LOG(info) << "found " << fDigiPar->GetNrOfModules() << " cells "; + fDigiBdfPar = (CbmTofDigiBdfPar*) (rtdb->getContainer("CbmTofDigiBdfPar")); +} + +void CbmTaskTofClusterizer::Exec(Option_t* option) +{ + if (fTofCalDigiVecOut) fTofCalDigiVecOut->clear(); + if (fEventsColl) { + if (NULL != fTsHeader) + LOG(info) << "New Ts " << iNbTs << ", size " << fEventsColl->GetSize() << " at " << fTsHeader->GetTsStartTime() + << " with " << fEventsColl->GetEntriesFast() << " events, " << fDigiMan->GetNofDigis(ECbmModuleId::kTof) + << " TOF digis + " << fDigiMan->GetNofDigis(ECbmModuleId::kT0) << " T0 digis "; + + TStopwatch timerTs; + timerTs.Start(); + + iNbTs++; + fiHitStart = 0; + int32_t iNbHits = 0; + int32_t iNbCalDigis = 0; + fTofDigiMatchCollOut->Delete(); // costly, FIXME + fTofHitsCollOut->Delete(); // costly, FIXME + + for (int32_t iEvent = 0; iEvent < fEventsColl->GetEntriesFast(); iEvent++) { + CbmEvent* tEvent = dynamic_cast<CbmEvent*>(fEventsColl->At(iEvent)); + fTofDigiVec.clear(); + LOG(debug) << "TS event " << iEvent << " with " << tEvent->GetNofData(ECbmDataType::kT0Digi) << " T0 and " + << tEvent->GetNofData(ECbmDataType::kTofDigi) << " Tof digis "; + + for (int32_t iDigi = 0; iDigi < tEvent->GetNofData(ECbmDataType::kT0Digi); iDigi++) { + int32_t iDigiIndex = static_cast<int32_t>(tEvent->GetIndex(ECbmDataType::kT0Digi, iDigi)); + CbmTofDigi tDigi(fDigiMan->Get<CbmBmonDigi>(iDigiIndex)); + if (tDigi.GetType() != 5) { + tDigi.SetAddress(0, 0, 0, 0, 5); // convert to Tof Address + } + if (tDigi.GetSide() == 1) { // HACK for May22 setup + tDigi.SetAddress(tDigi.GetSm(), tDigi.GetRpc(), tDigi.GetChannel() + 6, 0, tDigi.GetType()); + } + fTofDigiVec.push_back(tDigi); + } + for (int32_t iDigi = 0; iDigi < tEvent->GetNofData(ECbmDataType::kTofDigi); iDigi++) { + int32_t iDigiIndex = static_cast<int32_t>(tEvent->GetIndex(ECbmDataType::kTofDigi, iDigi)); + const CbmTofDigi* tDigi = fDigiMan->Get<CbmTofDigi>(iDigiIndex); + fTofDigiVec.push_back(CbmTofDigi(*tDigi)); + } + + ExecEvent(option, tEvent); + + // --- In event-by-event mode: copy caldigis, hits and matches to output array and register them to event + uint uDigi0 = fTofCalDigiVecOut->size(); //starting index of current event + LOG(debug) << "Append " << fTofCalDigiVec->size() << " CalDigis to event " << tEvent->GetNumber(); + for (uint32_t index = 0; index < fTofCalDigiVec->size(); index++) { + CbmTofDigi* tDigi = &(fTofCalDigiVec->at(index)); + tEvent->AddData(ECbmDataType::kTofCalDigi, iNbCalDigis); + fTofCalDigiVecOut->push_back(CbmTofDigi(*tDigi)); + iNbCalDigis++; + } + + int iHit0 = iNbHits; //starting index of current event + LOG(debug) << "Assign " << fTofHitsColl->GetEntriesFast() << " hits to event " << tEvent->GetNumber(); + for (int32_t index = 0; index < fTofHitsColl->GetEntriesFast(); index++) { + CbmTofHit* pHit = (CbmTofHit*) fTofHitsColl->At(index); + new ((*fTofHitsCollOut)[iNbHits]) CbmTofHit(*pHit); + tEvent->AddData(ECbmDataType::kTofHit, iNbHits); + + CbmMatch* pDigiMatch = (CbmMatch*) fTofDigiMatchColl->At(index); + + // update content of match object to TS array + CbmMatch* mDigiMatch = new CbmMatch(); + for (int32_t iLink = 0; iLink < pDigiMatch->GetNofLinks(); iLink++) { + CbmLink Link = pDigiMatch->GetLink(iLink); + Link.SetIndex(Link.GetIndex() + uDigi0); + mDigiMatch->AddLink(Link); + } + + new ((*fTofDigiMatchCollOut)[iNbHits]) CbmMatch(*mDigiMatch); + delete mDigiMatch; + iNbHits++; + } + + fiHitStart += iNbHits - iHit0; + fTofDigiVec.clear(); + fTofCalDigiVec->clear(); + fTofHitsColl->Delete(); //Clear("C"); + fTofDigiMatchColl->Delete(); //Clear("C"); + } + + /// PAL: add TS statistics for monitoring and perf evaluation + timerTs.Stop(); + LOG(debug) << GetName() << "::Exec: real time=" << timerTs.RealTime() << " CPU time=" << timerTs.CpuTime(); + fProcessTime += timerTs.RealTime(); + fuNbDigis += fDigiMan->GetNofDigis(ECbmModuleId::kTof) // TOF + + fDigiMan->GetNofDigis(ECbmModuleId::kT0); // T0 + fuNbHits += fiHitStart; + + std::stringstream logOut; + logOut << std::setw(20) << std::left << GetName() << " ["; + logOut << std::fixed << std::setw(8) << std::setprecision(1) << std::right << timerTs.RealTime() * 1000. << " ms] "; + logOut << "TS " << iNbTs; + logOut << ", events " << fEventsColl->GetEntriesFast(); + logOut << ", hits " << fiHitStart << ", time/1k-hit " << std::setprecision(4) + << timerTs.RealTime() * 1e6 / fiHitStart << " [ms]"; + LOG(info) << logOut.str(); + } + else { + // fTofDigisColl=fTofRawDigisColl; + // (VF) This does not work here. The digi manager does not foresee to add + // new data to the input array. So, I here copy the input digis into + // the array fTofDigisColl. Not very efficient, but temporary only, until + // also the internal data representations are changed to std::vectors. + + fTofDigiVec.clear(); + if (NULL != fT0DigiVec) { // 2022 data + for (int32_t iDigi = 0; iDigi < (int) (fT0DigiVec->size()); iDigi++) { + CbmTofDigi tDigi = fT0DigiVec->at(iDigi); + if (tDigi.GetType() != 5) + LOG(fatal) << "Wrong T0 type " << tDigi.GetType() << ", Addr 0x" << std::hex << tDigi.GetAddress(); + if (tDigi.GetSide() == 1) { // HACK for May22 setup + tDigi.SetAddress(tDigi.GetSm(), tDigi.GetRpc(), tDigi.GetChannel() + 6, 0, tDigi.GetType()); + } + fTofDigiVec.push_back(CbmTofDigi(tDigi)); + } + } + for (int32_t iDigi = 0; iDigi < fDigiMan->GetNofDigis(ECbmModuleId::kTof); iDigi++) { + const CbmTofDigi* tDigi = fDigiMan->Get<CbmTofDigi>(iDigi); + fTofDigiVec.push_back(CbmTofDigi(*tDigi)); + + if (iDigi == 10000) break; // Only use 10000 digis for now. D.Smith + } + ExecEvent(option); + } +} + +void CbmTaskTofClusterizer::ExecEvent(Option_t* /*option*/, CbmEvent* tEvent) +{ + // Clear output arrays + fTofCalDigiVec->clear(); + fTofHitsColl->Delete(); // Computationally costly!, but hopefully safe + fTofDigiMatchColl->Delete(); + FairRootFileSink* sink = (FairRootFileSink*) FairRootManager::Instance()->GetSink(); + if (sink) { fiOutputTreeEntry = sink->GetOutTree()->GetEntries(); } + + // Check for corruption (D.Smith: disabled ) + // if (fTofDigiVec.size() > 20. * fDigiBdfPar->GetNbDet()) { // FIXME constant in code, skip this event + // LOG(warn) << "Skip processing of Tof DigiEvent with " << fTofDigiVec.size() << " digis "; + // return; + // } + + fiNbHits = 0; + + BuildClusters(); +} + +/************************************************************************************/ +void CbmTaskTofClusterizer::Finish() +{ + LOG(info) << "Finish with " << fdEvent << " processed events"; + + /// PAL: add run statistics for monitoring and perf evaluation + LOG(info) << "====================================="; + LOG(info) << GetName() << ": Finish run"; + LOG(info) << GetName() << ": Run summary "; + LOG(info) << GetName() << ": Processing time : " << std::fixed << std::setprecision(3) << fProcessTime; + LOG(info) << GetName() << ": Nr of events : " << fdEvent; + LOG(info) << GetName() << ": Nr of input digis : " << fuNbDigis; + LOG(info) << GetName() << ": Nr of produced hits : " << fuNbHits; + LOG(info) << GetName() << ": Nr of hits / event : " << std::fixed << std::setprecision(2) + << (fdEvent > 0 ? fuNbHits / fdEvent : 0); + LOG(info) << GetName() << ": Nr of hits / digis : " << std::fixed << std::setprecision(2) + << (fuNbDigis > 0 ? fuNbHits / (double) fuNbDigis : 0); + LOG(info) << "====================================="; +} + +void CbmTaskTofClusterizer::Finish(double calMode) +{ + SetCalMode(calMode); + Finish(); +} + +/************************************************************************************/ +// Functions common for all clusters approximations +bool CbmTaskTofClusterizer::RegisterInputs() +{ + FairRootManager* fManager = FairRootManager::Instance(); + + if (NULL == fManager) { + LOG(error) << "CbmTaskTofClusterizer::RegisterInputs => Could not find " + "FairRootManager!!!"; + return false; + } + + fEventsColl = dynamic_cast<TClonesArray*>(fManager->GetObject("Event")); + if (NULL == fEventsColl) fEventsColl = dynamic_cast<TClonesArray*>(fManager->GetObject("CbmEvent")); + + if (NULL == fEventsColl) LOG(info) << "CbmEvent not found in input file, assume eventwise input"; + + fDigiMan = CbmDigiManager::Instance(); + fDigiMan->Init(); + if (!fDigiMan->IsPresent(ECbmModuleId::kTof)) { + LOG(error) << GetName() << ": No Tof digi input!"; + return false; + } + if (fDigiMan->IsPresent(ECbmModuleId::kT0)) { + LOG(info) << GetName() << ": separate T0 digi input!"; + //fT0DigiVec = fManager->InitObjectAs<std::vector<CbmTofDigi> const*>("TzdDigi"); + } + else { + LOG(info) << "No separate T0 digi input found."; + } // if( ! fT0DigiVec ) + + fTsHeader = fManager->InitObjectAs<CbmTsEventHeader const*>("EventHeader."); //for data + if (NULL == fTsHeader) { LOG(info) << "CbmTaskTofClusterizer::RegisterInputs => Could not get TsHeader Object"; } + + return true; +} +bool CbmTaskTofClusterizer::RegisterOutputs() +{ + FairRootManager* rootMgr = FairRootManager::Instance(); + rootMgr->InitSink(); + + fTofCalDigiVec = new std::vector<CbmTofDigi>(); + fTofHitsColl = new TClonesArray("CbmTofHit", 100); + fTofDigiMatchColl = new TClonesArray("CbmMatch", 100); + + if (NULL == fEventsColl) { + // Flag check to control whether digis are written in output root file + rootMgr->RegisterAny("TofCalDigi", fTofCalDigiVec, fbWriteDigisInOut); + + // Flag check to control whether digis are written in output root file + rootMgr->Register("TofHit", "Tof", fTofHitsColl, fbWriteHitsInOut); + fTofHitsCollOut = fTofHitsColl; + + rootMgr->Register("TofHitCalDigiMatch", "Tof", fTofDigiMatchColl, fbWriteHitsInOut); + LOG(info) << Form("Register DigiMatch in TS mode at %p with %d", fTofDigiMatchColl, (int) fbWriteHitsInOut); + fTofDigiMatchCollOut = fTofDigiMatchColl; + } + else { // CbmEvent - mode + fTofCalDigiVecOut = new std::vector<CbmTofDigi>(); + fTofHitsCollOut = new TClonesArray("CbmTofHit", 10000); + fTofDigiMatchCollOut = new TClonesArray("CbmMatch", 10000); + + rootMgr->RegisterAny("TofCalDigi", fTofCalDigiVecOut, fbWriteDigisInOut); + rootMgr->Register("TofHit", "Tof", fTofHitsCollOut, fbWriteHitsInOut); + rootMgr->Register("TofHitCalDigiMatch", "Tof", fTofDigiMatchCollOut, fbWriteHitsInOut); + LOG(info) << Form("Register DigiMatch in event mode at %p with %d ", fTofDigiMatchCollOut, (int) fbWriteHitsInOut); + } + return true; +} +bool CbmTaskTofClusterizer::InitParameters() +{ + + // Initialize the TOF GeoHandler + bool isSimulation = false; + LOG(info) << "CbmTaskTofClusterizer::InitParameters - Geometry, Mapping, ... ??"; + + // Get Base Container + FairRun* ana = FairRun::Instance(); + FairRuntimeDb* rtdb = ana->GetRuntimeDb(); + + CbmTofGeoHandler geoHandler; + int32_t iGeoVersion = geoHandler.Init(isSimulation); + if (k14a > iGeoVersion) { + LOG(error) << "CbmTaskTofClusterizer::InitParameters => Only compatible " + "with geometries after v14a !!!"; + return false; + } + if (iGeoVersion == k14a) fTofId = new CbmTofDetectorId_v14a(); + else + fTofId = new CbmTofDetectorId_v21a(); + + // create digitization parameters from geometry file + CbmTofCreateDigiPar* tofDigiPar = new CbmTofCreateDigiPar("TOF Digi Producer", "TOF task"); + LOG(info) << "Create DigiPar "; + tofDigiPar->Init(); + + fDigiPar = (CbmTofDigiPar*) (rtdb->getContainer("CbmTofDigiPar")); + if (0 == fDigiPar) { + LOG(error) << "CbmTaskTofClusterizer::InitParameters => Could not obtain " + "the CbmTofDigiPar "; + return false; + } + + fDigiBdfPar = (CbmTofDigiBdfPar*) (rtdb->getContainer("CbmTofDigiBdfPar")); + if (0 == fDigiBdfPar) { + LOG(error) << "CbmTaskTofClusterizer::InitParameters => Could not obtain " + "the CbmTofDigiBdfPar "; + return false; + } + + rtdb->initContainers(ana->GetRunId()); + + LOG(info) << "CbmTaskTofClusterizer::InitParameter: currently " << fDigiPar->GetNrOfModules() << " digi cells "; + + fdMaxTimeDist = fDigiBdfPar->GetMaxTimeDist(); // in ns + fdMaxSpaceDist = fDigiBdfPar->GetMaxDistAlongCh(); // in cm + + if (fMaxTimeDist != fdMaxTimeDist) { + fdMaxTimeDist = fMaxTimeDist; // modify default + fdMaxSpaceDist = fdMaxTimeDist * fDigiBdfPar->GetSignalSpeed() + * 0.5; // cut consistently on positions (with default signal velocity) + } + + LOG(info) << " BuildCluster with MaxTimeDist " << fdMaxTimeDist << ", MaxSpaceDist " << fdMaxSpaceDist; + + fvDeadStrips.resize(fDigiBdfPar->GetNbDet()); + return true; +} +/************************************************************************************/ +bool CbmTaskTofClusterizer::InitCalibParameter() +{ + // dimension and initialize calib parameter + int32_t iNbSmTypes = fDigiBdfPar->GetNbSmTypes(); + + if (fTotMean != 0.) fdTTotMean = fTotMean; // adjust target mean for TOT + + fvCPTOff.resize(iNbSmTypes); + fvCPTotGain.resize(iNbSmTypes); + fvCPTotOff.resize(iNbSmTypes); + fvCPWalk.resize(iNbSmTypes); + fvCPTOffY.resize(iNbSmTypes); + fvCPTOffYBinWidth.resize(iNbSmTypes); + fvCPTOffYRange.resize(iNbSmTypes); + for (int32_t iSmType = 0; iSmType < iNbSmTypes; iSmType++) { + int32_t iNbSm = fDigiBdfPar->GetNbSm(iSmType); + int32_t iNbRpc = fDigiBdfPar->GetNbRpc(iSmType); + fvCPTOff[iSmType].resize(iNbSm * iNbRpc); + fvCPTotGain[iSmType].resize(iNbSm * iNbRpc); + fvCPTotOff[iSmType].resize(iNbSm * iNbRpc); + fvCPWalk[iSmType].resize(iNbSm * iNbRpc); + fvCPTOffY[iSmType].resize(iNbSm * iNbRpc); + fvCPTOffYBinWidth[iSmType].resize(iNbSm * iNbRpc); + fvCPTOffYRange[iSmType].resize(iNbSm * iNbRpc); + for (int32_t iSm = 0; iSm < iNbSm; iSm++) { + for (int32_t iRpc = 0; iRpc < iNbRpc; iRpc++) { + + fvCPTOffYBinWidth[iSmType][iSm * iNbRpc + iRpc] = 0.; // initialize + fvCPTOffYRange[iSmType][iSm * iNbRpc + iRpc] = 0.; // initialize + + /* D.Smith 23.8.23: For testing hit time calibration. Please remove when done. + fvCPTOffYBinWidth[iSmType][iSm * iNbRpc + iRpc] = 1.; // initialize + fvCPTOffYRange[iSmType][iSm * iNbRpc + iRpc] = 200.; // initialize + fvCPTOffY[iSmType][iSm * iNbRpc + iRpc] = std::vector<double>(10000, 1000.); + for( size_t i = 0; i < fvCPTOffY[iSmType][iSm * iNbRpc + iRpc].size(); i++ ) + { + fvCPTOffY[iSmType][iSm * iNbRpc + iRpc][i] = 1000.+500.*i; + } +*/ + int32_t iNbChan = fDigiBdfPar->GetNbChan(iSmType, iRpc); + fvCPTOff[iSmType][iSm * iNbRpc + iRpc].resize(iNbChan); + fvCPTotGain[iSmType][iSm * iNbRpc + iRpc].resize(iNbChan); + fvCPTotOff[iSmType][iSm * iNbRpc + iRpc].resize(iNbChan); + fvCPWalk[iSmType][iSm * iNbRpc + iRpc].resize(iNbChan); + int32_t nbSide = 2 - fDigiBdfPar->GetChanType(iSmType, iRpc); + for (int32_t iCh = 0; iCh < iNbChan; iCh++) { + fvCPTOff[iSmType][iSm * iNbRpc + iRpc][iCh].resize(nbSide); + fvCPTotGain[iSmType][iSm * iNbRpc + iRpc][iCh].resize(nbSide); + fvCPTotOff[iSmType][iSm * iNbRpc + iRpc][iCh].resize(nbSide); + fvCPWalk[iSmType][iSm * iNbRpc + iRpc][iCh].resize(nbSide); + for (int32_t iSide = 0; iSide < nbSide; iSide++) { + fvCPTOff[iSmType][iSm * iNbRpc + iRpc][iCh][iSide] = 0.; //initialize + fvCPTotGain[iSmType][iSm * iNbRpc + iRpc][iCh][iSide] = 1.; //initialize + fvCPTotOff[iSmType][iSm * iNbRpc + iRpc][iCh][iSide] = 0.; //initialize + fvCPWalk[iSmType][iSm * iNbRpc + iRpc][iCh][iSide].resize(nbClWalkBinX); + for (int32_t iWx = 0; iWx < nbClWalkBinX; iWx++) { + fvCPWalk[iSmType][iSm * iNbRpc + iRpc][iCh][iSide][iWx] = 0.; + } + } + } + } + } + } + LOG(info) << "CbmTaskTofClusterizer::InitCalibParameter: defaults set"; + + if (fCalMode <= 0) { return true; } // Skip calibration from histograms in mode zero + + /// Save old global file and folder pointer to avoid messing with FairRoot + // <= To prevent histos from being sucked in by the param file of the TRootManager! + TFile* oldFile = gFile; + TDirectory* oldDir = gDirectory; + + LOG(info) << "CbmTaskTofClusterizer::InitCalibParameter: read histos from " + << "file " << fCalParFileName; + + // read parameter from histos + if (fCalParFileName.IsNull()) return true; + + TFile* calParFile = new TFile(fCalParFileName, "READ"); + if (NULL == calParFile) { + if (fCalMode % 10 == 9) { //modify reference file name + int iCalMode = fCalParFileName.Index("tofClust") - 3; + fCalParFileName(iCalMode) = '3'; + LOG(info) << "Modified CalFileName = " << fCalParFileName; + calParFile = new TFile(fCalParFileName, "update"); + if (NULL == calParFile) + LOG(fatal) << "CbmTaskTofClusterizer::InitCalibParameter: " + << "file " << fCalParFileName << " does not exist!"; + + return true; + } + LOG(fatal) << "Calibration parameter file not existing!"; + } + + for (int32_t iSmType = 0; iSmType < iNbSmTypes; iSmType++) { + int32_t iNbSm = fDigiBdfPar->GetNbSm(iSmType); + int32_t iNbRpc = fDigiBdfPar->GetNbRpc(iSmType); + TProfile* hSvel = (TProfile*) gDirectory->FindObjectAny(Form("cl_SmT%01d_Svel", iSmType)); + for (int32_t iSm = 0; iSm < iNbSm; iSm++) + for (int32_t iRpc = 0; iRpc < iNbRpc; iRpc++) { + + std::vector<std::vector<double>>& vCPTotGain = fvCPTotGain[iSmType][iSm * iNbRpc + iRpc]; + std::vector<std::vector<double>>& vCPTOff = fvCPTOff[iSmType][iSm * iNbRpc + iRpc]; + std::vector<std::vector<double>>& vCPTotOff = fvCPTotOff[iSmType][iSm * iNbRpc + iRpc]; + std::vector<std::vector<std::vector<double>>>& vCPWalk = fvCPWalk[iSmType][iSm * iNbRpc + iRpc]; + + std::vector<double>& vCPTOffY = fvCPTOffY[iSmType][iSm * iNbRpc + iRpc]; + double& vCPTOffYBinWidth = fvCPTOffYBinWidth[iSmType][iSm * iNbRpc + iRpc]; + double& vCPTOffYRange = fvCPTOffYRange[iSmType][iSm * iNbRpc + iRpc]; + + // update default parameter + if (NULL != hSvel) { + double Vscal = hSvel->GetBinContent(iSm * iNbRpc + iRpc + 1); + if (Vscal == 0.) Vscal = 1.; + Vscal *= fdModifySigvel; //1.03; // testing the effect of wrong signal velocity, FIXME + fDigiBdfPar->SetSigVel(iSmType, iSm, iRpc, fDigiBdfPar->GetSigVel(iSmType, iSm, iRpc) * Vscal); + LOG(info) << "Modify " << iSmType << iSm << iRpc << " Svel by " << Vscal << " to " + << fDigiBdfPar->GetSigVel(iSmType, iSm, iRpc); + } + TH2D* htempPos_pfx = + (TH2D*) gDirectory->FindObjectAny(Form("cl_CorSmT%01d_sm%03d_rpc%03d_Pos_pfx", iSmType, iSm, iRpc)); + TH2D* htempTOff_pfx = + (TH2D*) gDirectory->FindObjectAny(Form("cl_CorSmT%01d_sm%03d_rpc%03d_TOff_pfx", iSmType, iSm, iRpc)); + TH1D* htempTot_Mean = + (TH1D*) gDirectory->FindObjectAny(Form("cl_CorSmT%01d_sm%03d_rpc%03d_Tot_Mean", iSmType, iSm, iRpc)); + TH1D* htempTot_Off = + (TH1D*) gDirectory->FindObjectAny(Form("cl_CorSmT%01d_sm%03d_rpc%03d_Tot_Off", iSmType, iSm, iRpc)); + if (NULL != htempPos_pfx && NULL != htempTOff_pfx && NULL != htempTot_Mean && NULL != htempTot_Off) { + int32_t iNbCh = fDigiBdfPar->GetNbChan(iSmType, iRpc); + //int32_t iNbinTot = htempTot_Mean->GetNbinsX();//not used any more + for (int32_t iCh = 0; iCh < iNbCh; iCh++) { + for (int32_t iSide = 0; iSide < 2; iSide++) { + double TotMean = htempTot_Mean->GetBinContent(iCh * 2 + 1 + iSide); //nh +1 empirical(?) + if (0.001 < TotMean) { vCPTotGain[iCh][iSide] *= fdTTotMean / TotMean; } + vCPTotOff[iCh][iSide] = htempTot_Off->GetBinContent(iCh * 2 + 1 + iSide); + } + double YMean = ((TProfile*) htempPos_pfx)->GetBinContent(iCh + 1); //nh +1 empirical(?) + double TMean = ((TProfile*) htempTOff_pfx)->GetBinContent(iCh + 1); + if (std::isnan(YMean) || std::isnan(TMean)) { + LOG(warn) << "Invalid calibration for TSRC " << iSmType << iSm << iRpc << iCh << ", use default!"; + continue; + } + double dTYOff = YMean / fDigiBdfPar->GetSigVel(iSmType, iSm, iRpc); + if (5 == iSmType || 8 == iSmType) dTYOff = 0.; // no valid Y positon for pad counters + vCPTOff[iCh][0] += -dTYOff + TMean; + vCPTOff[iCh][1] += +dTYOff + TMean; + + if (5 == iSmType || 8 == iSmType) { // for PAD counters + vCPTOff[iCh][1] = vCPTOff[iCh][0]; + vCPTotGain[iCh][1] = vCPTotGain[iCh][0]; + vCPTotOff[iCh][1] = vCPTotOff[iCh][0]; + } + //if(iSmType==0 && iSm==4 && iRpc==2 && iCh==26) + if (iSmType == 0 && iSm == 2 && iRpc == 0) + //if (iSmType == 9 && iSm == 0 && iRpc == 0 && iCh == 10) // select specific channel + LOG(info) << "InitCalibParameter:" + << " TSRC " << iSmType << iSm << iRpc << iCh + << Form(": YMean %6.3f, TYOff %6.3f, TMean %6.3f", YMean, dTYOff, TMean) << " -> " + << Form(" TOff %f, %f, TotG %f, %f ", vCPTOff[iCh][0], vCPTOff[iCh][1], vCPTotGain[iCh][0], + vCPTotGain[iCh][1]); + + TH1D* htempWalk0 = (TH1D*) gDirectory->FindObjectAny( + Form("Cor_SmT%01d_sm%03d_rpc%03d_Ch%03d_S0_Walk_px", iSmType, iSm, iRpc, iCh)); + TH1D* htempWalk1 = (TH1D*) gDirectory->FindObjectAny( + Form("Cor_SmT%01d_sm%03d_rpc%03d_Ch%03d_S1_Walk_px", iSmType, iSm, iRpc, iCh)); + if (NULL == htempWalk0 && NULL == htempWalk1) { // regenerate Walk histos + int iSide = 0; + htempWalk0 = new TH1D(Form("Cor_SmT%01d_sm%03d_rpc%03d_Ch%03d_S0_Walk_px", iSmType, iSm, iRpc, iCh), + Form("Walk in SmT%01d_sm%03d_rpc%03d_Ch%03d_S%01d_Walk; Tot [a.u.]; #DeltaT [ns]", + iSmType, iSm, iRpc, iCh, iSide), + nbClWalkBinX, fdTOTMin, fdTOTMax); + iSide = 1; + htempWalk1 = new TH1D(Form("Cor_SmT%01d_sm%03d_rpc%03d_Ch%03d_S0_Walk_px", iSmType, iSm, iRpc, iCh), + Form("Walk in SmT%01d_sm%03d_rpc%03d_Ch%03d_S%01d_Walk; Tot [a.u.]; #DeltaT [ns]", + iSmType, iSm, iRpc, iCh, iSide), + nbClWalkBinX, fdTOTMin, fdTOTMax); + } + if (NULL != htempWalk0 && NULL != htempWalk1) { // reinitialize Walk array + LOG(debug) << "Initialize Walk correction for " + << Form(" SmT%01d_sm%03d_rpc%03d_Ch%03d", iSmType, iSm, iRpc, iCh); + if (htempWalk0->GetNbinsX() != nbClWalkBinX) + LOG(error) << "CbmTaskTofClusterizer::InitCalibParameter: " + "Inconsistent Walk histograms"; + for (int32_t iBin = 0; iBin < nbClWalkBinX; iBin++) { + vCPWalk[iCh][0][iBin] = htempWalk0->GetBinContent(iBin + 1); + vCPWalk[iCh][1][iBin] = htempWalk1->GetBinContent(iBin + 1); + if (iSmType == 0 && iSm == 0 && iRpc == 2 && iCh == 15) // debugging + LOG(info) << Form("Read new SmT%01d_sm%03d_rpc%03d_Ch%03d bin %d cen %f walk %f %f", iSmType, iSm, + iRpc, iCh, iBin, htempWalk0->GetBinCenter(iBin + 1), vCPWalk[iCh][0][iBin], + vCPWalk[iCh][1][iBin]); + if (5 == iSmType || 8 == iSmType) { // Pad structure, enforce consistency + if (vCPWalk[iCh][1][iBin] != vCPWalk[iCh][0][iBin]) { + LOG(fatal) << "Inconsisten walk values for TSRC " << iSmType << iSm << iRpc << iCh << ", Bin " + << iBin << ": " << vCPWalk[iCh][0][iBin] << ", " << vCPWalk[iCh][1][iBin]; + } + vCPWalk[iCh][1][iBin] = vCPWalk[iCh][0][iBin]; + htempWalk1->SetBinContent(iBin + 1, vCPWalk[iCh][1][iBin]); + } + } + } + else { + LOG(info) << "No Walk histograms for TSRC " << iSmType << iSm << iRpc << iCh; + } + } + // look for TcorY corrections + LOG(info) << "Check for TCorY in " << gDirectory->GetName(); + TH1* hTCorY = + (TH1*) gDirectory->FindObjectAny(Form("calXY_SmT%d_sm%03d_rpc%03d_TOff_z_all_TcorY", iSmType, iSm, iRpc)); + if (NULL != hTCorY) { + vCPTOffYBinWidth = hTCorY->GetBinWidth(0); + vCPTOffYRange = hTCorY->GetXaxis()->GetXmax(); + LOG(info) << "Initialize TCorY: TSR " << iSmType << iSm << iRpc << ", Bwid " << vCPTOffYBinWidth + << ", Range " << vCPTOffYRange; + vCPTOffY.resize(hTCorY->GetNbinsX()); + for (int iB = 0; iB < hTCorY->GetNbinsX(); iB++) { + vCPTOffY[iB] = hTCorY->GetBinContent(iB + 1); + } + } + } + else { + LOG(warning) << " Calibration histos " << Form("cl_SmT%01d_sm%03d_rpc%03d_XXX", iSmType, iSm, iRpc) + << " not found. "; + } + } + } + + /// Restore old global file and folder pointer to avoid messing with FairRoot + // <= To prevent histos from being sucked in by the param file of the TRootManager! + gFile = oldFile; + gDirectory = oldDir; + LOG(info) << "CbmTaskTofClusterizer::InitCalibParameter: initialization done"; + return true; +} + +bool CbmTaskTofClusterizer::InitAlgos() +{ + // Needed as external TOT values might be different than ones used for input histo reading (TO DO: FIX) + if (fTotMax != 0.) fdTOTMax = fTotMax; + if (fTotMin != 0.) fdTOTMin = fTotMin; + LOG(info) << "ToT init to Min " << fdTOTMin << " Max " << fdTOTMax; + + /// Go to Top volume of the geometry in the GeoManager to make sure our nodes are found + gGeoManager->CdTop(); + + int32_t iNbSmTypes = fDigiBdfPar->GetNbSmTypes(); + + //Prepare storage vectors + fStorDigi.resize(iNbSmTypes); + for (int32_t iSmType = 0; iSmType < iNbSmTypes; iSmType++) { + int32_t iNbSm = fDigiBdfPar->GetNbSm(iSmType); + int32_t iNbRpc = fDigiBdfPar->GetNbRpc(iSmType); + fStorDigi[iSmType].resize(iNbSm * iNbRpc); + } + + // Create map with unique detector IDs and fill (needed only for dead strip array) + std::map<uint32_t, uint32_t> detIdIndexMap; + for (int32_t ind = 0; ind < fDigiBdfPar->GetNbDet(); ind++) { + int32_t iUniqueId = fDigiBdfPar->GetDetUId(ind); + detIdIndexMap[iUniqueId] = ind; + } + + // Create one algorithm per RPC and configure it with parameters + for (int32_t iSmType = 0; iSmType < iNbSmTypes; iSmType++) { + int32_t iNbSm = fDigiBdfPar->GetNbSm(iSmType); + int32_t iNbRpc = fDigiBdfPar->GetNbRpc(iSmType); + for (int32_t iSm = 0; iSm < iNbSm; iSm++) { + for (int32_t iRpc = 0; iRpc < iNbRpc; iRpc++) { + std::unique_ptr<cbm::algo::ClusterizerTofRpcPar> par(new cbm::algo::ClusterizerTofRpcPar()); + + const int32_t iDetId = CbmTofAddress::GetUniqueAddress(iSm, iRpc, 0, 0, iSmType); + const int32_t iDetIndx = detIdIndexMap[iDetId]; + par->fDeadStrips = fvDeadStrips[iDetIndx]; + par->fPosYMaxScal = fPosYMaxScal; + par->fdMaxTimeDist = fdMaxTimeDist; + par->fdMaxSpaceDist = fdMaxSpaceDist; + par->fCPTOffYBinWidth = fvCPTOffYBinWidth[iSmType][iSm * iNbRpc + iRpc]; + par->fCPTOffY = fvCPTOffY[iSmType][iSm * iNbRpc + iRpc]; + par->fCPTOffYRange = fvCPTOffYRange[iSmType][iSm * iNbRpc + iRpc]; + par->fSigVel = fDigiBdfPar->GetSigVel(iSmType, iSm, iRpc); + par->fTimeRes = 0.08; + par->numClWalkBinX = nbClWalkBinX; + par->TOTMax = fdTOTMax; + par->TOTMin = fdTOTMin; + par->swapChannelSides = fbSwapChannelSides; + par->channelDeadtime = fdChannelDeadtime; + + int32_t iNbChan = fDigiBdfPar->GetNbChan(iSmType, iRpc); + par->fChanPar.resize(iNbChan); + for (int32_t iCh = 0; iCh < iNbChan; iCh++) { + + const int32_t address = CbmTofAddress::GetUniqueAddress(iSm, iRpc, iCh, 0, iSmType); + CbmTofCell* channelInfo = fDigiPar->GetCell(address); + if (channelInfo == nullptr) { continue; } //D.Smith 17.8.23: Needed as channel info sometimes not defined + + gGeoManager->FindNode(channelInfo->GetX(), channelInfo->GetY(), channelInfo->GetZ()); + const double* tra_ptr = gGeoManager->MakePhysicalNode()->GetMatrix()->GetTranslation(); + + //init Tof cell + cbm::algo::TofCell& cell = par->fChanPar[iCh].cell; + cell.pos = ROOT::Math::XYZVector(tra_ptr[0], tra_ptr[1], tra_ptr[2]); + + /* why doesn't this variant work? + cell.pos.SetX(channelInfo->GetX()); + cell.pos.SetY(channelInfo->GetY()); + cell.pos.SetZ(channelInfo->GetZ()); +*/ + cell.sizeX = channelInfo->GetSizex(); + cell.sizeY = channelInfo->GetSizey(); + + // prepare local->global trafo + gGeoManager->FindNode(channelInfo->GetX(), channelInfo->GetY(), channelInfo->GetZ()); + double* rot_ptr = gGeoManager->GetCurrentMatrix()->GetRotationMatrix(); + cell.rotation = ROOT::Math::Rotation3D(&rot_ptr[0], &rot_ptr[9]); + + par->fChanPar[iCh].fvCPTOff = fvCPTOff[iSmType][iSm * iNbRpc + iRpc][iCh]; + par->fChanPar[iCh].fvCPTotGain = fvCPTotGain[iSmType][iSm * iNbRpc + iRpc][iCh]; + par->fChanPar[iCh].fvCPTotOff = fvCPTotOff[iSmType][iSm * iNbRpc + iRpc][iCh]; + par->fChanPar[iCh].fvCPWalk = fvCPWalk[iSmType][iSm * iNbRpc + iRpc][iCh]; + par->fChanPar[iCh].address = address; + } + fAlgo[iSmType][iSm * iNbRpc + iRpc].SetParams(std::move(par)); + } + } + } + return true; +} + + +/************************************************************************************/ +bool CbmTaskTofClusterizer::BuildClusters() +{ + gGeoManager->CdTop(); + + if (fTofDigiVec.empty()) { + LOG(info) << " No RawDigis defined ! Check! "; + return false; + } + LOG(info) << "Build clusters from " << fTofDigiVec.size() << " digis in event " << fdEvent + 1; + + int32_t iNbTofDigi = fTofDigiVec.size(); + + if (bAddBeamCounterSideDigi) { + // Duplicate type "5" - digis + for (int32_t iDigInd = 0; iDigInd < iNbTofDigi; iDigInd++) { + CbmTofDigi* pDigi = &(fTofDigiVec.at(iDigInd)); + if (pDigi->GetType() == 5) { // || pDigi->GetType() == 8) { + if (pDigi->GetSide() == 1) { + bAddBeamCounterSideDigi = false; // disable for current data set + LOG(info) << "Start counter digi duplication disabled"; + break; + } + fTofDigiVec.push_back(CbmTofDigi(*pDigi)); + CbmTofDigi* pDigiN = &(fTofDigiVec.back()); + pDigiN->SetAddress(pDigi->GetSm(), pDigi->GetRpc(), pDigi->GetChannel(), (0 == pDigi->GetSide()) ? 1 : 0, + pDigi->GetType()); + LOG(debug) << "Duplicated digi " << fTofDigiVec.size() << " with address 0x" << std::hex + << pDigiN->GetAddress(); + } + } + iNbTofDigi = fTofDigiVec.size(); + } + + //D.Smith 10.8.23: This entire if(true){...} block doesn't seem to actually do anything + //for run 2391 data if enabled. Check with PA whether it can be dropped (might be obsolete). + //The original purpose was to correct missing hits, which might have been fixed. + if (true) { + for (int32_t iDigInd = 0; iDigInd < iNbTofDigi; iDigInd++) { + CbmTofDigi* pDigi = &(fTofDigiVec.at(iDigInd)); + const double SmType = pDigi->GetType(); + const double Sm = pDigi->GetSm(); + const double Rpc = pDigi->GetRpc(); + const double Chan = pDigi->GetChannel(); + const double Side = pDigi->GetSide(); + int32_t iDetIndx = fDigiBdfPar->GetDetInd(pDigi->GetAddress() & DetMask); + + if (fDigiBdfPar->GetNbDet() - 1 < iDetIndx || iDetIndx < 0) { + LOG(debug) << Form(" Wrong DetIndx %d >< %d ", iDetIndx, fDigiBdfPar->GetNbDet()); + break; + } + CbmTofDigi* pDigi2Min = NULL; + double dTDifMin = dDoubleMax; + + if (fDutId > -1) { + for (int32_t iDigI2 = 0; iDigI2 < iNbTofDigi; iDigI2++) { + CbmTofDigi* pDigi2 = &(fTofDigiVec.at(iDigI2)); + const double SmType2 = pDigi2->GetType(); + const double Sm2 = pDigi2->GetSm(); + const double Rpc2 = pDigi2->GetRpc(); + const double Chan2 = pDigi2->GetChannel(); + const double Side2 = pDigi2->GetSide(); + + if (iDetIndx == fDigiBdfPar->GetDetInd(pDigi2->GetAddress())) { + if (Side != Side2) { + if (Chan == Chan2) { + double dTDif = TMath::Abs(pDigi->GetTime() - pDigi2->GetTime()); + if (dTDif < dTDifMin) { + dTDifMin = dTDif; + pDigi2Min = pDigi2; + } + } + else if (TMath::Abs(Chan - Chan2) + == 1) { // opposite side missing, neighbouring channel has hit on opposite side // FIXME + // check that same side digi of neighbouring channel is absent + int32_t iDigI3 = 0; + for (; iDigI3 < iNbTofDigi; iDigI3++) { + CbmTofDigi* pDigi3 = &(fTofDigiVec.at(iDigI3)); + if (pDigi3->GetSide() == Side && Chan2 == pDigi3->GetChannel()) break; + } + if (iDigI3 == iNbTofDigi) // same side neighbour did not fire + { + int32_t iCorMode = 0; // Missing hit correction mode + switch (iCorMode) { + case 0: // no action + break; + case 1: // shift found hit + if (Side == 0) pDigi2->SetAddress(Sm, Rpc, Chan, 1 - Side, SmType); + else + pDigi->SetAddress(Sm2, Rpc2, Chan2, 1 - Side2, SmType2); + + break; + case 2: // insert missing hits + fTofDigiVec.push_back(CbmTofDigi(*pDigi)); + CbmTofDigi* pDigiN = &(fTofDigiVec.back()); + pDigiN->SetAddress(Sm, Rpc, Chan2, Side, SmType); + pDigiN->SetTot(pDigi2->GetTot()); + + fTofDigiVec.push_back(CbmTofDigi(*pDigi2)); + CbmTofDigi* pDigiN2 = &(fTofDigiVec.back()); + pDigiN2->SetAddress(Sm2, Rpc2, Chan, Side2, SmType2); + pDigiN2->SetTot(pDigi->GetTot()); + + break; + } + } + } + } + } + } + } + + if (pDigi2Min != NULL) { + CbmTofDetectorInfo xDetInfo(ECbmModuleId::kTof, SmType, Sm, Rpc, 0, Chan); + int32_t iChId = fTofId->SetDetectorInfo(xDetInfo); + CbmTofCell* cell = fDigiPar->GetCell(iChId); + if (NULL == cell) { + LOG(warning) << Form("BuildClusters: invalid ChannelInfo for 0x%08x", iChId); + continue; + } + if (fDigiBdfPar->GetSigVel(SmType, Sm, Rpc) * dTDifMin * 0.5 < fPosYMaxScal * cell->GetSizey()) { + //check consistency + if (8 == SmType || 5 == SmType) { + if (pDigi->GetTime() != pDigi2Min->GetTime()) { + LOG(warning) << " BuildClusters: Inconsistent duplicated digis in event " << fdEvent + 1 + << ", Ind: " << iDigInd; // << "CTyp: " << pDigi->GetCounterType; + LOG(warning) << " " << pDigi->ToString(); + LOG(warning) << " " << pDigi2Min->ToString(); + + pDigi2Min->SetTot(pDigi->GetTot()); + pDigi2Min->SetTime(pDigi->GetTime()); + } + } + } + } + } + } // true end + + // Calibrate RawDigis + *fTofCalDigiVec = CalibRawDigis(fTofDigiVec); + // *fTofCalDigiVec = fTofDigiVec; //disable calibration for now (done in algo). Linked indices will differ! + + // Then loop over the digis array and store the Digis in separate vectors for + // each RPC modules + for (int32_t iDigInd = 0; iDigInd < fTofCalDigiVec->size(); iDigInd++) { + CbmTofDigi* pDigi = &(fTofCalDigiVec->at(iDigInd)); + + // These are doubles in the digi class + const double SmType = pDigi->GetType(); + const double Sm = pDigi->GetSm(); + const double Rpc = pDigi->GetRpc(); + const int NbRpc = fDigiBdfPar->GetNbRpc(SmType); + fStorDigi[SmType][Sm * NbRpc + Rpc].push_back(std::make_pair(pDigi, iDigInd)); + } + // inspect digi array (D.Smith: Block removed for cleanup. Could be in monitoring class in principle.) + + for (uint32_t iSmType = 0; iSmType < fDigiBdfPar->GetNbSmTypes(); iSmType++) { + const uint32_t iNbSm = fDigiBdfPar->GetNbSm(iSmType); + const uint32_t iNbRpc = fDigiBdfPar->GetNbRpc(iSmType); + for (uint32_t iSm = 0; iSm < iNbSm; iSm++) { + for (uint32_t iRpc = 0; iRpc < iNbRpc; iRpc++) { + //read digis + const uint32_t rpcIdx = iSm * iNbRpc + iRpc; + std::vector<std::pair<CbmTofDigi*, int32_t>>& digiExp = fStorDigi[iSmType][rpcIdx]; + + //call cluster finder + std::vector<cbm::algo::TofCluster> clusters = fAlgo[iSmType][rpcIdx](digiExp); + + //Store hits and match + for (auto const& cluster : clusters) { + const int32_t hitIndex = fTofHitsColl->GetEntriesFast(); + TVector3 hitpos = TVector3(cluster.globalPos.X(), cluster.globalPos.Y(), cluster.globalPos.Z()); + TVector3 hiterr = TVector3(cluster.globalErr.X(), cluster.globalErr.Y(), cluster.globalErr.Z()); + new ((*fTofHitsColl)[hitIndex]) + CbmTofHit(cluster.detId, hitpos, hiterr, fiNbHits, cluster.weightedTime, cluster.weightedTimeErr, + cluster.vDigiIndRef.size(), int32_t(cluster.weightsSum * 10.)); + + fiNbHits++; + //if (event) event->AddData(ECbmDataType::kTofHit, hitIndex); + + CbmMatch* digiMatch = new ((*fTofDigiMatchColl)[hitIndex]) CbmMatch(); + for (uint32_t i = 0; i < cluster.vDigiIndRef.size(); i++) { + double tot = fTofCalDigiVec->at(cluster.vDigiIndRef.at(i)).GetTot(); + digiMatch->AddLink(CbmLink(tot, cluster.vDigiIndRef.at(i), fiOutputTreeEntry, fiFileIndex)); + } + } + // digiExp.clear(); + // digiInd.clear(); + } + } + } + std::cout << "hits " << fiNbHits << std::endl; + + fdEvent++; + return true; +} + +/* No longer used. Keeping for reference / discussion of comments +void CbmTaskTofClusterizer::StoreHit(TofCluster& cluster, int32_t iSmType, int32_t iChId, int32_t iNbCh, int32_t iSm, + int32_t iRpc) +{ + double* tra_ptr = fCellIdGeoMap[iChId]->GetMatrix()->GetTranslation(); + double* rot_ptr = fCellIdGeoMap[iChId]->GetMatrix()->GetRotationMatrix(); + + TofCell cell; + cell.pos = ROOT::Math::XYZVector(tra_ptr[0], tra_ptr[1], tra_ptr[2]); + cell.rotation = ROOT::Math::Rotation3D(&rot_ptr[0], &rot_ptr[9]); + cell.sizeX = fDigiPar->GetCell(iChId)->GetSizex(); + + //D.Smith 10.8.23: Replacing this with the lines above seems to work, although the exact + //cell might be a different one when called from AddNextChan(). + //int32_t iChm = floor(cluster.weightedPos.X() / fChannelInfo->GetSizex()) + iNbCh / 2; + + //D.Smith 15.8.23: Can we drop the condition? + if (5 != iSmType) { cluster.finalize(cell, iNbCh, ClusterizerTofRpcPar()); } + + TVector3 hitPos(cluster.globalPos.X(), cluster.globalPos.Y(), cluster.globalPos.Z()); + TVector3 hitPosErr(cluster.globalErr.X(), cluster.globalErr.Y(), cluster.globalErr.Z()); + + const int32_t iDetId = CbmTofAddress::GetUniqueAddress(iSm, iRpc, cluster.channel, 0, iSmType); + + if (cluster.vDigiIndRef.size() < 2) { + LOG(warning) << "Digi refs for Hit " << fiNbHits << ": vDigiIndRef.size()"; + } + CbmTofHit* pHit = new CbmTofHit(iDetId, hitPos, + hitPosErr, //local detector coordinates + fiNbHits, // this number is used as reference!! + cluster.weightedTime, + cluster.vDigiIndRef.size(), // number of linked digis = 2*CluSize + int32_t(cluster.weightsSum * 10.)); //channel -> Tot + pHit->SetTimeError(dTimeRes); + + // output hit + new ((*fTofHitsColl)[fiNbHits]) CbmTofHit(*pHit); + pHit->Delete(); + + CbmMatch* digiMatch = new ((*fTofDigiMatchColl)[fiNbHits]) CbmMatch(); + + for (size_t i = 0; i < cluster.vDigiIndRef.size(); i++) { + double dTot = fTofCalDigiVec->at(cluster.vDigiIndRef.at(i)).GetTot(); + digiMatch->AddLink(CbmLink(dTot, cluster.vDigiIndRef.at(i), fiOutputTreeEntry, fiFileIndex)); + } + fiNbHits++; +} +*/ + +std::vector<CbmTofDigi> CbmTaskTofClusterizer::CalibRawDigis(const std::vector<CbmTofDigi>& digiVec) +{ + // channel deadtime map + std::map<int32_t, double> mChannelDeadTime; + std::vector<CbmTofDigi> calDigiVecOut; + + for (int32_t iDigi = 0; iDigi < digiVec.size(); iDigi++) { + + CbmTofDigi pDigi = digiVec.at(iDigi); + const double SmType = pDigi.GetType(); + const double Sm = pDigi.GetSm(); + const double Rpc = pDigi.GetRpc(); + const double Chan = pDigi.GetChannel(); + const double Side = pDigi.GetSide(); + const int NbRpc = fDigiBdfPar->GetNbRpc(SmType); + const uint32_t rpcIdx = Sm * NbRpc + Rpc; + + if (fbSwapChannelSides && 5 != SmType && 8 != SmType) { + pDigi.SetAddress(Sm, Rpc, Chan, (0 == Side) ? 1 : 0, SmType); + } + + // Check dead time + const int32_t iAddr = pDigi.GetAddress(); + auto it = mChannelDeadTime.find(iAddr); + if (it != mChannelDeadTime.end() && pDigi.GetTime() <= it->second) { + it->second = pDigi.GetTime() + fdChannelDeadtime; + continue; + } + mChannelDeadTime[iAddr] = pDigi.GetTime() + fdChannelDeadtime; + + // Create calibrated digi + CbmTofDigi pCalDigi(pDigi); + + // calibrate Digi Time + pCalDigi.SetTime(pCalDigi.GetTime() - fvCPTOff[SmType][rpcIdx][Chan][Side]); + + // subtract Offset + double dTot = pCalDigi.GetTot() - fvCPTotOff[SmType][rpcIdx][Chan][Side]; + if (dTot < 0.001) dTot = 0.001; + + // calibrate Digi ToT + pCalDigi.SetTot(dTot * fvCPTotGain[SmType][rpcIdx][Chan][Side]); + + // walk correction + std::vector<double>& walk = fvCPWalk[SmType][rpcIdx][Chan][Side]; + const double dTotBinSize = (fdTOTMax - fdTOTMin) / nbClWalkBinX; + int32_t iWx = (int32_t)((pCalDigi.GetTot() - fdTOTMin) / dTotBinSize); + + if (0 > iWx) { iWx = 0; } + if (iWx >= nbClWalkBinX) { iWx = nbClWalkBinX - 1; } + + const double dDTot = (pCalDigi.GetTot() - fdTOTMin) / dTotBinSize - (double) iWx - 0.5; + double dWT = walk[iWx]; + + // linear interpolation to next bin //memory leak??? (D.Smith 10.8.23: Clarify this comment!) + if (dDTot > 0) { + if (iWx < nbClWalkBinX - 1) { dWT += dDTot * (walk[iWx + 1] - walk[iWx]); } + } + else { + if (0 < iWx) { dWT -= dDTot * (walk[iWx - 1] - walk[iWx]); } + } + pCalDigi.SetTime(pCalDigi.GetTime() - dWT); // calibrate Digi Time + calDigiVecOut.push_back(pCalDigi); + } + + // D.Smith 10.8.23: Sorting might not be needed. Check with P.A. + LOG(debug) << "CbmTaskTofClusterizer::BuildClusters: Sort " << calDigiVecOut.size() << " calibrated digis "; + /// Sort the buffers of hits due to the time offsets applied + std::sort(calDigiVecOut.begin(), calDigiVecOut.end(), + [](const CbmTofDigi& a, const CbmTofDigi& b) -> bool { return a.GetTime() < b.GetTime(); }); + + return calDigiVecOut; +} + +void CbmTaskTofClusterizer::SetDeadStrips(int32_t iDet, uint32_t ival) +{ + if (fvDeadStrips.size() < static_cast<size_t>(iDet + 1)) fvDeadStrips.resize(iDet + 1); + fvDeadStrips[iDet] = ival; +} diff --git a/reco/tasks/CbmTaskTofClusterizer.h b/reco/tasks/CbmTaskTofClusterizer.h new file mode 100644 index 0000000000000000000000000000000000000000..d5e9df9cd4d064e17fee69ca48357140f782b953 --- /dev/null +++ b/reco/tasks/CbmTaskTofClusterizer.h @@ -0,0 +1,227 @@ +/* Copyright (C) 2023 Facility for Antiproton and Ion Research in Europe, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Dominik Smith [committer], Pierre-Alain Loizeau, Norbert Herrmann */ + +#ifndef CBMTASKTOFCLUSTERIZER_H +#define CBMTASKTOFCLUSTERIZER_H 1 + +// TOF Classes and includes +// Input/Output +class CbmEvent; +// Geometry +class CbmTofDetectorId; +class CbmTofDigiPar; +class CbmTofDigiBdfPar; +class CbmTofCell; +class CbmDigiManager; +class CbmTsEventHeader; + +#include "CbmMatch.h" +#include "CbmTofDigi.h" + +#include "tof/ClusterizerTof.h" + +// ROOT Classes and includes +#include "Math/Rotation3D.h" +#include "Math/Vector3Dfwd.h" + +// FAIR classes and includes +#include "FairTask.h" + +// ROOT Classes and includes +class TClonesArray; + +// C++ Classes and includes +#include <vector> + +class CbmTaskTofClusterizer : public FairTask { + +public: + /** + ** @brief Constructor. + **/ + CbmTaskTofClusterizer(); + + /** + ** @brief Constructor. + **/ + CbmTaskTofClusterizer(const char* name, int32_t verbose = 1, bool writeDataInOut = true); + /** + ** @brief Destructor. + **/ + virtual ~CbmTaskTofClusterizer(); + + /** + ** @brief Inherited from FairTask. + **/ + virtual InitStatus Init(); + + /** + ** @brief Inherited from FairTask. + **/ + virtual void SetParContainers(); + + /** + ** @brief Inherited from FairTask. + **/ + virtual void Exec(Option_t* option); + virtual void ExecEvent(Option_t* option, CbmEvent* tEvent = NULL); + + /** + ** @brief Inherited from FairTask. + **/ + virtual void Finish(); + virtual void Finish(double calMode); + + inline void SetCalMode(int32_t iMode) { fCalMode = iMode; } + inline void SetDutId(int32_t Id) { fDutId = Id; } + + inline void PosYMaxScal(double val) { fPosYMaxScal = val; } + inline void SetTotMax(double val) { fTotMax = val; } + inline void SetTotMin(double val) { fTotMin = val; } + inline void SetTotMean(double val) { fTotMean = val; } + inline void SetMaxTimeDist(double val) { fMaxTimeDist = val; } + inline void SetChannelDeadtime(double val) { fdChannelDeadtime = val; } + inline void SetCalParFileName(TString CalParFileName) { fCalParFileName = CalParFileName; } + + inline int GetNbHits() { return fiNbHits; } + inline double GetTotMean() { return fTotMean; } + + void SwapChannelSides(bool bSwap) { fbSwapChannelSides = bSwap; } + void SetFileIndex(int32_t iIndex) { fiFileIndex = iIndex; } + void SetWriteDigisInOut(bool bDigis) { fbWriteDigisInOut = bDigis; } + void SetWriteHitsInOut(bool bHits) { fbWriteHitsInOut = bHits; } + void SetDeadStrips(int32_t iDet, uint32_t ival); + +protected: +private: + int32_t iNbTs = 0; + int fiHitStart = 0; + bool bAddBeamCounterSideDigi = true; + const double dDoubleMax = 1.E300; + const int32_t nbClWalkBinX = 50; // was 100 (11.10.2018) + const int32_t nbClWalkBinY = 41; // choose odd number to have central bin symmetric around 0 + const int32_t DetMask = 0x001FFFFF; // geo v21a + + std::vector<CbmTofDigi>* fT0DigiVec = nullptr; //! T0 Digis + + + /** + ** @brief Copy constructor. + **/ + CbmTaskTofClusterizer(const CbmTaskTofClusterizer&); + /** + ** @brief Copy operator. + **/ + CbmTaskTofClusterizer& operator=(const CbmTaskTofClusterizer&); + + // Functions common for all clusters approximations + /** + ** @brief Recover pointer on input TClonesArray: TofPoints, TofDigis... + **/ + bool RegisterInputs(); + /** + ** @brief Create and register output TClonesArray of Tof Hits. + **/ + bool RegisterOutputs(); + /** + ** @brief Initialize other parameters not included in parameter classes. + **/ + bool InitParameters(); + /** + ** @brief Initialize other parameters not included in parameter classes. + **/ + bool InitCalibParameter(); + + /** + ** @brief Apply calibration to digis + **/ + std::vector<CbmTofDigi> CalibRawDigis(const std::vector<CbmTofDigi>& digiVec); + + /** + ** @brief Build clusters out of ToF Digis and store the resulting info in a TofHit. + **/ + bool BuildClusters(); + + /** + ** @brief Create one algo object for each RPC + **/ + bool InitAlgos(); + + // Hit finder algo + std::map<uint32_t, std::map<uint32_t, cbm::algo::ClusterizerTof>> fAlgo = {}; //[nbType][nbSm*nbRpc] + + // ToF geometry variables + CbmTofDetectorId* fTofId; + CbmTofDigiPar* fDigiPar; + CbmTofDigiBdfPar* fDigiBdfPar; + + const CbmTsEventHeader* fTsHeader; + + // Input variables + std::vector<CbmTofDigi> fTofDigiVec {}; //! TOF Digis + CbmDigiManager* fDigiMan; // TOF Input Digis + TClonesArray* fEventsColl; // CBMEvents (time based) + + // Output variables + bool fbWriteHitsInOut; + bool fbWriteDigisInOut; + std::vector<CbmTofDigi>* fTofCalDigiVec = nullptr; //! // Calibrated TOF Digis + TClonesArray* fTofHitsColl; // TOF hits + TClonesArray* fTofDigiMatchColl; // TOF Digi Links + std::vector<CbmTofDigi>* fTofCalDigiVecOut = nullptr; //! // Calibrated TOF Digis + TClonesArray* fTofHitsCollOut; // TOF hits + TClonesArray* fTofDigiMatchCollOut; // TOF Digi Links + int32_t fiNbHits; // Index of the CbmTofHit TClonesArray + + // Intermediate storage variables (digi, index) + std::vector<std::vector<std::vector<std::pair<CbmTofDigi*, int32_t>>>> fStorDigi; //[nbType][nbSm*nbRpc][nDigis] + + //Calibration parameters + std::vector<std::vector<std::vector<std::vector<double>>>> fvCPTOff; //[nSMT][nRpc][nCh][nbSide] + std::vector<std::vector<std::vector<std::vector<double>>>> fvCPTotGain; //[nSMT][nRpc][nCh][nbSide] + std::vector<std::vector<std::vector<std::vector<double>>>> fvCPTotOff; //[nSMT][nRpc][nCh][nbSide] + std::vector<std::vector<std::vector<std::vector<std::vector<double>>>>> + fvCPWalk; //[nSMT][nRpc][nCh][nbSide][nbWalkBins] + std::vector<std::vector<std::vector<double>>> fvCPTOffY; //[nSMT][nRpc][nBin] + std::vector<std::vector<double>> fvCPTOffYBinWidth; //[nSMT][nRpc] + std::vector<std::vector<double>> fvCPTOffYRange; //[nSMT][nRpc] + + std::vector<uint32_t> fvDeadStrips; //[nbDet] + + // Calib + int32_t fCalMode; + int32_t fDutId; + + double fPosYMaxScal; + double fTotMax; + double fTotMin; + double fTotOff; + double fTotMean; + double fMaxTimeDist; + double fdChannelDeadtime; + + TString fCalParFileName; // name of the file name with Calibration Parameters + + double fdTOTMax; + double fdTOTMin; + double fdTTotMean; + + double fdMaxTimeDist; // Isn't this just a local variable? Why make it global and preset?!? + double fdMaxSpaceDist; // Isn't this just a local variable? Why make it global and preset?!? + double fdModifySigvel; + + double fdEvent; + + double fProcessTime = 0.0; + uint64_t fuNbDigis = 0; + uint64_t fuNbHits = 0; + + bool fbSwapChannelSides; + int32_t fiOutputTreeEntry; + int32_t fiFileIndex; + + ClassDef(CbmTaskTofClusterizer, 1); +}; + +#endif // CBMTASKTOFCLUSTERIZER_H