LDMX Software
TrigMipReco.cxx
2
3#include <chrono>
4
5#include "Trigger/Event/TrigCaloHit.h"
6#include "Trigger/Event/TrigMip.h"
7
8namespace trigger {
9
11 profiling_map_["processing_time_"] = 0;
12}
13
15 hit_coll_name_ = ps.get<std::string>("hit_coll_name");
16 pass_coll_name_ = ps.get<std::string>("pass_coll_name");
17 hit_coll_passname_ = ps.get<std::string>("hit_coll_passname");
18 calorimeter_type_is_hcal_ = ps.get<bool>("calorimeter_type_is_hcal");
19
20 max_layer_ = ps.get<int>("max_layer", 32);
21 // mm
22 search_radius_ = ps.get<double>("search_radius", 50.0);
23 min_track_length_ = ps.get<int>("min_track_length", 5);
24 // MeV; Change as needed
25 isolation_e_cut_ = ps.get<double>("isolation_e_cut", 180.0);
26 hole_fraction_max_ = ps.get<double>("hole_fraction_max", 0.2);
27
28 if (calorimeter_type_is_hcal_) {
29 // MIP peak is 10-11 MeV
30 hcal_min_energy_ = ps.get<double>("hcal_min_energy", 8.0);
31 } else { // ECAL
32 // MIP peak around 17 MeV
33 ecal_min_energy_ = ps.get<double>("ecal_min_energy", 3.0);
34 ecal_max_energy_ = ps.get<double>("ecal_max_energy", 26.0);
35 }
36}
37
39 auto start = std::chrono::high_resolution_clock::now();
40 nevents_++;
41
42 if (!event.exists(hit_coll_name_, hit_coll_passname_)) return;
43
44 const auto calo_hits = event.getObject<std::vector<TrigCaloHit>>(
45 hit_coll_name_, hit_coll_passname_);
46
47 if (calorimeter_type_is_hcal_) { // HCAL MIP Reconstruction
48 std::vector<TrigCaloHit> sorted_hits;
49 int even_matrix[24][5] = {};
50 int even_start[5] = {99, 99, 99, 99, 99};
51 int even_end[5] = {};
52 int even_counts[5] = {};
53 int odd_matrix[24][5] = {};
54 int odd_start[5] = {99, 99, 99, 99, 99};
55 int odd_end[5] = {};
56 int odd_counts[5] = {};
57 // Start in first 5 layers
58 constexpr int layer_start = 80;
59
60 for (const auto& tp : calo_hits) {
61 if (tp.section() > 0 || tp.energy() < hcal_min_energy_ ||
62 tp.layer() > 47) {
63 continue;
64 }
65
66 sorted_hits.push_back(tp);
67 const int layer_index = tp.layer() / 2;
68 const int strip = tp.strip();
69 if (tp.layer() % 2) {
70 odd_matrix[layer_index][strip] = 1;
71 odd_start[strip] = std::min(odd_start[strip], tp.layer());
72 odd_end[strip] = std::max(odd_end[strip], tp.layer());
73 } else {
74 even_matrix[layer_index][strip] = 1;
75 even_start[strip] = std::min(even_start[strip], tp.layer());
76 even_end[strip] = std::max(even_end[strip], tp.layer());
77 }
78 }
79
80 for (int i = 0; i < 24; i++) {
81 for (int j = 0; j < 5; j++) {
82 if (even_matrix[i][j]) {
83 even_counts[j]++;
84 }
85 if (odd_matrix[i][j]) {
86 odd_counts[j]++;
87 }
88 }
89 }
90
91 // straight MIP reco
92 std::vector<TrigMip> mips;
93 // 5 elements in the even/odd_start matrices
94 for (int i = 0; i < 5; i++) {
95 if (odd_start[i] < layer_start) {
96 TrigMip m;
97 m.setStartLayer(odd_start[i]);
98 m.setEndLayer(odd_end[i]);
99 m.setNHits(odd_counts[i]);
100 m.setLength(odd_end[i] - odd_start[i] + 1);
101 int holes = m.length() / 2 - m.nHits();
102 if (holes < 0) {
103 holes = 0;
104 }
105 m.setNHoles(holes);
106 mips.push_back(m);
107 }
108 if (even_start[i] < layer_start) {
109 TrigMip m;
110 m.setStartLayer(even_start[i]);
111 m.setEndLayer(even_end[i]);
112 m.setNHits(even_counts[i]);
113 m.setLength(even_end[i] - even_start[i] + 1);
114 int holes = m.length() / 2 - m.nHits();
115 if (holes < 0) {
116 holes = 0;
117 }
118 m.setNHoles(holes);
119 mips.push_back(m);
120 }
121 }
122
123 std::sort(mips.begin(), mips.end());
124 event.add(pass_coll_name_, mips);
125
126 auto end = std::chrono::high_resolution_clock::now();
127 auto time_diff = end - start;
128 processing_time_ +=
129 std::chrono::duration<double, std::milli>(time_diff).count();
130
131 return;
132 // ECAL MIP Reconstruction
133 } else {
134 const float radius_cut_2 = search_radius_ * search_radius_;
135 std::map<int, std::vector<TrigCaloHit>> layer_hits;
136 std::set<const TrigCaloHit*> used_hits;
137 std::vector<std::vector<const TrigCaloHit*>> candidate_tracks;
138 std::map<const TrigCaloHit*, size_t> hit_to_best_track;
139
140 // Filter for section = 0 and hits < 33 layers
141 for (const auto& hit : calo_hits) {
142 if (hit.section() > 0 || hit.layer() > max_layer_) continue;
143 layer_hits[hit.layer()].push_back(hit);
144 }
145
146 // Find mip seeds
147 for (const auto& [seed_layer, seeds] : layer_hits) {
148 for (const auto& seed : seeds) {
149 // Skip if hit already used or outside mip energy range
150 if (used_hits.count(&seed) || seed.energy() < ecal_min_energy_ ||
151 seed.energy() > ecal_max_energy_) {
152 continue;
153 }
154
155 std::vector<const TrigCaloHit*> track{&seed};
156 // Most recent hit in track
157 const TrigCaloHit* last = &seed;
158 int holes = 0;
159 float growth_factor = 1.0f;
160
161 // Look layer by layer for next hit within dR
162 for (int l = seed.layer() + 1; l <= max_layer_; ++l) {
163 const TrigCaloHit* best_hit = nullptr;
164 // Grow search window if there is a hole
165 float best_d_r_2 = radius_cut_2 * growth_factor * growth_factor;
166
167 for (const auto& cand : layer_hits[l]) {
168 if (used_hits.count(&cand) || cand.energy() < ecal_min_energy_ ||
169 cand.energy() > ecal_max_energy_) {
170 continue;
171 }
172
173 // Corrects for layer shift in x-direction, calculated as 4.82 mm
174 const float layer_shift_last =
175 (last->layer() % 2 == 0) ? 0.0f : 4.82f;
176 const float layer_shift_cand =
177 (cand.layer() % 2 == 0) ? 0.0f : 4.82f;
178 const float dx = (cand.positionX() - layer_shift_cand) -
179 (last->positionX() - layer_shift_last);
180 const float dy = cand.positionY() - last->positionY();
181 const float d_r_2 = dx * dx + dy * dy;
182
183 if (d_r_2 < best_d_r_2) {
184 best_d_r_2 = d_r_2;
185 // Closest unused hit in next layer
186 best_hit = &cand;
187 }
188 }
189
190 if (best_hit) {
191 // Builds track from best hits
192 track.push_back(best_hit);
193 last = best_hit;
194 holes = 0;
195 // Reset search window
196 growth_factor = 1.0f;
197 } else {
198 holes++;
199 // Keep expanding
200 growth_factor = static_cast<float>(holes + 1);
201 }
202 }
203
204 bool is_isolated = true;
205 if (track.size() >= min_track_length_) {
206 // Isolation area energy check
207 for (const auto* hit : track) {
208 const int layer = hit->layer();
209 const float hit_x = hit->positionX();
210 const float hit_y = hit->positionY();
211 float sum_e = 0.0f;
212
213 for (const auto& cand : layer_hits[layer]) {
214 // Skips self
215 if (&cand == hit) continue;
216
217 const float dx = cand.positionX() - hit_x;
218 const float dy = cand.positionY() - hit_y;
219 const float d_r_2 = dx * dx + dy * dy;
220
221 if (d_r_2 < radius_cut_2) {
222 sum_e += cand.energy();
223 }
224 }
225
226 if (sum_e >= isolation_e_cut_) {
227 is_isolated = false;
228 // Adds used hits to vector so they cannot be used again
229 used_hits.insert(hit);
230 break;
231 }
232 }
233
234 if (!is_isolated) {
235 // Reject track if any hit is not isolated
236 continue;
237 }
238
239 const size_t i = candidate_tracks.size();
240 candidate_tracks.push_back(track);
241
242 for (const auto* hit : track) {
243 if (!hit_to_best_track.count(hit) ||
244 candidate_tracks[i].size() >
245 candidate_tracks[hit_to_best_track[hit]].size()) {
246 hit_to_best_track[hit] = i;
247 // Adds used hits to vector so they cannot be used again
248 used_hits.insert(hit);
249 }
250 }
251 }
252 }
253 }
254
255 std::set<size_t> valid_track_i_ds;
256 for (const auto& [hit, idx] : hit_to_best_track) {
257 valid_track_i_ds.insert(idx);
258 }
259
260 std::vector<TrigMip> mips;
261 for (const size_t idx : valid_track_i_ds) {
262 const auto& track = candidate_tracks[idx];
263 TrigMip mip;
264 mip.setStartLayer(track.front()->layer());
265 mip.setEndLayer(track.back()->layer());
266 mip.setNHits(track.size());
267 mip.setLength(track.back()->layer() - track.front()->layer());
268 int holes = mip.length() - mip.nHits();
269 if (holes < 0) {
270 holes = 0;
271 }
272 mip.setNHoles(holes);
273
274 const float hole_fraction =
275 static_cast<float>(mip.nHoles()) / mip.length();
276 // Remove mip tracks with hole fraction > 0.2
277 if (hole_fraction >= hole_fraction_max_) continue;
278
279 float total_isolation_e_sum = 0.0f;
280 for (const auto* hit : track) {
281 const int layer = hit->layer();
282 const float hit_x = hit->positionX();
283 const float hit_y = hit->positionY();
284 for (const auto& cand : layer_hits[layer]) {
285 if (&cand == hit) continue;
286 const float dx = cand.positionX() - hit_x;
287 const float dy = cand.positionY() - hit_y;
288 const float d_r_2 = dx * dx + dy * dy;
289 if (d_r_2 < radius_cut_2) {
290 total_isolation_e_sum += cand.energy();
291 }
292 }
293 }
294 mip.setSumEinIsolationRegion(total_isolation_e_sum);
295 mips.push_back(mip);
296 }
297
298 std::sort(mips.begin(), mips.end());
299 event.add(pass_coll_name_, mips);
300 }
301
302 auto end = std::chrono::high_resolution_clock::now();
303 auto time_diff = end - start;
304 processing_time_ +=
305 std::chrono::duration<double, std::milli>(time_diff).count();
306}
307
309 ldmx_log(info) << "AVG Time/Event: " << std::fixed << std::setprecision(3)
310 << processing_time_ / nevents_ << " ms";
311}
312
313} // namespace trigger
314
#define DECLARE_PRODUCER(CLASS)
Macro which allows the framework to construct a producer given its name during configuration.
Trigger Calo MIP finding algorithm.
Implements an event buffer system for storing event data.
Definition Event.h:40
bool exists(const std::string &name, const std::string &passName, bool unique=true) const
Check for the existence of an object or collection with the given name and pass name in the event.
Definition Event.cxx:107
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
Run-specific configuration and data stored in its own output TTree alongside the event TTree in the o...
Definition RunHeader.h:68
Class for calo hits used in trigger computations.
Definition TrigCaloHit.h:13
void configure(framework::config::Parameters &ps) override
Callback for the EventProcessor to configure itself from the given set of parameters.
void onNewRun(const ldmx::RunHeader &rh) override
onNewRun is the first function called for each processor after the conditions are fully configured an...
void produce(framework::Event &event) override
Process the event and put new data products into it.
void onProcessEnd() override
Callback for the EventProcessor to take any necessary action when the processing of events finishes,...
Class for clusters built from trigger calo hits.
Definition TrigMip.h:11