File indexing completed on 2025-06-08 07:53:21
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011 #include "algorithms/calorimetry/ImagingClusterReco.h"
0012
0013 #include <Evaluator/DD4hepUnits.h>
0014 #include <edm4hep/RawCalorimeterHit.h>
0015 #include <edm4hep/SimCalorimeterHit.h>
0016 #include <edm4hep/Vector3f.h>
0017 #include <edm4hep/utils/vector_utils.h>
0018 #include <fmt/core.h>
0019 #include <podio/ObjectID.h>
0020 #include <podio/RelationRange.h>
0021 #include <Eigen/Core>
0022 #include <Eigen/Householder> // IWYU pragma: keep
0023 #include <Eigen/Jacobi>
0024 #include <Eigen/QR>
0025 #include <Eigen/SVD>
0026 #include <algorithm>
0027 #include <cmath>
0028 #include <gsl/pointers>
0029 #include <map>
0030
0031 #include "algorithms/calorimetry/ClusterTypes.h"
0032 #include "algorithms/calorimetry/ImagingClusterRecoConfig.h"
0033
0034 namespace eicrecon {
0035
0036 void ImagingClusterReco::process(const Input& input, const Output& output) const {
0037
0038 #if EDM4EIC_VERSION_MAJOR >= 7
0039 const auto [proto, mchitassociations] = input;
0040 #else
0041 const auto [proto, mchits] = input;
0042 #endif
0043 auto [clusters, associations, layers] = output;
0044
0045 for (const auto& pcl : *proto) {
0046 if (!pcl.getHits().empty() && !pcl.getHits(0).isAvailable()) {
0047 warning("Protocluster hit relation is invalid, skipping protocluster");
0048 continue;
0049 }
0050
0051 auto cl = reconstruct_cluster(pcl);
0052 auto cl_layers = reconstruct_cluster_layers(pcl);
0053
0054
0055 auto [theta, phi] = fit_track(cl_layers);
0056 cl.setIntrinsicTheta(theta);
0057 cl.setIntrinsicPhi(phi);
0058
0059
0060
0061 for (const auto& layer : cl_layers) {
0062 layers->push_back(layer);
0063 cl.addToClusters(layer);
0064 }
0065 clusters->push_back(cl);
0066
0067
0068 #if EDM4EIC_VERSION_MAJOR >= 7
0069 if (mchitassociations->size() == 0) {
0070 debug("Provided MCRecoCalorimeterHitAssociation collection is empty. No truth associations "
0071 "will be performed.");
0072 continue;
0073 } else {
0074 associate_mc_particles(cl, mchitassociations, associations);
0075 }
0076 #else
0077 if (mchits->size() == 0) {
0078 debug("Provided SimCalorimeterHitCollection is empty. No truth association will be "
0079 "performed.");
0080 continue;
0081 } else {
0082 associate_mc_particles(cl, mchits, associations);
0083 }
0084 #endif
0085 }
0086
0087
0088 for (const auto& cl : *clusters) {
0089 debug("Cluster {:d}: Edep = {:.3f} MeV, Dir = ({:.3f}, {:.3f}) deg", cl.getObjectID().index,
0090 cl.getEnergy() * 1000., cl.getIntrinsicTheta() / M_PI * 180.,
0091 cl.getIntrinsicPhi() / M_PI * 180.);
0092 }
0093 }
0094
0095 std::vector<edm4eic::MutableCluster>
0096 ImagingClusterReco::reconstruct_cluster_layers(const edm4eic::ProtoCluster& pcl) const {
0097 const auto& hits = pcl.getHits();
0098 const auto& weights = pcl.getWeights();
0099
0100 std::map<int, std::vector<std::pair<const edm4eic::CalorimeterHit, float>>> layer_map;
0101 for (unsigned i = 0; i < hits.size(); ++i) {
0102 const auto hit = hits[i];
0103 auto lid = hit.getLayer();
0104
0105
0106
0107
0108 layer_map[lid].push_back({hit, weights[i]});
0109 }
0110
0111
0112 std::vector<edm4eic::MutableCluster> cl_layers;
0113 for (const auto& [lid, layer_hits] : layer_map) {
0114 auto layer = reconstruct_layer(layer_hits);
0115 cl_layers.push_back(layer);
0116 }
0117 return cl_layers;
0118 }
0119
0120 edm4eic::MutableCluster ImagingClusterReco::reconstruct_layer(
0121 const std::vector<std::pair<const edm4eic::CalorimeterHit, float>>& hits) const {
0122 edm4eic::MutableCluster layer;
0123 layer.setType(Jug::Reco::ClusterType::kClusterSlice);
0124
0125 double energy{0};
0126 double energyError{0};
0127 double time{0};
0128 double timeError{0};
0129 double sumOfWeights{0};
0130 auto pos = layer.getPosition();
0131 for (const auto& [hit, weight] : hits) {
0132 energy += hit.getEnergy() * weight;
0133 energyError += std::pow(hit.getEnergyError() * weight, 2);
0134 time += hit.getTime() * weight;
0135 timeError += std::pow(hit.getTimeError() * weight, 2);
0136 pos = pos + hit.getPosition() * weight;
0137 sumOfWeights += weight;
0138 layer.addToHits(hit);
0139 }
0140 layer.setEnergy(energy);
0141 layer.setEnergyError(std::sqrt(energyError));
0142 layer.setTime(time / sumOfWeights);
0143 layer.setTimeError(std::sqrt(timeError) / sumOfWeights);
0144 layer.setNhits(hits.size());
0145 layer.setPosition(pos / sumOfWeights);
0146
0147
0148
0149
0150 double radius = 0.;
0151 for (const auto& [hit, weight] : hits) {
0152 radius += std::pow(edm4hep::utils::magnitude(hit.getPosition() - layer.getPosition()), 2);
0153 }
0154 layer.addToShapeParameters(std::sqrt(radius / layer.getNhits()));
0155
0156
0157 return layer;
0158 }
0159
0160 edm4eic::MutableCluster
0161 ImagingClusterReco::reconstruct_cluster(const edm4eic::ProtoCluster& pcl) const {
0162 edm4eic::MutableCluster cluster;
0163
0164 const auto& hits = pcl.getHits();
0165 const auto& weights = pcl.getWeights();
0166
0167 cluster.setType(Jug::Reco::ClusterType::kCluster3D);
0168 double energy = 0.;
0169 double energyError = 0.;
0170 double time = 0.;
0171 double timeError = 0.;
0172 double meta = 0.;
0173 double mphi = 0.;
0174 double r = 9999 * dd4hep::cm;
0175 for (unsigned i = 0; i < hits.size(); ++i) {
0176 const auto& hit = hits[i];
0177 const auto& weight = weights[i];
0178 energy += hit.getEnergy() * weight;
0179 energyError += std::pow(hit.getEnergyError() * weight, 2);
0180
0181 const double energyWeight = hit.getEnergy() * weight;
0182 time += hit.getTime() * energyWeight;
0183 timeError += std::pow(hit.getTimeError() * energyWeight, 2);
0184 meta += edm4hep::utils::eta(hit.getPosition()) * energyWeight;
0185 mphi += edm4hep::utils::angleAzimuthal(hit.getPosition()) * energyWeight;
0186 r = std::min(edm4hep::utils::magnitude(hit.getPosition()), r);
0187 cluster.addToHits(hit);
0188 }
0189 cluster.setEnergy(energy);
0190 cluster.setEnergyError(std::sqrt(energyError));
0191 cluster.setTime(time / energy);
0192 cluster.setTimeError(std::sqrt(timeError) / energy);
0193 cluster.setNhits(hits.size());
0194 cluster.setPosition(edm4hep::utils::sphericalToVector(
0195 r, edm4hep::utils::etaToAngle(meta / energy), mphi / energy));
0196
0197
0198 double radius = 0.;
0199 for (const auto& hit : hits) {
0200 radius += std::pow(std::hypot((edm4hep::utils::eta(hit.getPosition()) -
0201 edm4hep::utils::eta(cluster.getPosition())),
0202 (edm4hep::utils::angleAzimuthal(hit.getPosition()) -
0203 edm4hep::utils::angleAzimuthal(cluster.getPosition()))),
0204 2.0);
0205 }
0206 cluster.addToShapeParameters(std::sqrt(radius / cluster.getNhits()));
0207
0208
0209
0210
0211
0212
0213
0214
0215
0216 return cluster;
0217 }
0218
0219 std::pair<double , double >
0220 ImagingClusterReco::fit_track(const std::vector<edm4eic::MutableCluster>& layers) const {
0221 int nrows = 0;
0222 decltype(edm4eic::ClusterData::position) mean_pos{0, 0, 0};
0223 for (const auto& layer : layers) {
0224 if ((layer.getNhits() > 0) && (layer.getHits(0).getLayer() <= m_cfg.trackStopLayer)) {
0225 mean_pos = mean_pos + layer.getPosition();
0226 nrows += 1;
0227 }
0228 }
0229
0230
0231 if (nrows < 2) {
0232 return {};
0233 }
0234
0235 mean_pos = mean_pos / nrows;
0236
0237 Eigen::MatrixXd pos(nrows, 3);
0238 int ir = 0;
0239 for (const auto& layer : layers) {
0240 if ((layer.getNhits() > 0) && (layer.getHits(0).getLayer() <= m_cfg.trackStopLayer)) {
0241 auto delta = layer.getPosition() - mean_pos;
0242 pos(ir, 0) = delta.x;
0243 pos(ir, 1) = delta.y;
0244 pos(ir, 2) = delta.z;
0245 ir += 1;
0246 }
0247 }
0248
0249 Eigen::JacobiSVD<Eigen::MatrixXd> svd(pos, Eigen::ComputeThinU | Eigen::ComputeThinV);
0250 const auto dir = svd.matrixV().col(0);
0251
0252 return {std::acos(dir(2)), std::atan2(dir(1), dir(0))};
0253 }
0254
0255 void ImagingClusterReco::associate_mc_particles(
0256 const edm4eic::Cluster& cl,
0257 #if EDM4EIC_VERSION_MAJOR >= 7
0258 const edm4eic::MCRecoCalorimeterHitAssociationCollection* mchitassociations,
0259 #else
0260 const edm4hep::SimCalorimeterHitCollection* mchits,
0261 #endif
0262 edm4eic::MCRecoClusterParticleAssociationCollection* assocs) const {
0263
0264
0265
0266
0267
0268
0269
0270
0271
0272
0273
0274
0275
0276
0277 auto compare = [](const edm4hep::MCParticle& lhs, const edm4hep::MCParticle& rhs) {
0278 if (lhs.getObjectID().collectionID == rhs.getObjectID().collectionID) {
0279 return (lhs.getObjectID().index < rhs.getObjectID().index);
0280 } else {
0281 return (lhs.getObjectID().collectionID < rhs.getObjectID().collectionID);
0282 }
0283 };
0284
0285
0286 std::map<edm4hep::MCParticle, double, decltype(compare)> mapMCParToContrib(compare);
0287
0288
0289
0290
0291 double eSimHitSum = 0.;
0292 for (auto clhit : cl.getHits()) {
0293
0294 std::vector<edm4hep::SimCalorimeterHit> vecAssocSimHits;
0295
0296 #if EDM4EIC_VERSION_MAJOR >= 7
0297 for (const auto& hitAssoc : *mchitassociations) {
0298
0299
0300 if (clhit.getRawHit() == hitAssoc.getRawHit()) {
0301 vecAssocSimHits.push_back(hitAssoc.getSimHit());
0302 eSimHitSum += vecAssocSimHits.back().getEnergy();
0303 }
0304 }
0305 #else
0306 for (const auto& mchit : *mchits) {
0307 if (mchit.getCellID() == clhit.getCellID()) {
0308 vecAssocSimHits.push_back(mchit);
0309 eSimHitSum += vecAssocSimHits.back().getEnergy();
0310 break;
0311 }
0312 }
0313
0314
0315
0316 if (vecAssocSimHits.empty()) {
0317 debug("No matching SimHit for hit {}", clhit.getCellID());
0318 continue;
0319 }
0320 #endif
0321 debug("{} associated sim hits found for reco hit (cell ID = {})", vecAssocSimHits.size(),
0322 clhit.getCellID());
0323
0324
0325
0326
0327 for (const auto& simHit : vecAssocSimHits) {
0328 for (const auto& contrib : simHit.getContributions()) {
0329
0330
0331
0332 edm4hep::MCParticle primary = get_primary(contrib);
0333 mapMCParToContrib[primary] += contrib.getEnergy();
0334
0335 trace("Identified primary: id = {}, pid = {}, total energy = {}, contributed = {}",
0336 primary.getObjectID().index, primary.getPDG(), primary.getEnergy(),
0337 mapMCParToContrib[primary]);
0338 }
0339 }
0340 }
0341 debug("Found {} primaries contributing a total of {} GeV", mapMCParToContrib.size(), eSimHitSum);
0342
0343
0344
0345
0346 for (auto [part, contribution] : mapMCParToContrib) {
0347
0348 const double weight = contribution / eSimHitSum;
0349
0350
0351 auto assoc = assocs->create();
0352 assoc.setRecID(cl.getObjectID().index);
0353 assoc.setSimID(part.getObjectID().index);
0354 assoc.setWeight(weight);
0355 assoc.setRec(cl);
0356 assoc.setSim(part);
0357 debug("Associated cluster #{} to MC Particle #{} (pid = {}, status = {}, energy = {}) with "
0358 "weight ({})",
0359 cl.getObjectID().index, part.getObjectID().index, part.getPDG(),
0360 part.getGeneratorStatus(), part.getEnergy(), weight);
0361 }
0362 }
0363
0364 edm4hep::MCParticle
0365 ImagingClusterReco::get_primary(const edm4hep::CaloHitContribution& contrib) const {
0366
0367 const auto contributor = contrib.getParticle();
0368
0369
0370
0371
0372 edm4hep::MCParticle primary = contributor;
0373 while (primary.parents_size() > 0) {
0374 if (primary.getGeneratorStatus() != 0)
0375 break;
0376 primary = primary.getParents(0);
0377 }
0378 return primary;
0379 }
0380
0381 }