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