Skip to main content

gondola_core/events/
tof_hit.rs

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