Belle II Software development
SingleHoughSpaceFastInterceptFinder.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#include <tracking/vxdHoughTracking/findlets/SingleHoughSpaceFastInterceptFinder.h>
9#include <tracking/vxdHoughTracking/entities/VXDHoughState.h>
10#include <tracking/spacePointCreation/SpacePoint.h>
11#include <tracking/spacePointCreation/SpacePointTrackCand.h>
12#include <tracking/trackingUtilities/utilities/StringManipulation.h>
13#include <tracking/trackingUtilities/utilities/Algorithms.h>
14#include <vxd/dataobjects/VxdID.h>
15#include <framework/core/ModuleParamList.h>
16
17using namespace Belle2;
18using namespace TrackingUtilities;
19using namespace vxdHoughTracking;
20
24
25void SingleHoughSpaceFastInterceptFinder::exposeParameters(ModuleParamList* moduleParamList, const std::string& prefix)
26{
27 Super::exposeParameters(moduleParamList, prefix);
28
29 moduleParamList->addParameter(TrackingUtilities::prefixed(prefix, "maximumRecursionLevel"), m_maxRecursionLevel,
30 "Maximum recursion level for the fast Hough trafo algorithm.", m_maxRecursionLevel);
31
32 moduleParamList->addParameter(TrackingUtilities::prefixed(prefix, "nAngleSectors"), m_nAngleSectors,
33 "Number of angle sectors (= x-axis) dividing the Hough space.", m_nAngleSectors);
34
35 moduleParamList->addParameter(TrackingUtilities::prefixed(prefix, "nVerticalSectors"), m_nVerticalSectors,
36 "Number of vertical sectors (= y-axis) dividing the Hough space.", m_nVerticalSectors);
37
38 moduleParamList->addParameter(TrackingUtilities::prefixed(prefix, "verticalHoughSpaceSize"), m_verticalHoughSpaceSize,
39 "Vertical size of the Hough space.", m_verticalHoughSpaceSize);
40
41 moduleParamList->addParameter(TrackingUtilities::prefixed(prefix, "HoughSpaceMinimumX"), m_minimumX,
42 "Minimum x value of the Hough space.", m_minimumX);
43
44 moduleParamList->addParameter(TrackingUtilities::prefixed(prefix, "HoughSpaceMaximumX"), m_maximumX,
45 "Maximum x value of the Hough space.", m_maximumX);
46
47 moduleParamList->addParameter(TrackingUtilities::prefixed(prefix, "minimumHSClusterSize"), m_MinimumHSClusterSize,
48 "Maximum x value of the Hough space.", m_MinimumHSClusterSize);
49
50 moduleParamList->addParameter(TrackingUtilities::prefixed(prefix, "maximumHSClusterSize"), m_MaximumHSClusterSize,
51 "Maximum x value of the Hough space.", m_MaximumHSClusterSize);
52
53 moduleParamList->addParameter(TrackingUtilities::prefixed(prefix, "maximumHSClusterSizeX"), m_MaximumHSClusterSizeX,
54 "Maximum x value of the Hough space.", m_MaximumHSClusterSizeX);
55
56 moduleParamList->addParameter(TrackingUtilities::prefixed(prefix, "maximumHSClusterSizeY"), m_MaximumHSClusterSizeY,
57 "Maximum x value of the Hough space.", m_MaximumHSClusterSizeY);
58
59}
60
62{
64
65 const ushort maxRecursionLevelFromSectors = ceil(log2(std::max(m_nAngleSectors, m_nVerticalSectors))) - 1;
66 m_maxRecursionLevel = std::max(maxRecursionLevelFromSectors, m_maxRecursionLevel);
67 B2ASSERT("The maximum number of recursions (maximumRecursionLevel) must not be larger than " << c_maxAllowedRecusionLevel <<
68 ", but it is " << m_maxRecursionLevel <<
69 ", please choose a smaller value for maximumRecursionLevel, and / or for nAngleSectors and / or nVerticalSectors.",
71 B2ASSERT("The maximum number of angleSectors must not be larger than " << c_maxHSSectorNumber <<
72 ", but it is " << m_nAngleSectors << ", please choose a smaller value for nAngleSectors.",
74 B2ASSERT("The maximum number of verticalSectors must not be larger than " << c_maxHSSectorNumber <<
75 ", but it is " << m_nVerticalSectors << ", please choose a smaller value for nAngleSectors.",
77
80
81 for (ushort i = 0; i < m_nAngleSectors; i++) {
82 float x = m_minimumX + m_unitX * (float)i;
83 float xc = x + 0.5 * m_unitX;
84
85 m_HSSinValuesLUT[i] = sin(x);
86 m_HSCosValuesLUT[i] = cos(x);
87 m_HSCenterSinValuesLUT[i] = sin(xc);
88 m_HSCenterCosValuesLUT[i] = cos(xc);
89 }
92
93 // Reserve recursion buffers to reduce runtime allocations in the hot path.
94 // We reserve a modest amount — tune if you have a good upper bound for typical contained-hits per cell.
95 constexpr size_t reservePerLevel = 64;
96 for (ushort i = 0; i <= c_maxAllowedRecusionLevel; ++i) {
97 m_recursionBuffers[i].reserve(reservePerLevel);
98 m_recursionLayerHits[i].reset();
99 }
100
101 B2DEBUG(29, "HS size x: " << (m_maximumX - m_minimumX) << " HS size y: " << m_verticalHoughSpaceSize <<
102 " unitX: " << m_unitX << " unitY: " << m_unitY);
103}
104
105
106void SingleHoughSpaceFastInterceptFinder::apply(std::vector<VXDHoughState>& hits,
107 std::vector<std::vector<VXDHoughState*>>& rawTrackCandidates)
108{
109 m_trackCandidates.clear();
110 m_activeSectorsMap.clear();
112
113 const std::vector<VXDHoughState*> currentEventHitList = TrackingUtilities::as_pointers<VXDHoughState>(hits);
114
115 fastInterceptFinder2d(currentEventHitList, 0, m_nAngleSectors, 0, m_nVerticalSectors, 0);
116
118
119 for (auto& trackCand : m_trackCandidates) {
120 // sort for layer, and 2D radius in case of same layer before storing as SpacePointTrackCand
121 // outer hit goes first, as later on tracks are build from outside to inside
122 std::sort(trackCand.begin(), trackCand.end(),
123 [](const VXDHoughState * a, const VXDHoughState * b) {
124 return
125 (a->getDataCache().layer > b->getDataCache().layer) or
126 (a->getDataCache().layer == b->getDataCache().layer
127 and a->getHit()->getPosition().Perp2() > b->getHit()->getPosition().Perp2());
128 });
129
130 rawTrackCandidates.emplace_back(trackCand);
131 }
132
133 B2DEBUG(29, "m_trackCandidates.size: " << m_trackCandidates.size());
134
135}
136
137
138void SingleHoughSpaceFastInterceptFinder::fastInterceptFinder2d(const std::vector<VXDHoughState*>& hits,
139 const ushort xmin, const ushort xmax, const ushort ymin, const ushort ymax, const ushort currentRecursion)
140{
141 // Fast-paths
142 if (hits.empty()) return;
143 if (currentRecursion > m_maxRecursionLevel) return;
144
145 // these int-divisions can cause {min, center} or {center, max} to be the same, which is a desired behaviour
146 const ushort centerx = xmin + (ushort)((xmax - xmin) >> 1); // >> 1 equals a division by 2
147 const ushort centery = ymin + (ushort)((ymax - ymin) >> 1); // >> 1 equals a division by 2
148 const ushort xIndexCache[3] = {xmin, centerx, xmax};
149 const ushort yIndexCache[3] = {ymin, centery, ymax};
150
151 // Pointers to LUTs to avoid member lookup in the hot loop
152 const float* const sinLUT = m_HSSinValuesLUT.data();
153 const float* const cosLUT = m_HSCosValuesLUT.data();
154 const float* const sinCenterLUT = m_HSCenterSinValuesLUT.data();
155 const float* const cosCenterLUT = m_HSCenterCosValuesLUT.data();
156
157 // Use per-recursion temporary containers (preallocated) to avoid repeated allocations.
158 auto& containedHitsBuffer = m_recursionBuffers[currentRecursion];
159 auto& layerBitsBuffer = m_recursionLayerHits[currentRecursion];
160
161 for (int i = 0; i < 2 ; ++i) {
162 const ushort left = xIndexCache[i];
163 const ushort right = xIndexCache[i + 1];
164 const ushort localIndexX = left;
165
166 if (left == right) continue;
167
168 const float sinLeft = sinLUT[left];
169 const float cosLeft = cosLUT[left];
170 const float sinRight = sinLUT[right];
171 const float cosRight = cosLUT[right];
172
173 // the sin and cos of the current center can't be stored in a LUT for arbitrary centers,
174 // but we approximate via the center LUT as before — compute the center index once.
175 const ushort centerIndex = (left + right) >> 1;
176 const float sinCenter = sinCenterLUT[centerIndex];
177 const float cosCenter = cosCenterLUT[centerIndex];
178
179 // Precompute negated sines used for derivative calculations
180 const float negSinLeft = -sinLeft;
181 const float negSinRight = -sinRight;
182 const float negSinCenter = -sinCenter;
183
184 for (int j = 0; j < 2; ++j) {
185 const ushort lowerIndex = yIndexCache[j];
186 const ushort upperIndex = yIndexCache[j + 1];
187
188 if (lowerIndex == upperIndex) continue;
189
190 const ushort localIndexY = lowerIndex;
191 // Sector counting for y starts at positive values, i.e. the topmost sector has index 0,
192 // and the bottommost sector has the highest index
193 const float localUpperCoordinate = m_verticalHoughSpaceSize - m_unitY * lowerIndex;
194 const float localLowerCoordinate = m_verticalHoughSpaceSize - m_unitY * upperIndex;
195
196 // reset layerHits and containedHits for this sub-cell
197 layerBitsBuffer.reset();
198 containedHitsBuffer.clear();
199
200 // iterate over hits once for this sub-cell
201 for (VXDHoughState* hit : hits) {
202
203 const VXDHoughState::DataCache& hitData = hit->getDataCache();
204 const float m = hitData.xConformal;
205 const float a = hitData.yConformal;
206
207 // compute derivatives (use pre-negated sine)
208 const float derivativeyLeft = m * negSinLeft + a * cosLeft;
209 const float derivativeyRight = m * negSinRight + a * cosRight;
210 const float derivativeyCenter = m * negSinCenter + a * cosCenter;
211
212 // Only interested in the rising arm of the sinosoidal curves.
213 // Thus if derivative on both sides of the cell is negative, ignore and continue.
214 if (derivativeyLeft < 0.0f && derivativeyRight < 0.0f && derivativeyCenter < 0.0f) continue;
215
216 const float yLeft = m * cosLeft + a * sinLeft;
217 const float yRight = m * cosRight + a * sinRight;
218 const float yCenter = m * cosCenter + a * sinCenter;
219
220 /* Check if HS-parameter curve is inside (or outside) actual sub-HS */
221 if ((yLeft <= localUpperCoordinate && yRight >= localLowerCoordinate) ||
222 (yCenter <= localUpperCoordinate && yLeft >= localLowerCoordinate && yRight >= localLowerCoordinate) ||
223 (yCenter >= localLowerCoordinate && yLeft <= localUpperCoordinate && yRight <= localUpperCoordinate)) {
224 layerBitsBuffer[hitData.layer] = true;
225 containedHitsBuffer.emplace_back(hit);
226 }
227 }
228
229 if (layerFilter(layerBitsBuffer) > 0) {
230 // recursive call of fastInterceptFinder2d, until currentRecursion == m_maxRecursionLevel
231 if (currentRecursion < m_maxRecursionLevel) {
232 // Pass a copy view of containedHitsBuffer (vector already holds pointers)
233 fastInterceptFinder2d(containedHitsBuffer, left, right, lowerIndex, upperIndex, currentRecursion + 1);
234 } else {
235 // As usual in creating a 2D-array as 1D, calculate the global index as
236 // xIndex + yIndex * ySize
237 // A bit more complicated here though, since the y-axis is inverted on the fly.
238 // Instead of starting the coordinate system in the top-left corner, it has to start in the bottom-left corner
239 // for creating HS clusters from bottom to top and from left to right, so this becomes
240 // xIndex + ySize * (ySize - yIndex) (eq. 0)
241 const uint globalIndex = localIndexX + c_maxHSSectorNumber * (c_maxHSSectorNumber - localIndexY);
242 m_activeSectorsMap.insert({globalIndex, containedHitsBuffer});
243 m_activeSectorsIndices.push_back(globalIndex);
244 }
245 }
246 }
247 }
248}
249
250
252{
253 m_clusterCount = 1;
254
255 // Sort vector to create HS clusters from bottom left to top right
256 std::sort(m_activeSectorsIndices.begin(), m_activeSectorsIndices.end());
257
258 for (const uint& currentGlobalSectorIndex : m_activeSectorsIndices) {
259
260 const auto currentCellHits = m_activeSectorsMap.find(currentGlobalSectorIndex);
261 if (currentCellHits == m_activeSectorsMap.end()) {
262 continue;
263 }
264
265 // Get local (x, y) indices out of the globalSectorIndex by reverting (eq. 0)
266 // Bitwise and with c_xIndexBitMask (which is (c_maxHSSectorNumber - 1))
267 // (currentGlobalSectorIndex & c_xIndexBitMask)
268 // equals % c_maxHSSectorNumber, i.e.
269 // currentGlobalSectorIndex % c_maxHSSectorNumber.
270 // Bitshifting right by c_maxAllowedRecusionLevel, i.e.
271 // >> c_maxAllowedRecusionLevel
272 // equals a division by 2^c_maxAllowedRecusionLevel but is much faster.
273 m_clusterInitialPosition = std::make_pair((currentGlobalSectorIndex & c_xIndexBitMask),
274 c_maxHSSectorNumber - (currentGlobalSectorIndex >> c_maxAllowedRecusionLevel));
275 m_clusterSize = 1;
276
278 for (VXDHoughState* hit : currentCellHits->second) {
279 m_currentTrackCandidate.emplace_back(hit);
280 }
281 // this sector now has been used and the hits have been processed, so it can be removed from the map
282 m_activeSectorsMap.erase(currentGlobalSectorIndex);
283
284 // Check for HS sectors connected to each other which could form a cluster
285 DepthFirstSearch(currentGlobalSectorIndex);
286 // if cluster valid (i.e. not too small and not too big): finalize!
290 }
292 }
293}
294
295void SingleHoughSpaceFastInterceptFinder::DepthFirstSearch(const uint lastGlobalSectorIndex)
296{
298
299 // Get local (x, y) indices out of the globalSectorIndex by reverting (eq. 0)
300 // Bitwise and with c_xIndexBitMask (which is (c_maxHSSectorNumber - 1))
301 // (currentGlobalSectorIndex & c_xIndexBitMask)
302 // equals % c_maxHSSectorNumber, i.e.
303 // currentGlobalSectorIndex % c_maxHSSectorNumber.
304 // Bitshifting right by c_maxAllowedRecusionLevel, i.e.
305 // >> c_maxAllowedRecusionLevel
306 // equals a division by 2^c_maxAllowedRecusionLevel but is much faster.
307 const ushort lastLocalIndexX = (lastGlobalSectorIndex & c_xIndexBitMask);
308 const ushort lastLocalIndexY = c_maxHSSectorNumber - (lastGlobalSectorIndex >> c_maxAllowedRecusionLevel);
309
310 // For the iterative / recursive search, just check the direct neighbours in x and y direction
311 for (ushort currentLocalIndexY = lastLocalIndexY; currentLocalIndexY >= lastLocalIndexY - 1; currentLocalIndexY--) {
312 if (std::abs(static_cast<short>(m_clusterInitialPosition.second) - static_cast<short>(currentLocalIndexY)) >=
314 or m_clusterSize >= m_MaximumHSClusterSize or currentLocalIndexY > m_nVerticalSectors) {
315 return;
316 }
317
318 for (ushort currentLocalIndexX = lastLocalIndexX; currentLocalIndexX <= lastLocalIndexX + 1; currentLocalIndexX++) {
319 if (std::abs(static_cast<short>(m_clusterInitialPosition.first) - static_cast<short>(currentLocalIndexX)) >= m_MaximumHSClusterSizeX
320 or m_clusterSize >= m_MaximumHSClusterSize or currentLocalIndexX > m_nAngleSectors) {
321 return;
322 }
323
324 // Calculate the global index for this sector by applying (eq. 0)
325 const uint currentGlobalSectorIndex = currentLocalIndexX + c_maxHSSectorNumber * (c_maxHSSectorNumber - currentLocalIndexY);
326 // If currentGlobalSectorIndex == lastGlobalSectorIndex, the sector in check is the "parent" we came from
327 // and thus has already been checked, so continue
328 if (currentGlobalSectorIndex == lastGlobalSectorIndex) {
329 continue;
330 }
331
332 // first check bounds to avoid out-of-bound array access
333 // as they are uints, they are always >= 0, and in case of an overflow they would be too large
334 if (currentLocalIndexX < m_nAngleSectors and currentLocalIndexY < m_nVerticalSectors) {
335
336 const auto activeSectorHits = m_activeSectorsMap.find(currentGlobalSectorIndex);
337 // Only continue searching if the current cluster is smaller than the maximum cluster size
338 if (activeSectorHits != m_activeSectorsMap.end()) {
340
341 // No need to check whether currentGlobalSectorIndex exists as a key in m_activeSectorsMap as they were
342 // created at the same time so it's certain the key exists.
343 for (VXDHoughState* hit : activeSectorHits->second) {
344 if (not TrackingUtilities::is_in(hit, m_currentTrackCandidate)) {
345 m_currentTrackCandidate.emplace_back(hit);
346 }
347 }
348 // this sector now has been used and the hits have been processed, so it can be removed from the map
349 m_activeSectorsMap.erase(currentGlobalSectorIndex);
350
351 // search in the next Hough Space cells...
352 DepthFirstSearch(currentGlobalSectorIndex);
353 }
354 }
355 }
356 }
357}
The Module parameter list class.
virtual void exposeParameters(ModuleParamList *moduleParamList, const std::string &prefix)
std::unordered_map< uint, std::vector< VXDHoughState * > > m_activeSectorsMap
Map containing only active HS sectors, i.e.
ushort m_maxRecursionLevel
maximum number of recursive calls of FastInterceptFinder2d
static unsigned short layerFilter(const std::bitset< 8 > &layer)
layer filter, checks if at least hits from 3 layers are in a set of hits
std::array< std::vector< VXDHoughState * >, c_maxAllowedRecusionLevel+1 > m_recursionBuffers
per-recursion temporary buffers to hold the hits contained in a sub-cell (allocated once and reused)
std::array< float, c_maxLUTSize > m_HSSinValuesLUT
Look-Up-Tables for values as cache to speed up calculation sine values of the Hough Space sector boar...
std::vector< uint > m_activeSectorsIndices
Cache the global indices of the active sectors for sorted access.
std::vector< std::vector< VXDHoughState * > > m_trackCandidates
vector containing track candidates, consisting of the found intersection values in the Hough Space
ushort m_nVerticalSectors
number of sectors of the Hough Space on the vertical axis
float m_minimumX
minimum x value of the Hough Space, defaults to the value for u-side
std::pair< ushort, ushort > m_clusterInitialPosition
start cell of the recursive cluster finding in the Hough Space
std::array< float, c_maxHSSectorNumber > m_HSCenterSinValuesLUT
sine values of the Hough Space sector center coordinates
TrackingUtilities::Findlet< VXDHoughState, std::vector< VXDHoughState * > > Super
Parent class.
std::array< std::bitset< 8 >, c_maxAllowedRecusionLevel+1 > m_recursionLayerHits
per-recursion layer bitsets (reused)
void fastInterceptFinder2d(const std::vector< VXDHoughState * > &hits, const ushort xmin, const ushort xmax, const ushort ymin, const ushort ymax, const ushort currentRecursion)
find intercepts in the 2D Hough Space by recursively calling itself until no hits are assigned to a g...
ushort m_MaximumHSClusterSizeX
maximum cluster size in x of sectors belonging to intercepts in the Hough Space
ushort m_MaximumHSClusterSize
maximum cluster size of sectors belonging to intercepts in the Hough Space
static const ushort c_maxAllowedRecusionLevel
Define some magic numbers Maximum allowed recursion level.
ushort m_nAngleSectors
number of sectors of the Hough Space on the horizontal axis
static constexpr ushort c_maxHSSectorNumber
Maximum number of HS sectors in x and y, also the size of some of the the Look-Up-Tables (LUTs) below...
std::array< float, c_maxLUTSize > m_HSCosValuesLUT
cosine values of the Hough Space sector boarder coordinates
std::array< float, c_maxHSSectorNumber > m_HSCenterCosValuesLUT
cosine values of the Hough Space sector center coordinates
std::vector< VXDHoughState * > m_currentTrackCandidate
the current track candidate
ushort m_MinimumHSClusterSize
minimum cluster size of sectors belonging to intercepts in the Hough Space
void exposeParameters(ModuleParamList *moduleParamList, const std::string &prefix) override
Expose the parameters of the sub findlets.
void apply(std::vector< VXDHoughState > &hits, std::vector< std::vector< VXDHoughState * > > &rawTrackCandidates) override
Load in the prepared hits and create track candidates for further processing like hit filtering and f...
float m_verticalHoughSpaceSize
vertical size of the Hough Space, defaults to the value for u-side
static constexpr ushort c_xIndexBitMask
Bit mask for coordinate transformation, creates all-ones for the coordinate system conversion.
float m_maximumX
maximum x value of the Hough Space, defaults to the value for u-side
void DepthFirstSearch(const uint lastGlobalSectorIndex)
Perform depth first search recursive algorithm to find clusters in the Hough Space.
ushort m_MaximumHSClusterSizeY
maximum cluster size in y of sectors belonging to intercepts in the Hough Space
Simple container for hit information to be used during intercept finding.
void addParameter(const std::string &name, T &paramVariable, const std::string &description, const T &defaultValue)
Adds a new parameter to the module list.
Abstract base class for different kinds of events.
Cache containing the most important information of this state which will often be needed.
float yConformal
conformal transformed y coordinate of this hit
unsigned short layer
Geometrical Layer this state is based on.
float xConformal
conformal transformed x coordinate of this hit