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
17
18#include <algorithm>
19#include <numeric>
20
21namespace o2::trk
22{
23
24//__________________________________________________
25void Clusterer::process(gsl::span<const Digit> digits,
26 gsl::span<const DigROFRecord> digitROFs,
27 std::vector<o2::trk::Cluster>& clusters,
28 std::vector<unsigned char>& patterns,
29 std::vector<o2::trk::ROFRecord>& clusterROFs,
30 const ConstDigitTruth* digitLabels,
31 ClusterTruth* clusterLabels,
32 gsl::span<const DigMC2ROFRecord> digMC2ROFs,
33 std::vector<o2::trk::MC2ROFRecord>* clusterMC2ROFs)
34{
35 if (!mThread) {
36 mThread = std::make_unique<ClustererThread>(this);
37 }
38
40
41 for (size_t iROF = 0; iROF < digitROFs.size(); ++iROF) {
42 const auto& inROF = digitROFs[iROF];
43 const auto outFirst = static_cast<int>(clusters.size());
44 const int first = inROF.getFirstEntry();
45 const int nEntries = inROF.getNEntries();
46
47 if (nEntries == 0) {
48 clusterROFs.emplace_back(inROF.getBCData(), inROF.getROFrame(), outFirst, 0);
49 continue;
50 }
51
52 // Sort digit indices within this ROF by (chipID, col, row) so we can process
53 // chip by chip, column by column -- the same ordering the ALPIDE scanner expects.
54 mSortIdx.resize(nEntries);
55 std::iota(mSortIdx.begin(), mSortIdx.end(), first);
56 std::sort(mSortIdx.begin(), mSortIdx.end(), [&digits](int a, int b) {
57 const auto& da = digits[a];
58 const auto& db = digits[b];
59 if (da.getChipIndex() != db.getChipIndex()) {
60 return da.getChipIndex() < db.getChipIndex();
61 }
62 if (da.getColumn() != db.getColumn()) {
63 return da.getColumn() < db.getColumn();
64 }
65 return da.getRow() < db.getRow();
66 });
67
68 // Process one chip at a time
69 int sliceStart = 0;
70 while (sliceStart < nEntries) {
71 const int chipFirst = sliceStart;
72 const uint16_t chipID = digits[mSortIdx[sliceStart]].getChipIndex();
73 while (sliceStart < nEntries && digits[mSortIdx[sliceStart]].getChipIndex() == chipID) {
74 ++sliceStart;
75 }
76 const int chipN = sliceStart - chipFirst;
77
78 mThread->processChip(digits, chipFirst, chipN, &clusters, &patterns, digitLabels, clusterLabels, geom);
79 }
80
81 clusterROFs.emplace_back(inROF.getBCData(), inROF.getROFrame(),
82 outFirst, static_cast<int>(clusters.size()) - outFirst);
83 }
84
85 if (clusterMC2ROFs && !digMC2ROFs.empty()) {
86 clusterMC2ROFs->reserve(clusterMC2ROFs->size() + digMC2ROFs.size());
87 for (const auto& in : digMC2ROFs) {
88 clusterMC2ROFs->emplace_back(in.eventRecordID, in.rofRecordID, in.minROF, in.maxROF);
89 }
90 }
91}
92
93//__________________________________________________
94void Clusterer::ClustererThread::processChip(gsl::span<const Digit> digits,
95 int chipFirst, int chipN,
96 std::vector<Cluster>* clustersOut,
97 std::vector<unsigned char>* patternsOut,
98 const ConstDigitTruth* labelsDigPtr,
99 ClusterTruth* labelsClusPtr,
100 GeometryTGeo* geom)
101{
102 // chipFirst and chipN are relative to mSortIdx (i.e. mSortIdx[chipFirst..chipFirst+chipN-1]
103 // are the global digit indices for this chip, already sorted by col then row).
104 // We use parent->mSortIdx to resolve the global index of each pixel.
105 const auto& sortIdx = parent->mSortIdx;
106
107 if (chipN == 1) {
108 finishChipSingleHitFast(digits, sortIdx[chipFirst], labelsDigPtr, labelsClusPtr, geom);
109 } else {
110 initChip(digits, sortIdx[chipFirst], geom);
111 for (int i = chipFirst + 1; i < chipFirst + chipN; ++i) {
112 updateChip(digits, sortIdx[i]);
113 }
114 finishChip(digits, labelsDigPtr, labelsClusPtr, geom);
115 }
116
117 // Flush per-thread output into the caller's containers
118 if (!clusters.empty()) {
119 clustersOut->insert(clustersOut->end(), clusters.begin(), clusters.end());
120 clusters.clear();
121 }
122 if (!patterns.empty()) {
123 patternsOut->insert(patternsOut->end(), patterns.begin(), patterns.end());
124 patterns.clear();
125 }
126 if (labelsClusPtr && labels.getNElements()) {
127 labelsClusPtr->mergeAtBack(labels);
128 labels.clear();
129 }
130}
131
132//__________________________________________________
133void Clusterer::ClustererThread::initChip(gsl::span<const Digit> digits, uint32_t first, GeometryTGeo* geom)
134{
135 const uint16_t chipID = digits[first].getChipIndex();
136
137 // Determine the number of rows for this chip's sensor type
138 size = constants::moduleMLOT::chip::nRows + 2; // default for ML/OT
139 if (geom) {
140 if (geom->getSubDetID(chipID) == 0) { // VD
141 const int layer = geom->getLayer(chipID);
142 size = constants::VD::petal::layer::nRows[layer] + 2;
143 }
144 }
145
146 delete[] column1;
147 delete[] column2;
148 column1 = new int[size];
149 column2 = new int[size];
150 column1[0] = column1[size - 1] = -1;
151 column2[0] = column2[size - 1] = -1;
152 prev = column1 + 1;
153 curr = column2 + 1;
154 resetColumn(curr);
155
156 pixels.clear();
157 preClusterHeads.clear();
158 preClusterIndices.clear();
159
160 const auto& pix = digits[first];
161 currCol = pix.getColumn();
162 curr[pix.getRow()] = 0;
163 preClusterHeads.push_back(0);
164 preClusterIndices.push_back(0);
165 pixels.emplace_back(-1, first);
166 noLeftCol = true;
167}
168
169//__________________________________________________
170void Clusterer::ClustererThread::updateChip(gsl::span<const Digit> digits, uint32_t ip)
171{
172 const auto& pix = digits[ip];
173 uint16_t row = pix.getRow();
174
175 if (currCol != pix.getColumn()) {
176 swapColumnBuffers();
177 resetColumn(curr);
178 noLeftCol = false;
179 if (pix.getColumn() > currCol + 1) {
180 // gap: no connection with previous column
181 currCol = pix.getColumn();
182 addNewPreCluster(ip, row);
183 noLeftCol = true;
184 return;
185 }
186 currCol = pix.getColumn();
187 }
188
189 bool orphan = true;
190
191 if (noLeftCol) {
192 if (curr[row - 1] >= 0) {
193 expandPreCluster(ip, row, curr[row - 1]);
194 return;
195 }
196 } else {
197#ifdef _ALLOW_DIAGONAL_TRK_CLUSTERS_
198 int neighbours[]{curr[row - 1], prev[row], prev[row + 1], prev[row - 1]};
199#else
200 int neighbours[]{curr[row - 1], prev[row]};
201#endif
202 for (auto pci : neighbours) {
203 if (pci < 0) {
204 continue;
205 }
206 if (orphan) {
207 expandPreCluster(ip, row, pci);
208 orphan = false;
209 continue;
210 }
211 // merge two pre-clusters: assign the smaller index to both
212 if (preClusterIndices[pci] < preClusterIndices[curr[row]]) {
213 preClusterIndices[curr[row]] = preClusterIndices[pci];
214 } else {
215 preClusterIndices[pci] = preClusterIndices[curr[row]];
216 }
217 }
218 }
219 if (orphan) {
220 addNewPreCluster(ip, row);
221 }
222}
223
224//__________________________________________________
225void Clusterer::ClustererThread::finishChip(gsl::span<const Digit> digits,
226 const ConstDigitTruth* labelsDigPtr,
227 ClusterTruth* labelsClusPtr,
228 GeometryTGeo* geom)
229{
230 const uint16_t chipID = digits[pixels[0].second].getChipIndex();
231
232 for (size_t i1 = 0; i1 < preClusterHeads.size(); ++i1) {
233 auto ci = preClusterIndices[i1];
234 if (ci < 0) {
235 continue;
236 }
237 BBox bbox(chipID);
238 int nlab = 0;
239 uint32_t totalCharge = 0;
240 pixArrBuff.clear();
241
242 // Walk the linked list for this pre-cluster head
243 auto collectPixels = [&](int head) {
244 int next = head;
245 while (next >= 0) {
246 const auto& pixEntry = pixels[next];
247 const auto& d = digits[pixEntry.second];
248 uint16_t r = d.getRow(), c = d.getColumn();
249 pixArrBuff.emplace_back(r, c);
250 bbox.adjust(r, c);
251 totalCharge += d.getCharge();
252 if (labelsClusPtr) {
253 fetchMCLabels(pixEntry.second, labelsDigPtr, nlab);
254 }
255 next = pixEntry.first;
256 }
257 };
258
259 collectPixels(preClusterHeads[i1]);
260 preClusterIndices[i1] = -1;
261
262 for (size_t i2 = i1 + 1; i2 < preClusterHeads.size(); ++i2) {
263 if (preClusterIndices[i2] != ci) {
264 continue;
265 }
266 collectPixels(preClusterHeads[i2]);
267 preClusterIndices[i2] = -1;
268 }
269
270 // Determine geometry info
271 int subDetID = -1, layer = -1, disk = -1;
272 if (geom) {
273 subDetID = geom->getSubDetID(chipID);
274 layer = geom->getLayer(chipID);
275 disk = geom->getDisk(chipID);
276 }
277
278 const bool doLabels = (labelsClusPtr != nullptr);
279 if (bbox.isAcceptableSize()) {
280 streamCluster(bbox, pixArrBuff, totalCharge, doLabels, nlab, chipID, subDetID, layer, disk);
281 } else {
282 // Huge cluster: split into MaxRowSpan x MaxColSpan tiles (same as ITS3)
283 auto warnLeft = MaxHugeClusWarn - parent->mNHugeClus;
284 if (warnLeft > 0) {
285 LOGP(warn, "Splitting huge TRK cluster: chipID {}, rows {}:{} cols {}:{}{}",
286 chipID, bbox.rowMin, bbox.rowMax, bbox.colMin, bbox.colMax,
287 warnLeft == 1 ? " (further warnings muted)" : "");
288 parent->mNHugeClus++;
289 }
290 BBox bboxT(chipID);
291 bboxT.colMin = bbox.colMin;
292 do {
293 bboxT.rowMin = bbox.rowMin;
294 bboxT.colMax = std::min(bbox.colMax, uint16_t(bboxT.colMin + o2::itsmft::ClusterPattern::MaxColSpan - 1));
295 do {
296 bboxT.rowMax = std::min(bbox.rowMax, uint16_t(bboxT.rowMin + o2::itsmft::ClusterPattern::MaxRowSpan - 1));
297 std::vector<std::pair<uint16_t, uint16_t>> subPix;
298 uint32_t subCharge = 0;
299 for (const auto& [r, c] : pixArrBuff) {
300 if (bboxT.isInside(r, c)) {
301 subPix.emplace_back(r, c);
302 subCharge += 1;
303 }
304 }
305 if (!subPix.empty()) {
306 streamCluster(bboxT, subPix, subCharge, doLabels, nlab, chipID, subDetID, layer, disk);
307 }
308 bboxT.rowMin = bboxT.rowMax + 1;
309 } while (bboxT.rowMin <= bbox.rowMax);
310 bboxT.colMin = bboxT.colMax + 1;
311 } while (bboxT.colMin <= bbox.colMax);
312 }
313 }
314 // flush per-thread output to the caller via processChip
315}
316
317//__________________________________________________
318void Clusterer::ClustererThread::finishChipSingleHitFast(gsl::span<const Digit> digits, uint32_t hit,
319 const ConstDigitTruth* labelsDigPtr,
320 ClusterTruth* labelsClusPtr,
321 GeometryTGeo* geom)
322{
323 const auto& d = digits[hit];
324 const uint16_t chipID = d.getChipIndex();
325 const uint16_t row = d.getRow();
326 const uint16_t col = d.getColumn();
327
328 if (labelsClusPtr) {
329 int nlab = 0;
330 fetchMCLabels(hit, labelsDigPtr, nlab);
331 const auto cnt = static_cast<uint32_t>(clusters.size());
332 for (int i = nlab; i--;) {
333 labels.addElement(cnt, labelsBuff[i]);
334 }
335 }
336
337 // 1×1 pattern: rowSpan=1, colSpan=1, one byte = 0x80
338 patterns.emplace_back(1);
339 patterns.emplace_back(1);
340 patterns.emplace_back(0x80);
341
342 Cluster cluster;
343 cluster.chipID = chipID;
344 cluster.row = row;
345 cluster.col = col;
346 cluster.size = 1;
347 if (geom) {
348 cluster.subDetID = geom->getSubDetID(chipID);
349 cluster.layer = geom->getLayer(chipID);
350 cluster.disk = geom->getDisk(chipID);
351 }
352 clusters.emplace_back(cluster);
353}
354
355//__________________________________________________
356void Clusterer::ClustererThread::streamCluster(const BBox& bbox,
357 const std::vector<std::pair<uint16_t, uint16_t>>& pixbuf,
358 uint32_t totalCharge,
359 bool doLabels, int nlab,
360 uint16_t chipID, int subDetID, int layer, int disk)
361{
362 if (doLabels) {
363 const auto cnt = static_cast<uint32_t>(clusters.size());
364 for (int i = nlab; i--;) {
365 labels.addElement(cnt, labelsBuff[i]); // accumulate in thread-local buffer
366 }
367 }
368
369 const uint16_t rowSpanW = bbox.rowSpan();
370 const uint16_t colSpanW = bbox.colSpan();
371
372 // Encode the pixel pattern bitmap (rowSpan, colSpan, bytes...)
373 std::array<unsigned char, o2::itsmft::ClusterPattern::MaxPatternBytes> patt{};
374 for (const auto& [r, c] : pixbuf) {
375 uint32_t ir = r - bbox.rowMin, ic = c - bbox.colMin;
376 int nbit = ir * colSpanW + ic;
377 patt[nbit >> 3] |= (0x1 << (7 - (nbit % 8)));
378 }
379 patterns.emplace_back(static_cast<unsigned char>(rowSpanW));
380 patterns.emplace_back(static_cast<unsigned char>(colSpanW));
381 int nBytes = (rowSpanW * colSpanW + 7) / 8;
382 patterns.insert(patterns.end(), patt.begin(), patt.begin() + nBytes);
383
384 Cluster cluster;
385 cluster.chipID = chipID;
386 cluster.row = bbox.rowMin;
387 cluster.col = bbox.colMin;
388 cluster.size = static_cast<uint16_t>(pixbuf.size());
389 cluster.subDetID = static_cast<int16_t>(subDetID);
390 cluster.layer = static_cast<int16_t>(layer);
391 cluster.disk = static_cast<int16_t>(disk);
392 clusters.emplace_back(cluster);
393}
394
395//__________________________________________________
396void Clusterer::ClustererThread::fetchMCLabels(uint32_t digID, const ConstDigitTruth* labelsDig, int& nfilled)
397{
398 if (nfilled >= MaxLabels) {
399 return;
400 }
401 if (!labelsDig || digID >= labelsDig->getIndexedSize()) {
402 return;
403 }
404 const auto& lbls = labelsDig->getLabels(digID);
405 for (int i = lbls.size(); i--;) {
406 int ic = nfilled;
407 for (; ic--;) {
408 if (labelsBuff[ic] == lbls[i]) {
409 return; // already present
410 }
411 }
412 labelsBuff[nfilled++] = lbls[i];
413 if (nfilled >= MaxLabels) {
414 break;
415 }
416 }
417}
418
419} // namespace o2::trk
std::vector< std::string > labels
int32_t i
int collectPixels(int which, int N, double *xyDxy, double *q)
uint32_t col
Definition RawData.h:4
uint32_t c
Definition RawData.h:2
Definition of the TRK cluster finder.
gsl::span< const TruthElement > getLabels(uint32_t dataindex) const
void mergeAtBack(MCTruthContainer< TruthElement > const &other)
static constexpr uint8_t MaxRowSpan
static constexpr uint8_t MaxColSpan
void process(gsl::span< const Digit > digits, gsl::span< const DigROFRecord > digitROFs, std::vector< o2::trk::Cluster > &clusters, std::vector< unsigned char > &patterns, std::vector< o2::trk::ROFRecord > &clusterROFs, const ConstDigitTruth *digitLabels=nullptr, ClusterTruth *clusterLabels=nullptr, gsl::span< const DigMC2ROFRecord > digMC2ROFs={}, std::vector< o2::trk::MC2ROFRecord > *clusterMC2ROFs=nullptr)
Definition Clusterer.cxx:25
int getSubDetID(int index) const
int getLayer(int index) const
int getDisk(int index) const
static GeometryTGeo * Instance()
GLsizeiptr size
Definition glcorearb.h:659
GLint first
Definition glcorearb.h:399
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLint GLint GLsizei GLint GLenum GLenum const void * pixels
Definition glcorearb.h:275
GLenum GLuint GLint GLint layer
Definition glcorearb.h:1310
GLboolean r
Definition glcorearb.h:1233
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
int16_t layer
Definition Cluster.h:28
uint16_t size
Definition Cluster.h:26
uint16_t row
Definition Cluster.h:24
uint16_t col
Definition Cluster.h:25
int16_t subDetID
Definition Cluster.h:27
int16_t disk
Definition Cluster.h:29
uint16_t chipID
Definition Cluster.h:23
bool isInside(uint16_t r, uint16_t c) const
Definition Clusterer.h:62
o2::InteractionRecord ir(0, 0)
std::vector< Cluster > clusters
std::vector< Digit > digits
std::vector< int > row