Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2024-11-15 08:59:47

0001 // SPDX-License-Identifier: LGPL-3.0-or-later
0002 // Copyright (C) 2022 Chao Peng, Sylvester Joosten, Whitney Armstrong, Wouter Deconinck
0003 
0004 /*
0005  *  Topological Cell Clustering Algorithm for Imaging Calorimetry
0006  *  1. group all the adjacent pixels
0007  *
0008  *  Author: Chao Peng (ANL), 06/02/2021
0009  *  Original reference: https://arxiv.org/pdf/1603.02934.pdf
0010  *
0011  *  Modifications:
0012  *
0013  *  Wouter Deconinck (Manitoba), 08/24/2024
0014  *  - converted hit storage model from std::vector to std::set sorted on layer
0015  *    where only hits remaining to be assigned to a group are in the set
0016  *  - erase hits that are too low in energy to be part of a cluster
0017  *  - converted group storage model from std::set to std::list to allow adding
0018  *    hits while keeping iterators valid
0019  *
0020  */
0021 #pragma once
0022 
0023 #include <algorithm>
0024 #include <numeric>
0025 
0026 #include <algorithms/algorithm.h>
0027 #include <DD4hep/BitFieldCoder.h>
0028 #include <DDRec/CellIDPositionConverter.h>
0029 #include <DDRec/Surface.h>
0030 #include <DDRec/SurfaceManager.h>
0031 
0032 #include <spdlog/spdlog.h>
0033 
0034 // Event Model related classes
0035 #include <edm4eic/CalorimeterHitCollection.h>
0036 #include <edm4eic/ProtoClusterCollection.h>
0037 #include <edm4hep/utils/vector_utils.h>
0038 
0039 #include "algorithms/interfaces/WithPodConfig.h"
0040 #include "ImagingTopoClusterConfig.h"
0041 
0042 namespace eicrecon {
0043 
0044   using ImagingTopoClusterAlgorithm = algorithms::Algorithm<
0045     algorithms::Input<
0046       edm4eic::CalorimeterHitCollection
0047     >,
0048     algorithms::Output<
0049       edm4eic::ProtoClusterCollection
0050     >
0051   >;
0052 
0053   class ImagingTopoCluster
0054       : public ImagingTopoClusterAlgorithm,
0055         public WithPodConfig<ImagingTopoClusterConfig> {
0056 
0057   public:
0058     ImagingTopoCluster(std::string_view name)
0059       : ImagingTopoClusterAlgorithm{name,
0060                             {"inputHitCollection"},
0061                             {"outputProtoClusterCollection"},
0062                             "Topological cell clustering algorithm for imaging calorimetry."} {}
0063 
0064   private:
0065 
0066     // unitless counterparts of the input parameters
0067     std::array<double,2> localDistXY{0, 0};
0068     std::array<double,2> layerDistEtaPhi{0, 0};
0069     std::array<double,2> layerDistXY{0, 0};
0070     double sectorDist{0};
0071     double minClusterHitEdep{0};
0072     double minClusterCenterEdep{0};
0073     double minClusterEdep{0};
0074 
0075   public:
0076     void init() {
0077         // unitless conversion
0078         // sanity checks
0079         if (m_cfg.localDistXY.size() != 2) {
0080             error( "Expected 2 values (x_dist, y_dist) for localDistXY");
0081             return;
0082         }
0083         if (m_cfg.layerDistEtaPhi.size() != 2) {
0084             error( "Expected 2 values (eta_dist, phi_dist) for layerDistEtaPhi" );
0085             return;
0086         }
0087         if (m_cfg.minClusterCenterEdep < m_cfg.minClusterHitEdep) {
0088             error( "minClusterCenterEdep must be greater than or equal to minClusterHitEdep" );
0089             return;
0090         }
0091 
0092         // using juggler internal units (GeV, dd4hep::mm, dd4hep::ns, dd4hep::rad)
0093         localDistXY[0] = m_cfg.localDistXY[0] / dd4hep::mm;
0094         localDistXY[1] = m_cfg.localDistXY[1] / dd4hep::mm;
0095         layerDistXY[0] = m_cfg.layerDistXY[0] / dd4hep::mm;
0096         layerDistXY[1] = m_cfg.layerDistXY[1] / dd4hep::mm;
0097         layerDistEtaPhi[0] = m_cfg.layerDistEtaPhi[0];
0098         layerDistEtaPhi[1] = m_cfg.layerDistEtaPhi[1] / dd4hep::rad;
0099         sectorDist = m_cfg.sectorDist / dd4hep::mm;
0100         minClusterHitEdep = m_cfg.minClusterHitEdep / dd4hep::GeV;
0101         minClusterCenterEdep = m_cfg.minClusterCenterEdep / dd4hep::GeV;
0102         minClusterEdep = m_cfg.minClusterEdep / dd4hep::GeV;
0103 
0104         // summarize the clustering parameters
0105         info("Local clustering (same sector and same layer): "
0106                     "Local [x, y] distance between hits <= [{:.4f} mm, {:.4f} mm].",
0107                     localDistXY[0], localDistXY[1]
0108         );
0109         switch (m_cfg.layerMode) {
0110         case ImagingTopoClusterConfig::ELayerMode::etaphi:
0111           info("Neighbour layers clustering (same sector and layer id within +- {:d}: "
0112                "Global [eta, phi] distance between hits <= [{:.4f}, {:.4f} rad].",
0113                m_cfg.neighbourLayersRange, layerDistEtaPhi[0], layerDistEtaPhi[1]
0114                );
0115           break;
0116         case ImagingTopoClusterConfig::ELayerMode::xy:
0117           info("Neighbour layers clustering (same sector and layer id within +- {:d}: "
0118                "Local [x, y] distance between hits <= [{:.4f} mm, {:.4f} mm].",
0119                m_cfg.neighbourLayersRange, layerDistXY[0], layerDistXY[1]
0120                );
0121           break;
0122         default:
0123           error("Unknown layer mode.");
0124         }
0125         info("Neighbour sectors clustering (different sector): "
0126                     "Global distance between hits <= {:.4f} mm.",
0127                     sectorDist
0128         );
0129     }
0130 
0131     void process(const Input& input, const Output& output) const final {
0132 
0133         const auto [hits] = input;
0134         auto [proto] = output;
0135 
0136         // Sort hit indices (podio collections do not support std::sort)
0137         auto compare = [&hits](const auto& a, const auto& b) {
0138             // if !(a < b) and !(b < a), then a and b are equivalent
0139             // and only one of them will be allowed in a set
0140             if ((*hits)[a].getLayer() == (*hits)[b].getLayer()) {
0141               return (*hits)[a].getObjectID().index < (*hits)[b].getObjectID().index;
0142             }
0143             return (*hits)[a].getLayer() < (*hits)[b].getLayer();
0144         };
0145         // indices contains the remaining hit indices that have not
0146         // been assigned to a group yet
0147         std::set<std::size_t, decltype(compare)> indices(compare);
0148         // set does not have a size yet, so cannot fill with iota
0149         for (std::size_t i = 0; i < hits->size(); ++i) {
0150             indices.insert(i);
0151         }
0152         // ensure no hits were dropped due to equivalency in set
0153         if (hits->size() != indices.size()) {
0154             error("equivalent hits were dropped: #hits {:d}, #indices {:d}", hits->size(), indices.size());
0155         }
0156 
0157         // Group neighbouring hits
0158         std::vector<std::list<std::size_t>> groups;
0159         // because indices changes, the loop over indices requires some care:
0160         // - we must use iterators instead of range-for
0161         // - erase returns an incremented iterator and therefore acts as idx++
0162         // - when the set becomes empty on erase, idx is invalid and idx++ will be too
0163         // (also applies to loop in bfs_group below)
0164         for (auto idx = indices.begin(); idx != indices.end();
0165                  indices.empty() ? idx = indices.end() : idx) {
0166 
0167             debug("hit {:d}: local position = ({}, {}, {}), global position = ({}, {}, {}), energy = {}", *idx,
0168                          (*hits)[*idx].getLocal().x, (*hits)[*idx].getLocal().y, (*hits)[*idx].getPosition().z,
0169                          (*hits)[*idx].getPosition().x, (*hits)[*idx].getPosition().y, (*hits)[*idx].getPosition().z,
0170                          (*hits)[*idx].getEnergy()
0171             );
0172 
0173             // not energetic enough for cluster center, but could still be cluster hit
0174             if ((*hits)[*idx].getEnergy() < minClusterCenterEdep) {
0175               idx++;
0176               continue;
0177             }
0178 
0179             // create a new group, and group all the neighbouring hits
0180             groups.emplace_back(std::list{*idx});
0181             bfs_group(*hits, indices, groups.back(), *idx);
0182 
0183             // wait with erasing until after bfs_group to ensure iterator is not invalidated in bfs_group
0184             idx = indices.erase(idx); // takes role of idx++
0185         }
0186         debug("found {} potential clusters (groups of hits)", groups.size());
0187         for (size_t i = 0; i < groups.size(); ++i) {
0188             debug("group {}: {} hits", i, groups[i].size());
0189         }
0190 
0191         // form clusters
0192         for (const auto &group : groups) {
0193             if (group.size() < m_cfg.minClusterNhits) {
0194                 continue;
0195             }
0196             double energy = 0.;
0197             for (std::size_t idx : group) {
0198                 energy += (*hits)[idx].getEnergy();
0199             }
0200             if (energy < minClusterEdep) {
0201                 continue;
0202             }
0203             auto pcl = proto->create();
0204             for (std::size_t idx : group) {
0205                 pcl.addToHits((*hits)[idx]);
0206                 pcl.addToWeights(1);
0207             }
0208         }
0209     }
0210 
0211   private:
0212 
0213     // helper function to group hits
0214     bool is_neighbour(const edm4eic::CalorimeterHit& h1, const edm4eic::CalorimeterHit& h2) const {
0215         // different sectors, simple distance check
0216         if (h1.getSector() != h2.getSector()) {
0217             return std::hypot((h1.getPosition().x - h2.getPosition().x),
0218                               (h1.getPosition().y - h2.getPosition().y),
0219                               (h1.getPosition().z - h2.getPosition().z)) <= sectorDist;
0220         }
0221 
0222         // layer check
0223         int ldiff = std::abs(h1.getLayer() - h2.getLayer());
0224         // same layer, check local positions
0225         if (ldiff == 0) {
0226             return (std::abs(h1.getLocal().x - h2.getLocal().x) <= localDistXY[0]) &&
0227                    (std::abs(h1.getLocal().y - h2.getLocal().y) <= localDistXY[1]);
0228         } else if (ldiff <= m_cfg.neighbourLayersRange) {
0229           switch(m_cfg.layerMode){
0230           case eicrecon::ImagingTopoClusterConfig::ELayerMode::etaphi:
0231             return (std::abs(edm4hep::utils::eta(h1.getPosition()) - edm4hep::utils::eta(h2.getPosition())) <= layerDistEtaPhi[0]) &&
0232                    (std::abs(edm4hep::utils::angleAzimuthal(h1.getPosition()) - edm4hep::utils::angleAzimuthal(h2.getPosition())) <=
0233                     layerDistEtaPhi[1]);
0234           case eicrecon::ImagingTopoClusterConfig::ELayerMode::xy:
0235             return (std::abs(h1.getPosition().x - h2.getPosition().x) <= layerDistXY[0]) &&
0236                    (std::abs(h1.getPosition().y - h2.getPosition().y) <= layerDistXY[1]);
0237           }
0238         }
0239         // not in adjacent layers
0240         return false;
0241     }
0242 
0243     // grouping function with Breadth-First Search
0244     // note: template to allow Compare only known in local scope of caller
0245     template<typename Compare>
0246     void bfs_group(const edm4eic::CalorimeterHitCollection &hits, std::set<std::size_t,Compare>& indices, std::list<std::size_t> &group, const std::size_t idx) const {
0247 
0248       // loop over group as it grows, until the end is stable and we reach it
0249       for (auto idx1 = group.begin(); idx1 != group.end(); ++idx1) {
0250         // check neighbours (note comments on loop over set above)
0251         for (auto idx2 = indices.begin(); idx2 != indices.end();
0252             indices.empty() ? idx2 = indices.end() : idx2) {
0253 
0254           // skip idx1 and original idx
0255           // (we cannot erase idx since it would invalidate iterator in calling scope)
0256           if (*idx2 == *idx1 || *idx2 == idx) {
0257             idx2++;
0258             continue;
0259           }
0260 
0261           // skip rest of list of hits when we're past relevant layers
0262           //if (hits[*idx2].getLayer() - hits[*idx1].getLayer() > m_cfg.neighbourLayersRange) {
0263           //  break;
0264           //}
0265 
0266           // not energetic enough for cluster hit
0267           if (hits[*idx2].getEnergy() < m_cfg.minClusterHitEdep) {
0268             idx2 = indices.erase(idx2);
0269             continue;
0270           }
0271 
0272           if (is_neighbour(hits[*idx1], hits[*idx2])) {
0273             group.push_back(*idx2);
0274             idx2 = indices.erase(idx2); // takes role of idx2++
0275           } else {
0276             idx2++;
0277           }
0278         }
0279       }
0280     }
0281 
0282   };
0283 
0284 } // namespace eicrecon