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