Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-03-28 07:45:51

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
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   // require space points or input measurements (or both), but at least one kind
0047   // of input
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   // ensure geometry selection contains only valid inputs
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   // remove geometry selection duplicates
0086   //
0087   // the geometry selections must be mutually exclusive, i.e. if we have a
0088   // selection that contains both a volume and a layer within that same volume,
0089   // we would create the space points for the layer twice.
0090   auto isDuplicate = [](Acts::GeometryIdentifier ref,
0091                         Acts::GeometryIdentifier cmp) {
0092     // code assumes ref < cmp and that only volume and layer can be non-zero
0093     // root node always contains everything
0094     if (ref.volume() == 0) {
0095       return true;
0096     }
0097     // unequal volumes always means separate hierarchies
0098     if (ref.volume() != cmp.volume()) {
0099       return false;
0100     }
0101     // within the same volume hierarchy only consider layers
0102     return (ref.layer() == cmp.layer());
0103   };
0104   // sort geometry selection so the unique filtering works
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   // Fill convenience variables
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   // clear our Hough measurements out from the previous iteration, if at all
0143   houghMeasurementStructs.clear();
0144 
0145   // add SPs to the inputs
0146   addSpacePoints(ctx);
0147 
0148   // add ACTS measurements
0149   addMeasurements(ctx);
0150 
0151   static thread_local ProtoTrackContainer protoTracks;
0152   protoTracks.clear();
0153 
0154   // loop over our subregions and run the Hough Transform on each
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         // Now we need to unpack the hits; there should be multiple track
0166         // candidates if we have multiple hits in a given layer. So the first
0167         // thing is to unpack the indices (which is what we need) by layer
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         // Loop over all combinations.
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   // clear the vector
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     // This scans over y (pT) because that is more efficient in memory
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       // Find the min/max x bins
0223       auto xBins =
0224           yToXBins(y_bin_min, y_bin_max, meas->radius, meas->phi, meas->layer);
0225       // Update the houghHist
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   // Pass window threshold
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   // Pass local-maximum check, if used
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;  // favor bottom-left (low phi, low neg q/pt)
0296             }
0297           }
0298         }
0299       }
0300     }
0301   }
0302 
0303   return true;
0304 }
0305 
0306 ///////////////////////////////////////////////////////////////////////////////
0307 // Helpers
0308 
0309 // Quantizes val, given a range [min, max) split into nSteps. Returns the bin
0310 // below.
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 // Returns the lower bound of the bin specified by step
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;  // d0 correction TO DO allow for this
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 // Find the min/max x bins of the hit's line, in each y bin. Max is exclusive.
0345 // Note this assumes yToX is monotonic. Returns {0, 0} if hit lies out of
0346 // bounds.
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};  // out of bounds
0357   }
0358 
0359   // Get bins
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;  // exclusive
0363 
0364   // Extend bins
0365   unsigned extend = getExtension(yBin_min, layer);
0366   x_bin_min -= extend;
0367   x_bin_max += extend;
0368 
0369   // Clamp bins
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 // We allow variable extension based on the size of m_hitExtend_x. See comments
0381 // below.
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     // different extension for low pt vs high pt, split in half but irrespective
0389     // of sign first nLayers entries of m_hitExtend_x is for low pt half, rest
0390     // are for high pt half
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  * Given a list of sizes (of arrays), generates a list of all combinations of
0402  * indices to index one element from each array.
0403  *
0404  * For example, given [2 3], generates [(0 0) (1 0) (0 1) (1 1) (0 2) (1 2)].
0405  *
0406  * This basically amounts to a positional number system of where each digit has
0407  * its own base. The number of digits is sizes.size(), and the base of digit i
0408  * is sizes[i]. Then all combinations can be uniquely represented just by
0409  * counting from [0, nCombs).
0410  *
0411  * For a decimal number like 1357, you get the thousands digit with n / 1000 = n
0412  * / (10 * 10 * 10). So here, you get the 0th digit with n / (base_1 * base_2 *
0413  * base_3);
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   // construct the combined input container of space point pointers from all
0453   // configured input sources.
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     // select volume/layer depending on what is set in the geometry id
0485     auto range =
0486         selectLowestNonZeroGeometryObject(measurements.orderedIndices(), geoId);
0487     // groupByModule only works with geometry containers, not with an
0488     // arbitrary range. do the equivalent grouping manually
0489     auto groupedByModule = makeGroupBy(range, detail::GeometryIdGetter());
0490 
0491     for (const auto& [moduleGeoId, moduleSourceLinks] : groupedByModule) {
0492       // find corresponding surface
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         // extract a local position/covariance independent of the concrete
0502         // measurement content. since we do not know if and where the local
0503         // parameters are contained in the measurement parameters vector, they
0504         // are transformed to the bound space where we do know their location.
0505         // if the local parameters are not measured, this results in a
0506         // zero location, which is a reasonable default fall-back.
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         // transform local position to global coordinates
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 }  // namespace ActsExamples