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