File indexing completed on 2025-02-22 09:55:35
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014 #include <algorithm>
0015 #include <functional>
0016 #include <tuple>
0017
0018 #include "fmt/format.h"
0019
0020 #include "Gaudi/Property.h"
0021 #include "GaudiAlg/GaudiAlgorithm.h"
0022 #include "GaudiAlg/GaudiTool.h"
0023 #include "GaudiAlg/Transformer.h"
0024 #include "GaudiKernel/PhysicalConstants.h"
0025 #include "GaudiKernel/RndmGenerators.h"
0026 #include "GaudiKernel/ToolHandle.h"
0027
0028 #include "DDRec/CellIDPositionConverter.h"
0029 #include "DDRec/Surface.h"
0030 #include "DDRec/SurfaceManager.h"
0031
0032 #include "JugBase/DataHandle.h"
0033 #include "JugBase/IGeoSvc.h"
0034
0035
0036 #include "edm4eic/CalorimeterHitCollection.h"
0037 #include "edm4eic/ClusterCollection.h"
0038 #include "edm4eic/ProtoClusterCollection.h"
0039 #include "edm4hep/utils/vector_utils.h"
0040 #include "edm4hep/Vector3f.h"
0041 #include "edm4hep/Vector2f.h"
0042
0043 #if defined __has_include
0044 # if __has_include ("edm4eic/Vector3f.h")
0045 # include "edm4eic/Vector3f.h"
0046 # endif
0047 # if __has_include ("edm4eic/Vector2f.h")
0048 # include "edm4eic/Vector2f.h"
0049 # endif
0050 #endif
0051
0052 namespace edm4eic {
0053 class Vector2f;
0054 class Vector3f;
0055 }
0056
0057 using namespace Gaudi::Units;
0058
0059 namespace {
0060
0061 using CaloHit = edm4eic::CalorimeterHit;
0062 using CaloHitCollection = edm4eic::CalorimeterHitCollection;
0063
0064 using Vector2f = std::conditional_t<
0065 std::is_same_v<decltype(edm4eic::CalorimeterHitData::position), edm4hep::Vector3f>,
0066 edm4hep::Vector2f,
0067 edm4eic::Vector2f
0068 >;
0069
0070
0071 static Vector2f localDistXY(const CaloHit& h1, const CaloHit& h2) {
0072 const auto delta = h1.getLocal() - h2.getLocal();
0073 return {delta.x, delta.y};
0074 }
0075 static Vector2f localDistXZ(const CaloHit& h1, const CaloHit& h2) {
0076 const auto delta = h1.getLocal() - h2.getLocal();
0077 return {delta.x, delta.z};
0078 }
0079 static Vector2f localDistYZ(const CaloHit& h1, const CaloHit& h2) {
0080 const auto delta = h1.getLocal() - h2.getLocal();
0081 return {delta.y, delta.z};
0082 }
0083 static Vector2f dimScaledLocalDistXY(const CaloHit& h1, const CaloHit& h2) {
0084 const auto delta = h1.getLocal() - h2.getLocal();
0085 const auto dimsum = h1.getDimension() + h2.getDimension();
0086 return {2 * delta.x / dimsum.x, 2 * delta.y / dimsum.y};
0087 }
0088 static Vector2f globalDistRPhi(const CaloHit& h1, const CaloHit& h2) {
0089 using vector_type = decltype(Vector2f::a);
0090 return {
0091 static_cast<vector_type>(
0092 edm4hep::utils::magnitude(h1.getPosition()) - edm4hep::utils::magnitude(h2.getPosition())
0093 ),
0094 static_cast<vector_type>(
0095 edm4hep::utils::angleAzimuthal(h1.getPosition()) - edm4hep::utils::angleAzimuthal(h2.getPosition())
0096 )
0097 };
0098 }
0099 static Vector2f globalDistEtaPhi(const CaloHit& h1,
0100 const CaloHit& h2) {
0101 using vector_type = decltype(Vector2f::a);
0102 return {
0103 static_cast<vector_type>(
0104 edm4hep::utils::eta(h1.getPosition()) - edm4hep::utils::eta(h2.getPosition())
0105 ),
0106 static_cast<vector_type>(
0107 edm4hep::utils::angleAzimuthal(h1.getPosition()) - edm4hep::utils::angleAzimuthal(h2.getPosition())
0108 )
0109 };
0110 }
0111
0112 static std::map<std::string,
0113 std::tuple<std::function<Vector2f(const CaloHit&, const CaloHit&)>, std::vector<double>>>
0114 distMethods{
0115 {"localDistXY", {localDistXY, {mm, mm}}}, {"localDistXZ", {localDistXZ, {mm, mm}}},
0116 {"localDistYZ", {localDistYZ, {mm, mm}}}, {"dimScaledLocalDistXY", {dimScaledLocalDistXY, {1., 1.}}},
0117 {"globalDistRPhi", {globalDistRPhi, {mm, rad}}}, {"globalDistEtaPhi", {globalDistEtaPhi, {1., rad}}},
0118 };
0119
0120 }
0121 namespace Jug::Reco {
0122
0123
0124
0125
0126
0127
0128
0129
0130
0131
0132
0133
0134
0135 class CalorimeterIslandCluster : public GaudiAlgorithm {
0136 private:
0137 Gaudi::Property<bool> m_splitCluster{this, "splitCluster", true};
0138 Gaudi::Property<double> m_minClusterHitEdep{this, "minClusterHitEdep", 0.};
0139 Gaudi::Property<double> m_minClusterCenterEdep{this, "minClusterCenterEdep", 50.0 * MeV};
0140 DataHandle<CaloHitCollection> m_inputHitCollection{"inputHitCollection", Gaudi::DataHandle::Reader, this};
0141 DataHandle<edm4eic::ProtoClusterCollection> m_outputProtoCollection{"outputProtoClusterCollection",
0142 Gaudi::DataHandle::Writer, this};
0143
0144
0145 Gaudi::Property<double> m_sectorDist{this, "sectorDist", 5.0 * cm};
0146 Gaudi::Property<std::vector<double>> u_localDistXY{this, "localDistXY", {}};
0147 Gaudi::Property<std::vector<double>> u_localDistXZ{this, "localDistXZ", {}};
0148 Gaudi::Property<std::vector<double>> u_localDistYZ{this, "localDistYZ", {}};
0149 Gaudi::Property<std::vector<double>> u_globalDistRPhi{this, "globalDistRPhi", {}};
0150 Gaudi::Property<std::vector<double>> u_globalDistEtaPhi{this, "globalDistEtaPhi", {}};
0151 Gaudi::Property<std::vector<double>> u_dimScaledLocalDistXY{this, "dimScaledLocalDistXY", {1.8, 1.8}};
0152
0153 std::function<Vector2f(const CaloHit&, const CaloHit&)> hitsDist;
0154
0155
0156 double minClusterHitEdep{0}, minClusterCenterEdep{0}, sectorDist{0};
0157 std::array<double, 2> neighbourDist = {0., 0.};
0158
0159 public:
0160 CalorimeterIslandCluster(const std::string& name, ISvcLocator* svcLoc) : GaudiAlgorithm(name, svcLoc) {
0161 declareProperty("inputHitCollection", m_inputHitCollection, "");
0162 declareProperty("outputProtoClusterCollection", m_outputProtoCollection, "");
0163 }
0164
0165 StatusCode initialize() override {
0166 if (GaudiAlgorithm::initialize().isFailure()) {
0167 return StatusCode::FAILURE;
0168 }
0169
0170
0171 minClusterHitEdep = m_minClusterHitEdep.value() / GeV;
0172 minClusterCenterEdep = m_minClusterCenterEdep.value() / GeV;
0173 sectorDist = m_sectorDist.value() / mm;
0174
0175
0176 auto set_dist_method = [this](const Gaudi::Property<std::vector<double>>& uprop) {
0177 if (uprop.size() == 0) {
0178 return false;
0179 }
0180 auto& [method, units] = distMethods[uprop.name()];
0181 if (uprop.size() != units.size()) {
0182 info() << units.size() << endmsg;
0183 warning() << fmt::format("Expect {} values from {}, received {}: ({}), ignored it.", units.size(), uprop.name(),
0184 uprop.size(), fmt::join(uprop.value(), ", "))
0185 << endmsg;
0186 return false;
0187 } else {
0188 for (size_t i = 0; i < units.size(); ++i) {
0189 neighbourDist[i] = uprop.value()[i] / units[i];
0190 }
0191 hitsDist = method;
0192 info() << fmt::format("Clustering uses {} with distances <= [{}]", uprop.name(), fmt::join(neighbourDist, ","))
0193 << endmsg;
0194 }
0195 return true;
0196 };
0197
0198 std::vector<Gaudi::Property<std::vector<double>>> uprops{
0199 u_localDistXY,
0200 u_localDistXZ,
0201 u_localDistYZ,
0202 u_globalDistRPhi,
0203 u_globalDistEtaPhi,
0204
0205 u_dimScaledLocalDistXY,
0206 };
0207
0208 bool method_found = false;
0209 for (auto& uprop : uprops) {
0210 if (set_dist_method(uprop)) {
0211 method_found = true;
0212 break;
0213 }
0214 }
0215 if (not method_found) {
0216 error() << "Cannot determine the clustering coordinates" << endmsg;
0217 return StatusCode::FAILURE;
0218 }
0219
0220 return StatusCode::SUCCESS;
0221 }
0222
0223 StatusCode execute() override {
0224
0225 const auto& hits = *(m_inputHitCollection.get());
0226
0227 auto& proto = *(m_outputProtoCollection.createAndPut());
0228
0229
0230 std::vector<std::vector<std::pair<uint32_t, CaloHit>>> groups;
0231
0232 std::vector<bool> visits(hits.size(), false);
0233 for (size_t i = 0; i < hits.size(); ++i) {
0234 if (msgLevel(MSG::DEBUG)) {
0235 const auto& hit = hits[i];
0236 debug() << fmt::format("hit {:d}: energy = {:.4f} MeV, local = ({:.4f}, {:.4f}) mm, "
0237 "global=({:.4f}, {:.4f}, {:.4f}) mm",
0238 i, hit.getEnergy() * 1000., hit.getLocal().x, hit.getLocal().y, hit.getPosition().x,
0239 hit.getPosition().y, hit.getPosition().z)
0240 << endmsg;
0241 }
0242
0243 if (visits[i]) {
0244 continue;
0245 }
0246 groups.emplace_back();
0247
0248 dfs_group(groups.back(), i, hits, visits);
0249 }
0250
0251 for (auto& group : groups) {
0252 if (group.empty()) {
0253 continue;
0254 }
0255 auto maxima = find_maxima(group, !m_splitCluster.value());
0256 split_group(group, maxima, proto);
0257 if (msgLevel(MSG::DEBUG)) {
0258 debug() << "hits in a group: " << group.size() << ", "
0259 << "local maxima: " << maxima.size() << endmsg;
0260 }
0261 }
0262
0263 return StatusCode::SUCCESS;
0264 }
0265
0266 private:
0267
0268 inline bool is_neighbour(const CaloHit& h1, const CaloHit& h2) const {
0269
0270 if (h1.getSector() == h2.getSector()) {
0271 auto dist = hitsDist(h1, h2);
0272 return (dist.a <= neighbourDist[0]) && (dist.b <= neighbourDist[1]);
0273
0274 } else {
0275
0276 return (edm4hep::utils::magnitude(h1.getPosition() - h2.getPosition()) <= sectorDist);
0277 }
0278 }
0279
0280
0281 void dfs_group(std::vector<std::pair<uint32_t, CaloHit>>& group, int idx,
0282 const CaloHitCollection& hits, std::vector<bool>& visits) const {
0283
0284 if (hits[idx].getEnergy() < minClusterHitEdep) {
0285 visits[idx] = true;
0286 return;
0287 }
0288
0289 group.emplace_back(idx, hits[idx]);
0290 visits[idx] = true;
0291 for (size_t i = 0; i < hits.size(); ++i) {
0292 if (visits[i] || !is_neighbour(hits[idx], hits[i])) {
0293 continue;
0294 }
0295 dfs_group(group, i, hits, visits);
0296 }
0297 }
0298
0299
0300 std::vector<CaloHit>
0301 find_maxima(const std::vector<std::pair<uint32_t, CaloHit>>& group,
0302 bool global = false) const {
0303 std::vector<CaloHit> maxima;
0304 if (group.empty()) {
0305 return maxima;
0306 }
0307
0308 if (global) {
0309 int mpos = 0;
0310 for (size_t i = 0; i < group.size(); ++i) {
0311 if (group[mpos].second.getEnergy() < group[i].second.getEnergy()) {
0312 mpos = i;
0313 }
0314 }
0315 if (group[mpos].second.getEnergy() >= minClusterCenterEdep) {
0316 maxima.push_back(group[mpos].second);
0317 }
0318 return maxima;
0319 }
0320
0321 for (const auto& [idx, hit] : group) {
0322
0323 if (hit.getEnergy() < minClusterCenterEdep) {
0324 continue;
0325 }
0326
0327 bool maximum = true;
0328 for (const auto& [idx2, hit2] : group) {
0329 if (hit == hit2) {
0330 continue;
0331 }
0332
0333 if (is_neighbour(hit, hit2) && hit2.getEnergy() > hit.getEnergy()) {
0334 maximum = false;
0335 break;
0336 }
0337 }
0338
0339 if (maximum) {
0340 maxima.push_back(hit);
0341 }
0342 }
0343
0344 return maxima;
0345 }
0346
0347
0348 inline static void vec_normalize(std::vector<double>& vals) {
0349 double total = 0.;
0350 for (auto& val : vals) {
0351 total += val;
0352 }
0353 for (auto& val : vals) {
0354 val /= total;
0355 }
0356 }
0357
0358
0359 void split_group(std::vector<std::pair<uint32_t, CaloHit>>& group, const std::vector<CaloHit>& maxima,
0360 edm4eic::ProtoClusterCollection& proto) const {
0361
0362 if (maxima.empty()) {
0363 if (msgLevel(MSG::VERBOSE)) {
0364 verbose() << "No maxima found, not building any clusters" << endmsg;
0365 }
0366 return;
0367 } else if (maxima.size() == 1) {
0368 edm4eic::MutableProtoCluster pcl;
0369 for (auto& [idx, hit] : group) {
0370 pcl.addToHits(hit);
0371 pcl.addToWeights(1.);
0372 }
0373 proto.push_back(pcl);
0374 if (msgLevel(MSG::VERBOSE)) {
0375 verbose() << "A single maximum found, added one ProtoCluster" << endmsg;
0376 }
0377 return;
0378 }
0379
0380
0381
0382 std::vector<double> weights(maxima.size(), 1.);
0383 std::vector<edm4eic::MutableProtoCluster> pcls;
0384 for (size_t k = 0; k < maxima.size(); ++k) {
0385 pcls.emplace_back();
0386 }
0387
0388 size_t i = 0;
0389 for (const auto& [idx, hit] : group) {
0390 size_t j = 0;
0391
0392 for (const auto& chit : maxima) {
0393 double dist_ref = chit.getDimension().x;
0394 double energy = chit.getEnergy();
0395 double dist = edm4hep::utils::magnitude(hitsDist(chit, hit));
0396 weights[j] = std::exp(-dist / dist_ref) * energy;
0397 j += 1;
0398 }
0399
0400
0401 vec_normalize(weights);
0402
0403
0404 for (auto& w : weights) {
0405 if (w < 0.02) {
0406 w = 0;
0407 }
0408 }
0409 vec_normalize(weights);
0410
0411
0412 for (size_t k = 0; k < maxima.size(); ++k) {
0413 double weight = weights[k];
0414 if (weight <= 1e-6) {
0415 continue;
0416 }
0417 pcls[k].addToHits(hit);
0418 pcls[k].addToWeights(weight);
0419 }
0420 i += 1;
0421 }
0422 for (auto& pcl : pcls) {
0423 proto.push_back(pcl);
0424 }
0425 if (msgLevel(MSG::VERBOSE)) {
0426 verbose() << "Multiple (" << maxima.size() << ") maxima found, added a ProtoClusters for each maximum" << endmsg;
0427 }
0428 }
0429 };
0430
0431
0432 DECLARE_COMPONENT(CalorimeterIslandCluster)
0433
0434 }