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