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