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="cable_delay")]
302  /// Get the cable correction time
303  fn get_cable_delay_py(&self) -> f32 {
304    self.get_cable_delay()
305  }
306
307  /// Get the delay relative to other readoutboards based 
308  /// on the channel9 sine wave
309  #[getter]
310  #[pyo3(name="phase_delay")]
311  fn get_phase_delay_py(&self) -> f32 { 
312    self.get_phase_delay()
313  }
314  
315  /// That this works, the length of the paddle has to 
316  /// be set before (in mm).
317  /// This assumes that the cable on both sides of the paddle are 
318  /// the same length
319  #[getter]
320  #[pyo3(name="t0")]
321  fn get_t0_py(&self) -> f32 {
322    self.get_t0()
323  }
324  
325  /// Event t0 is the calculated interaction time based on 
326  /// the RELATIVE phase shifts consdering ALL hits in this
327  /// event. This might be of importance to catch rollovers
328  /// in the phase of channel9. 
329  /// In total, we are restricting ourselves to a time of 
330  /// 50ns per events and adjust the phase in such a way that 
331  /// everything fits into this interval. This will 
332  /// significantly import the beta reconstruction for particles
333  /// which hit the TOF within this timing window.
334  ///
335  /// If a timing offset is set, this will be added
336  #[getter]
337  #[pyo3(name="event_t0")]
338  fn get_event_t0_py(&self) -> f32 {
339    self.get_t0()
340  }
341
342  /// Calculate the interaction time based on the peak timings measured 
343  /// at the paddle ends A and B
344  ///
345  /// This does not correct for any cable length
346  /// or ch9 phase shift
347  #[getter]
348  #[pyo3(name="t0_uncorrected")]
349  fn get_t0_uncorrected_py(&self) -> f32 {
350    self.get_t0_uncorrected()
351  }
352
353  /// Philip's energy deposition based on peak height
354  #[getter]
355  #[pyo3(name="edep")]
356  fn get_edep_py(&self) -> f32 {
357    self.get_edep()
358  }
359  
360  /// Elena's energy deposition based on peak height
361  #[getter]
362  #[pyo3(name="edep_att")]
363  fn get_edep_att_py(&self) -> f32 {
364    self.get_edep_att()
365  }
366
367  /// Arrival time of the photons at side A
368  #[getter]
369  #[pyo3(name="time_a")]
370  fn get_time_a_py(&self) -> f32 {
371    self.get_time_a()
372  }
373
374  /// Arrival time of the photons at side B
375  #[getter]
376  #[pyo3(name="time_b")]
377  fn get_time_b_py(&self) -> f32 {
378    self.get_time_b()
379  }
380
381  #[getter]
382  #[pyo3(name="TOT_low_a")]
383  fn get_tot_low_a_py(&self) -> f32 {
384      self.get_tot_low_a()
385  }
386
387  #[getter]
388  #[pyo3(name="TOT_low_b")]
389  fn get_tot_low_b_py(&self) -> f32 {
390      self.get_tot_low_b()
391  }
392
393  #[getter]
394  #[pyo3(name="TOT_high_a")]
395  fn get_tot_high_a_py(&self) -> f32 {
396      self.get_tot_high_a()
397  }
398
399  #[getter]
400  #[pyo3(name="TOT_high_b")]
401  fn get_tot_high_b_py(&self) -> f32 {
402      self.get_tot_high_b()
403  }
404
405  #[getter]
406  #[pyo3(name="TOT_slp_low_a")]
407  fn get_tot_slp_low_a_py(&self) -> f32 {
408      self.get_tot_slp_low_a()
409  }
410
411  #[getter]
412  #[pyo3(name="TOT_slp_low_b")]
413  fn get_tot_slp_low_b_py(&self) -> f32 {
414      self.get_tot_slp_low_b()
415  }
416
417  #[getter]
418  #[pyo3(name="TOT_slp_high_a")]
419  fn get_tot_slp_high_a_py(&self) -> f32 {
420      self.get_tot_slp_high_a()
421  }
422
423  #[getter]
424  #[pyo3(name="TOT_slp_high_b")]
425  fn get_tot_slp_high_b_py(&self) -> f32 {
426      self.get_tot_slp_high_b()
427  }
428
429  #[getter]
430  #[pyo3(name="peak_a")]
431  fn get_peak_a_py(&self) -> f32 {
432    self.get_peak_a()
433  }
434  
435  #[getter]
436  #[pyo3(name="peak_b")]
437  fn get_peak_b_py(&self) -> f32 {
438    self.get_peak_b()
439  }
440  
441  #[getter]
442  #[pyo3(name="charge_a")]
443  fn get_charge_a_py(&self) -> f32 {
444    self.get_charge_a()
445  }
446  
447  #[getter]
448  #[pyo3(name="charge_b")]
449  fn get_charge_b_py(&self) -> f32 {
450    self.get_charge_b()
451  }
452  
453  #[getter]
454  #[pyo3(name="baseline_a")]
455  fn get_bl_a_py(&self) -> f32 {
456    self.get_bl_a()
457  }
458  
459  #[getter]
460  #[pyo3(name="baseline_b")]
461  fn get_bl_b_py(&self) -> f32 {
462    self.get_bl_b()
463  }
464  
465  #[getter]
466  #[pyo3(name="baseline_a_rms")]
467  fn get_bl_a_rms_py(&self) -> f32 {
468    self.get_bl_a_rms()
469  }
470  
471  #[getter]
472  #[pyo3(name="baseline_b_rms")]
473  fn get_bl_b_rms_py(&self) -> f32 {
474    self.get_bl_b_rms()
475  }
476}
477
478// methods which are available in rust, but can 
479// have an implementation in python.
480// Sets the pymethods attribute conditionally
481#[cfg_attr(feature="pybindings", pymethods)]
482impl TofHit {
483  /// Calculate the distance to another hit. For this 
484  /// to work, the hit coordinates have had to be 
485  /// determined, so this will only return a 
486  /// propper result after the paddle information 
487  /// is added
488  pub fn distance(&self, other : &TofHit) -> f32 {
489    ((self.x - other.x).powi(2) + (self.y - other.y).powi(2) + (self.z - other.z).powi(2)).sqrt()
490  } 
491  
492  /// If the two reconstructed pulse times are not related to each other by the paddle length,
493  /// meaning that they can't be caused by the same event, we dub this hit as "not following
494  /// causality"
495  pub fn obeys_causality(&self) -> bool {
496    (self.paddle_len/(10.0*C_LIGHT_PADDLE)) - f32::abs(self.time_a.to_f32() - self.time_b.to_f32()) > 0.0
497    && self.get_t0_uncorrected() > 0.0
498  }
499}
500
501#[cfg(feature="pybindings")]
502pythonize!(TofHit);
503
504// methods which have wrapped pybindings
505impl TofHit {
506  
507  
508  
509  /// Calculate the position across the paddle from
510  /// the two times at the paddle ends
511  ///
512  /// **This will be measured from the A side**
513  ///
514  /// Just to be extra clear, this assumes the two 
515  /// sets of cables for each paddle end have the
516  /// same length
517  pub fn get_pos(&self) -> f32 {
518    let t0 = self.get_t0_uncorrected();
519    let clean_t_a = self.time_a.to_f32() - t0;
520    return clean_t_a*C_LIGHT_PADDLE*10.0; 
521  }
522  
523  /// Get the cable correction time
524  pub fn get_cable_delay(&self) -> f32 {
525    self.hart_cable_time - self.coax_cable_time 
526  }
527
528  /// Get the delay relative to other readoutboards based 
529  /// on the channel9 sine wave
530  pub fn get_phase_delay(&self) -> f32 { 
531    let freq : f32 = 20.0e6;
532    let phase = self.phase.to_f32();
533    // fit allows for negative phase shift.
534    // that means to distinguish 2 points, we
535    // only have HALF of the sine wave
536    // FIXME - implement warning?
537    //while phase < -PI {
538    //  phase += 2.0*PI;
539    //}
540    //while phase > PI {
541    //  phase -= 2.0*PI;
542    //}
543    (phase/(2.0*PI*freq))*1.0e9f32 
544  }
545  
546  /// That this works, the length of the paddle has to 
547  /// be set before (in mm).
548  /// This assumes that the cable on both sides of the paddle are 
549  /// the same length
550  pub fn get_t0(&self) -> f32 {
551    //self.get_t0_uncorrected() + self.get_phase_delay() + self.get_cable_delay()
552    self.event_t0 + self.timing_offset
553  }
554
555  /// Calculate the interaction time based on the peak timings measured 
556  /// at the paddle ends A and B
557  ///
558  /// This does not correct for any cable length
559  /// or ch9 phase shift
560  pub fn get_t0_uncorrected(&self) -> f32 {
561    0.5*(self.time_a.to_f32() + self.time_b.to_f32() - (self.paddle_len/(10.0*C_LIGHT_PADDLE)))
562  }
563
564  /// Philip's energy deposition based on peak height
565  pub fn get_edep(&self) -> f32 {
566    (1.29/34.3)*(self.peak_a.to_f32() + self.peak_b.to_f32()) / 2.0
567  }
568  
569  /// Elena's energy deposition including attenuation
570  pub fn get_edep_att(&self) -> f32 {
571    let x0    = self.get_pos();
572    let att_a = ((3.9-0.00126*( x0+self.paddle_len/2.))+22.15).exp() / ((3.9)+22.15).exp();
573    let att_b = ((3.9-0.00126*(-x0+self.paddle_len/2.))+22.15).exp() / ((3.9)+22.15).exp();
574    let edep  = 0.0159 * (self.get_peak_a()/att_a + self.get_peak_b()/att_b) / 2.; // vertical muon peak @ 0.97 MeV
575    return edep; 
576  }
577
578  /// Arrival time of the photons at side A
579  pub fn get_time_a(&self) -> f32 {
580    self.time_a.to_f32()
581  }
582
583  /// Arrival time of the photons at side B
584  pub fn get_time_b(&self) -> f32 {
585    self.time_b.to_f32()
586  }
587  
588  pub fn get_tot_low_a(&self) -> f32 {
589    self.tot_low_a.to_f32()
590  }
591
592  pub fn get_tot_low_b(&self) -> f32 {
593    self.tot_low_b.to_f32()
594  }
595
596  pub fn get_tot_high_a(&self) -> f32 {
597    self.tot_high_a.to_f32()
598  }
599
600  pub fn get_tot_high_b(&self) -> f32 {
601    self.tot_high_b.to_f32()
602  }
603
604  pub fn get_tot_slp_low_a(&self) -> f32 {
605    self.tot_slp_low_a.to_f32()
606  }
607
608  pub fn get_tot_slp_low_b(&self) -> f32 {
609    self.tot_slp_low_b.to_f32()
610  }
611
612  pub fn get_tot_slp_high_a(&self) -> f32 {
613    self.tot_slp_high_a.to_f32()
614  }
615  pub fn get_tot_slp_high_b(&self) -> f32 {
616    self.tot_slp_high_b.to_f32()
617  }
618  pub fn get_peak_a(&self) -> f32 {
619    self.peak_a.to_f32()
620  }
621  
622  pub fn get_peak_b(&self) -> f32 {
623    self.peak_b.to_f32()
624  }
625  
626  pub fn get_charge_a(&self) -> f32 {
627    self.charge_a.to_f32()
628  }
629  
630  pub fn get_charge_b(&self) -> f32 {
631    self.charge_b.to_f32()
632  }
633  
634  pub fn get_bl_a(&self) -> f32 {
635    self.baseline_a.to_f32()
636  }
637  
638  pub fn get_bl_b(&self) -> f32 {
639    self.baseline_b.to_f32()
640  }
641  
642  pub fn get_bl_a_rms(&self) -> f32 {
643    self.baseline_a_rms.to_f32()
644  }
645  
646  pub fn get_bl_b_rms(&self) -> f32 {
647    self.baseline_b_rms.to_f32()
648  }
649
650
651  /// Get the (official) paddle id
652  ///
653  /// Convert the paddle end id following 
654  /// the convention
655  ///
656  /// A-side : paddle id + 1000
657  /// B-side : paddle id + 2000
658  ///
659  /// FIXME - maybe return Result?
660  //#[deprecated(since="0.10", note="We are not using a paddle end id anymore")]
661  pub fn get_pid(paddle_end_id : u16) -> u8 {
662    if paddle_end_id < 1000 {
663      return 0;
664    }
665    if paddle_end_id > 2000 {
666      return (paddle_end_id - 2000) as u8;
667    }
668    if paddle_end_id < 2000 {
669      return (paddle_end_id - 1000) as u8;
670    }
671    return 0;
672  }
673
674
675
676  pub fn get_phase_rollovers(&self) -> i16 {
677    let mut phase = self.phase.to_f32();
678    let mut ro = 0i16;
679    while phase < PI/2.0 {
680      phase += PI/2.0;
681      ro += 1;
682    }
683    while phase > PI/2.0 {
684      phase -= PI/2.0;
685      ro -= 1;
686    }
687    ro
688  }
689  
690
691}
692
693#[cfg(feature="database")]
694impl TofHit {
695  pub fn set_paddle(&mut self, paddle : &TofPaddle) {
696    self.coax_cable_time = paddle.coax_cable_time;
697    self.hart_cable_time = paddle.harting_cable_time;
698    self.paddle_len = paddle.length * 10.0; // stupid units!
699    let pr          = paddle.principal();
700    //println!("Principal {:?}", pr);
701    let rel_pos     = self.get_pos();
702    let pos         = (paddle.global_pos_x_l0_A*10.0 + pr.0*rel_pos,
703                       paddle.global_pos_y_l0_A*10.0 + pr.1*rel_pos,
704                       paddle.global_pos_z_l0_A*10.0 + pr.2*rel_pos);
705    self.x          = pos.0;
706    self.y          = pos.1;
707    self.z          = pos.2;
708  }
709}
710
711
712// Implementation of traits 
713//-------------------------
714
715impl Default for TofHit {
716  fn default() -> Self {
717    Self::new()
718  }
719}
720
721impl fmt::Display for TofHit {
722  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
723    let mut paddle_info = String::from("");
724    if self.paddle_len == 0.0 {
725      paddle_info = String::from("NOT SET!");
726    }
727    write!(f, "<TofHit (version : {}):
728  Paddle ID       {}
729  Peak:
730    LE Time A/B   {:.2} {:.2}   
731    Height  A/B   {:.2} {:.2}
732    Charge  A/B   {:.2} {:.2}
733  ** time over threshold information
734    Lo TOT A/B    {:.2} {:.2}
735    Hi TOT A/B    {:.2} {:.2}
736    Lo Slope A/B  {:.2} {:.2}
737    Hi Slope A/B  {:.2} {:.2}
738  ** paddle {} ** 
739    Length        {:.2}
740    Timing offset {:.2} (ns)
741    Coax cbl time {:.2}
742    Hart cbl time {:.2}
743  ** reconstructed interaction
744    energy_dep    {:.2}   
745    edep_att      {:.2}
746    pos_across    {:.2}   
747    t0            {:.2}  
748    x, y, z       {:.2} {:.2} {:.2}
749  ** V1 variables
750    phase (ch9)   {:.4}
751      n phs ro    {}
752    baseline A/B  {:.2} {:.2}
753    bl. RMS  A/B  {:.2} {:.2}>",
754            self.version,
755            self.paddle_id,
756            self.get_time_a(),
757            self.get_time_b(),
758            self.get_peak_a(),
759            self.get_peak_b(),
760            self.get_charge_a(),
761            self.get_charge_b(),
762            self.get_tot_low_a(),
763            self.get_tot_low_b(),
764            self.get_tot_high_a(),
765            self.get_tot_high_b(),
766            self.get_tot_slp_low_a(),
767            self.get_tot_slp_low_b(),
768            self.get_tot_slp_high_a(),
769            self.get_tot_slp_high_b(),
770            paddle_info,
771            self.paddle_len,
772            self.timing_offset,
773            self.coax_cable_time,
774            self.hart_cable_time,
775            self.get_edep(),
776            self.get_edep_att(),
777            self.get_pos(),
778            self.get_t0(),
779            self.x,
780            self.y,
781            self.z,
782            self.phase,
783            self.get_phase_rollovers(),
784            self.baseline_a,
785            self.baseline_b,
786            self.baseline_a_rms,
787            self.baseline_b_rms,
788            )
789  }
790}
791
792impl Serialization for TofHit {
793  
794  const HEAD          : u16   = 61680; //0xF0F0)
795  const TAIL          : u16   = 3855;
796  const SIZE          : usize = 44; // size in bytes with HEAD and TAIL
797
798  /// Serialize the packet
799  ///
800  /// Not all fields will get serialized, 
801  /// only the relevant data for the 
802  /// flight computer
803  //
804  /// **A note about protocol versions **
805  /// When we serialize (to_bytestream) we will
806  /// always write the latest version.
807  /// Deserialization can also read previous versions
808  fn to_bytestream(&self) -> Vec<u8> {
809
810    let mut bytestream = Vec::<u8>::with_capacity(Self::SIZE);
811    bytestream.extend_from_slice(&Self::HEAD.to_le_bytes());
812    bytestream.push(self.paddle_id); 
813    bytestream.extend_from_slice(&self.time_a      .to_le_bytes()); 
814    bytestream.extend_from_slice(&self.time_b      .to_le_bytes()); 
815    bytestream.extend_from_slice(&self.peak_a      .to_le_bytes()); 
816    bytestream.extend_from_slice(&self.peak_b      .to_le_bytes()); 
817    bytestream.extend_from_slice(&self.charge_a    .to_le_bytes()); 
818    bytestream.extend_from_slice(&self.charge_b    .to_le_bytes()); 
819    bytestream.extend_from_slice(&self.tot_low_a   .to_le_bytes());
820    bytestream.extend_from_slice(&self.baseline_a   .to_le_bytes());
821    bytestream.extend_from_slice(&self.baseline_a_rms.to_le_bytes());
822    bytestream.extend_from_slice(&self.phase       .to_le_bytes());
823    bytestream.push(self.version.to_u8());
824    bytestream.extend_from_slice(&self.baseline_b  .to_le_bytes());
825    bytestream.extend_from_slice(&self.baseline_b_rms.to_le_bytes());
826    bytestream.extend_from_slice(&self.tot_low_b   .to_le_bytes());
827    bytestream.extend_from_slice(&self.tot_high_a  .to_le_bytes());
828    bytestream.extend_from_slice(&self.tot_high_b  .to_le_bytes());
829    bytestream.extend_from_slice(&self.tot_slp_low_a.to_le_bytes());
830    bytestream.extend_from_slice(&self.tot_slp_low_b.to_le_bytes());
831    bytestream.extend_from_slice(&self.tot_slp_high_a.to_le_bytes());
832    bytestream.extend_from_slice(&self.tot_slp_high_b.to_le_bytes());
833    bytestream.extend_from_slice(&Self::TAIL       .to_le_bytes()); 
834    bytestream
835  }
836
837
838  /// Deserialization
839  ///
840  ///
841  /// # Arguments:
842  ///
843  /// * bytestream : 
844  fn from_bytestream(stream : &Vec<u8>, pos : &mut usize) 
845    -> Result<Self, SerializationError> {
846    let mut pp             = Self::new();
847    let mut version_lt_011 = false;
848    let (size,_,__)        = Self::guess_size(stream, *pos, 28)?;
849    if size == 30 {
850      version_lt_011 = true;
851      let head      = parse_u16(stream, pos);
852      if head != Self::HEAD {
853        error!("Decoding of HEAD failed! Got {} instead!", head);
854        return Err(SerializationError::HeadInvalid);
855      }
856    } else {
857      Self::verify_fixed(stream, pos)?;
858    }
859    // since we passed the above test, the packet
860    // is valid
861    pp.valid          = true;
862    pp.paddle_id      = parse_u8(stream, pos);
863    pp.time_a         = parse_f16(stream, pos);
864    pp.time_b         = parse_f16(stream, pos);
865    pp.peak_a         = parse_f16(stream, pos);
866    pp.peak_b         = parse_f16(stream, pos);
867    pp.charge_a       = parse_f16(stream, pos);
868    pp.charge_b       = parse_f16(stream, pos);
869    pp.tot_low_a      = parse_f16(stream, pos);
870    pp.baseline_a     = parse_f16(stream, pos);
871    pp.baseline_a_rms = parse_f16(stream, pos);
872    let mut phase_vec = Vec::<u8>::new();
873    phase_vec.push(parse_u8(stream, pos));
874    phase_vec.push(parse_u8(stream, pos));
875    pp.phase          = parse_f16(&phase_vec, &mut 0);
876    let version       = ProtocolVersion::from(parse_u8(stream, pos));
877    pp.version        = version;
878    match pp.version {
879      ProtocolVersion::V1 => {
880      }
881      _ => ()
882    }
883    pp.baseline_b      = parse_f16(stream, pos);
884    pp.baseline_b_rms  = parse_f16(stream, pos);
885    if !version_lt_011 {
886      pp.tot_low_b       = parse_f16(stream, pos);
887      pp.tot_high_a      = parse_f16(stream, pos);
888      pp.tot_high_b      = parse_f16(stream, pos);
889      pp.tot_slp_low_a   = parse_f16(stream, pos);
890      pp.tot_slp_low_b   = parse_f16(stream, pos);
891      pp.tot_slp_high_a  = parse_f16(stream, pos);
892      pp.tot_slp_high_b  = parse_f16(stream, pos);
893      *pos += 2; // always have to do this when using verify fixed
894    } else {
895      let tail = parse_u16(stream, pos);
896      if tail != Self::TAIL {
897        error!("Decoding of TAIL failed for version {}! Got {} instead!", version, tail);
898        return Err(SerializationError::TailInvalid);
899      }
900    }
901    Ok(pp)
902  }
903}
904
905#[cfg(feature="random")]
906impl FromRandom for TofHit {
907  fn from_random() -> TofHit {
908    let mut pp  = TofHit::new();
909    let mut rng = rand::rng();
910    // randomly create old/new style hits 
911    let version_lt_011 = rng.random::<bool>();
912        
913    pp.paddle_id       = rng.random_range(0..161);
914    pp.time_a          = f16::from_f32(rng.random::<f32>());
915    pp.time_b          = f16::from_f32(rng.random::<f32>());
916    pp.peak_a          = f16::from_f32(rng.random::<f32>());
917    pp.peak_b          = f16::from_f32(rng.random::<f32>());
918    pp.charge_a        = f16::from_f32(rng.random::<f32>());
919    pp.charge_b        = f16::from_f32(rng.random::<f32>());
920    pp.version         = ProtocolVersion::from(rng.random::<u8>());
921    pp.baseline_a      = f16::from_f32(rng.random::<f32>());
922    pp.baseline_a_rms  = f16::from_f32(rng.random::<f32>());
923    pp.baseline_b      = f16::from_f32(rng.random::<f32>());
924    pp.baseline_b_rms  = f16::from_f32(rng.random::<f32>());
925    pp.phase           = f16::from_f32(rng.random::<f32>());
926    
927    if !version_lt_011 {
928      pp.tot_low_a      = f16::from_f32(rng.random::<f32>());
929      pp.tot_low_b      = f16::from_f32(rng.random::<f32>());
930      pp.tot_high_a     = f16::from_f32(rng.random::<f32>());
931      pp.tot_high_b     = f16::from_f32(rng.random::<f32>()); 
932      pp.tot_slp_low_a  = f16::from_f32(rng.random::<f32>());
933      pp.tot_slp_low_b  = f16::from_f32(rng.random::<f32>());
934      pp.tot_slp_high_a = f16::from_f32(rng.random::<f32>()); 
935      pp.tot_slp_high_b = f16::from_f32(rng.random::<f32>()); 
936    }
937    pp.paddle_len       = 0.0; 
938    pp.coax_cable_time  = 0.0; 
939    pp.hart_cable_time  = 0.0; 
940    pp.x                = 0.0; 
941    pp.y                = 0.0; 
942    pp.z                = 0.0; 
943    pp.event_t0         = 0.0; 
944    pp
945  }
946}
947
948//---------------------------------------------------------------
949
950#[cfg(feature = "random")]
951#[test]
952fn serialization_tofhit() {
953  for _ in 0..100 {
954    let mut pos = 0;
955    let data = TofHit::from_random();
956    let mut test = TofHit::from_bytestream(&data.to_bytestream(),&mut pos).unwrap();
957    // Manually zero these fields, since comparison with nan will fail and 
958    // from_random did not touch these
959    test.paddle_len       = 0.0; 
960    test.coax_cable_time  = 0.0; 
961    test.hart_cable_time  = 0.0; 
962    test.x                = 0.0; 
963    test.y                = 0.0; 
964    test.z                = 0.0; 
965    test.event_t0         = 0.0;
966    assert_eq!(pos, TofHit::SIZE);
967    assert_eq!(data, test);
968  }
969}