Project
Loading...
Searching...
No Matches
Clusterer.cxx
Go to the documentation of this file.
1// Copyright 2019-2026 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
14
15#include "Framework/Logger.h"
16
18
19#include <algorithm>
20#include <numeric>
21
22namespace o2::iotof
23{
24
25//__________________________________________________
26void Clusterer::process(gsl::span<const Digit> digits,
27 gsl::span<const DigROFRecord> digitROFs,
28 std::vector<o2::iotof::Cluster>& clusters,
29 std::vector<unsigned char>& patterns,
30 std::vector<o2::itsmft::ROFRecord>& clusterROFs,
31 const ConstDigitTruth* digitLabels,
32 ClusterTruth* clusterLabels,
33 gsl::span<const DigMC2ROFRecord> digMC2ROFs,
34 std::vector<o2::itsmft::MC2ROFRecord>* clusterMC2ROFs)
35{
36 LOG(info) << "RUNNING CLUSTERIZER ON " << digitROFs.size() << " ROFs, TOTAL DIGITS: " << digits.size();
37
38 if (!mThread) {
39 mThread = std::make_unique<ClustererThread>(this);
40 }
41
42 for (size_t iROF = 0; iROF < digitROFs.size(); ++iROF) {
43 LOG(debug) << "Processing ROF " << iROF << "/" << digitROFs.size();
44 const auto& digitsThisROF = digitROFs[iROF];
45 const auto nStoredCls = static_cast<int>(clusters.size());
46 const int first = digitsThisROF.getFirstEntry();
47 const int nDigits = digitsThisROF.getNEntries();
48
49 if (nDigits == 0) {
50 clusterROFs.emplace_back(digitsThisROF.getBCData(), digitsThisROF.getROFrame(), nStoredCls, 0);
51 continue;
52 }
53
54 // Sort digit indices within this ROF by (chipID, row, col, time)
55 // extended with time information from TRK.
56 mSortIdx.resize(nDigits);
57 std::iota(mSortIdx.begin(), mSortIdx.end(), first);
58 std::sort(mSortIdx.begin(), mSortIdx.end(), [&digits](int a, int b) {
59 const auto& da = digits[a];
60 const auto& db = digits[b];
61 if (da.getChipIndex() != db.getChipIndex()) {
62 return da.getChipIndex() < db.getChipIndex();
63 }
64 if (da.getRow() != db.getRow()) {
65 return da.getRow() < db.getRow();
66 }
67 if (da.getColumn() != db.getColumn()) {
68 return da.getColumn() < db.getColumn();
69 }
70 return da.getTime() < db.getTime();
71 });
72 LOG(debug) << "Found " << nDigits << " digits for ROF " << iROF;
73
74 // Process blocks of digits within the same chip (marked by chipID)
75 int iDigit = 0;
76 while (iDigit < nDigits) {
77 const int firstDigit = iDigit;
78 const uint16_t chipID = digits[mSortIdx[iDigit]].getChipIndex();
79
80 // Define the span of digits featuring the same chipID
81 while (iDigit < nDigits && digits[mSortIdx[iDigit]].getChipIndex() == chipID) {
82 ++iDigit;
83 }
84 const int nDigitsThisChip = iDigit - firstDigit;
85
86 LOG(debug) << "Processing chip " << chipID << " with " << nDigitsThisChip << " digits, next digit starts from index " << iDigit;
87 mThread->processChip(digits, firstDigit, nDigitsThisChip, &clusters, &patterns, digitLabels, clusterLabels);
88 }
89
90 LOG(debug) << "Finished processing digit ROF " << iROF << ", produced " << (clusters.size() - nStoredCls) << " clusters";
91 clusterROFs.emplace_back(digitsThisROF.getBCData(), digitsThisROF.getROFrame(),
92 nStoredCls, static_cast<int>(clusters.size()) - nStoredCls);
93 }
94
95 LOG(info) << "FINISHED PROCESSING ALL DIGIT ROFS, TOTAL CLUSTERS PRODUCED: " << clusters.size();
96 if (clusterMC2ROFs && !digMC2ROFs.empty()) {
97 clusterMC2ROFs->reserve(clusterMC2ROFs->size() + digMC2ROFs.size());
98 for (const auto& in : digMC2ROFs) {
99 clusterMC2ROFs->emplace_back(in.eventRecordID, in.rofRecordID, in.minROF, in.maxROF);
100 }
101 }
102
103 LOG(info) << "WRITING CLUSTER TOPOLOGY MAP TO FILE TF3ClusterTopologies.root";
104 mThread->writeTopologiesToFile("TF3ClusterTopologies.root");
105}
106
107//__________________________________________________
108void Clusterer::ClustererThread::processChip(gsl::span<const Digit> digits,
109 int firstDigitIdx, int nDigits,
110 std::vector<Cluster>* clustersOut,
111 std::vector<unsigned char>* patternsOut,
112 const ConstDigitTruth* labelsDigPtr,
113 ClusterTruth* labelsClusPtr)
114{
115 // firstDigitIdx and nDigits are relative to mSortIdx (i.e. mSortIdx[firstDigitIdx..firstDigitIdx+nDigits-1]
116 // are the global digit indices for this chip, already sorted by time, col then row).
117 // We use parent->mSortIdx to resolve the global index of each pixel.
118 const auto& sortIdx = mParent->mSortIdx;
119
120 if (nDigits == 1) {
121 findClustersSingleHit(digits, sortIdx[firstDigitIdx], labelsDigPtr, labelsClusPtr);
122 } else {
123 std::vector<uint32_t> digitIdxs(nDigits);
124
125 for (int i = 0; i < nDigits; ++i) {
126 digitIdxs[i] = sortIdx[firstDigitIdx + i];
127 }
128
129 findClustersMultipleHits(
130 digits,
131 gsl::span<const uint32_t>(digitIdxs),
132 labelsDigPtr,
133 labelsClusPtr);
134 }
135
136 // Flush per-thread output into the caller's containers
137
138 // Push-back cluster labels, dummy labels for clusters with
139 // empty labels, to ensure that the clusterLabels container
140 // WWhas the same size as the clustersOut container.
141 if (labelsClusPtr) {
142 const size_t base = clustersOut->size(); // before inserting this chip's clusters
143 // and store labels as you go, or copy from mLabels:
144 for (size_t i = 0; i < mClusters.size(); ++i) {
145 auto labels = mLabels.getLabels(i); // empty span if none
146 if (labels.empty()) {
147 labelsClusPtr->addNoLabelIndex(base + i);
148 } else {
149 for (const auto& l : labels) {
150 labelsClusPtr->addElement(base + i, l);
151 }
152 }
153 }
154 mLabels.clear();
155 }
156
157 if (!mClusters.empty()) {
158 clustersOut->insert(clustersOut->end(), mClusters.begin(), mClusters.end());
159 mClusters.clear();
160 }
161 if (!mPatterns.empty()) {
162 patternsOut->insert(patternsOut->end(), mPatterns.begin(), mPatterns.end());
163 mPatterns.clear();
164 }
165}
166
167//__________________________________________________
168void Clusterer::ClustererThread::findClustersSingleHit(gsl::span<const Digit> digits,
169 uint32_t digitIdx,
170 const ConstDigitTruth* labelsDigPtr,
171 ClusterTruth* labelsClusPtr)
172{
173 const auto& digit = digits[digitIdx];
174 const uint16_t chipID = digit.getChipIndex();
175 const uint16_t row = digit.getRow();
176 const uint16_t col = digit.getColumn();
177 const time_t time = digit.getTime();
178
179 if (labelsClusPtr) {
180 int nStoredLabels = 0;
181 fetchMCLabels(digitIdx, labelsDigPtr, nStoredLabels);
182 const auto nCls = static_cast<uint32_t>(mClusters.size());
183 for (int i = 0; i < nStoredLabels; i++) {
184 mLabels.addElement(nCls, mLabelsBuff[i]);
185 }
186 }
187
188 const uint16_t minRow = row;
189 const uint16_t minCol = col;
190 uint8_t rowSpan{1}, colSpan{1};
191 uint32_t clsTopology{0};
192 constexpr uint16_t firedDigitsMask = (1U << 0); // 0x0001 (1)
193 mClsTopoClassifier.getTopology(firedDigitsMask, minRow, rowSpan, minCol, colSpan, clsTopology);
194 // Bit 0 corresponds to (rowOffset=0, colOffset=0) in row-major order
195 Cluster cluster(row, col, rowSpan, colSpan, firedDigitsMask, clsTopology, chipID, time);
196
197 LOG(debug) << "Pushing back cluster with row: " << row << ", col: " << col
198 << ", rowSpan: " << static_cast<int>(rowSpan) << ", colSpan: " << static_cast<int>(colSpan)
199 << ", pattern: " << firedDigitsMask << ", topology: " << static_cast<int>(clsTopology)
200 << ", chipID: " << chipID << ", time: " << time;
201
202 mClusters.emplace_back(cluster);
203 mPatterns.emplace_back(static_cast<unsigned char>(firedDigitsMask));
204}
205
206std::vector<std::vector<uint32_t>> Clusterer::ClustererThread::buildPreclusters(gsl::span<const Digit> digits, gsl::span<const uint32_t> digitIdxs, int maxTimeDiffNSigma, float timeResolution)
207{
208 std::vector<std::vector<uint32_t>> preclusters;
209 std::vector<bool> used(digitIdxs.size(), false);
210
211 auto areNeighbours = [&](const Digit& a, const Digit& b) {
212 return std::abs(static_cast<int>(a.getRow()) - static_cast<int>(b.getRow())) <= 1 &&
213 std::abs(static_cast<int>(a.getColumn()) - static_cast<int>(b.getColumn())) <= 1 &&
214 std::abs(a.getTime() - b.getTime()) <= maxTimeDiffNSigma * timeResolution;
215 };
216
217 for (size_t i = 0; i < digitIdxs.size(); ++i) {
218 if (used[i]) {
219 continue; // already part of an earlier precluster
220 }
221
222 std::vector<uint32_t> precluster;
223 std::vector<size_t> toVisit{i};
224 used[i] = true;
225
226 while (!toVisit.empty()) {
227 const size_t cur = toVisit.back();
228 toVisit.pop_back();
229 precluster.push_back(digitIdxs[cur]);
230
231 // add every not-yet-used digit that touches the current one
232 for (size_t j = 0; j < digitIdxs.size(); ++j) {
233 if (!used[j] && areNeighbours(digits[digitIdxs[cur]], digits[digitIdxs[j]])) {
234 used[j] = true;
235 toVisit.push_back(j);
236 }
237 }
238 }
239
240 preclusters.push_back(std::move(precluster));
241 }
242 return preclusters;
243}
244
245//__________________________________________________
246void Clusterer::ClustererThread::findClustersMultipleHits(gsl::span<const Digit> digits,
247 gsl::span<const uint32_t> digitIdxs,
248 const ConstDigitTruth* labelsDigPtr,
249 ClusterTruth* labelsClusPtr)
250{
251
252 // Constraints on time resolution
253 const auto& digitizerParams = o2::iotof::DPLDigitizerParam::Instance();
254 float timeResolution = digitizerParams.timeResolution; // in ns
255 const auto& clustererParams = o2::iotof::ClustererParam::Instance();
256 int maxTimeDiffNSigma = clustererParams.maxTimeDiffNSigma; // in nsigma
257 int maxFiredDigitsForCls = clustererParams.maxFiredDigitsForCls; // max fired digits in a cluster
258
259 // Digits are ordered by (chipID, row, col, time) within the same chip,
260 // so we can group them into preclusters based on adjacency in row and column.
261 std::vector<std::vector<uint32_t>> preclusters = buildPreclusters(digits, digitIdxs, maxTimeDiffNSigma, timeResolution);
262 uint16_t chipID = digits[digitIdxs[0]].getChipIndex();
263
264 for (const auto& precluster : preclusters) {
265
266 const auto nStoredCls = static_cast<uint32_t>(mClusters.size());
267
268 // Single-digit cluster in chip with multiple fired digits
269 if (precluster.size() == 1) {
270 const auto& digit = digits[precluster[0]];
271 const uint16_t row = digit.getRow();
272 const uint16_t col = digit.getColumn();
273 const time_t time = digit.getTime();
274
275 if (labelsClusPtr) {
276 int nMcLabels = 0;
277 fetchMCLabels(precluster[0], labelsDigPtr, nMcLabels);
278 for (int i = nMcLabels; i--;) {
279 mLabels.addElement(nStoredCls, mLabelsBuff[i]);
280 }
281 }
282
283 const uint16_t minRow = row;
284 const uint16_t minCol = col;
285 uint8_t rowSpan{1}, colSpan{1};
286 uint32_t clsTopology{0};
287 // Bit 0 corresponds to (rowOffset=0, colOffset=0) in row-major order
288 constexpr uint16_t firedDigitsMask = (1U << 0); // 0x0001 (1)
289 mClsTopoClassifier.getTopology(firedDigitsMask, minRow, rowSpan, minCol, colSpan, clsTopology);
290 // Bit 0 corresponds to (rowOffset=0, colOffset=0) in row-major order
291 Cluster cluster(minRow, minCol, rowSpan, colSpan, firedDigitsMask, clsTopology, chipID, time);
292
293 LOG(debug) << "Pushing back cluster with row: " << row << ", col: " << col
294 << ", rowSpan: " << static_cast<int>(rowSpan) << ", colSpan: " << static_cast<int>(colSpan)
295 << ", pattern: " << firedDigitsMask << ", topology: " << static_cast<int>(clsTopology)
296 << ", chipID: " << chipID << ", time: " << time;
297
298 mClusters.emplace_back(cluster);
299 mPatterns.emplace_back(static_cast<unsigned char>(firedDigitsMask));
300 } else {
301 // Retrieve min row, min col of the precluster
302 uint16_t minRow = std::numeric_limits<uint16_t>::max();
303 uint16_t maxRow = std::numeric_limits<uint16_t>::min();
304 uint16_t minCol = std::numeric_limits<uint16_t>::max();
305 uint16_t maxCol = std::numeric_limits<uint16_t>::min();
306
307 int nMcLabels = 0;
308
309 // Compute average time for digits in the precluster
310 time_t clsTime = 0.0;
311 for (const auto& idx : precluster) {
312 const auto& digit = digits[idx];
313 minRow = std::min(minRow, digit.getRow());
314 minCol = std::min(minCol, digit.getColumn());
315 maxRow = std::max(maxRow, digit.getRow());
316 maxCol = std::max(maxCol, digit.getColumn());
317 clsTime += digit.getTime();
318 fetchMCLabels(idx, labelsDigPtr, nMcLabels);
319 }
320 clsTime /= precluster.size();
321 const uint8_t rowSpan = maxRow - minRow + 1;
322 const uint8_t colSpan = maxCol - minCol + 1;
323
324 // Fired digits bitmask packed into a single 16-bit pattern variable
325 uint16_t firedDigitsMask = 0;
326
327 if (rowSpan * colSpan > maxFiredDigitsForCls) {
328 // Overflow precluster: pass InvalidPatternID (or 0) and kHuge topology flag
329 Cluster cluster(minRow, minCol, rowSpan, colSpan, Cluster::InvalidPatternID, Topologies::kHuge, chipID, clsTime);
330 mClusters.emplace_back(cluster);
331 mPatterns.emplace_back(Cluster::InvalidPatternID);
332 continue;
333 }
334
335 // Fill firedDigitsMask in Row-Major order (bit 0 = (minRow, minCol))
336 for (const auto& idx : precluster) {
337 const auto& digit = digits[idx];
338 const uint16_t rowOffset = digit.getRow() - minRow;
339 const uint16_t colOffset = digit.getColumn() - minCol;
340
341 // Single bit position calculation
342 const uint16_t bitIndex = rowOffset * colSpan + colOffset;
343
344 // Set bit in LSB-to-MSB order
345 if (bitIndex < ClusterInfo::NBitsPattern) {
346 firedDigitsMask |= (1U << bitIndex);
347 }
348 }
349
350 uint32_t clsTopology{0};
351 mClsTopoClassifier.getTopology(firedDigitsMask, minRow, rowSpan, minCol, colSpan, clsTopology);
352
353 // Construct and add cluster using scalar pattern mask
354 for (int i = 0; i < nMcLabels; i++) {
355 mLabels.addElement(nStoredCls, mLabelsBuff[i]);
356 }
357 Cluster cluster(minRow, minCol, rowSpan, colSpan, firedDigitsMask, clsTopology, chipID, clsTime);
358 LOG(debug) << "Pushing back cluster with row: " << minRow << ", col: " << minCol
359 << ", rowSpan: " << static_cast<int>(rowSpan) << ", colSpan: " << static_cast<int>(colSpan)
360 << ", pattern: " << firedDigitsMask << ", topology: " << static_cast<int>(clsTopology)
361 << ", chipID: " << chipID << ", time: " << clsTime;
362 mClusters.emplace_back(cluster);
363 mPatterns.emplace_back(static_cast<unsigned char>(firedDigitsMask));
364 }
365 }
366}
367
368//__________________________________________________
369void Clusterer::ClustererThread::fetchMCLabels(uint32_t digID, const ConstDigitTruth* labelsDig, int& nFilled)
370{
371 if (!labelsDig || digID >= labelsDig->getIndexedSize()) {
372 return;
373 }
374 auto sortBuffer = [this]() { std::sort(this->mLabelsBuff.begin(), this->mLabelsBuff.end(), [](Label const& a, Label const& b) { return a.getTrackID() < b.getTrackID(); }); };
375 for (const auto& label : labelsDig->getLabels(digID)) {
376 bool skip = false;
377 for (int ic = 0; ic < nFilled; ic++) {
378 if (mLabelsBuff[ic] == label) {
379 skip = true;
380 break;
381 }
382 }
383 if (!skip) {
384 if (nFilled < MaxLabels) {
385 mLabelsBuff[nFilled++] = label;
386 if (nFilled == MaxLabels) {
387 sortBuffer();
388 }
389 } else if (mLabelsBuff.back().getTrackID() > label.getTrackID()) {
390 mLabelsBuff.back() = label;
391 sortBuffer();
392 }
393 }
394 }
395}
396
397//__________________________________________________
398void Clusterer::ClustererThread::writeTopologiesToFile(const char* filename)
399{
400 mClsTopoClassifier.saveCacheToFile("TF3ClusterTopologies.root");
401}
402
403} // namespace o2::iotof
std::ostringstream debug
int16_t time
Definition RawEventData.h:4
int32_t i
std::vector< o2::MCCompLabel > labels
uint32_t j
Definition RawData.h:0
uint32_t col
Definition RawData.h:4
Definition of the IOTOF cluster finder.
gsl::span< const TruthElement > getLabels(uint32_t dataindex) const
void addNoLabelIndex(uint32_t dataindex)
adds a data index that has no label
void addElement(uint32_t dataindex, TruthElement const &element, bool noElement=false)
virtual void process(gsl::span< const Digit > digits, gsl::span< const DigROFRecord > digitROFs, std::vector< o2::iotof::Cluster > &clusters, std::vector< unsigned char > &patterns, std::vector< o2::itsmft::ROFRecord > &clusterROFs, const ConstDigitTruth *digitLabels=nullptr, ClusterTruth *clusterLabels=nullptr, gsl::span< const DigMC2ROFRecord > digMC2ROFs={}, std::vector< o2::itsmft::MC2ROFRecord > *clusterMC2ROFs=nullptr)
Definition Clusterer.cxx:26
std::vector< int > mSortIdx
reusable per-ROF sort buffer
Definition Clusterer.h:106
std::unique_ptr< ClustererThread > mThread
Definition Clusterer.h:105
GLint first
Definition glcorearb.h:399
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLuint GLsizei const GLchar * label
Definition glcorearb.h:2519
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
std::string filename()
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"
std::vector< Cluster > clusters
std::vector< Digit > digits
std::vector< int > row