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