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