LDMX Software
StripClusterProcessor.cxx
1#include "Tracking/Reco/StripClusterProcessor.h"
2
3#include <cmath>
4#include <map>
5#include <unordered_set>
6
7#include "Acts/Definitions/Units.hpp"
8#include "Tracking/Event/FittedSiStripHit.h"
9#include "Tracking/Event/Measurement.h"
10
11using namespace framework;
12
13namespace tracking::reco {
14
15StripClusterProcessor::StripClusterProcessor(const std::string& name,
16 framework::Process& process)
17 : TrackingGeometryUser(name, process) {}
18
19// ---------------------------------------------------------------------------
20
21void StripClusterProcessor::configure(
23 in_collection_ =
24 parameters.get<std::string>("in_collection", "FittedSiStripHits");
25 in_pass_ = parameters.get<std::string>("in_pass", "");
26 out_collection_ =
27 parameters.get<std::string>("out_collection", "StripMeasurements");
28
29 seed_threshold_ = parameters.get<double>("seed_threshold", 4.0);
30 neighbor_threshold_ = parameters.get<double>("neighbor_threshold", 3.0);
31 cluster_threshold_ = parameters.get<double>("cluster_threshold", 4.0);
32 mean_time_ns_ = parameters.get<double>("mean_time_ns", 0.0);
33 time_window_ns_ = parameters.get<double>("time_window_ns", -1.0);
34 neighbor_delta_t_ns_ = parameters.get<double>("neighbor_delta_t_ns", -1.0);
35 max_chi2_ndf_ = parameters.get<double>("max_chi2_ndf", -1.0);
36}
37
38// ---------------------------------------------------------------------------
39
40void StripClusterProcessor::onProcessStart() {
41 using namespace tracking::digitization;
42
43 clusterer_ = std::make_unique<tracking::digitization::StripClusterer>(
44 seed_threshold_, neighbor_threshold_, cluster_threshold_, NOISE_SIGMA_ADC,
45 mean_time_ns_, time_window_ns_, neighbor_delta_t_ns_, max_chi2_ndf_);
46
47 ldmx_log(info) << "StripClusterProcessor configured:" << " seed_thr="
48 << seed_threshold_ << " nbr_thr=" << neighbor_threshold_
49 << " cls_thr=" << cluster_threshold_
50 << " noise=" << NOISE_SIGMA_ADC << " ADC"
51 << " pitch=" << READOUT_PITCH_MM << " mm"
52 << " sigma_v=" << SIGMA_V_MM << " mm";
53}
54
55// ---------------------------------------------------------------------------
56
57void StripClusterProcessor::produce(framework::Event& event) {
58 const auto& fitted_hits =
59 event.getCollection<ldmx::FittedSiStripHit>(in_collection_, in_pass_);
60
61 ldmx_log(debug) << "Clustering " << fitted_hits.size()
62 << " FittedSiStripHits";
63
64 // -------------------------------------------------------------------------
65 // Group fitted hits by layer.
66 // -------------------------------------------------------------------------
67 std::map<int, std::vector<ldmx::FittedSiStripHit>> hits_by_layer;
68 for (const auto& h : fitted_hits) {
69 hits_by_layer[h.getLayerID()].push_back(h);
70 }
71
72 // -------------------------------------------------------------------------
73 // Cluster each layer and convert to Measurements.
74 // -------------------------------------------------------------------------
75 std::vector<ldmx::Measurement> measurements;
76
77 for (const auto& [layer_id, layer_hits] : hits_by_layer) {
78 auto hit_surface = geometry().getSurface(layer_id);
79 if (!hit_surface) {
80 ldmx_log(warn) << "No surface found for layer_id=" << layer_id
81 << " — skipping " << layer_hits.size() << " hits";
82 continue;
83 }
84
85 // Build a strip-index → FittedSiStripHit map for truth lookup.
86 std::map<int, const ldmx::FittedSiStripHit*> strip_hit_map;
87 for (const auto& h : layer_hits) {
88 strip_hit_map[h.getStripID()] = &h;
89 }
90
91 const auto clusters = clusterer_->findClusters(layer_hits);
92 ldmx_log(debug) << " layer " << layer_id << ": " << layer_hits.size()
93 << " hits → " << clusters.size() << " clusters";
94
95 for (const auto& cl : clusters) {
96 // -------------------------------------------------------------------
97 // Local position: U from charge-weighted centroid, V unmeasured (= 0).
98 // With AC-coupled transfer efficiencies, each readout strip r is anchored
99 // at the position of its paired sense strip (position_in_group == 0),
100 // which is at U = (r - N_int) * readout_pitch where N_int = N/2
101 // (integer). For N=767: offset = 383, so readout 383 → U=0, 384 → U=60
102 // µm, etc.
103 // -------------------------------------------------------------------
104 using namespace tracking::digitization;
105 const int n_int_ = N_READOUT_STRIPS / 2; // integer division = 383
106 const double offset_ = static_cast<double>(n_int_);
107 const double local_u = (cl.centroid_strip - offset_) * READOUT_PITCH_MM;
108
109 // Cluster-size-dependent position uncertainty using sense pitch (30 µm).
110 // Divisors follow the HPS convention: 1/√12 for single-strip (binary
111 // resolution), 1/5 for 2-strip (best charge-sharing), then degrading.
112 double sigma_u;
113 switch (cl.n_strips) {
114 case 1:
115 sigma_u = SENSE_PITCH_MM / std::sqrt(12.0);
116 break; // 8.7 µm
117 case 2:
118 sigma_u = SENSE_PITCH_MM / 5.0;
119 break; // 6.0 µm
120 case 3:
121 sigma_u = SENSE_PITCH_MM / 3.0;
122 break; // 10.0 µm
123 case 4:
124 sigma_u = SENSE_PITCH_MM / 2.0;
125 break; // 15.0 µm
126 default:
127 sigma_u = SENSE_PITCH_MM;
128 break; // 30.0 µm
129 }
130 constexpr double local_v = 0.0;
131
132 // -------------------------------------------------------------------
133 // Global position via Acts surface transform.
134 // -------------------------------------------------------------------
135 Acts::Vector3 dummy_momentum;
136 const Acts::Vector3 global_pos = hit_surface->localToGlobal(
137 geometryContext(), Acts::Vector2(local_u, local_v), dummy_momentum);
138
139 // -------------------------------------------------------------------
140 // Build the Measurement.
141 // -------------------------------------------------------------------
143 meas.setLayerID(layer_id);
144 meas.setLocalPosition(static_cast<float>(local_u),
145 static_cast<float>(local_v));
147 static_cast<float>(sigma_u * sigma_u),
148 static_cast<float>(tracking::digitization::SIGMA_V_MM *
149 tracking::digitization::SIGMA_V_MM));
150 meas.setGlobalPosition(static_cast<float>(global_pos[0]),
151 static_cast<float>(global_pos[1]),
152 static_cast<float>(global_pos[2]));
153 meas.setTime(static_cast<float>(cl.time_ns));
154 meas.setNStrips(cl.n_strips);
155 meas.setClusterAmplitude(static_cast<float>(cl.total_amplitude));
156
157 // -------------------------------------------------------------------
158 // Reconstructed energy: convert total cluster amplitude to edep using
159 // the fixed detector constants from SiStripConstants.h.
160 // edep = total_amplitude [ADC] × ADC_ELECTRONS_PER_COUNT [e/ADC]
161 // × ENERGY_PER_EHP_MEV [MeV/e]
162 // -------------------------------------------------------------------
163 const float reco_edep = static_cast<float>(
164 cl.total_amplitude * ADC_ELECTRONS_PER_COUNT * ENERGY_PER_EHP_MEV);
165 meas.setEdep(reco_edep);
166
167 // -------------------------------------------------------------------
168 // Truth matching: collect unique track IDs from constituent strips.
169 // -------------------------------------------------------------------
170 std::unordered_set<int> seen_track_ids;
171 for (const int strip : cl.strip_ids) {
172 auto it = strip_hit_map.find(strip);
173 if (it != strip_hit_map.end()) {
174 const int tid = it->second->getTrackID();
175 if (tid >= 0 && seen_track_ids.insert(tid).second) {
176 meas.addTrackId(tid);
177 }
178 }
179 }
180
181 ldmx_log(trace) << " cluster: layer=" << layer_id
182 << " strips=" << cl.n_strips << " u=" << local_u << " mm"
183 << " sigma_u=" << sigma_u << " mm" << " t=" << cl.time_ns
184 << " ns" << " amp=" << cl.total_amplitude << " ADC"
185 << " n_track_ids=" << seen_track_ids.size();
186
187 measurements.push_back(meas);
188 }
189 }
190
191 ldmx_log(debug) << "Produced " << measurements.size() << " Measurements";
192 event.add(out_collection_, measurements);
193}
194
195} // namespace tracking::reco
196
#define DECLARE_PRODUCER(CLASS)
Macro which allows the framework to construct a producer given its name during configuration.
Implements an event buffer system for storing event data.
Definition Event.h:42
Class which represents the process under execution.
Definition Process.h:37
Class encapsulating parameters for configuring a processor.
Definition Parameters.h:29
const T & get(const std::string &name) const
Retrieve the parameter of the given name.
Definition Parameters.h:78
Result of fitting a pulse shape to the ADC samples of a single readout strip.
void setNStrips(int n)
Set the number of strips that formed this cluster (-1 if not a cluster).
void setLocalPosition(const float &meas_u, const float &meas_v)
Set the local position i.e.
Definition Measurement.h:60
void setLayerID(const int &layer_id)
Set the layer ID of the sensor where this measurement took place.
void addTrackId(int trk_id)
Add a trackId to the internal vector.
void setEdep(float e)
Set the energy deposited in the sensor [MeV].
void setGlobalPosition(const float &meas_x, const float &meas_y, const float &meas_z)
Set the global position i.e.
Definition Measurement.h:41
void setLocalCovariance(const float &cov_uu, const float &cov_vv)
Set cov(U,U) and cov(V, V).
Definition Measurement.h:76
void setClusterAmplitude(float amp)
Set the total cluster amplitude [ADC counts].
void setTime(const float &meas_t)
Set the measurement time in ns.
Definition Measurement.h:92
Clusters fitted silicon-strip hits and produces Measurement objects.
All classes in the ldmx-sw project use this namespace.