Skip to content
Snippets Groups Projects
TopologicalCellCluster.cpp 9.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     *  Topological Cell Clustering Algorithm for Sampling Calorimetry
     *  1. group all the adjacent modules
    
    Sylvester Joosten's avatar
    Sylvester Joosten committed
     *  2. TODO split local maxima (seems no need for Imaging Calorimeter with extremely fine
     * granularity)
    
     *
     *  Author: Chao Peng (ANL), 04/06/2021
     *  References: https://arxiv.org/pdf/1603.02934.pdf
     *
     */
    
    #include "fmt/format.h"
    
    Sylvester Joosten's avatar
    Sylvester Joosten committed
    #include <algorithm>
    
    
    #include "Gaudi/Property.h"
    #include "GaudiAlg/GaudiAlgorithm.h"
    #include "GaudiAlg/GaudiTool.h"
    
    Sylvester Joosten's avatar
    Sylvester Joosten committed
    #include "GaudiAlg/Transformer.h"
    
    #include "GaudiKernel/PhysicalConstants.h"
    
    Sylvester Joosten's avatar
    Sylvester Joosten committed
    #include "GaudiKernel/RndmGenerators.h"
    #include "GaudiKernel/ToolHandle.h"
    
    Sylvester Joosten's avatar
    Sylvester Joosten committed
    #include "DD4hep/BitFieldCoder.h"
    
    #include "DDRec/CellIDPositionConverter.h"
    #include "DDRec/Surface.h"
    
    Sylvester Joosten's avatar
    Sylvester Joosten committed
    #include "DDRec/SurfaceManager.h"
    
    
    // FCCSW
    #include "JugBase/DataHandle.h"
    #include "JugBase/IGeoSvc.h"
    
    // Event Model related classes
    #include "eicd/CalorimeterHitCollection.h"
    #include "eicd/ClusterCollection.h"
    
    using namespace Gaudi::Units;
    
    namespace Jug::Reco {
    
    
    Whitney Armstrong's avatar
    Whitney Armstrong committed
      /** Topological Cell Clustering Algorithm for Sampling Calorimetry.
       *
       *  1. group all the adjacent modules
       *  2. TODO split local maxima (seems no need for Imaging Calorimeter with extremely fine
       * granularity)
       *
       * \ingroup reco
       */
    
      class TopologicalCellCluster : public GaudiAlgorithm {
      public:
    
        // maximum difference in layer numbers that can be considered as neighbours
        Gaudi::Property<int> m_adjLayerDiff{this, "adjLayerDiff", 1};
        // geometry service name
        Gaudi::Property<std::string> m_geoSvcName{this, "geoServiceName", "GeoSvc"};
        // name of readout class
    
        Gaudi::Property<std::string> m_readout{this, "readoutClass", ""};
    
        // name of layer field in readout
        Gaudi::Property<std::string> m_layerField{this, "layerField", "layer"};
        // name of sector field in readout
        Gaudi::Property<std::string> m_sectorField{this, "sectorField", "sector"};
        // maximum distance of local (x, y) to be considered as neighbors at the same layer
    
    Sylvester Joosten's avatar
    Sylvester Joosten committed
        Gaudi::Property<std::vector<double>> u_localRanges{this, "localRanges", {1.0 * mm, 1.0 * mm}};
    
        // maximum distance of global (eta, phi) to be considered as neighbors at different layers
    
    Sylvester Joosten's avatar
    Sylvester Joosten committed
        Gaudi::Property<std::vector<double>> u_adjLayerRanges{
            this, "adjLayerRanges", {0.01 * M_PI, 0.01 * M_PI}};
    
        // maximum global distance to be considered as neighbors in different sectors
    
    Sylvester Joosten's avatar
    Sylvester Joosten committed
        Gaudi::Property<double> m_adjSectorDist{this, "adjSectorDist", 1.0 * cm};
    
        // minimum cluster center energy (to be considered as a seed for cluster)
    
        // @TODO One can not simply find a center by edep with extremely fine granularity.
    
    Sylvester Joosten's avatar
    Sylvester Joosten committed
        // Projecting to (eta, phi) with crude pixel size may help determine the center edep, which can
        // happen in the following reconstruction step
        Gaudi::Property<double> m_minClusterCenterEdep{this, "minClusterCenterEdep", 50 * keV};
    
        // input hits collection
    
    Sylvester Joosten's avatar
    Sylvester Joosten committed
        DataHandle<eic::CalorimeterHitCollection> m_inputHitCollection{"inputHitCollection",
                                                                       Gaudi::DataHandle::Reader, this};
    
        // output cluster collection
    
    Sylvester Joosten's avatar
    Sylvester Joosten committed
        DataHandle<eic::ClusterCollection> m_outputClusterCollection{"outputClusterCollection",
                                                                     Gaudi::DataHandle::Writer, this};
    
        // output split hits collection
        // @TODO not implemented, as with extreme fine granularity, there is no need to split hits
        // DataHandle<eic::CalorimeterHitCollection>
        //     m_splitHitCollection{"splitHitCollection", Gaudi::DataHandle::Writer, this};
    
        SmartIF<IGeoSvc> m_geoSvc;
        // visit readout fields
    
    Sylvester Joosten's avatar
    Sylvester Joosten committed
        dd4hep::BitFieldCoder* id_dec;
        size_t                 sector_idx, layer_idx;
    
    
        // ill-formed: using GaudiAlgorithm::GaudiAlgorithm;
        TopologicalCellCluster(const std::string& name, ISvcLocator* svcLoc)
            : GaudiAlgorithm(name, svcLoc)
        {
    
    Sylvester Joosten's avatar
    Sylvester Joosten committed
          declareProperty("inputHitCollection", m_inputHitCollection, "");
          declareProperty("outputClusterCollection", m_outputClusterCollection, "");
    
    Sylvester Joosten's avatar
    Sylvester Joosten committed
          if (GaudiAlgorithm::initialize().isFailure()) {
            return StatusCode::FAILURE;
          }
    
          // check local clustering range
          if (u_localRanges.size() < 2) {
            error() << "Need 2-dimensional ranges for same-layer clustering, only have "
                    << u_localRanges.size() << endmsg;
            return StatusCode::FAILURE;
          }
          info() << "Same layer hits group ranges"
                 << " (" << u_localRanges[0] / mm << " mm, " << u_localRanges[1] / mm << " mm)"
                 << endmsg;
    
          // check adjacent layer clustering range
          if (u_adjLayerRanges.size() < 2) {
            error() << "Need 2-dimensional ranges for adjacent-layer clustering, only have "
                    << u_adjLayerRanges.size() << endmsg;
            return StatusCode::FAILURE;
          }
          info() << "Same layer hits group ranges"
                 << " (" << u_adjLayerRanges[0] / M_PI * 1000. << " mrad, "
                 << u_adjLayerRanges[1] / M_PI * 1000. << " mrad)" << endmsg;
    
          // check geometry service
          m_geoSvc = service(m_geoSvcName);
          if (!m_geoSvc) {
            error() << "Unable to locate Geometry Service. "
                    << "Make sure you have GeoSvc and SimSvc in the right order in the configuration."
                    << endmsg;
            return StatusCode::FAILURE;
          }
    
          if (m_readout.value().empty()) {
            error() << "readoutClass is not provided, it is needed to know the fields in readout ids"
                    << endmsg;
            return StatusCode::FAILURE;
          }
    
          try {
            id_dec     = m_geoSvc->detector()->readout(m_readout).idSpec().decoder();
            sector_idx = id_dec->index(m_sectorField);
            layer_idx  = id_dec->index(m_layerField);
          } catch (...) {
            error() << "Failed to load ID decoder for " << m_readout << endmsg;
            return StatusCode::FAILURE;
          }
    
          return StatusCode::SUCCESS;
    
    Sylvester Joosten's avatar
    Sylvester Joosten committed
          // input collections
          const auto& hits = *m_inputHitCollection.get();
          // Create output collections
          auto& clusters = *m_outputClusterCollection.createAndPut();
    
          // group neighboring hits
          std::vector<bool>                             visits(hits.size(), false);
          std::vector<std::vector<eic::CalorimeterHit>> groups;
          for (size_t i = 0; i < hits.size(); ++i) {
            // already in a group, or not energetic enough to form a cluster
            if (visits[i] || hits[i].energy() < m_minClusterCenterEdep) {
              continue;
    
    Sylvester Joosten's avatar
    Sylvester Joosten committed
            groups.emplace_back();
            // create a new group, and group all the neighboring hits
            dfs_group(groups.back(), i, hits, visits);
          }
          debug() << "we have " << groups.size() << " groups of hits" << endmsg;
    
          for (const auto& group : groups) {
            auto cl = clusters.create();
          }
    
    Sylvester Joosten's avatar
    Sylvester Joosten committed
          return StatusCode::SUCCESS;
    
    Sylvester Joosten's avatar
    Sylvester Joosten committed
      private:
        template <typename T>
        static inline T pow2(const T& x)
        {
          return x * x;
        }
    
        // helper function to group hits
    
    Sylvester Joosten's avatar
    Sylvester Joosten committed
        bool is_neighbor(const eic::ConstCalorimeterHit& h1, const eic::ConstCalorimeterHit& h2) const
    
    Sylvester Joosten's avatar
    Sylvester Joosten committed
          // we will merge different sectors later using global positions
          int s1 = id_dec->get(h1.cellID(), sector_idx);
          int s2 = id_dec->get(h2.cellID(), sector_idx);
          if (s1 != s2) {
    
            return std::sqrt(pow2(h1.position().x - h2.position().x) + pow2(h1.position().y - h2.position().y) + pow2(h1.position().z - h2.position().z)) <=
    
    Sylvester Joosten's avatar
    Sylvester Joosten committed
                   m_adjSectorDist;
          }
    
          int l1 = id_dec->get(h1.cellID(), layer_idx);
          int l2 = id_dec->get(h2.cellID(), layer_idx);
    
          // layer check
          int ldiff = std::abs(l1 - l2);
          // same layer, check local positions
          if (!ldiff) {
    
            return (std::abs(h1.local().x - h2.local().x) <= u_localRanges[0]) &&
                   (std::abs(h1.local().y - h2.local().y) <= u_localRanges[1]);
    
    Sylvester Joosten's avatar
    Sylvester Joosten committed
          } else if (ldiff <= m_adjLayerDiff) {
            double eta1, phi1, r1, eta2, phi2, r2;
    
            calc_eta_phi_r(h1.position().x, h1.position().y, h1.position().z, eta1, phi1, r1);
            calc_eta_phi_r(h2.position().x, h2.position().y, h2.position().z, eta2, phi2, r2);
    
    Sylvester Joosten's avatar
    Sylvester Joosten committed
    
            return (std::abs(eta1 - eta2) <= u_adjLayerRanges[0]) &&
                   (std::abs(phi1 - phi2) <= u_adjLayerRanges[1]);
          }
    
          // not in adjacent layers
          return false;
    
    Sylvester Joosten's avatar
    Sylvester Joosten committed
        static void calc_eta_phi_r(double x, double y, double z, double& eta, double& phi, double& r)
        {
          r            = std::sqrt(x * x + y * y + z * z);
          phi          = std::atan2(y, x);
          double theta = std::acos(z / r);
          eta          = -std::log(std::tan(theta / 2.));
    
        }
    
        // grouping function with Depth-First Search
    
    Sylvester Joosten's avatar
    Sylvester Joosten committed
        void dfs_group(std::vector<eic::CalorimeterHit>& group, int idx,
                       const eic::CalorimeterHitCollection& hits, std::vector<bool>& visits) const
    
    Sylvester Joosten's avatar
    Sylvester Joosten committed
          const eic::CalorimeterHit hit{hits[idx].cellID(),    hits[idx].clusterID(),
                                        hits[idx].layerID(),   hits[idx].sectorID(),
                                        hits[idx].energy(),    hits[idx].time(),
                                        hits[idx].position(),  hits[idx].local(),
                                        hits[idx].dimension(), 1};
          group.push_back(hit);
          visits[idx] = true;
          for (size_t i = 0; i < hits.size(); ++i) {
            // visited, or not a neighbor
            if (visits[i] || !is_neighbor(hit, hits[i])) {
              continue;
    
    Sylvester Joosten's avatar
    Sylvester Joosten committed
            dfs_group(group, i, hits, visits);
          }
    
    Sylvester Joosten's avatar
    Sylvester Joosten committed
      DECLARE_COMPONENT(TopologicalCellCluster)