File indexing completed on 2025-07-11 07:50:24
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "ActsExamples/TrackFinding/MuonHoughSeeder.hpp"
0010
0011 #include "Acts/Definitions/Units.hpp"
0012 #include "ActsExamples/EventData/MuonHoughMaximum.hpp"
0013 #include "ActsExamples/EventData/MuonSegment.hpp"
0014 #include "ActsExamples/EventData/MuonSpacePoint.hpp"
0015
0016 #include <algorithm>
0017 #include <cmath>
0018 #include <stdexcept>
0019
0020 #include "TBox.h"
0021 #include "TCanvas.h"
0022 #include "TH2D.h"
0023 #include "TLegend.h"
0024 #include "TMarker.h"
0025 #include "TStyle.h"
0026
0027 namespace ActsExamples {
0028
0029
0030 auto etaHoughParamDC_left = [](double tanAlpha, const MuonSpacePoint& DC) {
0031 return DC.localPosition().y() - tanAlpha * DC.localPosition().z() -
0032 DC.driftRadius() * std::sqrt(1. + tanAlpha * tanAlpha);
0033 };
0034
0035 auto etaHoughParamDC_right = [](double tanAlpha, const MuonSpacePoint& DC) {
0036 return DC.localPosition().y() - tanAlpha * DC.localPosition().z() +
0037 DC.driftRadius() * std::sqrt(1. + tanAlpha * tanAlpha);
0038 };
0039
0040
0041 auto houghWidth_fromDC = [](double, const MuonSpacePoint& DC) {
0042
0043
0044 return std::min(std::sqrt(DC.covariance()(Acts::eY, Acts::eY)) * 3., 1.0);
0045 };
0046
0047
0048 auto etaHoughParam_strip = [](double tanAlpha, const MuonSpacePoint& strip) {
0049 return strip.localPosition().y() - tanAlpha * strip.localPosition().z();
0050 };
0051 auto phiHoughParam_strip = [](double tanBeta, const MuonSpacePoint& strip) {
0052 return strip.localPosition().x() - tanBeta * strip.localPosition().z();
0053 };
0054
0055
0056 auto etaHoughWidth_strip = [](double, const MuonSpacePoint& strip) {
0057 return std::sqrt(strip.covariance()(Acts::eY, Acts::eY)) * 3.;
0058 };
0059 auto phiHoughWidth_strip = [](double, const MuonSpacePoint& strip) {
0060 return std::sqrt(strip.covariance()(Acts::eX, Acts::eX)) * 3.;
0061 };
0062
0063 MuonHoughSeeder::MuonHoughSeeder(MuonHoughSeeder::Config cfg,
0064 Acts::Logging::Level lvl)
0065 : IAlgorithm("MuonHoughSeeder", lvl),
0066 m_cfg(std::move(cfg)),
0067 m_logger(Acts::getDefaultLogger(name(), lvl)) {
0068 if (m_cfg.inSpacePoints.empty()) {
0069 throw std::invalid_argument(
0070 "MuonHoughSeeder: Missing drift circle collection");
0071 }
0072 if (m_cfg.inTruthSegments.empty()) {
0073 throw std::invalid_argument(
0074 "MuonHoughSeeder: Missing truth segment collection");
0075 }
0076
0077 m_inputSpacePoints.initialize(m_cfg.inSpacePoints);
0078 m_inputTruthSegs.initialize(m_cfg.inTruthSegments);
0079 m_outputMaxima.initialize(m_cfg.outHoughMax);
0080 }
0081
0082 MuonHoughSeeder::~MuonHoughSeeder() = default;
0083
0084 ProcessCode MuonHoughSeeder::execute(const AlgorithmContext& ctx) const {
0085
0086 const MuonSpacePointContainer& gotSpacePoints = m_inputSpacePoints(ctx);
0087
0088
0089 Acts::HoughTransformUtils::HoughPlaneConfig etaPlaneCfg;
0090 etaPlaneCfg.nBinsX = 25;
0091 etaPlaneCfg.nBinsY = 25;
0092
0093 Acts::HoughTransformUtils::HoughPlaneConfig phiPlaneCfg;
0094 phiPlaneCfg.nBinsX = 10;
0095 phiPlaneCfg.nBinsY = 10;
0096
0097
0098 HoughPlane_t etaPlane{etaPlaneCfg};
0099 HoughPlane_t phiPlane{phiPlaneCfg};
0100 MuonHoughMaxContainer outMaxima{};
0101
0102 for (const MuonSpacePointContainer::value_type& bucket : gotSpacePoints) {
0103 MuonHoughMaxContainer etaMaxima = constructEtaMaxima(ctx, bucket, etaPlane);
0104 ACTS_VERBOSE(__func__ << "() - " << __LINE__ << " Found "
0105 << etaMaxima.size() << " eta maxima");
0106 MuonHoughMaxContainer stationMaxima =
0107 extendMaximaWithPhi(ctx, std::move(etaMaxima), phiPlane);
0108 ACTS_VERBOSE(__func__ << "() - " << __LINE__ << " Extended the maxima to "
0109 << stationMaxima.size() << " station maxima");
0110 outMaxima.insert(outMaxima.end(),
0111 std::make_move_iterator(stationMaxima.begin()),
0112 std::make_move_iterator(stationMaxima.end()));
0113 }
0114
0115 m_outputMaxima(ctx, std::move(outMaxima));
0116
0117 return ProcessCode::SUCCESS;
0118 }
0119
0120 MuonHoughMaxContainer MuonHoughSeeder::constructEtaMaxima(
0121 const AlgorithmContext& ctx, const MuonSpacePointBucket& bucket,
0122 HoughPlane_t& plane) const {
0123 MuonHoughMaxContainer etaMaxima{};
0124 AxisRange_t axisRanges{-3., 3., 100. * Acts::UnitConstants::m,
0125 -100. * Acts::UnitConstants::m};
0126
0127 for (const MuonSpacePoint& sp : bucket) {
0128
0129 if (!sp.id().measuresEta()) {
0130 continue;
0131 }
0132 const double y = sp.localPosition().y();
0133
0134 axisRanges.yMin = std::min(axisRanges.yMin, y - m_cfg.etaPlaneMarginIcept);
0135 axisRanges.yMax = std::min(axisRanges.yMax, y + m_cfg.etaPlaneMarginIcept);
0136 }
0137
0138 plane.reset();
0139 for (const MuonSpacePoint& sp : bucket) {
0140
0141 if (!sp.id().measuresEta()) {
0142 continue;
0143 }
0144 if (sp.id().technology() == MuonSpacePoint::MuonId::TechField::Mdt) {
0145 plane.fill<MuonSpacePoint>(sp, axisRanges, etaHoughParamDC_left,
0146 houghWidth_fromDC, &sp, sp.id().detLayer());
0147 plane.fill<MuonSpacePoint>(sp, axisRanges, etaHoughParamDC_right,
0148 houghWidth_fromDC, &sp, sp.id().detLayer());
0149 } else {
0150 plane.fill<MuonSpacePoint>(sp, axisRanges, etaHoughParam_strip,
0151 etaHoughWidth_strip, &sp, sp.id().detLayer());
0152 }
0153 }
0154
0155 PeakFinderCfg_t peakFinderCfg{};
0156 peakFinderCfg.fractionCutoff = 0.7f;
0157 peakFinderCfg.threshold = 3.f;
0158 peakFinderCfg.minSpacingBetweenPeaks = {0.f, 30.f};
0159
0160 PeakFinder_t peakFinder{peakFinderCfg};
0161
0162
0163 const MaximumVec_t maxima = peakFinder.findPeaks(plane, axisRanges);
0164 if (maxima.empty()) {
0165 ACTS_VERBOSE(__func__ << "() - " << __LINE__
0166 << " No maxima found for bucket");
0167 return etaMaxima;
0168 }
0169
0170
0171 MuonHoughMaximum::HitVec phiHits{};
0172 for (const MuonSpacePoint& sp : bucket) {
0173 if (!sp.id().measuresEta()) {
0174 phiHits.emplace_back(&sp);
0175 }
0176 }
0177 for (const Maximum_t& peakMax : maxima) {
0178 MuonHoughMaximum::HitVec hits{peakMax.hitIdentifiers.begin(),
0179 peakMax.hitIdentifiers.end()};
0180 hits.insert(hits.end(), phiHits.begin(), phiHits.end());
0181 etaMaxima.emplace_back(peakMax.x, peakMax.y, std::move(hits));
0182 }
0183 MuonId bucketId{bucket.front().id()};
0184 bucketId.setCoordFlags(true, false);
0185 displayMaxima(ctx, bucketId, maxima, plane, axisRanges);
0186 return etaMaxima;
0187 }
0188 MuonHoughMaxContainer MuonHoughSeeder::extendMaximaWithPhi(
0189 const AlgorithmContext& ctx, MuonHoughMaxContainer&& etaMaxima,
0190 HoughPlane_t& plane) const {
0191 MuonHoughMaxContainer outMaxima{};
0192
0193 PeakFinderCfg_t peakFinderCfg{};
0194 peakFinderCfg.fractionCutoff = 0.7;
0195 peakFinderCfg.threshold = 2.;
0196 peakFinderCfg.minSpacingBetweenPeaks = {0., 30.};
0197
0198 PeakFinder_t peakFinder{peakFinderCfg};
0199
0200 for (MuonHoughMaximum& etaMax : etaMaxima) {
0201 plane.reset();
0202 AxisRange_t axisRanges{-3., 3., 100. * Acts::UnitConstants::m,
0203 -100. * Acts::UnitConstants::m};
0204
0205 unsigned nPhiHits{0};
0206 for (const MuonSpacePoint* sp : etaMax.hits()) {
0207 if (!sp->id().measuresPhi()) {
0208 continue;
0209 }
0210 ++nPhiHits;
0211 const double x = sp->localPosition().x();
0212 axisRanges.yMax =
0213 std::min(axisRanges.yMax, x + m_cfg.phiPlaneMarginIcept);
0214 axisRanges.yMin =
0215 std::min(axisRanges.yMin, x - m_cfg.phiPlaneMarginIcept);
0216 }
0217 if (nPhiHits < 2) {
0218 outMaxima.emplace_back(std::move(etaMax));
0219 continue;
0220 }
0221 MuonHoughMaximum::HitVec etaHits{};
0222 etaHits.reserve(etaMax.hits().size());
0223 for (const MuonSpacePoint* sp : etaMax.hits()) {
0224 if (!sp->id().measuresPhi()) {
0225 etaHits.push_back(sp);
0226 continue;
0227 }
0228 plane.fill<MuonSpacePoint>(*sp, axisRanges, phiHoughParam_strip,
0229 phiHoughWidth_strip, sp, sp->id().detLayer());
0230 }
0231 const MaximumVec_t phiMaxima = peakFinder.findPeaks(plane, axisRanges);
0232 for (const Maximum_t& max : phiMaxima) {
0233 MuonHoughMaximum::HitVec hits{max.hitIdentifiers.begin(),
0234 max.hitIdentifiers.end()};
0235 hits.insert(hits.end(), etaHits.begin(), etaHits.end());
0236 outMaxima.emplace_back(max.x, max.y, etaMax.tanBeta(),
0237 etaMax.interceptY(), std::move(hits));
0238 }
0239 MuonId bucketId{etaMax.hits().front()->id()};
0240 bucketId.setCoordFlags(false, true);
0241 displayMaxima(ctx, bucketId, phiMaxima, plane, axisRanges);
0242 }
0243 return outMaxima;
0244 }
0245
0246 void MuonHoughSeeder::displayMaxima(const AlgorithmContext& ctx,
0247 const MuonId& bucketId,
0248 const MaximumVec_t& maxima,
0249 const HoughPlane_t& plane,
0250 const AxisRange_t& axis) const {
0251 if (!m_outCanvas) {
0252 return;
0253 }
0254 static std::mutex canvasMutex{};
0255
0256 const MuonSegmentContainer& gotTruthSegs = m_inputTruthSegs(ctx);
0257 std::lock_guard guard{canvasMutex};
0258 std::vector<std::unique_ptr<TObject>> primitives;
0259
0260
0261 TH2D houghHistoForPlot("houghHist", "HoughPlane;tan(#alpha);z0 [mm]",
0262 plane.nBinsX(), axis.xMin, axis.xMax, plane.nBinsY(),
0263 axis.yMin, axis.yMax);
0264 houghHistoForPlot.SetTitle(Form("Station %s, side %s, sector %2d",
0265 to_string(bucketId.msStation()).c_str(),
0266 to_string(bucketId.side()).c_str(),
0267 bucketId.sector()));
0268
0269
0270 for (int bx = 0; bx < houghHistoForPlot.GetNbinsX(); ++bx) {
0271 for (int by = 0; by < houghHistoForPlot.GetNbinsY(); ++by) {
0272 houghHistoForPlot.SetBinContent(bx + 1, by + 1, plane.nHits(bx, by));
0273 }
0274 }
0275
0276 auto maxHitsAsInt = static_cast<int>(plane.maxHits());
0277 houghHistoForPlot.SetContour(maxHitsAsInt + 1);
0278 for (int k = 0; k < maxHitsAsInt + 1; ++k) {
0279 houghHistoForPlot.SetContourLevel(k, k - 0.5);
0280 }
0281
0282 auto legend = std::make_unique<TLegend>(0.5, 0.7, 1. - gPad->GetRightMargin(),
0283 1. - gPad->GetTopMargin());
0284 legend->SetBorderSize(0);
0285 legend->SetFillStyle(0);
0286
0287
0288 MuonSegmentContainer::const_iterator truthItr = gotTruthSegs.begin();
0289 const int trueCoord = bucketId.measuresEta() ? Acts::eY : Acts::eX;
0290 while ((truthItr = std::find_if(truthItr, gotTruthSegs.end(),
0291 [bucketId](const MuonSegment& seg) {
0292 return seg.id().sameStation(bucketId);
0293 })) != gotTruthSegs.end()) {
0294 const MuonSegment& truthSeg{*truthItr};
0295 const float tanAlpha =
0296 truthSeg.localDirection()[trueCoord] / truthSeg.localDirection().z();
0297 const float intercept = truthSeg.localPosition()[trueCoord];
0298
0299 auto trueMarker =
0300 std::make_unique<TMarker>(tanAlpha, intercept, kOpenCrossX);
0301 trueMarker->SetMarkerSize(3);
0302 trueMarker->SetMarkerColor(kRed);
0303 legend->AddEntry(trueMarker.get(), "True coordinates");
0304 primitives.push_back(std::move(trueMarker));
0305 ACTS_VERBOSE("Draw true segment " << truthSeg.id()
0306 << " in bucket: " << bucketId);
0307 ++truthItr;
0308 }
0309
0310 bool addedLeg{false};
0311 for (const Maximum_t& max : maxima) {
0312 auto marker = std::make_unique<TMarker>(max.x, max.y, kFullSquare);
0313 marker->SetMarkerSize(1);
0314 marker->SetMarkerColor(kBlue);
0315
0316 auto box = std::make_unique<TBox>(max.x - max.wx, max.y - max.wy,
0317 max.x + max.wx, max.y + max.wy);
0318 box->SetLineColor(kBlue);
0319 box->SetFillStyle(1001);
0320 box->SetFillColorAlpha(kBlue, 0.1);
0321 box->SetLineWidth(0);
0322 if (!addedLeg) {
0323 legend->AddEntry(marker.get(), "Hough maxima");
0324 legend->AddEntry(box.get(), "Hough uncertainties");
0325 addedLeg = true;
0326 }
0327 }
0328 primitives.emplace_back(std::move(legend));
0329 for (auto& prim : primitives) {
0330 prim->Draw();
0331 }
0332
0333 m_outCanvas->SaveAs("HoughHistograms.pdf");
0334 }
0335 ProcessCode MuonHoughSeeder::initialize() {
0336
0337 m_outCanvas = std::make_unique<TCanvas>("canvas", "", 800, 800);
0338 m_outCanvas->SaveAs("HoughHistograms.pdf[");
0339 m_outCanvas->SetRightMargin(0.12);
0340 m_outCanvas->SetLeftMargin(0.12);
0341 gStyle->SetPalette(kGreyScale);
0342 gStyle->SetOptStat(0);
0343 return ProcessCode::SUCCESS;
0344 }
0345 ProcessCode MuonHoughSeeder::finalize() {
0346 m_outCanvas->SaveAs("HoughHistograms.pdf]");
0347 return ProcessCode::SUCCESS;
0348 }
0349 }