File indexing completed on 2024-06-29 07:05:54
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011 #include <Evaluator/DD4hepUnits.h>
0012 #include <boost/algorithm/string/join.hpp>
0013 #include <boost/iterator/iterator_facade.hpp>
0014 #include <boost/range/adaptor/map.hpp>
0015 #include <edm4eic/CalorimeterHitCollection.h>
0016 #include <edm4hep/CaloHitContributionCollection.h>
0017 #include <edm4hep/MCParticleCollection.h>
0018 #include <edm4hep/Vector3f.h>
0019 #include <edm4hep/utils/vector_utils.h>
0020 #include <fmt/core.h>
0021 #include <podio/ObjectID.h>
0022 #include <podio/RelationRange.h>
0023 #include <Eigen/Core>
0024 #include <Eigen/Eigenvalues>
0025 #include <Eigen/Householder> // IWYU pragma: keep
0026 #include <cctype>
0027 #include <complex>
0028 #include <cstddef>
0029 #include <gsl/pointers>
0030 #include <iterator>
0031 #include <limits>
0032 #include <map>
0033 #include <optional>
0034 #include <vector>
0035
0036 #include "CalorimeterClusterRecoCoG.h"
0037 #include "algorithms/calorimetry/CalorimeterClusterRecoCoGConfig.h"
0038
0039 namespace eicrecon {
0040
0041 using namespace dd4hep;
0042
0043 void CalorimeterClusterRecoCoG::init() {
0044
0045
0046 std::string ew = m_cfg.energyWeight;
0047
0048 std::transform(ew.begin(), ew.end(), ew.begin(), [](char s) { return std::tolower(s); });
0049 auto it = weightMethods.find(ew);
0050 if (it == weightMethods.end()) {
0051 error("Cannot find energy weighting method {}, choose one from [{}]", m_cfg.energyWeight, boost::algorithm::join(weightMethods | boost::adaptors::map_keys, ", "));
0052 return;
0053 }
0054 weightFunc = it->second;
0055 }
0056
0057 void CalorimeterClusterRecoCoG::process(
0058 const CalorimeterClusterRecoCoG::Input& input,
0059 const CalorimeterClusterRecoCoG::Output& output) const {
0060
0061 const auto [proto, mchits] = input;
0062 auto [clusters, associations] = output;
0063
0064 for (const auto& pcl : *proto) {
0065
0066
0067 if (pcl.hits_size() == 0) {
0068 continue;
0069 }
0070
0071 auto cl_opt = reconstruct(pcl);
0072 if (! cl_opt.has_value()) {
0073 continue;
0074 }
0075 auto cl = *std::move(cl_opt);
0076
0077 debug("{} hits: {} GeV, ({}, {}, {})", cl.getNhits(), cl.getEnergy() / dd4hep::GeV, cl.getPosition().x / dd4hep::mm, cl.getPosition().y / dd4hep::mm, cl.getPosition().z / dd4hep::mm);
0078 clusters->push_back(cl);
0079
0080
0081
0082
0083
0084 if (mchits->size() > 0) {
0085
0086
0087 auto pclhits = pcl.getHits();
0088 auto pclhit = std::max_element(
0089 pclhits.begin(),
0090 pclhits.end(),
0091 [](const auto& pclhit1, const auto& pclhit2) {
0092 return pclhit1.getEnergy() < pclhit2.getEnergy();
0093 }
0094 );
0095
0096
0097
0098
0099
0100
0101
0102
0103
0104
0105 auto mchit = mchits->begin();
0106 for ( ; mchit != mchits->end(); ++mchit) {
0107
0108 if ( mchit->getCellID() == pclhit->getCellID()) {
0109 break;
0110 }
0111 }
0112 if (!(mchit != mchits->end())) {
0113
0114 warning("Proto-cluster has highest energy in CellID {}, but no mc hit with that CellID was found.", pclhit->getCellID());
0115 trace("Proto-cluster hits: ");
0116 for (const auto& pclhit1: pclhits) {
0117 trace("{}: {}", pclhit1.getCellID(), pclhit1.getEnergy());
0118 }
0119 trace("MC hits: ");
0120 for (const auto& mchit1: *mchits) {
0121 trace("{}: {}", mchit1.getCellID(), mchit1.getEnergy());
0122 }
0123 break;
0124 }
0125
0126
0127 const auto& mcp = mchit->getContributions(0).getParticle();
0128
0129 debug("cluster has largest energy in cellID: {}", pclhit->getCellID());
0130 debug("pcl hit with highest energy {} at index {}", pclhit->getEnergy(), pclhit->getObjectID().index);
0131 debug("corresponding mc hit energy {} at index {}", mchit->getEnergy(), mchit->getObjectID().index);
0132 debug("from MCParticle index {}, PDG {}, {}", mcp.getObjectID().index, mcp.getPDG(), edm4hep::utils::magnitude(mcp.getMomentum()));
0133
0134
0135 auto clusterassoc = associations->create();
0136 clusterassoc.setRecID(cl.getObjectID().index);
0137 clusterassoc.setSimID(mcp.getObjectID().index);
0138 clusterassoc.setWeight(1.0);
0139 clusterassoc.setRec(cl);
0140 clusterassoc.setSim(mcp);
0141 } else {
0142 debug("No mcHitCollection was provided, so no truth association will be performed.");
0143 }
0144 }
0145 }
0146
0147
0148 std::optional<edm4eic::MutableCluster> CalorimeterClusterRecoCoG::reconstruct(const edm4eic::ProtoCluster& pcl) const {
0149 edm4eic::MutableCluster cl;
0150 cl.setNhits(pcl.hits_size());
0151
0152 debug("hit size = {}", pcl.hits_size());
0153
0154
0155 if (pcl.hits_size() == 0) {
0156 return {};
0157 }
0158
0159
0160 float totalE = 0.;
0161
0162 float minHitEta = std::numeric_limits<float>::max();
0163 float maxHitEta = std::numeric_limits<float>::min();
0164 auto time = pcl.getHits()[0].getTime();
0165 auto timeError = pcl.getHits()[0].getTimeError();
0166 for (unsigned i = 0; i < pcl.getHits().size(); ++i) {
0167 const auto& hit = pcl.getHits()[i];
0168 const auto weight = pcl.getWeights()[i];
0169 debug("hit energy = {} hit weight: {}", hit.getEnergy(), weight);
0170 auto energy = hit.getEnergy() * weight;
0171 totalE += energy;
0172 cl.addToHits(hit);
0173 cl.addToHitContributions(energy);
0174 const float eta = edm4hep::utils::eta(hit.getPosition());
0175 if (eta < minHitEta) {
0176 minHitEta = eta;
0177 }
0178 if (eta > maxHitEta) {
0179 maxHitEta = eta;
0180 }
0181 }
0182 cl.setEnergy(totalE / m_cfg.sampFrac);
0183 cl.setEnergyError(0.);
0184 cl.setTime(time);
0185 cl.setTimeError(timeError);
0186
0187
0188 float tw = 0.;
0189 auto v = cl.getPosition();
0190
0191 double logWeightBase=m_cfg.logWeightBase;
0192 if (m_cfg.logWeightBaseCoeffs.size() != 0){
0193 double l=log(cl.getEnergy()/m_cfg.logWeightBase_Eref);
0194 logWeightBase=0;
0195 for(std::size_t i =0; i<m_cfg.logWeightBaseCoeffs.size(); i++){
0196 logWeightBase += m_cfg.logWeightBaseCoeffs[i]*pow(l,i);
0197 }
0198 }
0199
0200 for (unsigned i = 0; i < pcl.getHits().size(); ++i) {
0201 const auto& hit = pcl.getHits()[i];
0202 const auto weight = pcl.getWeights()[i];
0203
0204 float w = weightFunc(hit.getEnergy() * weight, totalE, logWeightBase, 0);
0205 tw += w;
0206 v = v + (hit.getPosition() * w);
0207 }
0208 if (tw == 0.) {
0209 warning("zero total weights encountered, you may want to adjust your weighting parameter.");
0210 return {};
0211 }
0212 cl.setPosition(v / tw);
0213 cl.setPositionError({});
0214
0215
0216 if (m_cfg.enableEtaBounds) {
0217 const bool overflow = (edm4hep::utils::eta(cl.getPosition()) > maxHitEta);
0218 const bool underflow = (edm4hep::utils::eta(cl.getPosition()) < minHitEta);
0219 if (overflow || underflow) {
0220 const double newEta = overflow ? maxHitEta : minHitEta;
0221 const double newTheta = edm4hep::utils::etaToAngle(newEta);
0222 const double newR = edm4hep::utils::magnitude(cl.getPosition());
0223 const double newPhi = edm4hep::utils::angleAzimuthal(cl.getPosition());
0224 cl.setPosition(edm4hep::utils::sphericalToVector(newR, newTheta, newPhi));
0225 debug("Bound cluster position to contributing hits due to {}", (overflow ? "overflow" : "underflow"));
0226 }
0227 }
0228
0229
0230
0231
0232
0233 cl.setIntrinsicTheta(edm4hep::utils::anglePolar(cl.getPosition()));
0234 cl.setIntrinsicPhi(edm4hep::utils::angleAzimuthal(cl.getPosition()));
0235
0236
0237
0238
0239
0240
0241
0242
0243 float radius = 0, dispersion = 0, w_sum = 0;
0244
0245 Eigen::Matrix2f sum2_2D = Eigen::Matrix2f::Zero();
0246 Eigen::Matrix3f sum2_3D = Eigen::Matrix3f::Zero();
0247 Eigen::Vector2f sum1_2D = Eigen::Vector2f::Zero();
0248 Eigen::Vector3f sum1_3D = Eigen::Vector3f::Zero();
0249 Eigen::Vector2cf eigenValues_2D = Eigen::Vector2cf::Zero();
0250 Eigen::Vector3cf eigenValues_3D = Eigen::Vector3cf::Zero();
0251
0252 double axis_x=0, axis_y=0, axis_z=0;
0253 if (cl.getNhits() > 1) {
0254
0255 for (const auto& hit : pcl.getHits()) {
0256
0257 float w = weightFunc(hit.getEnergy(), totalE, logWeightBase, 0);
0258
0259
0260 Eigen::Vector2f pos2D( edm4hep::utils::anglePolar( hit.getPosition() ), edm4hep::utils::angleAzimuthal( hit.getPosition() ) );
0261
0262 Eigen::Vector3f pos3D( hit.getPosition().x, hit.getPosition().y, hit.getPosition().z );
0263
0264 const auto delta = cl.getPosition() - hit.getPosition();
0265 radius += delta * delta;
0266 dispersion += delta * delta * w;
0267
0268
0269 sum2_2D += w * pos2D * pos2D.transpose();
0270 sum2_3D += w * pos3D * pos3D.transpose();
0271
0272
0273 sum1_2D += w * pos2D;
0274 sum1_3D += w * pos3D;
0275
0276 w_sum += w;
0277 }
0278
0279 radius = sqrt((1. / (cl.getNhits() - 1.)) * radius);
0280 if( w_sum > 0 ) {
0281 dispersion = sqrt( dispersion / w_sum );
0282
0283
0284 sum2_2D /= w_sum;
0285 sum2_3D /= w_sum;
0286 sum1_2D /= w_sum;
0287 sum1_3D /= w_sum;
0288
0289
0290 Eigen::Matrix2f cov2 = sum2_2D - sum1_2D * sum1_2D.transpose();
0291 Eigen::Matrix3f cov3 = sum2_3D - sum1_3D * sum1_3D.transpose();
0292
0293
0294 Eigen::EigenSolver<Eigen::Matrix2f> es_2D(cov2, false);
0295 Eigen::EigenSolver<Eigen::Matrix3f> es_3D(cov3, true);
0296
0297
0298 eigenValues_2D = es_2D.eigenvalues();
0299 eigenValues_3D = es_3D.eigenvalues();
0300
0301 auto eigenvectors= es_3D.eigenvectors();
0302 auto max_eigenvalue_it = std::max_element(
0303 eigenValues_3D.begin(),
0304 eigenValues_3D.end(),
0305 [](auto a, auto b) {
0306 return std::real(a) < std::real(b);
0307 }
0308 );
0309 auto axis = eigenvectors.col(std::distance(
0310 eigenValues_3D.begin(),
0311 max_eigenvalue_it
0312 ));
0313 axis_x=axis(0,0).real();
0314 axis_y=axis(1,0).real();
0315 axis_z=axis(2,0).real();
0316 double norm=sqrt(axis_x*axis_x+axis_y*axis_y+axis_z*axis_z);
0317 if (norm!=0){
0318 axis_x/=norm;
0319 axis_y/=norm;
0320 axis_z/=norm;
0321 }
0322 }
0323 }
0324
0325 cl.addToShapeParameters( radius );
0326 cl.addToShapeParameters( dispersion );
0327 cl.addToShapeParameters( eigenValues_2D[0].real() );
0328 cl.addToShapeParameters( eigenValues_2D[1].real() );
0329 cl.addToShapeParameters( eigenValues_3D[0].real() );
0330 cl.addToShapeParameters( eigenValues_3D[1].real() );
0331 cl.addToShapeParameters( eigenValues_3D[2].real() );
0332
0333 cl.addToShapeParameters( axis_x );
0334 cl.addToShapeParameters( axis_y );
0335 cl.addToShapeParameters( axis_z );
0336 return std::move(cl);
0337 }
0338
0339 }