FairShip
Loading...
Searching...
No Matches
shipPatRec.py
Go to the documentation of this file.
1# SPDX-License-Identifier: LGPL-3.0-or-later
2# SPDX-FileCopyrightText: Copyright CERN for the benefit of the SHiP Collaboration
3
4__author__ = "Mikhail Hushchyn"
5
6import logging
7
8import global_variables
9import numpy as np
10
11logger = logging.getLogger(__name__)
12
13# Globals
14ReconstructibleMCTracks = []
15theTracks = []
16
17r_scale = 1.0
18max_x = global_variables.ShipGeo.strawtubes_geo.width # == 200 * u.cm
19
20
21def initialize(fgeo) -> None:
22 pass
23
24
25def execute(smeared_hits, ship_geo, method: str = ""):
26 """
27 Main function of track pattern recognition.
28
29 Parameters:
30 -----------
31 SmearedHits : list
32 Smeared hits. SmearedHits = [{'digiHit': key,
33 'xtop': xtop, 'ytop': ytop, 'z': ztop,
34 'xbot': xbot, 'ybot': ybot,
35 'dist': dist2wire, 'detID': detID}, {...}, ...]
36 """
37
38 recognized_tracks = {}
39
40 logger.debug("PatRec input: %d smeared hits, method=%s", len(smeared_hits), method or "none")
41
42 if method == "TemplateMatching":
43 recognized_tracks = template_matching_pattern_recognition(smeared_hits, ship_geo)
44 elif method == "FH":
45 recognized_tracks = fast_hough_transform_pattern_recognition(smeared_hits, ship_geo)
46 elif method == "AR":
47 recognized_tracks = artificial_retina_pattern_recognition(smeared_hits, ship_geo)
48 else:
49 hits_y12, hits_stereo12, hits_y34, hits_stereo34 = hits_split(smeared_hits)
50 atrack = {"y12": hits_y12, "stereo12": hits_stereo12, "y34": hits_y34, "stereo34": hits_stereo34}
51 recognized_tracks[0] = atrack
52
53 logger.debug("PatRec output: %d tracks", len(recognized_tracks))
54
55 return recognized_tracks
56
57
58def finalize() -> None:
59 pass
60
61
62
67
68
69def template_matching_pattern_recognition(SmearedHits, ShipGeo):
70 """
71 Main function of track pattern recognition.
72
73 Parameters:
74 -----------
75 SmearedHits : list
76 Smeared hits. SmearedHits = [{'digiHit': key,
77 'xtop': xtop, 'ytop': ytop, 'z': ztop,
78 'xbot': xbot, 'ybot': ybot,
79 'dist': dist2wire, 'detID': detID}, {...}, ...]
80 """
81
82 recognized_tracks = {}
83
84 if len(SmearedHits) > 500:
85 print("Too large hits in the event!")
86 return recognized_tracks
87
88 min_hits = 3
89
90
91 SmearedHits_12y, SmearedHits_12stereo, SmearedHits_34y, SmearedHits_34stereo = hits_split(SmearedHits)
92
93
94 recognized_tracks_y12 = pat_rec_view(SmearedHits_12y, min_hits)
95 # recognized_tracks_y12 = [{'hits_y': [hit1, hit2, ...], 'k_y': float, 'b_y': float}, {...}, ...]
96
97
98 recognized_tracks_12 = pat_rec_stereo_views(SmearedHits_12stereo, recognized_tracks_y12, min_hits)
99 # recognized_tracks_12 = [{'hits_y': [hit1, hit2, ...], 'hits_stereo': [hit1, hit2, ...], 'k_y': float, 'b_y': float}, {...}, ...]
100
101
102 recognized_tracks_y34 = pat_rec_view(SmearedHits_34y, min_hits)
103 # recognized_tracks_y34 = [{'hits_y': [hit1, hit2, ...], 'k_y': float, 'b_y': float}, {...}, ...]
104
105
106 recognized_tracks_34 = pat_rec_stereo_views(SmearedHits_34stereo, recognized_tracks_y34, min_hits)
107 # recognized_tracks_34 = [{'hits_y': [hit1, hit2, ...], 'hits_stereo': [hit1, hit2, ...], 'k_y': float, 'b_y': float}, {...}, ...]
108
109
110 recognized_tracks_combo = tracks_combination_using_extrapolation(
111 recognized_tracks_12, recognized_tracks_34, ShipGeo.Bfield.z
112 )
113
114 # Prepare output of PatRec
115 recognized_tracks = _prepare_output(recognized_tracks_combo, min_hits)
116
117 return recognized_tracks
118
119
120def pat_rec_view(SmearedHits, min_hits: int):
121 """
122 Main function of track pattern recognition.
123
124 Parameters:
125 -----------
126 SmearedHits : list
127 Smeared hits. SmearedHits = [{'digiHit': key,
128 'xtop': xtop, 'ytop': ytop, 'z': ztop,
129 'xbot': xbot, 'ybot': ybot,
130 'dist': dist2wire, 'detID': detID}, {...}, ...]
131 """
132
133 long_recognized_tracks = []
134 n_seeds_tried = 0
135 n_seeds_slope_cut = 0
136
137 # Take 2 hits as a track seed
138 for ahit1 in SmearedHits:
139 for ahit2 in SmearedHits:
140 if ahit1["z"] >= ahit2["z"]:
141 continue
142 if ahit1["detID"] == ahit2["detID"]:
143 continue
144
145 n_seeds_tried += 1
146 k_seed = 1.0 * (ahit2["ytop"] - ahit1["ytop"]) / (ahit2["z"] - ahit1["z"])
147 b_seed = ahit1["ytop"] - k_seed * ahit1["z"]
148
149 if abs(k_seed) > 1:
150 n_seeds_slope_cut += 1
151 continue
152
153 atrack = {}
154 atrack["hits_y"] = [ahit1, ahit2]
155 atrack_layers = [ahit1["detID"] // 10000, ahit2["detID"] // 10000]
156
157 # Add new hits to the seed
158 for ahit3 in SmearedHits:
159 if ahit3["detID"] == ahit1["detID"] or ahit3["detID"] == ahit2["detID"]:
160 continue
161
162 layer3 = ahit3["detID"] // 10000
163 if layer3 in atrack_layers:
164 continue
165
166 in_bin = hit_in_window(ahit3["z"], ahit3["ytop"], k_seed, b_seed, window_width=1.4 * r_scale)
167 if in_bin:
168 atrack["hits_y"].append(ahit3)
169 atrack_layers.append(layer3)
170
171 if len(atrack["hits_y"]) >= min_hits:
172 long_recognized_tracks.append(atrack)
173
174 # Remove clones
175 recognized_tracks = reduce_clones_using_one_track_per_hit(long_recognized_tracks, min_hits)
176
177 logger.debug(
178 "pat_rec_view: %d hits, %d seeds tried, %d cut by slope, %d tracks before clone removal, %d after",
179 len(SmearedHits),
180 n_seeds_tried,
181 n_seeds_slope_cut,
182 len(long_recognized_tracks),
183 len(recognized_tracks),
184 )
185
186 # Track fit
187 for atrack in recognized_tracks:
188 z_coords = [ahit["z"] for ahit in atrack["hits_y"]]
189 y_coords = [ahit["ytop"] for ahit in atrack["hits_y"]]
190 [atrack["k_y"], atrack["b_y"]] = np.polyfit(z_coords, y_coords, deg=1)
191
192 return recognized_tracks
193
194
195
200
201
203 """
204 Main function of track pattern recognition.
205
206 Parameters:
207 -----------
208 SmearedHits : list
209 Smeared hits. SmearedHits = [{'digiHit': key,
210 'xtop': xtop, 'ytop': ytop, 'z': ztop,
211 'xbot': xbot, 'ybot': ybot,
212 'dist': dist2wire, 'detID': detID}, {...}, ...]
213 """
214
215 recognized_tracks = {}
216
217 if len(SmearedHits) > 500:
218 print("Too large hits in the event!")
219 return recognized_tracks
220
221 min_hits = 3
222
223
224 SmearedHits_12y, SmearedHits_12stereo, SmearedHits_34y, SmearedHits_34stereo = hits_split(SmearedHits)
225
226
227 recognized_tracks_y12 = fast_hough_pat_rec_y_view(SmearedHits_12y, min_hits)
228
229
230 recognized_tracks_12 = fast_hough_pat_rec_stereo_views(SmearedHits_12stereo, recognized_tracks_y12, min_hits)
231
232
233 recognized_tracks_y34 = fast_hough_pat_rec_y_view(SmearedHits_34y, min_hits)
234
235
236 recognized_tracks_34 = fast_hough_pat_rec_stereo_views(SmearedHits_34stereo, recognized_tracks_y34, min_hits)
237
238
239 recognized_tracks_combo = tracks_combination_using_extrapolation(
240 recognized_tracks_12, recognized_tracks_34, ShipGeo.Bfield.z
241 )
242
243 # Prepare output of PatRec
244 recognized_tracks = _prepare_output(recognized_tracks_combo, min_hits)
245
246 return recognized_tracks
247
248
249def fast_hough_pat_rec_y_view(SmearedHits, min_hits: int):
250 """
251 Main function of track pattern recognition.
252
253 Parameters:
254 -----------
255 SmearedHits : list
256 Smeared hits. SmearedHits = [{'digiHit': key,
257 'xtop': xtop, 'ytop': ytop, 'z': ztop,
258 'xbot': xbot, 'ybot': ybot,
259 'dist': dist2wire, 'detID': detID}, {...}, ...]
260 """
261
262 long_recognized_tracks = []
263 n_seeds_tried = 0
264 n_seeds_slope_cut = 0
265
266 # Take 2 hits as a track seed
267 for ahit1 in SmearedHits:
268 for ahit2 in SmearedHits:
269 if ahit1["z"] >= ahit2["z"]:
270 continue
271 if ahit1["detID"] == ahit2["detID"]:
272 continue
273
274 n_seeds_tried += 1
275 k_seed = 1.0 * (ahit2["ytop"] - ahit1["ytop"]) / (ahit2["z"] - ahit1["z"])
276 b_seed = ahit1["ytop"] - k_seed * ahit1["z"]
277
278 if abs(k_seed) > 1:
279 n_seeds_slope_cut += 1
280 continue
281
282 atrack = {}
283 atrack["hits_y"] = [ahit1, ahit2]
284 atrack_layers = [ahit1["detID"] // 10000, ahit2["detID"] // 10000]
285
286 # Add new hits to the seed
287 for ahit3 in SmearedHits:
288 if ahit3["detID"] == ahit1["detID"] or ahit3["detID"] == ahit2["detID"]:
289 continue
290
291 layer3 = ahit3["detID"] // 10000
292 if layer3 in atrack_layers:
293 continue
294
295 in_bin = hit_in_bin(
296 ahit3["z"],
297 ahit3["ytop"],
298 k_seed,
299 b_seed,
300 k_size=0.7 / 2000 * r_scale,
301 b_size=1700.0 / 1000 * r_scale,
302 )
303 if in_bin:
304 atrack["hits_y"].append(ahit3)
305 atrack_layers.append(layer3)
306
307 if len(atrack["hits_y"]) >= min_hits:
308 long_recognized_tracks.append(atrack)
309
310 # Remove clones
311 recognized_tracks = reduce_clones_using_one_track_per_hit(long_recognized_tracks, min_hits)
312
313 logger.debug(
314 "fast_hough_y_view: %d hits, %d seeds tried, %d cut by slope, %d tracks before clone removal, %d after",
315 len(SmearedHits),
316 n_seeds_tried,
317 n_seeds_slope_cut,
318 len(long_recognized_tracks),
319 len(recognized_tracks),
320 )
321
322 # Track fit
323 for atrack in recognized_tracks:
324 z_coords = [ahit["z"] for ahit in atrack["hits_y"]]
325 y_coords = [ahit["ytop"] for ahit in atrack["hits_y"]]
326 [atrack["k_y"], atrack["b_y"]] = np.polyfit(z_coords, y_coords, deg=1)
327
328 return recognized_tracks
329
330
331def fast_hough_pat_rec_stereo_views(SmearedHits_stereo, recognized_tracks_y, min_hits: int):
332
333 recognized_tracks_stereo = []
334 used_hits = []
335 n_y_tracks_with_stereo = 0
336
337 for atrack_y in recognized_tracks_y:
338 k_y = atrack_y["k_y"]
339 b_y = atrack_y["b_y"]
340
341 # Get hit zx projections
342 for ahit in SmearedHits_stereo:
343 x_center = get_zy_projection(ahit["z"], ahit["ytop"], ahit["xtop"], ahit["ybot"], ahit["xbot"], k_y, b_y)
344 ahit["zx_projection"] = x_center
345
346 long_recognized_tracks_stereo = []
347
348 for ahit1 in SmearedHits_stereo:
349 for ahit2 in SmearedHits_stereo:
350 if ahit1["z"] >= ahit2["z"]:
351 continue
352 if ahit1["detID"] == ahit2["detID"]:
353 continue
354 if ahit1["digiHit"] in used_hits:
355 continue
356 if ahit2["digiHit"] in used_hits:
357 continue
358
359 if abs(ahit1["zx_projection"]) > max_x or abs(ahit2["zx_projection"]) > max_x:
360 continue
361
362 k_seed = 1.0 * (ahit2["zx_projection"] - ahit1["zx_projection"]) / (ahit2["z"] - ahit1["z"])
363 b_seed = ahit1["zx_projection"] - k_seed * ahit1["z"]
364
365 atrack_stereo = {}
366 atrack_stereo["hits_stereo"] = [ahit1, ahit2]
367 atrack_stereo_layers = [ahit1["detID"] // 10000, ahit2["detID"] // 10000]
368
369 for ahit3 in SmearedHits_stereo:
370 if ahit3["digiHit"] == ahit1["digiHit"] or ahit3["digiHit"] == ahit2["digiHit"]:
371 continue
372 if ahit3["digiHit"] in used_hits:
373 continue
374
375 if abs(ahit3["zx_projection"]) > max_x:
376 continue
377
378 layer3 = ahit3["detID"] // 10000
379 if layer3 in atrack_stereo_layers:
380 continue
381
382 in_bin = hit_in_bin(
383 ahit3["z"],
384 ahit3["zx_projection"],
385 k_seed,
386 b_seed,
387 k_size=0.6 / 200 * r_scale,
388 b_size=1000.0 / 70 * r_scale,
389 )
390 if in_bin:
391 atrack_stereo["hits_stereo"].append(ahit3)
392 atrack_stereo_layers.append(layer3)
393
394 if len(atrack_stereo["hits_stereo"]) >= min_hits:
395 long_recognized_tracks_stereo.append(atrack_stereo)
396
397 # Remove clones
398 max_track = None
399 max_n_hits = -999
400
401 for atrack_stereo in long_recognized_tracks_stereo:
402 if len(atrack_stereo["hits_stereo"]) > max_n_hits:
403 max_track = atrack_stereo
404 max_n_hits = len(atrack_stereo["hits_stereo"])
405
406 atrack = {}
407 atrack["hits_y"] = atrack_y["hits_y"]
408 atrack["k_y"] = atrack_y["k_y"]
409 atrack["b_y"] = atrack_y["b_y"]
410 atrack["hits_stereo"] = []
411
412 if max_track is not None:
413 atrack["hits_stereo"] = max_track["hits_stereo"]
414 n_y_tracks_with_stereo += 1
415 for ahit in max_track["hits_stereo"]:
416 used_hits.append(ahit["digiHit"])
417
418 recognized_tracks_stereo.append(atrack)
419
420 logger.debug(
421 "fast_hough_stereo: %d stereo hits, %d y-tracks, %d matched with stereo",
422 len(SmearedHits_stereo),
423 len(recognized_tracks_y),
424 n_y_tracks_with_stereo,
425 )
426
427 return recognized_tracks_stereo
428
429
430def hit_in_bin(x, y, k_bin, b_bin, k_size, b_size):
431 """
432 Counts hits in a bin of track parameter space (b, k).
433
434 Parameters
435 ---------
436 x : array-like
437 Array of x coordinates of hits.
438 y : array-like
439 Array of x coordinates of hits.
440 k_bin : float
441 Track parameter: y = k_bin * x + b_bin
442 b_bin : float
443 Track parameter: y = k_bin * x + b_bin
444
445 Return
446 ------
447 track_inds : array-like
448 Hit indexes of a track: [ind1, ind2, ...]
449 """
450
451 b_left = y - (k_bin - 0.5 * k_size) * x
452 b_right = y - (k_bin + 0.5 * k_size) * x
453
454 sel = (b_left >= b_bin - 0.5 * b_size) * (b_right <= b_bin + 0.5 * b_size) + (b_left <= b_bin + 0.5 * b_size) * (
455 b_right >= b_bin - 0.5 * b_size
456 )
457
458 return sel
459
460
461
466
467from scipy.optimize import minimize
468
469
471 """
472 Main function of track pattern recognition.
473
474 Parameters:
475 -----------
476 SmearedHits : list
477 Smeared hits. SmearedHits = [{'digiHit': key,
478 'xtop': xtop, 'ytop': ytop, 'z': ztop,
479 'xbot': xbot, 'ybot': ybot,
480 'dist': dist2wire, 'detID': detID}, {...}, ...]
481 """
482
483 recognized_tracks = {}
484
485 if len(SmearedHits) > 500:
486 print("Too large hits in the event!")
487 return recognized_tracks
488
489 min_hits = 3
490
491
492 SmearedHits_12y, SmearedHits_12stereo, SmearedHits_34y, SmearedHits_34stereo = hits_split(SmearedHits)
493
494
495 recognized_tracks_y12 = artificial_retina_pat_rec_y_view(SmearedHits_12y, min_hits)
496
497
498 recognized_tracks_12 = artificial_retina_pat_rec_stereo_views(SmearedHits_12stereo, recognized_tracks_y12, min_hits)
499
500
501 recognized_tracks_y34 = artificial_retina_pat_rec_y_view(SmearedHits_34y, min_hits)
502
503
504 recognized_tracks_34 = artificial_retina_pat_rec_stereo_views(SmearedHits_34stereo, recognized_tracks_y34, min_hits)
505
506
507 recognized_tracks_combo = tracks_combination_using_extrapolation(
508 recognized_tracks_12, recognized_tracks_34, ShipGeo.Bfield.z
509 )
510
511 # Prepare output of PatRec
512 recognized_tracks = _prepare_output(recognized_tracks_combo, min_hits)
513
514 return recognized_tracks
515
516
517def artificial_retina_pat_rec_y_view(SmearedHits, min_hits: int):
518 """
519 Main function of track pattern recognition.
520
521 Parameters:
522 -----------
523 SmearedHits : list
524 Smeared hits. SmearedHits = [{'digiHit': key,
525 'xtop': xtop, 'ytop': ytop, 'z': ztop,
526 'xbot': xbot, 'ybot': ybot,
527 'dist': dist2wire, 'detID': detID}, {...}, ...]
528 """
529
530 long_recognized_tracks = []
531 used_hits = np.zeros(len(SmearedHits))
532
533 hits_z = np.array([ahit["z"] for ahit in SmearedHits])
534 hits_y = np.array([ahit["ytop"] for ahit in SmearedHits])
535
536 for i in range(len(SmearedHits)):
537 hits_z_unused = hits_z[used_hits == 0]
538 hits_y_unused = hits_y[used_hits == 0]
539
540 sigma = 1.0 * r_scale
541 best_seed_params = get_best_seed(hits_z_unused, hits_y_unused, sigma, sample_weight=None)
542
543 res = minimize(
544 retina_func,
545 best_seed_params,
546 args=(hits_z_unused, hits_y_unused, sigma, None),
547 method="BFGS",
548 jac=retina_grad,
549 options={"gtol": 1e-6, "disp": False, "maxiter": 5},
550 )
551 [k_seed_upd, b_seed_upd] = res.x
552
553 atrack = {}
554 atrack["hits_y"] = []
555 atrack_layers = []
556 hit_ids = []
557
558 # Add new hits to the seed
559 for i_hit3 in range(len(SmearedHits)):
560 if used_hits[i_hit3] == 1:
561 continue
562
563 ahit3 = SmearedHits[i_hit3]
564 layer3 = ahit3["detID"] // 10000
565 if layer3 in atrack_layers:
566 continue
567
568 in_bin = hit_in_window(ahit3["z"], ahit3["ytop"], k_seed_upd, b_seed_upd, window_width=1.4 * r_scale)
569 if in_bin:
570 atrack["hits_y"].append(ahit3)
571 atrack_layers.append(layer3)
572 hit_ids.append(i_hit3)
573
574 if len(atrack["hits_y"]) >= min_hits:
575 long_recognized_tracks.append(atrack)
576 used_hits[hit_ids] = 1
577 else:
578 break
579
580 # Remove clones
581 recognized_tracks = reduce_clones_using_one_track_per_hit(long_recognized_tracks, min_hits)
582
583 logger.debug(
584 "ar_y_view: %d hits, %d tracks before clone removal, %d after",
585 len(SmearedHits),
586 len(long_recognized_tracks),
587 len(recognized_tracks),
588 )
589
590 # Track fit
591 for atrack in recognized_tracks:
592 z_coords = [ahit["z"] for ahit in atrack["hits_y"]]
593 y_coords = [ahit["ytop"] for ahit in atrack["hits_y"]]
594 [atrack["k_y"], atrack["b_y"]] = np.polyfit(z_coords, y_coords, deg=1)
595
596 return recognized_tracks
597
598
599def artificial_retina_pat_rec_stereo_views(SmearedHits_stereo, recognized_tracks_y, min_hits: int):
600
601 recognized_tracks_stereo = []
602 used_hits = []
603 n_y_tracks_with_stereo = 0
604
605 for atrack_y in recognized_tracks_y:
606 k_y = atrack_y["k_y"]
607 b_y = atrack_y["b_y"]
608
609 # Get hit zx projections
610 for ahit in SmearedHits_stereo:
611 x_center = get_zy_projection(ahit["z"], ahit["ytop"], ahit["xtop"], ahit["ybot"], ahit["xbot"], k_y, b_y)
612 ahit["zx_projection"] = x_center
613
614 long_recognized_tracks_stereo = []
615 hits_z = []
616 hits_x = []
617
618 for ahit in SmearedHits_stereo:
619 if ahit["digiHit"] in used_hits:
620 continue
621 if abs(ahit["zx_projection"]) > max_x:
622 continue
623 hits_z.append(ahit["z"])
624 hits_x.append(ahit["zx_projection"])
625 hits_z = np.array(hits_z)
626 hits_x = np.array(hits_x)
627
628 sigma = 15.0 * r_scale
629 best_seed_params = get_best_seed(hits_z, hits_x, sigma, sample_weight=None)
630
631 res = minimize(
632 retina_func,
633 best_seed_params,
634 args=(hits_z, hits_x, sigma, None),
635 method="BFGS",
636 jac=retina_grad,
637 options={"gtol": 1e-6, "disp": False, "maxiter": 5},
638 )
639 [k_seed_upd, b_seed_upd] = res.x
640
641 atrack_stereo = {}
642 atrack_stereo["hits_stereo"] = []
643 atrack_stereo_layers = []
644
645 for ahit3 in SmearedHits_stereo:
646 if ahit3["digiHit"] in used_hits:
647 continue
648
649 if abs(ahit3["zx_projection"]) > max_x:
650 continue
651
652 layer3 = ahit3["detID"] // 10000
653 if layer3 in atrack_stereo_layers:
654 continue
655
656 in_bin = hit_in_window(
657 ahit3["z"], ahit3["zx_projection"], k_seed_upd, b_seed_upd, window_width=15.0 * r_scale
658 )
659 if in_bin:
660 atrack_stereo["hits_stereo"].append(ahit3)
661 atrack_stereo_layers.append(layer3)
662
663 if len(atrack_stereo["hits_stereo"]) >= min_hits:
664 long_recognized_tracks_stereo.append(atrack_stereo)
665
666 # Remove clones
667 max_track = None
668 max_n_hits = -999
669
670 for atrack_stereo in long_recognized_tracks_stereo:
671 if len(atrack_stereo["hits_stereo"]) > max_n_hits:
672 max_track = atrack_stereo
673 max_n_hits = len(atrack_stereo["hits_stereo"])
674
675 atrack = {}
676 atrack["hits_y"] = atrack_y["hits_y"]
677 atrack["k_y"] = atrack_y["k_y"]
678 atrack["b_y"] = atrack_y["b_y"]
679 atrack["hits_stereo"] = []
680
681 if max_track is not None:
682 atrack["hits_stereo"] = max_track["hits_stereo"]
683 n_y_tracks_with_stereo += 1
684 for ahit in max_track["hits_stereo"]:
685 used_hits.append(ahit["digiHit"])
686
687 recognized_tracks_stereo.append(atrack)
688
689 logger.debug(
690 "ar_stereo: %d stereo hits, %d y-tracks, %d matched with stereo",
691 len(SmearedHits_stereo),
692 len(recognized_tracks_y),
693 n_y_tracks_with_stereo,
694 )
695
696 return recognized_tracks_stereo
697
698
699def get_best_seed(x, y, sigma: float, sample_weight=None):
700 best_retina_val = 0
701 best_seed_params = [0, 0]
702
703 for i_1 in range(len(x) - 1):
704 for i_2 in range(i_1 + 1, len(x)):
705 if x[i_1] >= x[i_2]:
706 continue
707
708 seed_k = (y[i_2] - y[i_1]) / (x[i_2] - x[i_1] + 10**-6)
709 seed_b = y[i_1] - seed_k * x[i_1]
710
711 retina_val = retina_func([seed_k, seed_b], x, y, sigma, sample_weight)
712
713 if retina_val < best_retina_val:
714 best_retina_val = retina_val
715 best_seed_params = [seed_k, seed_b]
716
717 return best_seed_params
718
719
720def retina_func(track_prams, x, y, sigma, sample_weight=None):
721 """
722 Calculates the artificial retina function value.
723 Parameters
724 ----------
725 track_prams : array-like
726 Track parameters [k, b].
727 x : array-like
728 Array of x coordinates of hits.
729 y : array-like
730 Array of x coordinates of hits.
731 sigma : float
732 Standard deviation of hit form a track.
733 sample_weight : array-like
734 Hit weights used during the track fit.
735 Returns
736 -------
737 retina : float
738 Negative value of the artificial retina function.
739 """
740
741 rs = track_prams[0] * x + track_prams[1] - y
742
743 if sample_weight is None:
744 exps = np.exp(-((rs / sigma) ** 2))
745 else:
746 exps = np.exp(-((rs / sigma) ** 2)) * sample_weight
747
748 retina = exps.sum()
749
750 return -retina
751
752
753def retina_grad(track_prams, x, y, sigma, sample_weight=None):
754 """
755 Calculates the artificial retina gradient.
756 Parameters
757 ----------
758 track_prams : array-like
759 Track parameters [k, b].
760 x : array-like
761 Array of x coordinates of hits.
762 y : array-like
763 Array of x coordinates of hits.
764 sigma : float
765 Standard deviation of hit form a track.
766 sample_weight : array-like
767 Hit weights used during the track fit.
768 Returns
769 -------
770 retina : float
771 Negative value of the artificial retina gradient.
772 """
773
774 rs = track_prams[0] * x + track_prams[1] - y
775
776 if sample_weight is None:
777 exps = np.exp(-((rs / sigma) ** 2))
778 else:
779 exps = np.exp(-((rs / sigma) ** 2)) * sample_weight
780
781 dks = -2.0 * rs / sigma**2 * exps * x
782 dbs = -2.0 * rs / sigma**2 * exps
783
784 return -np.array([dks.sum(), dbs.sum()])
785
786
787
792
793
794def hits_split(smeared_hits):
795 """
796 Split hits into groups of station views.
797
798 Parameters:
799 -----------
800 SmearedHits : list
801 Smeared hits. SmearedHits = [{'digiHit': key,
802 'xtop': xtop, 'ytop': ytop, 'z': ztop,
803 'xbot': xbot, 'ybot': ybot,
804 'dist': dist2wire, 'detID': detID}, {...}, ...]
805 """
806
807 smeared_hits_12y = []
808 smeared_hits_12stereo = []
809 smeared_hits_34y = []
810 smeared_hits_34stereo = []
811
812 for i_hit in range(len(smeared_hits)):
813 ahit = smeared_hits[i_hit]
814
815 detID = ahit["detID"]
816 decode = global_variables.modules["strawtubes"].StrawDecode(detID)
817 # StrawDecode returns a tuple of (statnb, vnb, lnb, snb).
818 is_y12 = ((decode[0] == 1) + (decode[0] == 2)) * ((decode[1] == 0) + (decode[1] == 3))
819 is_stereo12 = ((decode[0] == 1) + (decode[0] == 2)) * ((decode[1] == 1) + (decode[1] == 2))
820 is_y34 = ((decode[0] == 3) + (decode[0] == 4)) * ((decode[1] == 0) + (decode[1] == 3))
821 is_stereo34 = ((decode[0] == 3) + (decode[0] == 4)) * ((decode[1] == 1) + (decode[1] == 2))
822
823 if is_y12:
824 smeared_hits_12y.append(ahit)
825 if is_stereo12:
826 smeared_hits_12stereo.append(ahit)
827 if is_y34:
828 smeared_hits_34y.append(ahit)
829 if is_stereo34:
830 smeared_hits_34stereo.append(ahit)
831
832 logger.debug(
833 "hits_split: y12=%d, stereo12=%d, y34=%d, stereo34=%d",
834 len(smeared_hits_12y),
835 len(smeared_hits_12stereo),
836 len(smeared_hits_34y),
837 len(smeared_hits_34stereo),
838 )
839
840 return smeared_hits_12y, smeared_hits_12stereo, smeared_hits_34y, smeared_hits_34stereo
841
842
843def reduce_clones_using_one_track_per_hit(recognized_tracks, min_hits: int = 3):
844 """
845 Split hits into groups of station views.
846
847 Parameters:
848 -----------
849 recognized_tracks : list
850 Track hits. Tracks = [{'hits_y': [hit1, hit2, hit3, ...]}, {...}, ...]
851 min_hits : int
852 Minimal number of hits per track.
853 """
854
855 used_hits = []
856 tracks_no_clones = []
857 n_hits = [len(atrack["hits_y"]) for atrack in recognized_tracks]
858
859 for i_track in np.argsort(n_hits)[::-1]:
860 atrack = recognized_tracks[i_track]
861 new_track = {}
862 new_track["hits_y"] = []
863
864 for i_hit in range(len(atrack["hits_y"])):
865 ahit = atrack["hits_y"][i_hit]
866 if ahit["digiHit"] not in used_hits:
867 new_track["hits_y"].append(ahit)
868
869 if len(new_track["hits_y"]) >= min_hits:
870 tracks_no_clones.append(new_track)
871 for ahit in new_track["hits_y"]:
872 used_hits.append(ahit["digiHit"])
873
874 return tracks_no_clones
875
876
877def tracks_combination_using_extrapolation(recognized_tracks_12, recognized_tracks_34, z_magnet):
878 recognized_tracks_combo = []
879
880 i_track_y12 = []
881 i_track_y34 = []
882 deltas_y = []
883
884 for i_12 in range(len(recognized_tracks_12)):
885 atrack_12 = recognized_tracks_12[i_12]
886 y_center_y12 = atrack_12["k_y"] * z_magnet + atrack_12["b_y"]
887
888 for i_34 in range(len(recognized_tracks_34)):
889 atrack_34 = recognized_tracks_34[i_34]
890 y_center_y34 = atrack_34["k_y"] * z_magnet + atrack_34["b_y"]
891
892 i_track_y12.append(i_12)
893 i_track_y34.append(i_34)
894 deltas_y.append(abs(y_center_y12 - y_center_y34))
895
896 max_dy = 50
897 used_y12 = []
898 used_y34 = []
899
900 for i in np.argsort(deltas_y):
901 dy = deltas_y[i]
902 i_12 = i_track_y12[i]
903 i_34 = i_track_y34[i]
904
905 if (dy < max_dy) and (i_12 not in used_y12) and (i_34 not in used_y34):
906 atrack = {}
907 atrack["hits_y12"] = recognized_tracks_12[i_12]["hits_y"]
908 atrack["hits_stereo12"] = recognized_tracks_12[i_12]["hits_stereo"]
909 atrack["hits_y34"] = recognized_tracks_34[i_34]["hits_y"]
910 atrack["hits_stereo34"] = recognized_tracks_34[i_34]["hits_stereo"]
911 atrack["k_y12"] = recognized_tracks_12[i_12]["k_y"]
912 atrack["b_y12"] = recognized_tracks_12[i_12]["b_y"]
913 atrack["k_y34"] = recognized_tracks_34[i_34]["k_y"]
914 atrack["b_y34"] = recognized_tracks_34[i_34]["b_y"]
915 recognized_tracks_combo.append(atrack)
916 used_y12.append(i_12)
917 used_y34.append(i_34)
918
919 for i_12 in range(len(recognized_tracks_12)):
920 if i_12 not in used_y12:
921 atrack = {}
922 atrack["hits_y12"] = recognized_tracks_12[i_12]["hits_y"]
923 atrack["hits_stereo12"] = recognized_tracks_12[i_12]["hits_stereo"]
924 atrack["hits_y34"] = []
925 atrack["hits_stereo34"] = []
926 atrack["k_y12"] = recognized_tracks_12[i_12]["k_y"]
927 atrack["b_y12"] = recognized_tracks_12[i_12]["b_y"]
928 atrack["k_y34"] = None
929 atrack["b_y34"] = None
930 recognized_tracks_combo.append(atrack)
931
932 for i_34 in range(len(recognized_tracks_34)):
933 if i_34 not in used_y34:
934 atrack = {}
935 atrack["hits_y12"] = []
936 atrack["hits_stereo12"] = []
937 atrack["hits_y34"] = recognized_tracks_34[i_34]["hits_y"]
938 atrack["hits_stereo34"] = recognized_tracks_34[i_34]["hits_stereo"]
939 atrack["k_y12"] = None
940 atrack["b_y12"] = None
941 atrack["k_y34"] = recognized_tracks_34[i_34]["k_y"]
942 atrack["b_y34"] = recognized_tracks_34[i_34]["b_y"]
943 recognized_tracks_combo.append(atrack)
944
945 logger.debug(
946 "tracks_combo: %d 12-tracks, %d 34-tracks, %d matched pairs, %d total combos",
947 len(recognized_tracks_12),
948 len(recognized_tracks_34),
949 len(used_y12),
950 len(recognized_tracks_combo),
951 )
952
953 return recognized_tracks_combo
954
955
956def _prepare_output(recognized_tracks_combo, min_hits):
957 """Prepare PatRec output, filtering tracks with too few hits and preserving track parameters."""
958 recognized_tracks = {}
959 i_track = 0
960 n_rejected = 0
961 for atrack_combo in recognized_tracks_combo:
962 hits_y12 = atrack_combo["hits_y12"]
963 hits_stereo12 = atrack_combo["hits_stereo12"]
964 hits_y34 = atrack_combo["hits_y34"]
965 hits_stereo34 = atrack_combo["hits_stereo34"]
966
967 if (
968 len(hits_y12) >= min_hits
969 and len(hits_stereo12) >= min_hits
970 and len(hits_y34) >= min_hits
971 and len(hits_stereo34) >= min_hits
972 ):
973 atrack = {
974 "y12": hits_y12,
975 "stereo12": hits_stereo12,
976 "y34": hits_y34,
977 "stereo34": hits_stereo34,
978 "k_y12": atrack_combo.get("k_y12"),
979 "b_y12": atrack_combo.get("b_y12"),
980 "k_y34": atrack_combo.get("k_y34"),
981 "b_y34": atrack_combo.get("b_y34"),
982 }
983 recognized_tracks[i_track] = atrack
984 i_track += 1
985 else:
986 n_rejected += 1
987
988 logger.debug(
989 "output: %d tracks accepted, %d rejected by min_hits=%d filter",
990 i_track,
991 n_rejected,
992 min_hits,
993 )
994
995 return recognized_tracks
996
997
998def hit_in_window(x, y, k_bin, b_bin, window_width=1.0) -> bool:
999 """
1000 Check whether a hit falls within a window around a straight-line track.
1001
1002 Parameters
1003 ---------
1004 x : float
1005 Z coordinate of the hit.
1006 y : float
1007 Y (or projected X) coordinate of the hit.
1008 k_bin : float
1009 Track slope: y = k_bin * x + b_bin
1010 b_bin : float
1011 Track intercept: y = k_bin * x + b_bin
1012 window_width : float
1013 Half-width of the acceptance window in the y direction.
1014
1015 Return
1016 ------
1017 flag : bool
1018 True if the hit is within the window.
1019 """
1020
1021 y_approx = k_bin * x + b_bin
1022
1023 flag = False
1024 if np.abs(y_approx - y) <= window_width:
1025 flag = True
1026
1027 return flag
1028
1029
1030def get_zy_projection(z, xtop, ytop, xbot, ybot, k_y, b_y):
1031 """
1032 Project a stereo straw hit onto the ZX plane using the Y-view track parameters.
1033
1034 A stereo straw is tilted: its top-end is at (xtop, ytop) and bottom-end at
1035 (xbot, ybot), both at the same z. The Y-view track gives the y position at
1036 this z as y_track = k_y * z + b_y. This function finds the x coordinate
1037 where the straw wire crosses y_track by parameterising the wire as a line
1038 in the XY plane and evaluating it at y = y_track.
1039
1040 Note: the caller passes (ytop, xtop, ybot, xbot) — the argument names here
1041 are swapped relative to the geometric meaning because the call sites swap
1042 the x/y coordinates. This is intentional: the straw wire is parameterised
1043 as x(y), not y(x), because the stereo straws are nearly vertical.
1044
1045 Parameters
1046 ----------
1047 z : float
1048 Z coordinate of the hit.
1049 xtop : float
1050 First coordinate of wire top-end (actually ytop at call site).
1051 ytop : float
1052 Second coordinate of wire top-end (actually xtop at call site).
1053 xbot : float
1054 First coordinate of wire bottom-end (actually ybot at call site).
1055 ybot : float
1056 Second coordinate of wire bottom-end (actually xbot at call site).
1057 k_y : float
1058 Slope of the Y-view track: y = k_y * z + b_y.
1059 b_y : float
1060 Intercept of the Y-view track.
1061
1062 Returns
1063 -------
1064 y : float
1065 The projected x coordinate of the hit in the ZX plane.
1066 """
1067 x = k_y * z + b_y
1068 k = (ytop - ybot) / (xtop - xbot + 10**-6)
1069 b = ytop - k * xtop
1070 y = k * x + b
1071
1072 return y
1073
1074
1075def pat_rec_stereo_views(SmearedHits_stereo, recognized_tracks_y, min_hits: int):
1076
1077 recognized_tracks_stereo = []
1078 used_hits = []
1079 n_y_tracks_with_stereo = 0
1080
1081 for atrack_y in recognized_tracks_y:
1082 k_y = atrack_y["k_y"]
1083 b_y = atrack_y["b_y"]
1084
1085 # Get hit zx projections
1086 for ahit in SmearedHits_stereo:
1087 x_center = get_zy_projection(ahit["z"], ahit["ytop"], ahit["xtop"], ahit["ybot"], ahit["xbot"], k_y, b_y)
1088 ahit["zx_projection"] = x_center
1089
1090 long_recognized_tracks_stereo = []
1091
1092 for ahit1 in SmearedHits_stereo:
1093 for ahit2 in SmearedHits_stereo:
1094 if ahit1["z"] >= ahit2["z"]:
1095 continue
1096 if ahit1["detID"] == ahit2["detID"]:
1097 continue
1098 if ahit1["digiHit"] in used_hits:
1099 continue
1100 if ahit2["digiHit"] in used_hits:
1101 continue
1102
1103 if abs(ahit1["zx_projection"]) > max_x or abs(ahit2["zx_projection"]) > max_x:
1104 continue
1105
1106 k_seed = 1.0 * (ahit2["zx_projection"] - ahit1["zx_projection"]) / (ahit2["z"] - ahit1["z"])
1107 b_seed = ahit1["zx_projection"] - k_seed * ahit1["z"]
1108
1109 atrack_stereo = {}
1110 atrack_stereo["hits_stereo"] = [ahit1, ahit2]
1111 atrack_stereo_layers = [ahit1["detID"] // 10000, ahit2["detID"] // 10000]
1112
1113 for ahit3 in SmearedHits_stereo:
1114 if ahit3["digiHit"] == ahit1["digiHit"] or ahit3["digiHit"] == ahit2["digiHit"]:
1115 continue
1116 if ahit3["digiHit"] in used_hits:
1117 continue
1118
1119 if abs(ahit3["zx_projection"]) > max_x:
1120 continue
1121
1122 layer3 = ahit3["detID"] // 10000
1123 if layer3 in atrack_stereo_layers:
1124 continue
1125
1126 in_bin = hit_in_window(
1127 ahit3["z"], ahit3["zx_projection"], k_seed, b_seed, window_width=15.0 * r_scale
1128 )
1129 if in_bin:
1130 atrack_stereo["hits_stereo"].append(ahit3)
1131 atrack_stereo_layers.append(layer3)
1132
1133 if len(atrack_stereo["hits_stereo"]) >= min_hits:
1134 long_recognized_tracks_stereo.append(atrack_stereo)
1135
1136 # Remove clones
1137 max_track = None
1138 max_n_hits = -999
1139
1140 for atrack_stereo in long_recognized_tracks_stereo:
1141 if len(atrack_stereo["hits_stereo"]) > max_n_hits:
1142 max_track = atrack_stereo
1143 max_n_hits = len(atrack_stereo["hits_stereo"])
1144
1145 atrack = {}
1146 atrack["hits_y"] = atrack_y["hits_y"]
1147 atrack["k_y"] = atrack_y["k_y"]
1148 atrack["b_y"] = atrack_y["b_y"]
1149 atrack["hits_stereo"] = []
1150
1151 if max_track is not None:
1152 atrack["hits_stereo"] = max_track["hits_stereo"]
1153 n_y_tracks_with_stereo += 1
1154 for ahit in max_track["hits_stereo"]:
1155 used_hits.append(ahit["digiHit"])
1156
1157 recognized_tracks_stereo.append(atrack)
1158
1159 logger.debug(
1160 "pat_rec_stereo: %d stereo hits, %d y-tracks, %d matched with stereo",
1161 len(SmearedHits_stereo),
1162 len(recognized_tracks_y),
1163 n_y_tracks_with_stereo,
1164 )
1165
1166 return recognized_tracks_stereo
def artificial_retina_pat_rec_y_view(SmearedHits, int min_hits)
Definition: shipPatRec.py:517
def retina_grad(track_prams, x, y, sigma, sample_weight=None)
Definition: shipPatRec.py:753
def execute(smeared_hits, ship_geo, str method="")
Definition: shipPatRec.py:25
None finalize()
Definition: shipPatRec.py:58
def hits_split(smeared_hits)
The End of the PatRec Methods.
Definition: shipPatRec.py:794
def template_matching_pattern_recognition(SmearedHits, ShipGeo)
Template Matching.
Definition: shipPatRec.py:69
def tracks_combination_using_extrapolation(recognized_tracks_12, recognized_tracks_34, z_magnet)
Definition: shipPatRec.py:877
def retina_func(track_prams, x, y, sigma, sample_weight=None)
Definition: shipPatRec.py:720
def get_zy_projection(z, xtop, ytop, xbot, ybot, k_y, b_y)
Definition: shipPatRec.py:1030
def fast_hough_pat_rec_stereo_views(SmearedHits_stereo, recognized_tracks_y, int min_hits)
Definition: shipPatRec.py:331
def pat_rec_view(SmearedHits, int min_hits)
Definition: shipPatRec.py:120
def get_best_seed(x, y, float sigma, sample_weight=None)
Definition: shipPatRec.py:699
None initialize(fgeo)
Definition: shipPatRec.py:21
def fast_hough_transform_pattern_recognition(SmearedHits, ShipGeo)
Fast Hough Transform.
Definition: shipPatRec.py:202
def pat_rec_stereo_views(SmearedHits_stereo, recognized_tracks_y, int min_hits)
Definition: shipPatRec.py:1075
def _prepare_output(recognized_tracks_combo, min_hits)
Definition: shipPatRec.py:956
def fast_hough_pat_rec_y_view(SmearedHits, int min_hits)
Definition: shipPatRec.py:249
def reduce_clones_using_one_track_per_hit(recognized_tracks, int min_hits=3)
Definition: shipPatRec.py:843
def hit_in_bin(x, y, k_bin, b_bin, k_size, b_size)
Definition: shipPatRec.py:430
bool hit_in_window(x, y, k_bin, b_bin, window_width=1.0)
Definition: shipPatRec.py:998
def artificial_retina_pattern_recognition(SmearedHits, ShipGeo)
Definition: shipPatRec.py:470
def artificial_retina_pat_rec_stereo_views(SmearedHits_stereo, recognized_tracks_y, int min_hits)
Definition: shipPatRec.py:599