Belle II Software development
NDFinder.cc
1/**************************************************************************
2 * basf2 (Belle II Analysis Software Framework) *
3 * Author: The Belle II Collaboration *
4 * *
5 * See git log for contributors and copyright holders. *
6 * This file is licensed under LGPL-3.0, see LICENSE.md. *
7 **************************************************************************/
8
9#include "trg/cdc/NDFinder.h"
10
11#include <string>
12#include <vector>
13#include <array>
14#include <utility>
15#include <string>
16#include <fstream>
17#include <set>
18#include <cmath>
19#include <algorithm>
20#include <cstdint>
21
22#include <TMath.h>
23
24#include "framework/logging/Logger.h"
25
26using namespace Belle2;
27
28// Run the necessary initialization of the NDFinder
29void NDFinder::init(const NDFinderParameters& ndFinderParameters)
30{
31 m_ndFinderParams = ndFinderParameters;
32
33 // Initialization of the Hough space
34 const std::array<c3index, 3> shapeHough = {{m_nOmega, m_nPhi, m_nCot}};
35 m_houghSpace = new c3array(shapeHough);
36
37 // Fill hit to sector LUT
38 initHitToSectorMap();
39
40 // Load the axial and stereo track to hit relations from file.
41 bool useDB = m_ndFinderParams.axialFile.empty() && m_ndFinderParams.stereoFile.empty();
42 std::vector<unsigned short> axialArray;
43 std::vector<unsigned short> stereoArray;
44 if (useDB) {
45 if (!m_LUTsConditionsDB) {
46 B2FATAL("CDCTriggerNDFinderLUTs payload not available and no LUT files provided");
47 }
48 const CDCTriggerNDFinderLUTs& luts = *m_LUTsConditionsDB;
49 axialArray = luts.axial;
50 stereoArray = luts.stereo;
51 } else {
52 axialArray = loadFromFile(m_ndFinderParams.axialFile);
53 stereoArray = loadFromFile(m_ndFinderParams.stereoFile);
54 }
55 fillCompressedHitReps(axialArray, m_compAxialBins, m_compAxialHitReps);
56 fillCompressedHitReps(stereoArray, m_compStereoBins, m_compStereoHitReps);
57
58 // Fills the expanded hit representations (from compressed hits to weights)
59 fillExpandedHitReps(m_compAxialBins, m_compAxialHitReps, m_expAxialHitReps);
60 fillExpandedHitReps(m_compStereoBins, m_compStereoHitReps, m_expStereoHitReps);
61
62 // Reset the NDFinder data structure to process next event
63 reset();
64
65 // Parameters necessary for the peak finding algorithm
66 PeakFindingParameters peakFindingParams = {
67 ndFinderParameters.iterations,
68 ndFinderParameters.omegaTrim,
69 ndFinderParameters.phiTrim,
70 m_nOmega,
71 m_nPhi,
72 m_nCot
73 };
74 m_peakFinder = NDFinderPeakFinder(peakFindingParams);
75}
76
77// Add the hit info of a single track segment to the NDFinder
78void NDFinder::addHit(const HitInfo& hitInfo)
79{
80 m_hitIDs.push_back(hitInfo.hitID);
81 m_hitSLIDs.push_back(hitInfo.hitSLID);
82 m_priorityWireTime.push_back(hitInfo.hitPrioTime);
83 ++m_nHits;
84}
85
86// Fills the m_hitToSectorIDs array
87void NDFinder::initHitToSectorMap()
88{
89 // Number of first priority wires in each super layer (TS per SL)
90 constexpr std::array<unsigned short, 9> nWires = {160, 160, 192, 224, 256, 288, 320, 352, 384};
91 // Number of priority wires (= number of TS) per SL in a single (1/32) phi sector
92 std::vector<unsigned short> wiresPerSector;
93 // Lookup table: Maps the TS id to the SL number
94 std::vector<unsigned short> hitToSuperLayer;
95 // Integrated number of priority wires for each SL
96 std::vector<unsigned short> cumulativeWires = {0};
97 // Integrated number of sector priority wires for each SL (Axial even, Stereo odd)
98 std::vector<unsigned short> cumulativeSectorWires = {0, 0};
99 for (unsigned short superLayer = 0; superLayer < m_nSL; ++superLayer) {
100 wiresPerSector.push_back(nWires[superLayer] / m_phiGeo);
101 for (unsigned short _ = 0; _ < nWires[superLayer]; ++_) {
102 hitToSuperLayer.push_back(superLayer);
103 }
104 cumulativeWires.push_back(cumulativeWires[superLayer] + nWires[superLayer]);
105 cumulativeSectorWires.push_back(cumulativeSectorWires[superLayer] + nWires[superLayer] / m_phiGeo);
106 }
107 for (unsigned short hit = 0; hit < m_nTS; ++hit) {
108 unsigned short superLayer = hitToSuperLayer[hit];
109 bool isAxial = (superLayer % 2 == 0);
110 unsigned short wireIDinSL = hit - cumulativeWires[superLayer];
111 unsigned short wireIDinSector = wireIDinSL % wiresPerSector[superLayer];
112 unsigned short relativeWireIDinSector = cumulativeSectorWires[superLayer] + wireIDinSector;
113 unsigned short relativeSectorIDinSuperLayer = static_cast<unsigned short>(floor(wireIDinSL / wiresPerSector[superLayer]));
114 m_hitToSectorIDs[hit][0] = static_cast<unsigned short>(isAxial);
115 m_hitToSectorIDs[hit][1] = relativeWireIDinSector;
116 m_hitToSectorIDs[hit][2] = relativeSectorIDinSuperLayer;
117 }
118}
119
120// Fills the m_compAxialHitReps/m_compStereoHitReps (see below) arrays with the hit representations (hits to weights)
121void NDFinder::fillCompressedHitReps(const std::vector<unsigned short>& flatArray, const SectorBinning& compBins,
122 c4array& compHitsToWeights) const
123{
124 size_t arrayIndex = 0;
125 for (c4index hitID = 0; hitID < compBins.nHitIDs; ++hitID) {
126 for (c4index omegaIdx = 0; omegaIdx < compBins.omega; ++omegaIdx) {
127 for (c4index phiIdx = 0; phiIdx < compBins.phi; ++phiIdx) {
128 for (c4index cotIdx = 0; cotIdx < compBins.cot; ++cotIdx) {
129 compHitsToWeights[hitID][omegaIdx][phiIdx][cotIdx] =
130 flatArray[arrayIndex++];
131 }
132 }
133 }
134 }
135}
136
137// Loads the hit representations from a plain text file if the ConditionsDB is not used
138std::vector<unsigned short> NDFinder::loadFromFile(const std::string& fileName) const
139{
140 std::vector<unsigned short> flatArray;
141 std::ifstream arrayFile(fileName);
142 if (!arrayFile) {
143 B2ERROR("Could not open file: " << fileName);
144 return flatArray;
145 }
146 unsigned short value;
147 while (arrayFile >> value) {
148 flatArray.push_back(value);
149 }
150 return flatArray;
151}
152
153// Fills the m_expAxialHitReps/m_expStereoHitReps arrays
154void NDFinder::fillExpandedHitReps(const SectorBinning& compBins, const c4array& compHitsToWeights, c4array& expHitsToWeights) const
155{
156 for (c4index hitID = 0; hitID < compBins.nHitIDs; ++hitID) {
157 for (c4index omegaIdx = 0; omegaIdx < compBins.omega; ++omegaIdx) {
158 for (c4index cotIdx = 0; cotIdx < compBins.cot; ++cotIdx) {
159 unsigned short phiStart = compHitsToWeights[hitID][omegaIdx][0][cotIdx];
160 unsigned short nPhiEntries = compHitsToWeights[hitID][omegaIdx][1][cotIdx];
161 for (c4index phiEntry = 0; phiEntry < nPhiEntries; ++phiEntry) {
162 unsigned short houghPhiIdx = (phiStart + phiEntry) % m_nPhi; // houghPhiIdx goes now over the complete Hough space
163 expHitsToWeights[hitID][omegaIdx][houghPhiIdx][cotIdx] =
164 compHitsToWeights[hitID][omegaIdx][phiEntry + 2][cotIdx];
165 if (compBins.cot == 1) { // Axial case: expand the same curve in all cot bins
166 for (c4index axialCotIdx = 1; axialCotIdx < m_nCot; ++axialCotIdx) {
167 expHitsToWeights[hitID][omegaIdx][houghPhiIdx][axialCotIdx] =
168 compHitsToWeights[hitID][omegaIdx][phiEntry + 2][cotIdx];
169 }
170 }
171 }
172 }
173 }
174 }
175}
176
177// Reset the NDFinder data structure to process next event
178void NDFinder::reset()
179{
180 // Clear the vector of the found tracks
181 m_rawFinderTracks.clear();
182
183 // Clear the hit informations
184 m_hitIDs.clear();
185 m_hitSLIDs.clear();
186 m_nHits = 0;
187 m_priorityWireTime.clear();
188
189 // Create a new Hough space
190 delete m_houghSpace;
191 std::array<c3index, 3> shapeHough = {{m_nOmega, m_nPhi, m_nCot}};
192 m_houghSpace = new c3array(shapeHough);
193}
194
195// Public: Adds the hits to the Hough space and finds the tracks
196void NDFinder::findTracks()
197{
198 // Build the Hough plane by summing up all single hit contributions
199 for (unsigned short hitIdx = 0; hitIdx < m_nHits; ++hitIdx) {
200 processHitForHoughSpace(hitIdx);
201 }
202 runTrackFinding();
203}
204
205// Process a single axial or stereo hit
206void NDFinder::processHitForHoughSpace(const unsigned short hitIdx)
207{
208 unsigned short orientation = m_hitToSectorIDs[m_hitIDs[hitIdx]][0];
209 unsigned short relativeWireID = m_hitToSectorIDs[m_hitIDs[hitIdx]][1];
210 unsigned short relativeSectorID = m_hitToSectorIDs[m_hitIDs[hitIdx]][2];
211 unsigned short phiSectorStart = relativeSectorID * m_nPhiSector;
212
213 if (orientation == 1) {
214 writeHitToHoughSpace(relativeWireID, phiSectorStart, m_expAxialHitReps);
215 } else {
216 writeHitToHoughSpace(relativeWireID, phiSectorStart, m_expStereoHitReps);
217 }
218}
219
220// Write (add) a single hit (Hough curve) to the Hough space
221void NDFinder::writeHitToHoughSpace(const unsigned short relativeWireID, const unsigned short phiSectorStart,
222 const c4array& expHitsToWeights)
223{
224 c3array& houghSpace = *m_houghSpace;
225 for (unsigned short cotIdx = 0; cotIdx < m_nCot; ++cotIdx) {
226 for (unsigned short omegaIdx = 0; omegaIdx < m_nOmega; ++omegaIdx) {
227 for (unsigned short phiIdx = 0; phiIdx < m_nPhi; ++phiIdx) {
228 unsigned short houghPhiIdx = (phiIdx + phiSectorStart) % m_nPhi;
229 houghSpace[omegaIdx][houghPhiIdx][cotIdx] +=
230 expHitsToWeights[relativeWireID][omegaIdx][phiIdx][cotIdx];
231 }
232 }
233 }
234}
235
236// Core track finding logic in the constructed Hough space
237void NDFinder::runTrackFinding()
238{
239 c3array& houghSpace = *m_houghSpace;
240 m_peakFinder.setNewPlane(houghSpace);
241 std::vector<HoughPeak> allPeaks = m_peakFinder.findPeaks();
242 std::vector<HoughPeak> validPeaks = relateHitsToPeaks(allPeaks);
243
244 for (HoughPeak& peak : validPeaks) {
245 cell_index peakCell = peak.cell;
246 std::array<double, 3> estimatedParameters = getTrackParameterEstimate(peakCell);
247
248 // Readout of the complete Hough space
249 std::vector<uint8_t> readoutHoughSpace;
250 if (m_ndFinderParams.storeHoughSpace) {
251 readoutHoughSpace.reserve(m_nOmega * m_nPhi * m_nCot);
252 readoutHoughSpace.assign(houghSpace.data(), houghSpace.data() + houghSpace.num_elements());
253 }
254
255 m_rawFinderTracks.push_back(RawFinderTrack(estimatedParameters, std::move(peak), std::move(readoutHoughSpace)));
256 }
257}
258
259// Relate the hits in the peak cell of the peak. Applies a cut on the found peaks.
260std::vector<HoughPeak> NDFinder::relateHitsToPeaks(std::vector<HoughPeak>& peaks) const
261{
262 std::vector<std::vector<unsigned short>> hitsVsPeaks = getHitsVsPeaksTable(peaks);
263 std::vector<HoughPeak> goodPeaks;
264 if (hitsVsPeaks.empty()) return goodPeaks;
265
266 for (unsigned short peakIdx = 0; peakIdx < hitsVsPeaks.size(); ++peakIdx) {
267 std::vector<ContributionInfo> contributionInfos = extractContributionInfos(hitsVsPeaks[peakIdx]);
268 std::vector<unsigned short> peakHits;
269 for (unsigned short superLayer = 0; superLayer < 9; ++superLayer) {
270 int maximumHit = getMaximumHitInSuperLayer(contributionInfos, superLayer);
271 if (maximumHit >= 0) { // there exists a hit
272 peakHits.push_back(static_cast<unsigned short>(maximumHit));
273 }
274 }
275 peaks[peakIdx].hits = peakHits;
276 if (checkHitSuperLayers(peaks[peakIdx])) {
277 goodPeaks.push_back(peaks[peakIdx]);
278 }
279 }
280 return goodPeaks;
281}
282
283// Create hits to peaks confusion matrix
284std::vector<std::vector<unsigned short>> NDFinder::getHitsVsPeaksTable(const std::vector<HoughPeak>& peaks) const
285{
286 // Creates a (Number Peaks)x(Number Hits) matrix
287 std::vector<unsigned short> hitElem(m_nHits, 0);
288 std::vector<std::vector<unsigned short>> hitsVsPeaks(peaks.size(), hitElem);
289 // Fill the matrix with all the hit contributions
290 for (unsigned short peakIdx = 0; peakIdx < peaks.size(); ++peakIdx) {
291 HoughPeak peak = peaks[peakIdx];
292 cell_index peakCell = peak.cell;
293 for (unsigned short hitIdx = 0; hitIdx < m_hitIDs.size(); ++hitIdx) {
294 unsigned short contribution = getHitContribution(peakCell, hitIdx);
295 hitsVsPeaks[peakIdx][hitIdx] = contribution;
296 }
297 }
298 return hitsVsPeaks;
299}
300
301// Returns the hit contribution of a TS at a certain maximum cell
302unsigned short NDFinder::getHitContribution(const cell_index& peakCell, const unsigned short hitIdx) const
303{
304 unsigned short contribution = 0;
305 unsigned short omegaIdx = peakCell[0];
306 unsigned short houghPhiIdx = peakCell[1];
307 unsigned short cotIdx = peakCell[2];
308
309 unsigned short orientation = m_hitToSectorIDs[m_hitIDs[hitIdx]][0];
310 unsigned short relativeWireID = m_hitToSectorIDs[m_hitIDs[hitIdx]][1];
311 unsigned short relativeSectorID = m_hitToSectorIDs[m_hitIDs[hitIdx]][2];
312 unsigned short phiSectorStart = relativeSectorID * m_nPhiSector;
313
314 // Inverse Hough transformation (inverse of writeHitToHoughSpace method)
315 unsigned short phiIdx = (houghPhiIdx - phiSectorStart + m_nPhi) % m_nPhi;
316
317 if (orientation == 1) { // Axial TS
318 contribution = m_expAxialHitReps[relativeWireID][omegaIdx][phiIdx][cotIdx];
319 } else { // Stereo TS
320 contribution = m_expStereoHitReps[relativeWireID][omegaIdx][phiIdx][cotIdx];
321 }
322
323 return contribution;
324}
325
326// Extract relevant hit information (hitIdx, contribution, super layer, drift time)
327std::vector<NDFinder::ContributionInfo> NDFinder::extractContributionInfos(const std::vector<unsigned short>& peakHits) const
328{
329 std::vector<ContributionInfo> contributionInfos;
330 for (unsigned short hitIdx = 0; hitIdx < m_hitIDs.size(); ++hitIdx) {
331 unsigned short contribution = peakHits[hitIdx];
332 if (contribution > 0) {
333 ContributionInfo contributionInfo = {
334 hitIdx,
335 contribution,
336 m_hitSLIDs[hitIdx],
337 m_priorityWireTime[hitIdx]
338 };
339 contributionInfos.push_back(contributionInfo);
340 }
341 }
342 return contributionInfos;
343}
344
345// Find the hit with the maximum contribution in a given super layer
346int NDFinder::getMaximumHitInSuperLayer(const std::vector<ContributionInfo>& contributionInfos, unsigned short superLayer) const
347{
348 std::vector<std::vector<int>> contributionsInSL;
349 for (const ContributionInfo& contributionInfo : contributionInfos) {
350 if (contributionInfo.superLayer == superLayer) {
351 unsigned short hitIdx = contributionInfo.hitIndex;
352 unsigned short contribution = contributionInfo.contribution;
353 short priorityTime = contributionInfo.priorityTime;
354 contributionsInSL.push_back({hitIdx, contribution, priorityTime});
355 }
356 }
357 if (contributionsInSL.empty()) return -1;
358 // Sort by drift time
359 std::sort(contributionsInSL.begin(), contributionsInSL.end(),
360 [](const std::vector<int>& a, const std::vector<int>& b) { return a[2] < b[2]; });
361 // Find max contribution
362 int maximumHit = contributionsInSL[0][0];
363 int maximumContribution = contributionsInSL[0][1];
364 for (const auto& hit : contributionsInSL) {
365 if (hit[1] > maximumContribution) {
366 maximumHit = hit[0];
367 maximumContribution = hit[1];
368 }
369 }
370 return maximumHit;
371}
372
373// Cut on the number of hit axial/stereo super layers
374bool NDFinder::checkHitSuperLayers(const HoughPeak& peak) const
375{
376 std::vector<unsigned short> peakHits = peak.hits;
377 std::set<unsigned short> uniqueSLNumbers;
378 // Add all hit super layers
379 for (unsigned short hitIdx : peakHits) {
380 uniqueSLNumbers.insert(m_hitSLIDs[hitIdx]);
381 }
382 // Calculate the number of hit axial and stereo super layers
383 unsigned short nSL = uniqueSLNumbers.size();
384 uniqueSLNumbers.insert({0, 2, 4, 6, 8});
385 unsigned short withAxialSLs = uniqueSLNumbers.size();
386 unsigned short axialNumber = 5 - (withAxialSLs - nSL);
387 unsigned short stereoNumber = nSL - axialNumber;
388 // Cut away all peaks that do have enough hit super layers
389 bool isValid = axialNumber >= m_ndFinderParams.minSuperAxial && stereoNumber >= m_ndFinderParams.minSuperStereo;
390 return isValid;
391}
392
393// Transform the center of gravity (of cells) into the estimated track parameters
394std::array<double, 3> NDFinder::getTrackParameterEstimate(const cell_index& peakCell) const
395{
396 std::array<double, 3> estimatedParameters;
397 for (unsigned short dimension = 0; dimension < 3; ++dimension) {
398 double trackParameter = m_acceptanceRanges[dimension][0] + (static_cast<double>(peakCell[dimension]) + 0.5) * m_binSizes[dimension];
399 estimatedParameters[dimension] = trackParameter;
400 }
401 return transformTrackParameters(estimatedParameters);
402}
403
404// Transform to physical units
405std::array<double, 3> NDFinder::transformTrackParameters(const std::array<double, 3>& estimatedParameters) const
406{
407 std::array<double, 3> transformed;
408 // Omega
409 if (estimatedParameters[0] == 0.) {
410 transformed[0] = estimatedParameters[0];
411 } else { // omega = sign(q)/r, r in cm
412 transformed[0] = -1 / getTrackRadius(1. / estimatedParameters[0]);
413 }
414 // Phi
415 if (estimatedParameters[1] > 180) {
416 transformed[1] = (estimatedParameters[1] - 360) * TMath::DegToRad();
417 } else {
418 transformed[1] = (estimatedParameters[1]) * TMath::DegToRad();
419 }
420 // Cot
421 transformed[2] = estimatedParameters[2];
422 return transformed;
423}
bool isValid(EForwardBackward eForwardBackward)
Check whether the given enum instance is one of the valid values.
Abstract base class for different kinds of events.