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