File indexing completed on 2026-03-28 07:45:51
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "ActsExamples/TrackFinding/HoughTransformSeeder.hpp"
0010
0011 #include "Acts/Definitions/Algebra.hpp"
0012 #include "Acts/Definitions/Common.hpp"
0013 #include "Acts/Definitions/TrackParametrization.hpp"
0014 #include "Acts/Geometry/TrackingGeometry.hpp"
0015 #include "Acts/Surfaces/Surface.hpp"
0016 #include "Acts/Utilities/Enumerate.hpp"
0017 #include "ActsExamples/EventData/GeometryContainers.hpp"
0018 #include "ActsExamples/EventData/Index.hpp"
0019 #include "ActsExamples/EventData/IndexSourceLink.hpp"
0020 #include "ActsExamples/EventData/Measurement.hpp"
0021 #include "ActsExamples/EventData/ProtoTrack.hpp"
0022 #include "ActsExamples/EventData/SpacePoint.hpp"
0023 #include "ActsExamples/Framework/AlgorithmContext.hpp"
0024 #include "ActsExamples/TrackFinding/DefaultHoughFunctions.hpp"
0025 #include "ActsExamples/Utilities/GroupBy.hpp"
0026
0027 #include <algorithm>
0028 #include <cmath>
0029 #include <iterator>
0030 #include <ostream>
0031 #include <stdexcept>
0032
0033 namespace ActsExamples {
0034
0035 static inline int quant(double min, double max, unsigned nSteps, double val);
0036 static inline double unquant(double min, double max, unsigned nSteps, int step);
0037 template <typename T>
0038 static inline std::string to_string(std::vector<T> v);
0039
0040 thread_local std::vector<std::shared_ptr<HoughMeasurementStruct>>
0041 houghMeasurementStructs;
0042
0043 HoughTransformSeeder::HoughTransformSeeder(
0044 const Config& cfg, std::unique_ptr<const Acts::Logger> logger)
0045 : IAlgorithm("HoughTransformSeeder", std::move(logger)), m_cfg(cfg) {
0046
0047
0048 if (m_cfg.inputMeasurements.empty() && m_cfg.inputSpacePoints.empty()) {
0049 throw std::invalid_argument(
0050 "HoughTransformSeeder: Missing some kind of input (measurements of "
0051 "space points)");
0052 }
0053
0054 if (m_cfg.outputProtoTracks.empty()) {
0055 throw std::invalid_argument(
0056 "HoughTransformSeeder: Missing hough tracks output collection");
0057 }
0058 if (m_cfg.outputSeeds.empty()) {
0059 throw std::invalid_argument(
0060 "HoughTransformSeeder: Missing hough track seeds output collection");
0061 }
0062
0063 m_inputMeasurements.maybeInitialize(m_cfg.inputMeasurements);
0064 m_inputSpacePoints.maybeInitialize(m_cfg.inputSpacePoints);
0065 m_outputProtoTracks.initialize(m_cfg.outputProtoTracks);
0066
0067 if (!m_cfg.trackingGeometry) {
0068 throw std::invalid_argument(
0069 "HoughTransformSeeder: Missing tracking geometry");
0070 }
0071
0072 if (m_cfg.geometrySelection.empty()) {
0073 throw std::invalid_argument(
0074 "HoughTransformSeeder: Missing geometry selection");
0075 }
0076
0077 for (const auto& geoId : m_cfg.geometrySelection) {
0078 if ((geoId.approach() != 0u) || (geoId.boundary() != 0u) ||
0079 (geoId.sensitive() != 0u)) {
0080 throw std::invalid_argument(
0081 "HoughTransformSeeder: Invalid geometry selection: only volume and "
0082 "layer are allowed to be set");
0083 }
0084 }
0085
0086
0087
0088
0089
0090 auto isDuplicate = [](Acts::GeometryIdentifier ref,
0091 Acts::GeometryIdentifier cmp) {
0092
0093
0094 if (ref.volume() == 0) {
0095 return true;
0096 }
0097
0098 if (ref.volume() != cmp.volume()) {
0099 return false;
0100 }
0101
0102 return (ref.layer() == cmp.layer());
0103 };
0104
0105 std::ranges::sort(m_cfg.geometrySelection,
0106 std::less<Acts::GeometryIdentifier>{});
0107 auto geoSelBeg = m_cfg.geometrySelection.begin();
0108 auto geoSelEnd = m_cfg.geometrySelection.end();
0109 auto geoSelLastUnique = std::unique(geoSelBeg, geoSelEnd, isDuplicate);
0110 if (geoSelLastUnique != geoSelEnd) {
0111 ACTS_LOG_WITH_LOGGER(this->logger(), Acts::Logging::WARNING,
0112 "Removed "
0113 << std::distance(geoSelLastUnique, geoSelEnd)
0114 << " geometry selection duplicates");
0115 m_cfg.geometrySelection.erase(geoSelLastUnique, geoSelEnd);
0116 }
0117 ACTS_LOG_WITH_LOGGER(this->logger(), Acts::Logging::INFO,
0118 "Hough geometry selection:");
0119 for (const auto& geoId : m_cfg.geometrySelection) {
0120 ACTS_LOG_WITH_LOGGER(this->logger(), Acts::Logging::INFO, " " << geoId);
0121 }
0122
0123
0124 m_step_x = (m_cfg.xMax - m_cfg.xMin) / m_cfg.houghHistSize_x;
0125 m_step_y = (m_cfg.yMax - m_cfg.yMin) / m_cfg.houghHistSize_y;
0126 for (unsigned i = 0; i <= m_cfg.houghHistSize_x; i++) {
0127 m_bins_x.push_back(
0128 unquant(m_cfg.xMin, m_cfg.xMax, m_cfg.houghHistSize_x, i));
0129 }
0130 for (unsigned i = 0; i <= m_cfg.houghHistSize_y; i++) {
0131 m_bins_y.push_back(
0132 unquant(m_cfg.yMin, m_cfg.yMax, m_cfg.houghHistSize_y, i));
0133 }
0134
0135 m_cfg.fieldCorrector
0136 .connect<&DefaultHoughFunctions::fieldCorrectionDefault>();
0137 m_cfg.layerIDFinder.connect<&DefaultHoughFunctions::findLayerIDDefault>();
0138 m_cfg.sliceTester.connect<&DefaultHoughFunctions::inSliceDefault>();
0139 }
0140
0141 ProcessCode HoughTransformSeeder::execute(const AlgorithmContext& ctx) const {
0142
0143 houghMeasurementStructs.clear();
0144
0145
0146 addSpacePoints(ctx);
0147
0148
0149 addMeasurements(ctx);
0150
0151 static thread_local ProtoTrackContainer protoTracks;
0152 protoTracks.clear();
0153
0154
0155 for (int subregion : m_cfg.subRegions) {
0156 ACTS_DEBUG("Processing subregion " << subregion);
0157 HoughHist m_houghHist = createHoughHist(subregion);
0158
0159 for (unsigned y = 0; y < m_cfg.houghHistSize_y; y++) {
0160 for (unsigned x = 0; x < m_cfg.houghHistSize_x; x++) {
0161 if (!passThreshold(m_houghHist, x, y)) {
0162 continue;
0163 }
0164
0165
0166
0167
0168
0169 std::vector<std::vector<std::vector<Index>>> hitIndicesAll(
0170 m_cfg.nLayers);
0171 std::vector<std::size_t> nHitsPerLayer(m_cfg.nLayers);
0172 for (auto measurementIndex : m_houghHist.atLocalBins({y, x}).second) {
0173 HoughMeasurementStruct* meas =
0174 houghMeasurementStructs[measurementIndex].get();
0175 hitIndicesAll[meas->layer].push_back(meas->indices);
0176 nHitsPerLayer[meas->layer]++;
0177 }
0178
0179 std::vector<std::vector<int>> combs = getComboIndices(nHitsPerLayer);
0180
0181
0182 for (auto [icomb, hit_indices] : Acts::enumerate(combs)) {
0183 ProtoTrack protoTrack;
0184 for (unsigned layer = 0; layer < m_cfg.nLayers; layer++) {
0185 if (hit_indices[layer] >= 0) {
0186 for (auto index : hitIndicesAll[layer][hit_indices[layer]]) {
0187 protoTrack.push_back(index);
0188 }
0189 }
0190 }
0191 protoTracks.push_back(protoTrack);
0192 }
0193 }
0194 }
0195 }
0196 ACTS_DEBUG("Created " << protoTracks.size() << " proto track");
0197
0198 m_outputProtoTracks(ctx, ProtoTrackContainer{protoTracks});
0199
0200 houghMeasurementStructs.clear();
0201 return ProcessCode::SUCCESS;
0202 }
0203
0204 HoughHist HoughTransformSeeder::createLayerHoughHist(unsigned layer,
0205 int subregion) const {
0206 HoughHist houghHist(Axis(0, m_cfg.houghHistSize_y, m_cfg.houghHistSize_y),
0207 Axis(0, m_cfg.houghHistSize_x, m_cfg.houghHistSize_x));
0208 for (unsigned index = 0; index < houghMeasurementStructs.size(); index++) {
0209 HoughMeasurementStruct* meas = houghMeasurementStructs[index].get();
0210 if (meas->layer != layer) {
0211 continue;
0212 }
0213 if (!m_cfg.sliceTester(meas->z, meas->layer, subregion).value()) {
0214 continue;
0215 }
0216
0217
0218 for (unsigned y_ = 0; y_ < m_cfg.houghHistSize_y; y_++) {
0219 unsigned y_bin_min = y_;
0220 unsigned y_bin_max = (y_ + 1);
0221
0222
0223 auto xBins =
0224 yToXBins(y_bin_min, y_bin_max, meas->radius, meas->phi, meas->layer);
0225
0226 for (unsigned y = y_bin_min; y < y_bin_max; y++) {
0227 for (unsigned x = xBins.first; x < xBins.second; x++) {
0228 houghHist.atLocalBins({y, x}).first++;
0229 houghHist.atLocalBins({y, x}).second.insert(index);
0230 }
0231 }
0232 }
0233 }
0234
0235 return houghHist;
0236 }
0237
0238 HoughHist HoughTransformSeeder::createHoughHist(int subregion) const {
0239 HoughHist houghHist(Axis(0, m_cfg.houghHistSize_y, m_cfg.houghHistSize_y),
0240 Axis(0, m_cfg.houghHistSize_x, m_cfg.houghHistSize_x));
0241
0242 for (unsigned i = 0; i < m_cfg.nLayers; i++) {
0243 HoughHist layerHoughHist = createLayerHoughHist(i, subregion);
0244 for (unsigned x = 0; x < m_cfg.houghHistSize_x; ++x) {
0245 for (unsigned y = 0; y < m_cfg.houghHistSize_y; ++y) {
0246 if (layerHoughHist.atLocalBins({y, x}).first > 0) {
0247 houghHist.atLocalBins({y, x}).first++;
0248 houghHist.atLocalBins({y, x}).second.insert(
0249 layerHoughHist.atLocalBins({y, x}).second.begin(),
0250 layerHoughHist.atLocalBins({y, x}).second.end());
0251 }
0252 }
0253 }
0254 }
0255
0256 return houghHist;
0257 }
0258
0259 bool HoughTransformSeeder::passThreshold(HoughHist const& houghHist, unsigned x,
0260 unsigned y) const {
0261
0262 unsigned width = m_cfg.threshold.size() / 2;
0263 if (x < width || m_cfg.houghHistSize_x - x < width) {
0264 return false;
0265 }
0266 for (unsigned i = 0; i < m_cfg.threshold.size(); i++) {
0267 if (houghHist.atLocalBins({y, x - width + i}).first < m_cfg.threshold[i]) {
0268 return false;
0269 }
0270 }
0271
0272
0273 if (m_cfg.localMaxWindowSize != 0) {
0274 for (int j = -m_cfg.localMaxWindowSize; j <= m_cfg.localMaxWindowSize;
0275 j++) {
0276 for (int i = -m_cfg.localMaxWindowSize; i <= m_cfg.localMaxWindowSize;
0277 i++) {
0278 if (i == 0 && j == 0) {
0279 continue;
0280 }
0281 if (y + j < m_cfg.houghHistSize_y && x + i < m_cfg.houghHistSize_x) {
0282 if (houghHist.atLocalBins({y + j, x + i}).first >
0283 houghHist.atLocalBins({y, x}).first) {
0284 return false;
0285 }
0286 if (houghHist.atLocalBins({y + j, x + i}).first ==
0287 houghHist.atLocalBins({y, x}).first) {
0288 if (houghHist.atLocalBins({y + j, x + i}).second.size() >
0289 houghHist.atLocalBins({y, x}).second.size()) {
0290 return false;
0291 }
0292 if (houghHist.atLocalBins({y + j, x + i}).second.size() ==
0293 houghHist.atLocalBins({y, x}).second.size() &&
0294 j <= 0 && i <= 0) {
0295 return false;
0296 }
0297 }
0298 }
0299 }
0300 }
0301 }
0302
0303 return true;
0304 }
0305
0306
0307
0308
0309
0310
0311 static inline int quant(double min, double max, unsigned nSteps, double val) {
0312 return static_cast<int>((val - min) / (max - min) * nSteps);
0313 }
0314
0315
0316 static inline double unquant(double min, double max, unsigned nSteps,
0317 int step) {
0318 return min + (max - min) * step / nSteps;
0319 }
0320
0321 template <typename T>
0322 static inline std::string to_string(std::vector<T> v) {
0323 std::ostringstream oss;
0324 oss << "[";
0325 if (!v.empty()) {
0326 std::copy(v.begin(), v.end() - 1, std::ostream_iterator<T>(oss, ", "));
0327 oss << v.back();
0328 }
0329 oss << "]";
0330 return oss.str();
0331 }
0332
0333 double HoughTransformSeeder::yToX(double y, double r, double phi) const {
0334 double d0 = 0;
0335 double x = asin(r * HoughTransformSeeder::m_cfg.kA * y - d0 / r) + phi;
0336
0337 if (m_cfg.fieldCorrector.connected()) {
0338 x += (m_cfg.fieldCorrector(0, y, r)).value();
0339 }
0340
0341 return x;
0342 }
0343
0344
0345
0346
0347 std::pair<unsigned, unsigned> HoughTransformSeeder::yToXBins(
0348 std::size_t yBin_min, std::size_t yBin_max, double r, double phi,
0349 unsigned layer) const {
0350 double x_min = yToX(m_bins_y[yBin_min], r, phi);
0351 double x_max = yToX(m_bins_y[yBin_max], r, phi);
0352 if (x_min > x_max) {
0353 std::swap(x_min, x_max);
0354 }
0355 if (x_max < m_cfg.xMin || x_min > m_cfg.xMax) {
0356 return {0, 0};
0357 }
0358
0359
0360 int x_bin_min = quant(m_cfg.xMin, m_cfg.xMax, m_cfg.houghHistSize_x, x_min);
0361 int x_bin_max = quant(m_cfg.xMin, m_cfg.xMax, m_cfg.houghHistSize_x, x_max) +
0362 1;
0363
0364
0365 unsigned extend = getExtension(yBin_min, layer);
0366 x_bin_min -= extend;
0367 x_bin_max += extend;
0368
0369
0370 if (x_bin_min < 0) {
0371 x_bin_min = 0;
0372 }
0373 if (x_bin_max > static_cast<int>(m_cfg.houghHistSize_x)) {
0374 x_bin_max = m_cfg.houghHistSize_x;
0375 }
0376
0377 return {x_bin_min, x_bin_max};
0378 }
0379
0380
0381
0382 unsigned HoughTransformSeeder::getExtension(unsigned y, unsigned layer) const {
0383 if (m_cfg.hitExtend_x.size() == m_cfg.nLayers) {
0384 return m_cfg.hitExtend_x[layer];
0385 }
0386
0387 if (m_cfg.hitExtend_x.size() == m_cfg.nLayers * 2) {
0388
0389
0390
0391 if (y < m_cfg.houghHistSize_y / 4 || y > 3 * m_cfg.houghHistSize_y / 4) {
0392 return m_cfg.hitExtend_x[layer];
0393 }
0394
0395 return m_cfg.hitExtend_x[m_cfg.nLayers + layer];
0396 }
0397 return 0;
0398 }
0399
0400
0401
0402
0403
0404
0405
0406
0407
0408
0409
0410
0411
0412
0413
0414
0415 std::vector<std::vector<int>> HoughTransformSeeder::getComboIndices(
0416 std::vector<std::size_t>& sizes) const {
0417 std::size_t nCombs = 1;
0418 std::vector<std::size_t> nCombs_prior(sizes.size());
0419 std::vector<int> temp(sizes.size(), 0);
0420
0421 for (std::size_t i = 0; i < sizes.size(); i++) {
0422 if (sizes[i] > 0) {
0423 nCombs_prior[i] = nCombs;
0424 nCombs *= sizes[i];
0425 } else {
0426 temp[i] = -1;
0427 }
0428 }
0429
0430 std::vector<std::vector<int>> combos(nCombs, temp);
0431
0432 for (std::size_t icomb = 0; icomb < nCombs; icomb++) {
0433 std::size_t index = icomb;
0434 for (std::size_t isize = sizes.size() - 1; isize < sizes.size(); isize--) {
0435 if (sizes[isize] == 0) {
0436 continue;
0437 }
0438 combos[icomb][isize] = static_cast<int>(index / nCombs_prior[isize]);
0439 index = index % nCombs_prior[isize];
0440 }
0441 }
0442
0443 return combos;
0444 }
0445
0446 void HoughTransformSeeder::addSpacePoints(const AlgorithmContext& ctx) const {
0447 if (m_cfg.inputSpacePoints.empty()) {
0448 return;
0449 }
0450 const SpacePointContainer& spacePoints = m_inputSpacePoints(ctx);
0451
0452
0453
0454 ACTS_DEBUG("Inserting " << spacePoints.size() << " space points from "
0455 << m_inputSpacePoints.key());
0456 for (ConstSpacePointProxy sp : spacePoints) {
0457 const float z = sp.z();
0458 const float r = sp.r();
0459 const float phi = std::atan2(sp.y(), sp.x());
0460 ResultUnsigned hitlayer = m_cfg.layerIDFinder(r).value();
0461 if (!hitlayer.ok()) {
0462 continue;
0463 }
0464 std::vector<Index> indices;
0465 for (const auto& slink : sp.sourceLinks()) {
0466 const auto& islink = slink.get<IndexSourceLink>();
0467 indices.push_back(islink.index());
0468 }
0469
0470 auto meas =
0471 std::shared_ptr<HoughMeasurementStruct>(new HoughMeasurementStruct(
0472 hitlayer.value(), phi, r, z, indices, HoughHitType::SP));
0473 houghMeasurementStructs.push_back(meas);
0474 }
0475 }
0476
0477 void HoughTransformSeeder::addMeasurements(const AlgorithmContext& ctx) const {
0478 const auto& measurements = m_inputMeasurements(ctx);
0479
0480 ACTS_DEBUG("Inserting " << measurements.size() << " space points from "
0481 << m_cfg.inputMeasurements);
0482
0483 for (Acts::GeometryIdentifier geoId : m_cfg.geometrySelection) {
0484
0485 auto range =
0486 selectLowestNonZeroGeometryObject(measurements.orderedIndices(), geoId);
0487
0488
0489 auto groupedByModule = makeGroupBy(range, detail::GeometryIdGetter());
0490
0491 for (const auto& [moduleGeoId, moduleSourceLinks] : groupedByModule) {
0492
0493 const Acts::Surface* surface =
0494 m_cfg.trackingGeometry->findSurface(moduleGeoId);
0495 if (surface == nullptr) {
0496 ACTS_ERROR("Could not find surface " << moduleGeoId);
0497 return;
0498 }
0499
0500 for (auto& sourceLink : moduleSourceLinks) {
0501
0502
0503
0504
0505
0506
0507 const ConstVariableBoundMeasurementProxy measurement =
0508 measurements.getMeasurement(sourceLink.index());
0509
0510 assert(measurement.contains(Acts::eBoundLoc0) &&
0511 "Measurement does not contain the required bound loc0");
0512 assert(measurement.contains(Acts::eBoundLoc1) &&
0513 "Measurement does not contain the required bound loc1");
0514
0515 auto boundLoc0 = measurement.indexOf(Acts::eBoundLoc0);
0516 auto boundLoc1 = measurement.indexOf(Acts::eBoundLoc1);
0517
0518 Acts::Vector2 localPos{measurement.parameters()[boundLoc0],
0519 measurement.parameters()[boundLoc1]};
0520
0521
0522 Acts::Vector3 globalFakeMom(1, 1, 1);
0523 Acts::Vector3 globalPos =
0524 surface->localToGlobal(ctx.geoContext, localPos, globalFakeMom);
0525 double r = globalPos.head<2>().norm();
0526 double phi = std::atan2(globalPos[Acts::ePos1], globalPos[Acts::ePos0]);
0527 double z = globalPos[Acts::ePos2];
0528 ResultUnsigned hitlayer = m_cfg.layerIDFinder(r);
0529 if (hitlayer.ok()) {
0530 std::vector<Index> index;
0531 index.push_back(sourceLink.index());
0532 auto houghMeas = std::shared_ptr<HoughMeasurementStruct>(
0533 new HoughMeasurementStruct(hitlayer.value(), phi, r, z, index,
0534 HoughHitType::MEASUREMENT));
0535 houghMeasurementStructs.push_back(houghMeas);
0536 }
0537 }
0538 }
0539 }
0540 }
0541
0542 }