Repository: eic/EICrecon
Pull request #1768: Track cluster matching
Author: login: tlprotzman
Created: 2025-03-25T03:26:19Z | Merged: 2025-03-28T22:34:53Z

### Briefly, what does this PR introduce?
A centralized algorithm for cluster-track matching which does not rely on any truth issue. 

### What kind of change does this PR introduce?
- [ ] Bug fix (issue #__)
- [X] New feature (issue #1648 )
- [ ] Documentation update
- [ ] Other: __

### Please check if this PR fulfills the following:
- [ ] Tests for the changes have been added
- [ ] Documentation has been added / updated
- [ ] Changes have been communicated to collaborators

### Does this PR introduce breaking changes? What changes might users need to make to their code?

### Does this PR change default behavior?
Adds `"TrackClusterMatches` and `BarrelClusters` collections to default output 


Diff:
diff --git a/src/algorithms/reco/TrackClusterMatch.cc b/src/algorithms/reco/TrackClusterMatch.cc
new file mode 100644
index 0000000000..6e1a560f2e
--- /dev/null
+++ b/src/algorithms/reco/TrackClusterMatch.cc
@@ -0,0 +1,96 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2025 Tristan Protzman
+
+#include <edm4eic/EDM4eicVersion.h> // Needs edm4eic::TrackClusterMatch
+#include <fmt/core.h>
+#include <podio/RelationRange.h>
+#include <stdint.h>
+#include <gsl/pointers>
+#include <optional>
+#include <set>
+#include <vector>
+#if EDM4EIC_VERSION_MAJOR >= 8
+
+#include <DD4hep/Detector.h>
+#include <edm4eic/ClusterCollection.h>
+#include <edm4eic/TrackPoint.h>
+#include <edm4hep/Vector3f.h>
+#include <edm4hep/utils/vector_utils.h>
+
+#include "algorithms/reco/TrackClusterMatch.h"
+#include "algorithms/reco/TrackClusterMatchConfig.h"
+
+namespace eicrecon {
+    void TrackClusterMatch::process(const TrackClusterMatch::Input& input, const TrackClusterMatch::Output& output) const {
+        auto [tracks, clusters] = input;
+        auto [matched_particles] = output;
+        trace("We have {} tracks and {} clusters", tracks->size(), clusters->size());
+
+        std::set<int> used_tracks;
+        // Loop across each cluster, and find the cloeset projected track
+        for (auto cluster : *clusters) {
+            const double cluster_eta = edm4hep::utils::eta(cluster.getPosition());
+            const double cluster_phi = edm4hep::utils::angleAzimuthal(cluster.getPosition());
+            trace("Cluster at eta={}, phi={}", cluster_eta, cluster_phi);
+            // TODO: Get the detector ID so I can check if a projection is in the appropriate calorimeter
+
+            std::optional<edm4eic::TrackSegment> closest_segment;
+            int closest_segment_id = -1;
+            double closest_distance = m_cfg.matching_distance;
+            // Loop through each track segment, and its points
+            for (int closest_id = 0; auto track : *tracks) {
+                if (used_tracks.count(closest_id) > 0) {
+                    trace("Skipping track segment already used");
+                    continue;
+                }
+                for (auto point : track.getPoints()) {
+                    // Check if the point is at the calorimeter
+                    // int id = m_detector->volumeManager().lookupDetector(cluster.getHits()[0].getCellID()).id(); // TODO: Find programmatic way to get detector cluster is from
+                    uint32_t ecal_barrel_id = m_geo.detector()->constant<int>("EcalBarrel_ID");
+                    uint32_t hcal_barrel_id = m_geo.detector()->constant<int>("HcalBarrel_ID");
+                    bool is_ecal = point.system == ecal_barrel_id;
+                    bool is_hcal = point.system == hcal_barrel_id;
+                    bool is_surface = point.surface == 1;
+
+                    if (!(is_ecal || is_hcal) || !is_surface) {
+                        trace("Skipping track point not at the calorimeter");
+                        continue;
+                    }
+                    const double track_eta = edm4hep::utils::eta(point.position);
+                    const double track_phi = edm4hep::utils::angleAzimuthal(point.position);
+                    trace("Track point at eta={}, phi={}", track_eta, track_phi);
+
+                    double delta = distance(cluster.getPosition(), point.position);
+                    trace("Distance between cluster and track point: {}", delta);
+                    if (delta < closest_distance) {
+                        closest_distance = delta;
+                        closest_segment = track;
+                        closest_segment_id = closest_id;
+                    }
+                }
+                closest_id++;
+            }
+            // If we found a point, create a new particle
+            if (closest_segment) {
+                trace("Found a closest point at distance {}", closest_distance);
+                auto particle = matched_particles->create();
+                particle.setCluster(cluster);
+                particle.setTrack(closest_segment.value().getTrack());
+                used_tracks.insert(closest_segment_id);
+            }
+        }
+        trace("Matched {} particles", matched_particles->size());
+    }
+
+    double TrackClusterMatch::distance(const edm4hep::Vector3f& v1, const edm4hep::Vector3f& v2) const {
+        double cluster_eta = edm4hep::utils::eta(v1);
+        double cluster_phi = edm4hep::utils::angleAzimuthal(v1);
+        double track_eta = edm4hep::utils::eta(v2);
+        double track_phi = edm4hep::utils::angleAzimuthal(v2);
+        double deta = cluster_eta - track_eta;
+        double dphi = Phi_mpi_pi(cluster_phi - track_phi);
+        return std::hypot(deta, dphi);
+    }
+}
+
+#endif // EDM4EIC_VERSION_MAJOR >= 8
diff --git a/src/algorithms/reco/TrackClusterMatch.h b/src/algorithms/reco/TrackClusterMatch.h
new file mode 100644
index 0000000000..3a579ac142
--- /dev/null
+++ b/src/algorithms/reco/TrackClusterMatch.h
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2025 Tristan Protzman
+
+#pragma once
+
+#include <algorithms/algorithm.h>
+#include <algorithms/geo.h>
+#include <edm4eic/ClusterCollection.h>
+#include <edm4eic/TrackClusterMatchCollection.h>
+#include <edm4eic/TrackSegmentCollection.h>
+#include <edm4hep/Vector3f.h>
+#include <cmath>
+#include <string>
+#include <string_view>
+
+#include "algorithms/interfaces/WithPodConfig.h"
+#include "algorithms/reco/TrackClusterMatchConfig.h"
+
+namespace eicrecon {
+    using TrackClusterMatchAlgorithm = algorithms::Algorithm<
+    algorithms::Input<edm4eic::TrackSegmentCollection, edm4eic::ClusterCollection>,
+    algorithms::Output<edm4eic::TrackClusterMatchCollection>
+    >;
+
+
+    class TrackClusterMatch : public TrackClusterMatchAlgorithm, public WithPodConfig<TrackClusterMatchConfig> {
+    private:
+        const algorithms::GeoSvc& m_geo = algorithms::GeoSvc::instance();
+        double distance(const edm4hep::Vector3f& v1, const edm4hep::Vector3f& v2) const;
+        static double Phi_mpi_pi(double phi) {return std::remainder(phi, 2 * M_PI);}
+
+
+    public:
+        TrackClusterMatch(std::string_view name) :
+                TrackClusterMatchAlgorithm{name, {"inputTracks", "inputClusters"}, {"outputParticles"}, ""} {}
+
+        void init() final {};
+        void process(const Input&, const Output&) const final;
+    };
+}
diff --git a/src/algorithms/reco/TrackClusterMatchConfig.h b/src/algorithms/reco/TrackClusterMatchConfig.h
new file mode 100644
index 0000000000..24409a55dd
--- /dev/null
+++ b/src/algorithms/reco/TrackClusterMatchConfig.h
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2025 Tristan Protzman
+
+#pragma once
+
+#include <string>
+#include <DD4hep/DD4hepUnits.h>
+
+namespace eicrecon {
+    struct TrackClusterMatchConfig {
+        double matching_distance = 0.5;
+    };
+}
diff --git a/src/global/reco/TrackClusterMatch_factory.h b/src/global/reco/TrackClusterMatch_factory.h
new file mode 100644
index 0000000000..e376fe56ca
--- /dev/null
+++ b/src/global/reco/TrackClusterMatch_factory.h
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2025 Tristan Protzman
+
+
+#pragma once
+
+#include <spdlog/logger.h>
+#include "extensions/jana/JOmniFactory.h"
+
+#include "algorithms/reco/TrackClusterMatch.h"
+#include "algorithms/reco/TrackClusterMatchConfig.h"
+#include "services/geometry/dd4hep/DD4hep_service.h"
+
+
+
+namespace eicrecon {
+    class TrackClusterMatch_factory : public JOmniFactory<TrackClusterMatch_factory, TrackClusterMatchConfig> {
+    private:
+        // Underlying algorithm
+        std::unique_ptr<eicrecon::TrackClusterMatch> m_algo;
+
+        // Declare inputs
+        PodioInput<edm4eic::TrackSegment> m_tracks {this};
+        PodioInput<edm4eic::Cluster> m_clusters {this};
+
+        // Declare outputs
+        PodioOutput<edm4eic::TrackClusterMatch> m_matched_particles {this};
+
+        // Declare parameters
+        ParameterRef<double> m_matching_distance {this, "matchingDistance", config().matching_distance};
+
+    public:
+        void Configure() {
+            m_algo = std::make_unique<eicrecon::TrackClusterMatch>(GetPrefix());
+            m_algo->applyConfig(config());
+            m_algo->init();
+        }
+
+        void ChangeRun(int64_t run_number) {}
+
+        void Process(int64_t run_number, uint64_t event_number) {
+            m_algo->process({m_tracks(), m_clusters()}, {m_matched_particles().get()});
+        }
+    };
+}
diff --git a/src/global/reco/reco.cc b/src/global/reco/reco.cc
index 7206c74cae..8a5e1f38f1 100644
--- a/src/global/reco/reco.cc
+++ b/src/global/reco/reco.cc
@@ -54,6 +54,11 @@
 #include "global/reco/ScatteredElectronsEMinusPz_factory.h"
 #include "global/reco/ScatteredElectronsTruth_factory.h"
 
+#if EDM4EIC_VERSION_MAJOR >= 8
+#include "global/reco/TrackClusterMatch_factory.h"
+#endif
+
+
 #include "services/geometry/dd4hep/DD4hep_service.h"
 
 extern "C" {
@@ -308,6 +313,25 @@ void InitPlugin(JApplication *app) {
         app
     ));
 
+    app->Add(new JOmniFactoryGeneratorT<CollectionCollector_factory<edm4eic::Cluster>>(
+      "BarrelClusters",
+      {
+        "HcalBarrelClusters",
+        "EcalBarrelClusters",
+      },
+      {"BarrelClusters"},
+      app));
+
+    #if EDM4EIC_VERSION_MAJOR >= 8
+    app->Add(new JOmniFactoryGeneratorT<TrackClusterMatch_factory>(
+      "TrackClusterMatcher",
+      {"CalorimeterTrackProjections", "BarrelClusters"},
+      {"TrackClusterMatches"},
+      {},
+      app
+    ));
+    #endif // EDM4EIC_VERSION_MAJOR >= 8
+
     app->Add(new JOmniFactoryGeneratorT<TransformBreitFrame_factory>(
             "ReconstructedBreitFrameParticles",
             {"MCParticles","InclusiveKinematicsElectron","ReconstructedParticles"},
@@ -434,5 +458,7 @@ void InitPlugin(JApplication *app) {
     ));
 
 
+
+
 }
 } // extern "C"
diff --git a/src/services/io/podio/JEventProcessorPODIO.cc b/src/services/io/podio/JEventProcessorPODIO.cc
index b2521baef0..b1761424dd 100644
--- a/src/services/io/podio/JEventProcessorPODIO.cc
+++ b/src/services/io/podio/JEventProcessorPODIO.cc
@@ -220,6 +220,7 @@ JEventProcessorPODIO::JEventProcessorPODIO() {
             "ScatteredElectronsTruth",
             "ScatteredElectronsEMinusPz",
             "PrimaryVertices",
+            "BarrelClusters",
 #if EDM4EIC_VERSION_MAJOR >= 6
             "HadronicFinalState",
 #endif
@@ -345,6 +346,10 @@ JEventProcessorPODIO::JEventProcessorPODIO() {
             "EcalFarForwardZDCRawHitAssociations",
             "HcalFarForwardZDCRawHitAssociations",
 #endif
+#if EDM4EIC_VERSION_MAJOR >= 8
+            "TrackClusterMatches",
+#endif
+
     };
     std::vector<std::string> output_exclude_collections;  // need to get as vector, then convert to set
     std::string output_include_collections = "DEPRECATED";


Reviews:
user: login: veprbl
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-25T18:16:32Z
user: login: tlprotzman
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T18:28:59Z
user: login: tlprotzman
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T18:32:39Z
user: login: veprbl
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-25T19:14:29Z
user: login: veprbl
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-25T19:22:33Z
user: login: veprbl
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-25T19:22:55Z
user: login: veprbl
body: LGTM
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-03-27T20:06:06Z
user: login: tlprotzman
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T20:30:29Z
user: login: veprbl
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-27T20:31:10Z
user: login: ruse-traveler
body: 🚀 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T23:14:38Z

Review comments:
diff_hunk: @@ -0,0 +1,95 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2025 Tristan Protzman
+
+#include "algorithms/reco/TrackClusterMatch.h"
+#include "algorithms/reco/TrackClusterMatchConfig.h"
+#include <spdlog/logger.h>
+
+#include <edm4hep/Vector3f.h>
+#include <edm4hep/utils/vector_utils.h>
+
+#include <edm4eic/Cluster.h>
+#include <edm4eic/ClusterCollection.h>
+#include <edm4eic/Track.h>
+#include <edm4eic/TrackCollection.h>
+#include <edm4eic/TrackPoint.h>
+#include <edm4eic/TrackSegment.h>
+
+#include <DD4hep/Detector.h>
+
+namespace eicrecon {
+    void TrackClusterMatch::init(std::shared_ptr<spdlog::logger> logger, const dd4hep::Detector* detector) {
+        m_log = logger;
+        m_detector = detector;
+    }
+
+    void TrackClusterMatch::execute(const TrackClusterMatch::Input& input, const TrackClusterMatch::Output& output) const {
+        auto [tracks, clusters] = input;
+        auto [matched_particles] = output;
+        m_log->trace("We have {} tracks and {} clusters", tracks->size(), clusters->size());
+
+        std::set<int> used_tracks;
+        // Loop across each cluster, and find the cloeset projected track
+        for (auto cluster : *clusters) {
+            const double cluster_eta = edm4hep::utils::eta(cluster.getPosition());
+            const double cluster_phi = edm4hep::utils::angleAzimuthal(cluster.getPosition());
+            m_log->trace("Cluster at eta={}, phi={}", cluster_eta, cluster_phi);
+            // TODO: Get the detector ID so I can check if a projection is in the appropriate calorimeter
+
+            std::optional<edm4eic::TrackSegment> closest_segment;
+            int closest_segment_id = -1;
+            double closest_distance = m_cfg.matching_distance;
+            // Loop through each track segment, and its points
+            for (int closest_id = 0; auto track : *tracks) {
+                if (used_tracks.count(closest_id) > 0) {
+                    m_log->trace("Skipping track segment already used");
+                    continue;
+                }
+                for (auto point : track.getPoints()) {
+                    // Check if the point is at the calorimeter
+                    // int id = m_detector->volumeManager().lookupDetector(cluster.getHits()[0].getCellID()).id(); // TODO: Find programmatic way to get detector cluster is from
+                    uint32_t ecal_barrel_id = 101;
+                    uint32_t hcal_barrel_id = 111;
+                    bool is_ecal = point.system == ecal_barrel_id;
+                    bool is_hcal = point.system == hcal_barrel_id;
+                    bool is_surface = point.surface == 1;
+
+                    if (!(is_ecal || is_hcal) || !is_surface) {
+                        m_log->trace("Skipping track point not at the calorimeter");
+                        continue;
+                    }
+                    const double track_eta = edm4hep::utils::eta(point.position);
+                    const double track_phi = edm4hep::utils::angleAzimuthal(point.position);
+                    m_log->trace("Track point at eta={}, phi={}", track_eta, track_phi);
+
+                    double delta = distance(cluster.getPosition(), point.position);
+                    m_log->trace("Distance between cluster and track point: {}", delta);
+                    if (delta < closest_distance) {
+                        closest_distance = delta;
+                        closest_segment = track;
+                        closest_segment_id = closest_id;
+                    }
+                }
+                closest_id++;
+            }
+            // If we found a point, create a new particle
+            if (closest_segment) {
+                m_log->trace("Found a closest point at distance {}", closest_distance);
+                auto particle = matched_particles->create();
+                particle.setCluster(cluster);
+                particle.setTrack(closest_segment.value().getTrack());
+                used_tracks.insert(closest_segment_id);
+            }
+        }
+    }
+
+    double TrackClusterMatch::distance(const edm4hep::Vector3f& v1, const edm4hep::Vector3f& v2) const {
+        double cluster_eta = edm4hep::utils::eta(v1);
+        double cluster_phi = edm4hep::utils::angleAzimuthal(v1);
+        double track_eta = edm4hep::utils::eta(v2);
+        double track_phi = edm4hep::utils::angleAzimuthal(v2);
+        double deta = cluster_eta - track_eta;
+        double dphi = cluster_phi - track_phi;
path: src/algorithms/reco/TrackClusterMatch.cc
user: login: veprbl
body: ```c++
static double Phi_mpi_pi(double phi) {
      return std::remainder(phi, 2 * M_PI);
}
```
```suggestion
        double dphi = Phi_mpi_pi(cluster_phi - track_phi);
```
created_at: 2025-03-25T18:04:37Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,95 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2025 Tristan Protzman
+
+#include "algorithms/reco/TrackClusterMatch.h"
+#include "algorithms/reco/TrackClusterMatchConfig.h"
+#include <spdlog/logger.h>
+
+#include <edm4hep/Vector3f.h>
+#include <edm4hep/utils/vector_utils.h>
+
+#include <edm4eic/Cluster.h>
+#include <edm4eic/ClusterCollection.h>
+#include <edm4eic/Track.h>
+#include <edm4eic/TrackCollection.h>
+#include <edm4eic/TrackPoint.h>
+#include <edm4eic/TrackSegment.h>
+
+#include <DD4hep/Detector.h>
+
+namespace eicrecon {
+    void TrackClusterMatch::init(std::shared_ptr<spdlog::logger> logger, const dd4hep::Detector* detector) {
+        m_log = logger;
+        m_detector = detector;
+    }
+
+    void TrackClusterMatch::execute(const TrackClusterMatch::Input& input, const TrackClusterMatch::Output& output) const {
+        auto [tracks, clusters] = input;
+        auto [matched_particles] = output;
+        m_log->trace("We have {} tracks and {} clusters", tracks->size(), clusters->size());
+
+        std::set<int> used_tracks;
+        // Loop across each cluster, and find the cloeset projected track
+        for (auto cluster : *clusters) {
+            const double cluster_eta = edm4hep::utils::eta(cluster.getPosition());
+            const double cluster_phi = edm4hep::utils::angleAzimuthal(cluster.getPosition());
+            m_log->trace("Cluster at eta={}, phi={}", cluster_eta, cluster_phi);
+            // TODO: Get the detector ID so I can check if a projection is in the appropriate calorimeter
+
+            std::optional<edm4eic::TrackSegment> closest_segment;
+            int closest_segment_id = -1;
+            double closest_distance = m_cfg.matching_distance;
+            // Loop through each track segment, and its points
+            for (int closest_id = 0; auto track : *tracks) {
+                if (used_tracks.count(closest_id) > 0) {
+                    m_log->trace("Skipping track segment already used");
+                    continue;
+                }
+                for (auto point : track.getPoints()) {
+                    // Check if the point is at the calorimeter
+                    // int id = m_detector->volumeManager().lookupDetector(cluster.getHits()[0].getCellID()).id(); // TODO: Find programmatic way to get detector cluster is from
+                    uint32_t ecal_barrel_id = 101;
+                    uint32_t hcal_barrel_id = 111;
path: src/algorithms/reco/TrackClusterMatch.cc
user: login: veprbl
body: No hardcoded numbers, please. Should be something like
`detector->constant<int>(m_cfg.hcal_id_constant);`
and
`ParameterRef<double> m_matching_distance {this, "hcalIdConstant", config().hcal_id_constant};`
created_at: 2025-03-25T18:04:49Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,41 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2025 Tristan Protzman
+
+#pragma once
+
+#include <algorithms/algorithm.h>
+#include <edm4eic/TrackCollection.h>
+#include <edm4eic/ClusterCollection.h>
+#include <edm4eic/TrackSegmentCollection.h>
+#include <edm4eic/ReconstructedParticleCollection.h>
+#include <edm4eic/TrackClusterMatchCollection.h>
+#include "algorithms/interfaces/WithPodConfig.h"
+#include <spdlog/logger.h>
+#include <DD4hep/Detector.h>
+#include <edm4hep/Vector3f.h>
+
+
+#include "algorithms/reco/TrackClusterMatchConfig.h"
+
+namespace eicrecon {
+    using TrackClusterMatchAlgorithm = algorithms::Algorithm<
+    algorithms::Input<edm4eic::TrackSegmentCollection, edm4eic::ClusterCollection>,
+    algorithms::Output<edm4eic::TrackClusterMatchCollection>
+    >;
+
+
+    class TrackClusterMatch : public TrackClusterMatchAlgorithm, WithPodConfig<TrackClusterMatchConfig> {
+    private:
+        std::shared_ptr<spdlog::logger> m_log;
+        const dd4hep::Detector* m_detector;
+        double distance(const edm4hep::Vector3f& v1, const edm4hep::Vector3f& v2) const;
+
+
+    public:
+        TrackClusterMatch(std::string_view name) :
+                TrackClusterMatchAlgorithm{name, {"inputTracks", "inputClusters"}, {"outputParticles"}, ""} {}
+
+        void init(std::shared_ptr<spdlog::logger> logger, const dd4hep::Detector* detector);
+        void execute(const Input&, const Output&) const;
path: src/algorithms/reco/TrackClusterMatch.h
user: login: veprbl
body: Where did you find this?

```suggestion
        void init() final;
        void execute(const Input&, const Output&) const final;
```
The `algorithms::Algorithm` provides logging methods (basically, remove the "m_log->"). GeoSvc (the class, not to be confused with "m_GeoSvc" the variable of class `DD4hep_service`) is to be used for `detector`, if needed.
created_at: 2025-03-25T18:06:38Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,54 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2025 Tristan Protzman
+
+
+#pragma once
+
+#include <spdlog/logger.h>
+#include "extensions/jana/JOmniFactory.h"
+
+#include "algorithms/reco/TrackClusterMatch.h"
+#include "algorithms/reco/TrackClusterMatchConfig.h"
+#include "services/geometry/dd4hep/DD4hep_service.h"
+
+
+
+namespace eicrecon {
+    class TrackClusterMatch_factory : public JOmniFactory<TrackClusterMatch_factory, TrackClusterMatchConfig> {
+    private:
+        // Underlying algorithm
+        std::unique_ptr<eicrecon::TrackClusterMatch> m_algo;
+
+        // Declare inputs
+        PodioInput<edm4eic::TrackSegment> m_tracks {this};
+        PodioInput<edm4eic::Cluster> m_clusters {this};
+
+        // Declare outputs
+        PodioOutput<edm4eic::TrackClusterMatch> m_matched_particles {this};
+
+        // Declare parameters
+        ParameterRef<double> m_matching_distance {this, "matchingDistance", config().matching_distance};
+
+        // Helpers
+        std::shared_ptr<spdlog::logger> m_log;
+        Service<DD4hep_service> m_GeoSvc {this};
+        const dd4hep::Detector* m_detector;
+
+    public:
+        void Configure() {
+            m_algo = std::make_unique<eicrecon::TrackClusterMatch>(GetPrefix());
+            m_log = logger();
+            m_detector = m_GeoSvc().detector();
+            m_algo->init(m_log, m_detector);
+            m_log->trace("Configured TrackClusterMatch with matching distance {}", m_matching_distance());
+        }
+
+        void ChangeRun(int64_t run_number) {}
+
+        void Process(int64_t run_number, uint64_t event_number) {
+            m_log->trace("Processing event {} in run {}", event_number, run_number);
+            m_algo->execute({m_tracks(), m_clusters()}, {m_matched_particles().get()});
+            m_log->trace("Matched {} particles", m_matched_particles()->size());
path: src/global/reco/TrackClusterMatch_factory.h
user: login: veprbl
body: Please make consistent with every other factory in eicrecon. If you need traces, move them to algorithm.
created_at: 2025-03-25T18:08:19Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,95 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2025 Tristan Protzman
+
+#include "algorithms/reco/TrackClusterMatch.h"
+#include "algorithms/reco/TrackClusterMatchConfig.h"
+#include <spdlog/logger.h>
+
+#include <edm4hep/Vector3f.h>
+#include <edm4hep/utils/vector_utils.h>
+
+#include <edm4eic/Cluster.h>
+#include <edm4eic/ClusterCollection.h>
+#include <edm4eic/Track.h>
+#include <edm4eic/TrackCollection.h>
+#include <edm4eic/TrackPoint.h>
+#include <edm4eic/TrackSegment.h>
+
+#include <DD4hep/Detector.h>
+
+namespace eicrecon {
+    void TrackClusterMatch::init(std::shared_ptr<spdlog::logger> logger, const dd4hep::Detector* detector) {
+        m_log = logger;
+        m_detector = detector;
+    }
+
+    void TrackClusterMatch::execute(const TrackClusterMatch::Input& input, const TrackClusterMatch::Output& output) const {
+        auto [tracks, clusters] = input;
+        auto [matched_particles] = output;
+        m_log->trace("We have {} tracks and {} clusters", tracks->size(), clusters->size());
path: src/algorithms/reco/TrackClusterMatch.cc
user: login: veprbl
body: ```suggestion
        trace("We have {} tracks and {} clusters", tracks->size(), clusters->size());
```
and so on
created_at: 2025-03-25T18:09:54Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,95 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2025 Tristan Protzman
+
+#include "algorithms/reco/TrackClusterMatch.h"
+#include "algorithms/reco/TrackClusterMatchConfig.h"
+#include <spdlog/logger.h>
+
+#include <edm4hep/Vector3f.h>
+#include <edm4hep/utils/vector_utils.h>
+
+#include <edm4eic/Cluster.h>
+#include <edm4eic/ClusterCollection.h>
+#include <edm4eic/Track.h>
+#include <edm4eic/TrackCollection.h>
+#include <edm4eic/TrackPoint.h>
+#include <edm4eic/TrackSegment.h>
+
+#include <DD4hep/Detector.h>
+
+namespace eicrecon {
+    void TrackClusterMatch::init(std::shared_ptr<spdlog::logger> logger, const dd4hep::Detector* detector) {
+        m_log = logger;
+        m_detector = detector;
+    }
+
+    void TrackClusterMatch::execute(const TrackClusterMatch::Input& input, const TrackClusterMatch::Output& output) const {
+        auto [tracks, clusters] = input;
+        auto [matched_particles] = output;
+        m_log->trace("We have {} tracks and {} clusters", tracks->size(), clusters->size());
+
+        std::set<int> used_tracks;
+        // Loop across each cluster, and find the cloeset projected track
+        for (auto cluster : *clusters) {
+            const double cluster_eta = edm4hep::utils::eta(cluster.getPosition());
+            const double cluster_phi = edm4hep::utils::angleAzimuthal(cluster.getPosition());
+            m_log->trace("Cluster at eta={}, phi={}", cluster_eta, cluster_phi);
+            // TODO: Get the detector ID so I can check if a projection is in the appropriate calorimeter
+
+            std::optional<edm4eic::TrackSegment> closest_segment;
+            int closest_segment_id = -1;
+            double closest_distance = m_cfg.matching_distance;
+            // Loop through each track segment, and its points
+            for (int closest_id = 0; auto track : *tracks) {
+                if (used_tracks.count(closest_id) > 0) {
+                    m_log->trace("Skipping track segment already used");
+                    continue;
+                }
+                for (auto point : track.getPoints()) {
+                    // Check if the point is at the calorimeter
+                    // int id = m_detector->volumeManager().lookupDetector(cluster.getHits()[0].getCellID()).id(); // TODO: Find programmatic way to get detector cluster is from
+                    uint32_t ecal_barrel_id = 101;
+                    uint32_t hcal_barrel_id = 111;
path: src/algorithms/reco/TrackClusterMatch.cc
user: login: tlprotzman
body: Is there a way to get the detector ID from the calorimeter cluster hit?  I experimented with `int id = m_detector>volumeManager().lookupDetector(cluster.getHits([0].getCellID()).id();`, but that felt a bit convoluted to me.
created_at: 2025-03-25T18:28:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,95 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2025 Tristan Protzman
+
+#include "algorithms/reco/TrackClusterMatch.h"
+#include "algorithms/reco/TrackClusterMatchConfig.h"
+#include <spdlog/logger.h>
+
+#include <edm4hep/Vector3f.h>
+#include <edm4hep/utils/vector_utils.h>
+
+#include <edm4eic/Cluster.h>
+#include <edm4eic/ClusterCollection.h>
+#include <edm4eic/Track.h>
+#include <edm4eic/TrackCollection.h>
+#include <edm4eic/TrackPoint.h>
+#include <edm4eic/TrackSegment.h>
+
+#include <DD4hep/Detector.h>
+
+namespace eicrecon {
+    void TrackClusterMatch::init(std::shared_ptr<spdlog::logger> logger, const dd4hep::Detector* detector) {
+        m_log = logger;
+        m_detector = detector;
+    }
+
+    void TrackClusterMatch::execute(const TrackClusterMatch::Input& input, const TrackClusterMatch::Output& output) const {
+        auto [tracks, clusters] = input;
+        auto [matched_particles] = output;
+        m_log->trace("We have {} tracks and {} clusters", tracks->size(), clusters->size());
+
+        std::set<int> used_tracks;
+        // Loop across each cluster, and find the cloeset projected track
+        for (auto cluster : *clusters) {
+            const double cluster_eta = edm4hep::utils::eta(cluster.getPosition());
+            const double cluster_phi = edm4hep::utils::angleAzimuthal(cluster.getPosition());
+            m_log->trace("Cluster at eta={}, phi={}", cluster_eta, cluster_phi);
+            // TODO: Get the detector ID so I can check if a projection is in the appropriate calorimeter
+
+            std::optional<edm4eic::TrackSegment> closest_segment;
+            int closest_segment_id = -1;
+            double closest_distance = m_cfg.matching_distance;
+            // Loop through each track segment, and its points
+            for (int closest_id = 0; auto track : *tracks) {
+                if (used_tracks.count(closest_id) > 0) {
+                    m_log->trace("Skipping track segment already used");
+                    continue;
+                }
+                for (auto point : track.getPoints()) {
+                    // Check if the point is at the calorimeter
+                    // int id = m_detector->volumeManager().lookupDetector(cluster.getHits()[0].getCellID()).id(); // TODO: Find programmatic way to get detector cluster is from
+                    uint32_t ecal_barrel_id = 101;
+                    uint32_t hcal_barrel_id = 111;
+                    bool is_ecal = point.system == ecal_barrel_id;
+                    bool is_hcal = point.system == hcal_barrel_id;
+                    bool is_surface = point.surface == 1;
+
+                    if (!(is_ecal || is_hcal) || !is_surface) {
+                        m_log->trace("Skipping track point not at the calorimeter");
+                        continue;
+                    }
+                    const double track_eta = edm4hep::utils::eta(point.position);
+                    const double track_phi = edm4hep::utils::angleAzimuthal(point.position);
+                    m_log->trace("Track point at eta={}, phi={}", track_eta, track_phi);
+
+                    double delta = distance(cluster.getPosition(), point.position);
+                    m_log->trace("Distance between cluster and track point: {}", delta);
+                    if (delta < closest_distance) {
+                        closest_distance = delta;
+                        closest_segment = track;
+                        closest_segment_id = closest_id;
+                    }
+                }
+                closest_id++;
+            }
+            // If we found a point, create a new particle
+            if (closest_segment) {
+                m_log->trace("Found a closest point at distance {}", closest_distance);
+                auto particle = matched_particles->create();
+                particle.setCluster(cluster);
+                particle.setTrack(closest_segment.value().getTrack());
+                used_tracks.insert(closest_segment_id);
+            }
+        }
+    }
+
+    double TrackClusterMatch::distance(const edm4hep::Vector3f& v1, const edm4hep::Vector3f& v2) const {
+        double cluster_eta = edm4hep::utils::eta(v1);
+        double cluster_phi = edm4hep::utils::angleAzimuthal(v1);
+        double track_eta = edm4hep::utils::eta(v2);
+        double track_phi = edm4hep::utils::angleAzimuthal(v2);
+        double deta = cluster_eta - track_eta;
+        double dphi = cluster_phi - track_phi;
path: src/algorithms/reco/TrackClusterMatch.cc
user: login: tlprotzman
body: To clarify, is `Phi_mpi_pi` defined in some common header I can include, or just add it to my  code? 
created_at: 2025-03-25T18:32:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,95 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2025 Tristan Protzman
+
+#include "algorithms/reco/TrackClusterMatch.h"
+#include "algorithms/reco/TrackClusterMatchConfig.h"
+#include <spdlog/logger.h>
+
+#include <edm4hep/Vector3f.h>
+#include <edm4hep/utils/vector_utils.h>
+
+#include <edm4eic/Cluster.h>
+#include <edm4eic/ClusterCollection.h>
+#include <edm4eic/Track.h>
+#include <edm4eic/TrackCollection.h>
+#include <edm4eic/TrackPoint.h>
+#include <edm4eic/TrackSegment.h>
+
+#include <DD4hep/Detector.h>
+
+namespace eicrecon {
+    void TrackClusterMatch::init(std::shared_ptr<spdlog::logger> logger, const dd4hep::Detector* detector) {
+        m_log = logger;
+        m_detector = detector;
+    }
+
+    void TrackClusterMatch::execute(const TrackClusterMatch::Input& input, const TrackClusterMatch::Output& output) const {
+        auto [tracks, clusters] = input;
+        auto [matched_particles] = output;
+        m_log->trace("We have {} tracks and {} clusters", tracks->size(), clusters->size());
+
+        std::set<int> used_tracks;
+        // Loop across each cluster, and find the cloeset projected track
+        for (auto cluster : *clusters) {
+            const double cluster_eta = edm4hep::utils::eta(cluster.getPosition());
+            const double cluster_phi = edm4hep::utils::angleAzimuthal(cluster.getPosition());
+            m_log->trace("Cluster at eta={}, phi={}", cluster_eta, cluster_phi);
+            // TODO: Get the detector ID so I can check if a projection is in the appropriate calorimeter
+
+            std::optional<edm4eic::TrackSegment> closest_segment;
+            int closest_segment_id = -1;
+            double closest_distance = m_cfg.matching_distance;
+            // Loop through each track segment, and its points
+            for (int closest_id = 0; auto track : *tracks) {
+                if (used_tracks.count(closest_id) > 0) {
+                    m_log->trace("Skipping track segment already used");
+                    continue;
+                }
+                for (auto point : track.getPoints()) {
+                    // Check if the point is at the calorimeter
+                    // int id = m_detector->volumeManager().lookupDetector(cluster.getHits()[0].getCellID()).id(); // TODO: Find programmatic way to get detector cluster is from
+                    uint32_t ecal_barrel_id = 101;
+                    uint32_t hcal_barrel_id = 111;
path: src/algorithms/reco/TrackClusterMatch.cc
user: login: veprbl
body: `detector->constant` is the de-facto standard to do this. It will fetch values from the
```xml
<constant name="EcalBarrel_ID"        value="101"/>
<constant name="HcalBarrel_ID"      value="111"/>
```
from the compact files.
created_at: 2025-03-25T19:14:29Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,95 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2025 Tristan Protzman
+
path: src/algorithms/reco/TrackClusterMatch.cc
user: login: veprbl
body: This algorithm can no work on older supported EDM4eic's:
https://github.com/eic/EICrecon/actions/runs/14066224254/job/39389282345#step:8:551
you'll need to wrap it all in `ifdef`.

```suggestion

#include <edm4eic/EDM4eicVersion.h>
#if EDM4EIC_VERSION_MAJOR >= 8
```
created_at: 2025-03-25T19:19:32Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,95 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2025 Tristan Protzman
+
+#include "algorithms/reco/TrackClusterMatch.h"
+#include "algorithms/reco/TrackClusterMatchConfig.h"
+#include <spdlog/logger.h>
+
+#include <edm4hep/Vector3f.h>
+#include <edm4hep/utils/vector_utils.h>
+
+#include <edm4eic/Cluster.h>
+#include <edm4eic/ClusterCollection.h>
+#include <edm4eic/Track.h>
+#include <edm4eic/TrackCollection.h>
+#include <edm4eic/TrackPoint.h>
+#include <edm4eic/TrackSegment.h>
+
+#include <DD4hep/Detector.h>
+
+namespace eicrecon {
+    void TrackClusterMatch::init(std::shared_ptr<spdlog::logger> logger, const dd4hep::Detector* detector) {
+        m_log = logger;
+        m_detector = detector;
+    }
+
+    void TrackClusterMatch::execute(const TrackClusterMatch::Input& input, const TrackClusterMatch::Output& output) const {
+        auto [tracks, clusters] = input;
+        auto [matched_particles] = output;
+        m_log->trace("We have {} tracks and {} clusters", tracks->size(), clusters->size());
+
+        std::set<int> used_tracks;
+        // Loop across each cluster, and find the cloeset projected track
+        for (auto cluster : *clusters) {
+            const double cluster_eta = edm4hep::utils::eta(cluster.getPosition());
+            const double cluster_phi = edm4hep::utils::angleAzimuthal(cluster.getPosition());
+            m_log->trace("Cluster at eta={}, phi={}", cluster_eta, cluster_phi);
+            // TODO: Get the detector ID so I can check if a projection is in the appropriate calorimeter
+
+            std::optional<edm4eic::TrackSegment> closest_segment;
+            int closest_segment_id = -1;
+            double closest_distance = m_cfg.matching_distance;
+            // Loop through each track segment, and its points
+            for (int closest_id = 0; auto track : *tracks) {
+                if (used_tracks.count(closest_id) > 0) {
+                    m_log->trace("Skipping track segment already used");
+                    continue;
+                }
+                for (auto point : track.getPoints()) {
+                    // Check if the point is at the calorimeter
+                    // int id = m_detector->volumeManager().lookupDetector(cluster.getHits()[0].getCellID()).id(); // TODO: Find programmatic way to get detector cluster is from
+                    uint32_t ecal_barrel_id = 101;
+                    uint32_t hcal_barrel_id = 111;
+                    bool is_ecal = point.system == ecal_barrel_id;
+                    bool is_hcal = point.system == hcal_barrel_id;
+                    bool is_surface = point.surface == 1;
+
+                    if (!(is_ecal || is_hcal) || !is_surface) {
+                        m_log->trace("Skipping track point not at the calorimeter");
+                        continue;
+                    }
+                    const double track_eta = edm4hep::utils::eta(point.position);
+                    const double track_phi = edm4hep::utils::angleAzimuthal(point.position);
+                    m_log->trace("Track point at eta={}, phi={}", track_eta, track_phi);
+
+                    double delta = distance(cluster.getPosition(), point.position);
+                    m_log->trace("Distance between cluster and track point: {}", delta);
+                    if (delta < closest_distance) {
+                        closest_distance = delta;
+                        closest_segment = track;
+                        closest_segment_id = closest_id;
+                    }
+                }
+                closest_id++;
+            }
+            // If we found a point, create a new particle
+            if (closest_segment) {
+                m_log->trace("Found a closest point at distance {}", closest_distance);
+                auto particle = matched_particles->create();
+                particle.setCluster(cluster);
+                particle.setTrack(closest_segment.value().getTrack());
+                used_tracks.insert(closest_segment_id);
+            }
+        }
+    }
+
+    double TrackClusterMatch::distance(const edm4hep::Vector3f& v1, const edm4hep::Vector3f& v2) const {
+        double cluster_eta = edm4hep::utils::eta(v1);
+        double cluster_phi = edm4hep::utils::angleAzimuthal(v1);
+        double track_eta = edm4hep::utils::eta(v2);
+        double track_phi = edm4hep::utils::angleAzimuthal(v2);
+        double deta = cluster_eta - track_eta;
+        double dphi = cluster_phi - track_phi;
+        return std::hypot(deta, dphi);
+    }
+}
path: src/algorithms/reco/TrackClusterMatch.cc
user: login: veprbl
body: ```suggestion
}
#endif
```
created_at: 2025-03-25T19:20:43Z
author_association: MEMBER
diff_hunk: @@ -53,6 +53,7 @@
 #include "global/reco/ReconstructedElectrons_factory.h"
 #include "global/reco/ScatteredElectronsEMinusPz_factory.h"
 #include "global/reco/ScatteredElectronsTruth_factory.h"
+#include "global/reco/TrackClusterMatch_factory.h"
path: src/global/reco/reco.cc
user: login: veprbl
body: ```suggestion
#include <edm4eic/EDM4eicVersion.h>
#if EDM4EIC_VERSION_MAJOR >= 8
#include "global/reco/TrackClusterMatch_factory.h"
#endif
```
created_at: 2025-03-25T19:21:24Z
author_association: MEMBER
diff_hunk: @@ -308,6 +309,23 @@ void InitPlugin(JApplication *app) {
         app
     ));
 
+    app->Add(new JOmniFactoryGeneratorT<CollectionCollector_factory<edm4eic::Cluster>>(
+      "BarrelClusters",
+      {
+        "HcalBarrelClusters",
+        "EcalBarrelScFiClusters",
+      },
+      {"BarrelClusters"},
+      app));
+
+    app->Add(new JOmniFactoryGeneratorT<TrackClusterMatch_factory>(
+      "TrackClusterMatcher",
+      {"CalorimeterTrackProjections", "BarrelClusters"},
+      {"TrackClusterMatches"},
+      {},
+      app
+    ));
path: src/global/reco/reco.cc
user: login: veprbl
body: ```suggestion
#if EDM4EIC_VERSION_MAJOR >= 8
    app->Add(new JOmniFactoryGeneratorT<TrackClusterMatch_factory>(
      "TrackClusterMatcher",
      {"CalorimeterTrackProjections", "BarrelClusters"},
      {"TrackClusterMatches"},
      {},
      app
    ));
#endif
```
created_at: 2025-03-25T19:22:09Z
author_association: MEMBER
diff_hunk: @@ -220,6 +220,8 @@ JEventProcessorPODIO::JEventProcessorPODIO() {
             "ScatteredElectronsTruth",
             "ScatteredElectronsEMinusPz",
             "PrimaryVertices",
+            "TrackClusterMatches",
path: src/services/io/podio/JEventProcessorPODIO.cc
user: login: veprbl
body: ```suggestion
#if EDM4EIC_VERSION_MAJOR >= 8
            "TrackClusterMatches",
#endif
```
created_at: 2025-03-25T19:22:30Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,95 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2025 Tristan Protzman
+
+#include "algorithms/reco/TrackClusterMatch.h"
+#include "algorithms/reco/TrackClusterMatchConfig.h"
+#include <spdlog/logger.h>
+
+#include <edm4hep/Vector3f.h>
+#include <edm4hep/utils/vector_utils.h>
+
+#include <edm4eic/Cluster.h>
+#include <edm4eic/ClusterCollection.h>
+#include <edm4eic/Track.h>
+#include <edm4eic/TrackCollection.h>
+#include <edm4eic/TrackPoint.h>
+#include <edm4eic/TrackSegment.h>
+
+#include <DD4hep/Detector.h>
+
+namespace eicrecon {
+    void TrackClusterMatch::init(std::shared_ptr<spdlog::logger> logger, const dd4hep::Detector* detector) {
+        m_log = logger;
+        m_detector = detector;
+    }
+
+    void TrackClusterMatch::execute(const TrackClusterMatch::Input& input, const TrackClusterMatch::Output& output) const {
+        auto [tracks, clusters] = input;
+        auto [matched_particles] = output;
+        m_log->trace("We have {} tracks and {} clusters", tracks->size(), clusters->size());
+
+        std::set<int> used_tracks;
+        // Loop across each cluster, and find the cloeset projected track
+        for (auto cluster : *clusters) {
+            const double cluster_eta = edm4hep::utils::eta(cluster.getPosition());
+            const double cluster_phi = edm4hep::utils::angleAzimuthal(cluster.getPosition());
+            m_log->trace("Cluster at eta={}, phi={}", cluster_eta, cluster_phi);
+            // TODO: Get the detector ID so I can check if a projection is in the appropriate calorimeter
+
+            std::optional<edm4eic::TrackSegment> closest_segment;
+            int closest_segment_id = -1;
+            double closest_distance = m_cfg.matching_distance;
+            // Loop through each track segment, and its points
+            for (int closest_id = 0; auto track : *tracks) {
+                if (used_tracks.count(closest_id) > 0) {
+                    m_log->trace("Skipping track segment already used");
+                    continue;
+                }
+                for (auto point : track.getPoints()) {
+                    // Check if the point is at the calorimeter
+                    // int id = m_detector->volumeManager().lookupDetector(cluster.getHits()[0].getCellID()).id(); // TODO: Find programmatic way to get detector cluster is from
+                    uint32_t ecal_barrel_id = 101;
+                    uint32_t hcal_barrel_id = 111;
+                    bool is_ecal = point.system == ecal_barrel_id;
+                    bool is_hcal = point.system == hcal_barrel_id;
+                    bool is_surface = point.surface == 1;
+
+                    if (!(is_ecal || is_hcal) || !is_surface) {
+                        m_log->trace("Skipping track point not at the calorimeter");
+                        continue;
+                    }
+                    const double track_eta = edm4hep::utils::eta(point.position);
+                    const double track_phi = edm4hep::utils::angleAzimuthal(point.position);
+                    m_log->trace("Track point at eta={}, phi={}", track_eta, track_phi);
+
+                    double delta = distance(cluster.getPosition(), point.position);
+                    m_log->trace("Distance between cluster and track point: {}", delta);
+                    if (delta < closest_distance) {
+                        closest_distance = delta;
+                        closest_segment = track;
+                        closest_segment_id = closest_id;
+                    }
+                }
+                closest_id++;
+            }
+            // If we found a point, create a new particle
+            if (closest_segment) {
+                m_log->trace("Found a closest point at distance {}", closest_distance);
+                auto particle = matched_particles->create();
+                particle.setCluster(cluster);
+                particle.setTrack(closest_segment.value().getTrack());
+                used_tracks.insert(closest_segment_id);
+            }
+        }
+    }
+
+    double TrackClusterMatch::distance(const edm4hep::Vector3f& v1, const edm4hep::Vector3f& v2) const {
+        double cluster_eta = edm4hep::utils::eta(v1);
+        double cluster_phi = edm4hep::utils::angleAzimuthal(v1);
+        double track_eta = edm4hep::utils::eta(v2);
+        double track_phi = edm4hep::utils::angleAzimuthal(v2);
+        double deta = cluster_eta - track_eta;
+        double dphi = cluster_phi - track_phi;
path: src/algorithms/reco/TrackClusterMatch.cc
user: login: veprbl
body: No common headers so far. Just add it in the .cc file.
created_at: 2025-03-25T19:22:55Z
author_association: MEMBER
diff_hunk: @@ -308,6 +313,25 @@ void InitPlugin(JApplication *app) {
         app
     ));
 
+    app->Add(new JOmniFactoryGeneratorT<CollectionCollector_factory<edm4eic::Cluster>>(
+      "BarrelClusters",
+      {
+        "HcalBarrelClusters",
+        "EcalBarrelScFiClusters",
path: src/global/reco/reco.cc
user: login: veprbl
body: ```suggestion
        "EcalBarrelClusters",
```
created_at: 2025-03-27T20:06:00Z
author_association: MEMBER
diff_hunk: @@ -308,6 +313,25 @@ void InitPlugin(JApplication *app) {
         app
     ));
 
+    app->Add(new JOmniFactoryGeneratorT<CollectionCollector_factory<edm4eic::Cluster>>(
+      "BarrelClusters",
+      {
+        "HcalBarrelClusters",
+        "EcalBarrelScFiClusters",
path: src/global/reco/reco.cc
user: login: tlprotzman
body: Updated, and checked output locally.  I'll have time to start working on the detector benchmark tomorrow
created_at: 2025-03-27T20:30:29Z
author_association: CONTRIBUTOR

PR comments:
user: login: tlprotzman
created_at: 2025-03-25T17:28:29Z
body: This implements track-cluster matching for the barrel calorimeters.  I tested with single particle simulations, summarized here:

![image](https://github.com/user-attachments/assets/355777ef-2ce4-4da6-8285-6aee6222fab7)


The distributions for electrons and pions used are here:
![image](https://github.com/user-attachments/assets/36c14a34-0e7f-454f-bbdc-46a68a3bacc9)
![image](https://github.com/user-attachments/assets/ae87c5ce-6bcb-44c0-9d34-0eb3cd91a203)

and all the analysis code, including the notebook with more of these plots, can be found [here](https://github.com/tlprotzman/track_cluster_matching_test)



author_association: CONTRIBUTOR
user: login: veprbl
created_at: 2025-03-25T18:17:46Z
body: > and all the analysis code, including the notebook with more of these plots, can be found [here](https://github.com/tlprotzman/track_cluster_matching_test)

These look great. Can we get a benchmark in https://github.com/eic/detector_benchmarks ?
author_association: MEMBER
user: login: tlprotzman
created_at: 2025-03-25T20:43:09Z
body: > > and all the analysis code, including the notebook with more of these plots, can be found [here](https://github.com/tlprotzman/track_cluster_matching_test)
> 
> These look great. Can we get a benchmark in https://github.com/eic/detector_benchmarks ?

It will take me a day or two to get to it, but I will adapt them to run there.
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-25T21:36:54Z
body: ### Capybara summary for PR 1768
- [rec_dis_10x100_minQ2=0_craterlake](https://eicrecon.epic-eic.org/pr/1768/capybara/rec_dis_10x100_minQ2=0_craterlake/index.html)
- [rec_dis_10x100_minQ2=1000_craterlake_tracking_only](https://eicrecon.epic-eic.org/pr/1768/capybara/rec_dis_10x100_minQ2=1000_craterlake_tracking_only/index.html)
- [rec_dis_18x275_minQ2=0_craterlake_18x275](https://eicrecon.epic-eic.org/pr/1768/capybara/rec_dis_18x275_minQ2=0_craterlake_18x275/index.html)
- [rec_dis_18x275_minQ2=1000_craterlake_18x275](https://eicrecon.epic-eic.org/pr/1768/capybara/rec_dis_18x275_minQ2=1000_craterlake_18x275/index.html)
- [rec_dis_5x41_minQ2=0_craterlake_5x41](https://eicrecon.epic-eic.org/pr/1768/capybara/rec_dis_5x41_minQ2=0_craterlake_5x41/index.html)
- [rec_e_1GeV_20GeV_craterlake](https://eicrecon.epic-eic.org/pr/1768/capybara/rec_e_1GeV_20GeV_craterlake/index.html)
- [rec_pi_1GeV_20GeV_craterlake](https://eicrecon.epic-eic.org/pr/1768/capybara/rec_pi_1GeV_20GeV_craterlake/index.html)
<sup><sub>Last updated 2025-03-27T17:22-04:00 5352878b8fdc72a2667b5a806d834bf9b217662d</sub></sup>

author_association: CONTRIBUTOR