gondola_core/events/tof_hit.rs
1// The following file is part of gaps-online-software and published
2// under the GPLv3 license
3
4use crate::prelude::*;
5use std::f32::consts::PI;
6
7/// Waveform peak
8///
9/// Helper to form TofHits
10#[derive(Debug,Copy,Clone,PartialEq)]
11pub struct Peak {
12 pub paddle_end_id : u16,
13 pub time : f32,
14 pub charge : f32,
15 pub height : f32
16}
17
18impl Peak {
19 pub fn new() -> Self {
20 Self {
21 // but why??
22 paddle_end_id : 40,
23 time : 0.0,
24 charge : 0.0,
25 height : 0.0,
26 }
27 }
28}
29
30impl Default for Peak {
31 fn default() -> Self {
32 Self::new()
33 }
34}
35
36impl fmt::Display for Peak {
37 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
38 write!(f, "<Peak:
39 p_end_id : {:.2}
40 time // charge // height : {:.2} // {:.2} // {:.2}>",
41 self.paddle_end_id,
42 self.time,
43 self.charge,
44 self.height)
45 }
46}
47
48//----------------------------------------------------------------
49
50/// An extracted hit from a TofPaddle, as extracted by the
51/// online software and provided algorithm
52/// (in v0.11 algorithm is provided by J.Zweerink)
53///
54/// A TofHit holds the information for an extracted single
55/// hit on a peak, which is defined by a peak in at least one
56/// of the two waveforms.
57/// The TofHit holds extracted information for both of the
58/// waveforms, only if both are available a position reconstruction
59/// on the paddle can be attempted.
60///
61/// A and B are the different ends of the paddle
62
63#[derive(Debug,Copy,Clone,PartialEq)]
64#[cfg_attr(feature = "pybindings", pyclass, pyo3(name="TofHit2"))]
65pub struct TofHit {
66
67 // We currently have 3 bytes to spare
68
69 /// The ID of the paddle in TOF notation
70 /// (1-160)
71 pub paddle_id : u8,
72 pub time_a : f16,
73 pub time_b : f16,
74 pub peak_a : f16,
75 pub peak_b : f16,
76 pub charge_a : f16,
77 pub charge_b : f16,
78 // only 2 bytes of version
79 // are used thus we have a reserved field
80 pub reserved : u8,
81 pub version : ProtocolVersion,
82 // for now, but we want to use half instead
83 pub baseline_a : f16,
84 pub baseline_a_rms : f16,
85 pub baseline_b : f16,
86 pub baseline_b_rms : f16,
87 // phase of the sine fit
88 pub phase : f16,
89
90 //-------------------------------
91 // NON-SERIALIZED FIELDS
92 //-------------------------------
93
94 /// Length of the paddle the hit is on, will get
95 /// populated from db
96 pub paddle_len : f32,
97 /// (In)famous constant timing offset per paddle
98 pub timing_offset : f32,
99 pub coax_cable_time: f32,
100 pub hart_cable_time: f32,
101 /// normalized t0, where we have the phase difference
102 /// limited to -pi/2 -> pi/2
103 pub event_t0 : f32,
104 pub x : f32,
105 pub y : f32,
106 pub z : f32,
107
108 // fields which won't get
109 // serialized
110 pub valid : bool,
111}
112
113// methods without pybindings
114impl TofHit {
115 pub fn new() -> Self {
116 Self{
117 paddle_id : 0,
118 time_a : f16::from_f32(0.0),
119 time_b : f16::from_f32(0.0),
120 peak_a : f16::from_f32(0.0),
121 peak_b : f16::from_f32(0.0),
122 charge_a : f16::from_f32(0.0),
123 charge_b : f16::from_f32(0.0),
124 paddle_len : f32::NAN,
125 timing_offset : 0.0,
126 coax_cable_time : f32::NAN,
127 hart_cable_time : f32::NAN,
128 event_t0 : f32::NAN,
129 x : f32::NAN,
130 y : f32::NAN,
131 z : f32::NAN,
132
133 valid : true,
134 // v1 variables
135 version : ProtocolVersion::V1,
136 reserved : 0,
137 baseline_a : f16::from_f32(0.0),
138 baseline_a_rms : f16::from_f32(0.0),
139 baseline_b : f16::from_f32(0.0),
140 baseline_b_rms : f16::from_f32(0.0),
141 phase : f16::from_f32(0.0),
142 }
143 }
144
145 /// Adds an extracted peak to this TofHit. A peak will be
146 /// for only a single waveform only, so we have to take
147 /// care of the A/B sorting by means of PaddleEndId
148 pub fn add_peak(&mut self, peak : &Peak) {
149 if self.paddle_id != TofHit::get_pid(peak.paddle_end_id) {
150 //error!("Can't add peak to
151 }
152 if peak.paddle_end_id < 1000 {
153 error!("Invalide paddle end id {}", peak.paddle_end_id);
154 }
155 if peak.paddle_end_id > 2000 {
156 self.set_time_b (peak.time);
157 self.set_peak_b (peak.height);
158 self.set_charge_b(peak.charge);
159 } else if peak.paddle_end_id < 2000 {
160 self.set_time_a (peak.time);
161 self.set_peak_a (peak.height);
162 self.set_charge_a(peak.charge);
163 }
164 }
165
166 // None of the setters will have pybindings
167 pub fn set_time_b(&mut self, t : f32) {
168 self.time_b = f16::from_f32(t)
169 }
170
171 pub fn set_time_a(&mut self, t : f32) {
172 self.time_a = f16::from_f32(t);
173 }
174
175 pub fn set_peak_a(&mut self, p : f32) {
176 self.peak_a = f16::from_f32(p)
177 }
178
179 pub fn set_peak_b(&mut self, p : f32) {
180 self.peak_b = f16::from_f32(p)
181 }
182
183 pub fn set_charge_a(&mut self, c : f32) {
184 self.charge_a = f16::from_f32(c)
185 }
186
187 pub fn set_charge_b(&mut self, c : f32) {
188 self.charge_b = f16::from_f32(c)
189 }
190}
191
192// wrapper methods which duplicate the rust code,
193// but need addional configuration with pyo3, e.g.
194// the #getter attribute
195#[cfg(feature="pybindings")]
196#[pymethods]
197impl TofHit {
198
199 /// The paddle id (1-160) of the hit paddle
200 #[getter]
201 #[pyo3(name="paddle_id")]
202 fn paddle_id_py(&self) -> u8 {
203 self.paddle_id
204 }
205
206 /// The length of the paddle, only available after
207 /// the paddle information has been added through
208 /// "set_paddle"
209 #[getter]
210 #[pyo3(name="paddle_len")]
211 fn get_paddle_len_py(&self) -> f32 {
212 self.paddle_len
213 }
214
215 /// Set the length and cable length for the paddle
216 /// FIXME - take gaps_online.db.Paddle as argument
217 #[pyo3(name="set_paddle")]
218 fn set_paddle_py(&mut self, plen : f32, coax_cbl_time : f32, hart_cbl_time : f32 ) {
219 self.paddle_len = plen;
220 self.coax_cable_time = coax_cbl_time;
221 self.hart_cable_time = hart_cbl_time;
222 }
223
224 /// The time in ns the signal spends in the coax
225 /// cables from the SiPMs to the RAT
226 #[getter]
227 #[pyo3(name="coax_cbl_time")]
228 fn get_coax_cbl_time(&self) -> f32 {
229 self.coax_cable_time
230 }
231
232 /// The time in ns the signal spends in the Harting
233 /// cables from the RATs to the MTB
234 #[getter]
235 #[pyo3(name="hart_cbl_time")]
236 fn get_hart_cbl_time(&self) -> f32 {
237 self.hart_cable_time
238 }
239
240 /// Calculate the position across the paddle from
241 /// the two times at the paddle ends
242 ///
243 /// **This will be measured from the A side**
244 ///
245 /// Just to be extra clear, this assumes the two
246 /// sets of cables for each paddle end have the
247 /// same length
248 #[getter]
249 #[pyo3(name="pos")]
250 fn get_pos_py(&self) -> f32 {
251 self.get_pos()
252 }
253
254 #[getter]
255 #[pyo3(name="x")]
256 fn x_py(&self) -> f32 {
257 self.x
258 }
259
260 #[getter]
261 #[pyo3(name="y")]
262 fn y_py(&self) -> f32 {
263 self.y
264 }
265
266 #[getter]
267 #[pyo3(name="z")]
268 fn z_py(&self) -> f32 {
269 self.z
270 }
271
272 #[getter]
273 #[pyo3(name="version")]
274 fn version_py(&self) -> ProtocolVersion {
275 self.version
276 }
277
278 #[getter]
279 #[pyo3(name="phase")]
280 fn phase_py(&self) -> f32 {
281 self.phase.to_f32()
282 }
283
284 #[getter]
285 #[pyo3(name="cable_delay")]
286 /// Get the cable correction time
287 fn get_cable_delay_py(&self) -> f32 {
288 self.get_cable_delay()
289 }
290
291 /// Get the delay relative to other readoutboards based
292 /// on the channel9 sine wave
293 #[getter]
294 #[pyo3(name="phase_delay")]
295 fn get_phase_delay_py(&self) -> f32 {
296 self.get_phase_delay()
297 }
298
299 /// That this works, the length of the paddle has to
300 /// be set before (in mm).
301 /// This assumes that the cable on both sides of the paddle are
302 /// the same length
303 #[getter]
304 #[pyo3(name="t0")]
305 fn get_t0_py(&self) -> f32 {
306 self.get_t0()
307 }
308
309 /// Event t0 is the calculated interaction time based on
310 /// the RELATIVE phase shifts consdering ALL hits in this
311 /// event. This might be of importance to catch rollovers
312 /// in the phase of channel9.
313 /// In total, we are restricting ourselves to a time of
314 /// 50ns per events and adjust the phase in such a way that
315 /// everything fits into this interval. This will
316 /// significantly import the beta reconstruction for particles
317 /// which hit the TOF within this timing window.
318 ///
319 /// If a timing offset is set, this will be added
320 #[getter]
321 #[pyo3(name="event_t0")]
322 fn get_event_t0_py(&self) -> f32 {
323 self.get_t0()
324 }
325
326 /// Calculate the interaction time based on the peak timings measured
327 /// at the paddle ends A and B
328 ///
329 /// This does not correct for any cable length
330 /// or ch9 phase shift
331 #[getter]
332 #[pyo3(name="t0_uncorrected")]
333 fn get_t0_uncorrected_py(&self) -> f32 {
334 self.get_t0_uncorrected()
335 }
336
337 /// Philip's energy deposition based on peak height
338 #[getter]
339 #[pyo3(name="edep")]
340 fn get_edep_py(&self) -> f32 {
341 self.get_edep()
342 }
343
344 /// Elena's energy deposition based on peak height
345 #[getter]
346 #[pyo3(name="edep")]
347 fn get_edep_att_py(&self) -> f32 {
348 self.get_edep_att()
349 }
350
351 /// Arrival time of the photons at side A
352 #[getter]
353 #[pyo3(name="time_a")]
354 fn get_time_a_py(&self) -> f32 {
355 self.get_time_a()
356 }
357
358 /// Arrival time of the photons at side B
359 #[getter]
360 #[pyo3(name="time_b")]
361 fn get_time_b_py(&self) -> f32 {
362 self.get_time_b()
363 }
364
365 #[getter]
366 #[pyo3(name="peak_a")]
367 fn get_peak_a_py(&self) -> f32 {
368 self.get_peak_a()
369 }
370
371 #[getter]
372 #[pyo3(name="peak_b")]
373 fn get_peak_b_py(&self) -> f32 {
374 self.get_peak_b()
375 }
376
377 #[getter]
378 #[pyo3(name="charge_a")]
379 fn get_charge_a_py(&self) -> f32 {
380 self.get_charge_a()
381 }
382
383 #[getter]
384 #[pyo3(name="charge_b")]
385 fn get_charge_b_py(&self) -> f32 {
386 self.get_charge_b()
387 }
388
389 #[getter]
390 #[pyo3(name="baseline_a")]
391 fn get_bl_a_py(&self) -> f32 {
392 self.get_bl_a()
393 }
394
395 #[getter]
396 #[pyo3(name="baseline_b")]
397 fn get_bl_b_py(&self) -> f32 {
398 self.get_bl_b()
399 }
400
401 #[getter]
402 #[pyo3(name="baseline_a_rms")]
403 fn get_bl_a_rms_py(&self) -> f32 {
404 self.get_bl_a_rms()
405 }
406
407 #[getter]
408 #[pyo3(name="baseline_b_rms")]
409 fn get_bl_b_rms_py(&self) -> f32 {
410 self.get_bl_b_rms()
411 }
412}
413
414// methods which are available in rust, but can
415// have an implementation in python.
416// Sets the pymethods attribute conditionally
417#[cfg_attr(feature="pybindings", pymethods)]
418impl TofHit {
419 /// Calculate the distance to another hit. For this
420 /// to work, the hit coordinates have had to be
421 /// determined, so this will only return a
422 /// propper result after the paddle information
423 /// is added
424 pub fn distance(&self, other : &TofHit) -> f32 {
425 ((self.x - other.x).powi(2) + (self.y - other.y).powi(2) + (self.z - other.z).powi(2)).sqrt()
426 }
427
428 /// If the two reconstructed pulse times are not related to each other by the paddle length,
429 /// meaning that they can't be caused by the same event, we dub this hit as "not following
430 /// causality"
431 pub fn obeys_causality(&self) -> bool {
432 (self.paddle_len/(10.0*C_LIGHT_PADDLE)) - f32::abs(self.time_a.to_f32() - self.time_b.to_f32()) > 0.0
433 && self.get_t0_uncorrected() > 0.0
434 }
435}
436
437#[cfg(feature="pybindings")]
438pythonize!(TofHit);
439
440// methods which have wrapped pybindings
441impl TofHit {
442
443
444
445 /// Calculate the position across the paddle from
446 /// the two times at the paddle ends
447 ///
448 /// **This will be measured from the A side**
449 ///
450 /// Just to be extra clear, this assumes the two
451 /// sets of cables for each paddle end have the
452 /// same length
453 pub fn get_pos(&self) -> f32 {
454 let t0 = self.get_t0_uncorrected();
455 let clean_t_a = self.time_a.to_f32() - t0;
456 return clean_t_a*C_LIGHT_PADDLE*10.0;
457 }
458
459 /// Get the cable correction time
460 pub fn get_cable_delay(&self) -> f32 {
461 self.hart_cable_time - self.coax_cable_time
462 }
463
464 /// Get the delay relative to other readoutboards based
465 /// on the channel9 sine wave
466 pub fn get_phase_delay(&self) -> f32 {
467 let freq : f32 = 20.0e6;
468 let phase = self.phase.to_f32();
469 // fit allows for negative phase shift.
470 // that means to distinguish 2 points, we
471 // only have HALF of the sine wave
472 // FIXME - implement warning?
473 //while phase < -PI {
474 // phase += 2.0*PI;
475 //}
476 //while phase > PI {
477 // phase -= 2.0*PI;
478 //}
479 (phase/(2.0*PI*freq))*1.0e9f32
480 }
481
482 /// That this works, the length of the paddle has to
483 /// be set before (in mm).
484 /// This assumes that the cable on both sides of the paddle are
485 /// the same length
486 pub fn get_t0(&self) -> f32 {
487 //self.get_t0_uncorrected() + self.get_phase_delay() + self.get_cable_delay()
488 self.event_t0 + self.timing_offset
489 }
490
491 /// Calculate the interaction time based on the peak timings measured
492 /// at the paddle ends A and B
493 ///
494 /// This does not correct for any cable length
495 /// or ch9 phase shift
496 pub fn get_t0_uncorrected(&self) -> f32 {
497 0.5*(self.time_a.to_f32() + self.time_b.to_f32() - (self.paddle_len/(10.0*C_LIGHT_PADDLE)))
498 }
499
500 /// Philip's energy deposition based on peak height
501 pub fn get_edep(&self) -> f32 {
502 (1.29/34.3)*(self.peak_a.to_f32() + self.peak_b.to_f32()) / 2.0
503 }
504
505 /// Elena's energy deposition including attenuation
506 pub fn get_edep_att(&self) -> f32 {
507 let x0 = self.get_pos();
508 let att_a = ((3.9-0.00126*( x0+self.paddle_len/2.))+22.15).exp() / ((3.9)+22.15).exp();
509 let att_b = ((3.9-0.00126*(-x0+self.paddle_len/2.))+22.15).exp() / ((3.9)+22.15).exp();
510 let edep = 0.0159 * (self.get_peak_a()/att_a + self.get_peak_b()/att_b) / 2.; // vertical muon peak @ 0.97 MeV
511 return edep;
512 }
513
514 /// Arrival time of the photons at side A
515 pub fn get_time_a(&self) -> f32 {
516 self.time_a.to_f32()
517 }
518
519 /// Arrival time of the photons at side B
520 pub fn get_time_b(&self) -> f32 {
521 self.time_b.to_f32()
522 }
523
524 pub fn get_peak_a(&self) -> f32 {
525 self.peak_a.to_f32()
526 }
527
528 pub fn get_peak_b(&self) -> f32 {
529 self.peak_b.to_f32()
530 }
531
532 pub fn get_charge_a(&self) -> f32 {
533 self.charge_a.to_f32()
534 }
535
536 pub fn get_charge_b(&self) -> f32 {
537 self.charge_b.to_f32()
538 }
539
540 pub fn get_bl_a(&self) -> f32 {
541 self.baseline_a.to_f32()
542 }
543
544 pub fn get_bl_b(&self) -> f32 {
545 self.baseline_b.to_f32()
546 }
547
548 pub fn get_bl_a_rms(&self) -> f32 {
549 self.baseline_a_rms.to_f32()
550 }
551
552 pub fn get_bl_b_rms(&self) -> f32 {
553 self.baseline_b_rms.to_f32()
554 }
555
556
557 /// Get the (official) paddle id
558 ///
559 /// Convert the paddle end id following
560 /// the convention
561 ///
562 /// A-side : paddle id + 1000
563 /// B-side : paddle id + 2000
564 ///
565 /// FIXME - maybe return Result?
566 //#[deprecated(since="0.10", note="We are not using a paddle end id anymore")]
567 pub fn get_pid(paddle_end_id : u16) -> u8 {
568 if paddle_end_id < 1000 {
569 return 0;
570 }
571 if paddle_end_id > 2000 {
572 return (paddle_end_id - 2000) as u8;
573 }
574 if paddle_end_id < 2000 {
575 return (paddle_end_id - 1000) as u8;
576 }
577 return 0;
578 }
579
580
581
582 pub fn get_phase_rollovers(&self) -> i16 {
583 let mut phase = self.phase.to_f32();
584 let mut ro = 0i16;
585 while phase < PI/2.0 {
586 phase += PI/2.0;
587 ro += 1;
588 }
589 while phase > PI/2.0 {
590 phase -= PI/2.0;
591 ro -= 1;
592 }
593 ro
594 }
595
596
597}
598
599#[cfg(feature="database")]
600impl TofHit {
601 pub fn set_paddle(&mut self, paddle : &TofPaddle) {
602 self.coax_cable_time = paddle.coax_cable_time;
603 self.hart_cable_time = paddle.harting_cable_time;
604 self.paddle_len = paddle.length * 10.0; // stupid units!
605 let pr = paddle.principal();
606 //println!("Principal {:?}", pr);
607 let rel_pos = self.get_pos();
608 let pos = (paddle.global_pos_x_l0_A*10.0 + pr.0*rel_pos,
609 paddle.global_pos_y_l0_A*10.0 + pr.1*rel_pos,
610 paddle.global_pos_z_l0_A*10.0 + pr.2*rel_pos);
611 self.x = pos.0;
612 self.y = pos.1;
613 self.z = pos.2;
614 }
615}
616
617
618// Implementation of traits
619//-------------------------
620
621impl Default for TofHit {
622 fn default() -> Self {
623 Self::new()
624 }
625}
626
627impl fmt::Display for TofHit {
628 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
629 let mut paddle_info = String::from("");
630 if self.paddle_len == 0.0 {
631 paddle_info = String::from("NOT SET!");
632 }
633 write!(f, "<TofHit (version : {}):
634 Paddle ID {}
635 Peak:
636 LE Time A/B {:.2} {:.2}
637 Height A/B {:.2} {:.2}
638 Charge A/B {:.2} {:.2}
639 ** paddle {} **
640 Length {:.2}
641 Timing offset {:.2} (ns)
642 Coax cbl time {:.2}
643 Hart cbl time {:.2}
644 ** reconstructed interaction
645 energy_dep {:.2}
646 edep_att {:.2}
647 pos_across {:.2}
648 t0 {:.2}
649 x, y, z {:.2} {:.2} {:.2}
650 ** V1 variables
651 phase (ch9) {:.4}
652 n phs ro {}
653 baseline A/B {:.2} {:.2}
654 bl. RMS A/B {:.2} {:.2}>",
655 self.version,
656 self.paddle_id,
657 self.get_time_a(),
658 self.get_time_b(),
659 self.get_peak_a(),
660 self.get_peak_b(),
661 self.get_charge_a(),
662 self.get_charge_b(),
663 paddle_info,
664 self.paddle_len,
665 self.timing_offset,
666 self.coax_cable_time,
667 self.hart_cable_time,
668 self.get_edep(),
669 self.get_edep_att(),
670 self.get_pos(),
671 self.get_t0(),
672 self.x,
673 self.y,
674 self.z,
675 self.phase,
676 self.get_phase_rollovers(),
677 self.baseline_a,
678 self.baseline_b,
679 self.baseline_a_rms,
680 self.baseline_b_rms,
681 )
682 }
683}
684
685impl Serialization for TofHit {
686
687 const HEAD : u16 = 61680; //0xF0F0)
688 const TAIL : u16 = 3855;
689 const SIZE : usize = 30; // size in bytes with HEAD and TAIL
690
691 /// Serialize the packet
692 ///
693 /// Not all fields will get serialized,
694 /// only the relevant data for the
695 /// flight computer
696 //
697 /// **A note about protocol versions **
698 /// When we serialize (to_bytestream) we will
699 /// always write the latest version.
700 /// Deserialization can also read previous versions
701 fn to_bytestream(&self) -> Vec<u8> {
702
703 let mut bytestream = Vec::<u8>::with_capacity(Self::SIZE);
704 bytestream.extend_from_slice(&Self::HEAD.to_le_bytes());
705 bytestream.push(self.paddle_id);
706 bytestream.extend_from_slice(&self.time_a .to_le_bytes());
707 bytestream.extend_from_slice(&self.time_b .to_le_bytes());
708 bytestream.extend_from_slice(&self.peak_a .to_le_bytes());
709 bytestream.extend_from_slice(&self.peak_b .to_le_bytes());
710 bytestream.extend_from_slice(&self.charge_a .to_le_bytes());
711 bytestream.extend_from_slice(&self.charge_b .to_le_bytes());
712 // charge_min_i has been removed, so just insert a 0 here
713 bytestream.extend_from_slice(&0u16.to_le_bytes());
714 bytestream.extend_from_slice(&self.baseline_a .to_le_bytes());
715 bytestream.extend_from_slice(&self.baseline_a_rms.to_le_bytes());
716 bytestream.extend_from_slice(&self.phase .to_le_bytes());
717 bytestream.push(self.version.to_u8());
718 bytestream.extend_from_slice(&self.baseline_b.to_le_bytes());
719 bytestream.extend_from_slice(&self.baseline_b_rms.to_le_bytes());
720 bytestream.extend_from_slice(&Self::TAIL .to_le_bytes());
721 bytestream
722 }
723
724
725 /// Deserialization
726 ///
727 ///
728 /// # Arguments:
729 ///
730 /// * bytestream :
731 fn from_bytestream(stream : &Vec<u8>, pos : &mut usize)
732 -> Result<Self, SerializationError> {
733 let mut pp = Self::new();
734 Self::verify_fixed(stream, pos)?;
735 // since we passed the above test, the packet
736 // is valid
737 pp.valid = true;
738 pp.paddle_id = parse_u8(stream, pos);
739 pp.time_a = parse_f16(stream, pos);
740 pp.time_b = parse_f16(stream, pos);
741 pp.peak_a = parse_f16(stream, pos);
742 pp.peak_b = parse_f16(stream, pos);
743 pp.charge_a = parse_f16(stream, pos);
744 pp.charge_b = parse_f16(stream, pos);
745 // we have a currently empty field
746 *pos += 2;
747 //pp.charge_min_i = parse_u16(stream, pos);
748 pp.baseline_a = parse_f16(stream, pos);
749 pp.baseline_a_rms = parse_f16(stream, pos);
750 let mut phase_vec = Vec::<u8>::new();
751 phase_vec.push(parse_u8(stream, pos));
752 phase_vec.push(parse_u8(stream, pos));
753 pp.phase = parse_f16(&phase_vec, &mut 0);
754 let version = ProtocolVersion::from(parse_u8(stream, pos));
755 pp.version = version;
756 match pp.version {
757 ProtocolVersion::V1 => {
758 }
759 _ => ()
760 }
761 pp.baseline_b = parse_f16(stream, pos);
762 pp.baseline_b_rms = parse_f16(stream, pos);
763 *pos += 2; // always have to do this when using verify fixed
764 Ok(pp)
765 }
766}
767
768#[cfg(feature="random")]
769impl FromRandom for TofHit {
770 fn from_random() -> TofHit {
771 let mut pp = TofHit::new();
772 let mut rng = rand::rng();
773
774 pp.paddle_id = rng.random_range(0..161);
775 pp.time_a = f16::from_f32(rng.random::<f32>());
776 pp.time_b = f16::from_f32(rng.random::<f32>());
777 pp.peak_a = f16::from_f32(rng.random::<f32>());
778 pp.peak_b = f16::from_f32(rng.random::<f32>());
779 pp.charge_a = f16::from_f32(rng.random::<f32>());
780 pp.charge_b = f16::from_f32(rng.random::<f32>());
781 pp.version = ProtocolVersion::from(rng.random::<u8>());
782 pp.baseline_a = f16::from_f32(rng.random::<f32>());
783 pp.baseline_a_rms = f16::from_f32(rng.random::<f32>());
784 pp.baseline_b = f16::from_f32(rng.random::<f32>());
785 pp.baseline_b_rms = f16::from_f32(rng.random::<f32>());
786 pp.phase = f16::from_f32(rng.random::<f32>());
787
788 pp.paddle_len = 0.0;
789 pp.coax_cable_time = 0.0;
790 pp.hart_cable_time = 0.0;
791 pp.x = 0.0;
792 pp.y = 0.0;
793 pp.z = 0.0;
794 pp.event_t0 = 0.0;
795 pp
796 }
797}
798
799//---------------------------------------------------------------
800
801//#[cfg(feature="pybindings")]
802//#[pyclass]
803//#[pyo3(name="TofHit")]
804//pub struct PyTofHit {
805// hit : TofHit,
806//}
807//
808//#[cfg(feature="pybindings")]
809//#[pymethods]
810//impl PyTofHit {
811// //#[new]
812// //fn new() -> Self {
813// // Self {
814// // hit : TofHit::new(),
815// // }
816// //}
817//
818// pub fn set_timing_offset(&mut self, offset : f32) {
819// self.hit.timing_offset = offset;
820// }
821//
822// /// Calculate the distance to another hit. For this
823// /// to work, the hit coordinates have had to be
824// /// determined, so this will only return a
825// /// propper result after the paddle information
826// /// is added
827// pub fn distance(&self, other : &PyTofHit) -> f32 {
828// //((self.x - other.x).powi(2) + (self.y - other.y).powi(2) + (self.z - other.z).powi(2)).sqrt()
829// self.hit.distance(&other.hit)
830// }
831//
832// /// Set the length and cable length for the paddle
833// /// FIXME - take gaps_online.db.Paddle as argument
834// fn set_paddle(&mut self, plen : f32, coax_cbl_time : f32, hart_cbl_time : f32 ) {
835// self.hit.paddle_len = plen;
836// self.hit.coax_cable_time = coax_cbl_time;
837// self.hit.hart_cable_time = hart_cbl_time;
838// }
839//
840// #[getter]
841// fn x(&self) -> f32 {
842// self.hit.x
843// }
844//
845// #[getter]
846// fn y(&self) -> f32 {
847// self.hit.y
848// }
849//
850// #[getter]
851// fn z(&self) -> f32 {
852// self.hit.z
853// }
854//
855// #[getter]
856// fn get_t0_uncorrected(&self) -> f32 {
857// self.hit.get_t0_uncorrected()
858// }
859//
860// /// Event t0 is the calculated interaction time based on
861// /// the RELATIVE phase shifts consdering ALL hits in this
862// /// event. This might be of importance to catch rollovers
863// /// in the phase of channel9.
864// /// In total, we are restricting ourselves to a time of
865// /// 50ns per events and adjust the phase in such a way that
866// /// everything fits into this interval. This will
867// /// significantly import the beta reconstruction for particles
868// /// which hit the TOF within this timing window.
869// ///
870// /// If a timing offset is set, this will be added
871// #[getter]
872// fn get_event_t0(&self) -> f32 {
873// self.hit.get_t0()
874// }
875//
876// #[getter]
877// fn get_obeys_causality(&self) -> bool {
878// self.hit.obeys_causality()
879// }
880//
881// #[getter]
882// fn get_coax_cbl_time(&self) -> f32 {
883// self.hit.coax_cable_time
884// }
885//
886// #[getter]
887// fn get_hart_cbl_time(&self) -> f32 {
888// self.hit.hart_cable_time
889// }
890//
891// /// Reconstructed particle interaction time,
892// /// calculated from the waveforms of the two
893// /// different paddle ends. If the paddle has
894// /// been set, this takes phase and cable
895// /// length into account
896// #[getter]
897// fn t0(&self) -> f32 {
898// self.hit.get_t0()
899// }
900//
901// #[getter]
902// fn get_phase_delay(&self) -> f32 {
903// self.hit.get_phase_delay()
904// }
905//
906// #[getter]
907// fn get_cable_delay(&self) -> f32 {
908// self.hit.get_cable_delay()
909// }
910//
911// #[getter]
912// fn version(&self) -> ProtocolVersion {
913// self.hit.version
914// }
915//
916// #[getter]
917// fn phase(&self) -> f32 {
918// self.hit.phase.to_f32()
919// }
920//
921// #[getter]
922// fn baseline_a(&self) -> f32 {
923// self.hit.baseline_a.to_f32()
924// }
925//
926// #[getter]
927// fn baseline_a_rms(&self) -> f32 {
928// self.hit.baseline_a_rms.to_f32()
929// }
930//
931// #[getter]
932// fn baseline_b(&self) -> f32 {
933// self.hit.baseline_b.to_f32()
934// }
935//
936// #[getter]
937// fn baseline_b_rms(&self) -> f32 {
938// self.hit.baseline_b_rms.to_f32()
939// }
940//
941// #[getter]
942// fn peak_a(&self) -> f32 {
943// self.hit.get_peak_a()
944// }
945//
946// #[getter]
947// fn peak_b(&self) -> f32 {
948// self.hit.get_peak_b()
949// }
950//
951// #[getter]
952// fn charge_a(&self) -> f32 {
953// self.hit.get_charge_a()
954// }
955//
956// #[getter]
957// fn charge_b(&self) -> f32 {
958// self.hit.get_charge_b()
959// }
960//
961// #[getter]
962// fn time_a(&self) -> f32 {
963// self.hit.get_time_a()
964// }
965//
966// #[getter]
967// fn time_b(&self) -> f32 {
968// self.hit.get_time_b()
969// }
970//
971// /// Reconstructed particle interaction position
972// /// along the long axis of the paddle.
973// /// For the other dimensions, there is no information
974// /// about the position.
975// /// Reconstructed with the waveforms of both paddle ends.
976// #[getter]
977// fn pos(&self) -> f32 {
978// self.hit.get_pos()
979// }
980//
981// /// The paddle id (1-160) of the hit paddle
982// #[getter]
983// fn paddle_id(&self) -> u8 {
984// self.hit.paddle_id
985// }
986//
987// #[getter]
988// fn edep(&self) -> f32 {
989// self.hit.get_edep()
990// }
991//
992// #[getter]
993// fn get_paddle_len(&self) -> f32 {
994// self.hit.paddle_len
995// }
996//
997//}
998//
999//#[cfg(feature="pybindings")]
1000//impl_pythonize_display!(PyTofHit, |s: &PyTofHit| s.hit.to_string());
1001
1002
1003#[cfg(feature = "random")]
1004#[test]
1005fn serialization_tofhit() {
1006 for _ in 0..100 {
1007 let mut pos = 0;
1008 let data = TofHit::from_random();
1009 let mut test = TofHit::from_bytestream(&data.to_bytestream(),&mut pos).unwrap();
1010 // Manually zero these fields, since comparison with nan will fail and
1011 // from_random did not touch these
1012 test.paddle_len = 0.0;
1013 test.coax_cable_time = 0.0;
1014 test.hart_cable_time = 0.0;
1015 test.x = 0.0;
1016 test.y = 0.0;
1017 test.z = 0.0;
1018 test.event_t0 = 0.0;
1019 assert_eq!(pos, TofHit::SIZE);
1020 assert_eq!(data, test);
1021 }
1022}