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