gondola_core/events/
tof_hit.rs

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