Skip to main content

gondola_core/events/
telemetry_event.rs

1//! The TelemetryEvent or former "MergedEvent" is that what gets 
2//! sent over telemetry during flight
3// The following file is part of gaps-online-software and published 
4// under the GPLv3 license
5
6use crate::prelude::*;
7
8/// The basic event type as sent over telemetry, often 
9/// also dubbed as "merged event".
10///
11/// This event type contains tof as well as tracker 
12/// hits, and comes in several flavors, depending 
13/// on the suspected signalness of the online system
14/// determined by a specific algorith.
15/// For that, please see the TelemetryPacketHeader, 
16/// which identifiers these different flavors. 
17///
18/// For most purposes, this event is the to-go data 
19/// to start any analysis.
20#[cfg_attr(feature="pybindings", pyclass)]
21#[derive(Debug, Clone, PartialEq)]
22pub struct TelemetryEvent {
23  pub header              : TelemetryPacketHeader,
24  pub creation_time       : u64,
25  pub event_id            : u32,
26  pub tracker_hits        : Vec<TrackerHit>,
27  pub tracker_oscillators : Vec<u64>,
28  pub tof_event           : TofEvent,
29  //#[cfg(feature = "pybindings")]
30  //pub tof_event: pyo3::Py<TofEvent>,
31  //#[cfg(not(feature = "pybindings"))]
32  //pub tof_event           : TofEvent,
33  pub raw_data            : Vec<u8>,
34  pub flags0              : u8,
35  pub flags1              : u8,
36  pub version             : u8,
37  pub osc_flags           : u8,
38  pub oscillator_idx      : Vec<u8>,
39  // this is for re-merging and does not get serialized to disk 
40  pub expected_tr_hits    : u8,
41}
42
43impl TelemetryEvent {
44
45  pub fn new() -> Self {
46    let mut tracker_oscillators = Vec::<u64>::new();
47    for _ in 0..10 {
48      tracker_oscillators.push(0);
49    }
50    Self {
51      header              : TelemetryPacketHeader::new(),
52      creation_time       : 0,
53      event_id            : 0,
54      tracker_hits        : Vec::<TrackerHit>::new(),
55      tracker_oscillators : tracker_oscillators,
56      tof_event           : TofEvent::new(),
57      raw_data            : Vec::<u8>::new(),
58      flags0              : 0,
59      flags1              : 1,
60      version             : 0, 
61      osc_flags           : 0,
62      oscillator_idx      : Vec::<u8>::new(),
63      expected_tr_hits    : 0,
64    }
65  }
66
67  /// Create a TelemetryPacket from the 
68  /// TelemetryEvent.
69  ///
70  /// CAVEAT: Currently the checksum does 
71  /// not get re-evaluated
72  pub fn pack(&self) -> TelemetryPacket {
73    let mut tp = TelemetryPacket::new();
74    // this is bad, but currently I am not 
75    // sure how to make this better
76    tp.header  = self.header.clone();
77    tp.payload = self.to_bytestream();
78    tp
79  }
80
81  pub fn calibrate_trk_hits(&mut self, cali : &TrackerOfflineCalibration) 
82    -> Result<(), CalibrationError> {
83    cali.mask_hits(&mut self.tracker_hits);
84    cali.calibrate(&mut self.tracker_hits)?;
85    Ok(())
86  }
87
88  /// Restore position information from database
89  #[cfg(feature="database")]
90  pub fn hydrate(&mut self, tof_paddles : &HashMap<u8,TofPaddle>, trk_strips : &HashMap<u32, TrackerStrip>) {
91    self.tof_event.set_paddles(tof_paddles);
92    for h in &mut self.tracker_hits {  
93      h.set_coordinates(trk_strips);
94    }
95  }
96  
97  /// Delete tracker hits (e.g. in case they are supposed to 
98  /// be replaced by packet type 80 tracker hits)
99  pub fn delete_all_tracker_hits(&mut self) {
100    self.tracker_hits.clear();
101  }
102
103  /// Add tracker hits (e.g. hits from packet type 80)
104  pub fn add_tracker_hits(&mut self, hits:  &Vec<TrackerHit>) {
105    self.tracker_hits.extend_from_slice(hits);
106    // for each tracker hits, the packet length extends by 4 bytes 
107    self.header.length += (4*hits.len()) as u16;
108  }
109
110  #[cfg(feature="database")]
111  pub fn mask_strips(&mut self, masks : &HashMap<u32, TrackerStripMask>) {
112    let mut clean_hits = Vec::<TrackerHit>::with_capacity(self.tracker_hits.len());
113    for h in &self.tracker_hits {
114      if !masks.contains_key(&h.get_stripid()) {
115        warn!("We don't have a mask information for strip id {}", h.get_stripid());
116        continue;
117      }
118      if masks[&h.get_stripid()].active {
119        clean_hits.push(h.clone());
120      }
121    }
122    self.tracker_hits = clean_hits;
123  }
124
125
126  ///// Applies a cut based on adc values
127  //#[cfg(feature="database")]
128  //pub fn apply_signal_cut(&mut self, cut : f32, pedestals : &HashMap<u32, TrackerStripPedestal>) {
129  //  let mut clean_hits = Vec::<TrackerHit>::with_capacity(self.tracker_hits.len());
130  //  for h in &self.tracker_hits {
131  //    if !pedestals.contains_key(&h.get_stripid()) {
132  //      warn!("We don't have pedestal information for strip id {}", h.get_stripid());
133  //      continue;
134  //    }
135  //    let ped = &pedestals[&h.get_stripid()];
136  //    if (h.adc as f32) - ped.pedestal_mean > (cut * ped.pedestal_sigma){
137  //      clean_hits.push(h.clone());
138  //    }
139  //  }
140  //  self.tracker_hits = clean_hits;
141  //}
142 
143  ///// Calculate the absolute common noise (adc)
144  //#[cfg(feature="database")]
145  //pub fn cmn_noise(hit       : &TrackerHit, 
146  //                 pedestals : &HashMap<u32, TrackerStripPedestal>,
147  //                 cmn_noise : &HashMap<u32, TrackerStripCmnNoise>) -> Option<f32> {
148  //  let stripid = hit.get_stripid();
149  //  if !cmn_noise.contains_key(&stripid) {
150  //    warn!("We don't have pedestal information for strip id {}", stripid);
151  //    return None;
152  //  }
153  //  if !pedestals.contains_key(&stripid) {
154  //    warn!("We don't have pedestal information for strip id {}", stripid);
155  //    return None;
156  //  }
157  //  let mut cmn_level = 0.0f32;
158  //  let adc_ped_sub   = hit.adc as f32 - pedestals[&stripid].pedestal_mean; 
159  //  if adc_ped_sub < 400.0 {
160  //    cmn_level = cmn_noise[&stripid].common_level(hit.adc as f32);
161  //  }
162  //  return Some(adc_ped_sub - cmn_noise[&stripid].gain * cmn_level);
163  //}
164
165  ///// Calculate the energy deposition
166  //#[cfg(feature="database")]
167  //pub fn get_trk_energy(adc : f32, tf : &TrackerStripTransferFunction) -> f32 {
168  //  let mut energy = 0.0f32;
169  //  let mut adc_m  = adc; 
170  //  if adc_m <= 0.0 {
171  //    return energy;
172  //  }
173  //  if adc_m > 1600.0 {
174  //    adc_m = 1600.0;
175  //  }
176  //  #[allow(non_snake_case)]
177  //  let mV2keV  = 0.841f32;
178  //  // the max and min range are basically defined by the 
179  //  // polynominal [0-1600]
180  //  let voltage = tf.transfer_fn(adc_m);
181  //  println!("voltage {}", voltage);
182  //  energy  = voltage*mV2keV;
183  //  energy /= 1000.0;
184  //  println!("adc : {} , energy {}", adc_m, energy);
185  //  return energy;
186  //}
187
188  //#[cfg(feature="database")]
189  //pub fn calibrate_tracker(&mut self, 
190  //                         remove_cmn_noise : bool,
191  //                         pedestals        : &HashMap<u32, TrackerStripPedestal>,
192  //                         transfer_fn      : &HashMap<u32, TrackerStripTransferFunction>,
193  //                         cmn_noise        : &HashMap<u32, TrackerStripCmnNoise>) {
194  //  //println!("Will remove CMN noise {}", remove_cmn_noise);
195  //  for h in &mut self.tracker_hits {
196  //    let stripid = h.get_stripid();
197  //    //println!("Running TRK calibration for strip {}", stripid);
198  //    if !pedestals.contains_key(&stripid) {
199  //      warn!("Pedestal map does not contain strip {}. Will not calculate energy!", stripid);
200  //      continue;
201  //    }
202  //    if !transfer_fn.contains_key(&stripid) {
203  //      warn!("Transfer fn for strip {} not available. Will not calculate energy!", stripid);
204  //      continue;
205  //    }
206  //    let adc_no_ped = h.adc as f32 - pedestals[&stripid].pedestal_mean;
207  //    //println!("ADC NO PED {}", adc_no_ped);
208  //    if remove_cmn_noise {
209  //      match Self::cmn_noise(&h, pedestals, cmn_noise) { 
210  //        None => {
211  //          h.energy = Self::get_trk_energy(adc_no_ped, &transfer_fn[&stripid]); 
212  //        }
213  //        Some(cmn) => {
214  //          h.energy = Self::get_trk_energy(cmn, &transfer_fn[&stripid]);
215  //        }
216  //      }    
217  //    } else {
218  //      h.energy = Self::get_trk_energy(adc_no_ped, &transfer_fn[&stripid]);
219  //    }
220  //  }
221  //}
222}
223
224
225impl TelemetryPackable for TelemetryEvent {
226  // trivial for TelemetryEvent, since the default 
227  // already contains the packet types
228}
229
230impl Serialization for TelemetryEvent {
231  
232  fn from_bytestream(stream : &Vec<u8>,
233                     pos    : &mut usize)
234    -> Result<Self, SerializationError> {
235    let mut me       = Self::new();
236    let version      = parse_u8(stream, pos);
237    me.version       = version;
238    me.flags0        = parse_u8(stream, pos);
239    // skip a bunch of Alex newly implemented things
240    // FIXME
241    if version == 0 {
242      me.flags1      = parse_u8(stream, pos);
243    } else {
244      *pos += 8;
245    }
246
247    me.event_id       = parse_u32(stream, pos);
248    //println!("EVENT ID {}", me.event_id);
249    let _tof_delim    = parse_u8(stream, pos);
250    //println!("TOF delim : {}", _tof_delim);
251    if stream.len() <= *pos + 2 {
252      error!("Not able to parse merged event!");
253      return Err(SerializationError::StreamTooShort);
254    }
255    let num_tof_bytes = parse_u16(stream, pos) as usize;
256    //println!("Num TOF bytes : {}", num_tof_bytes);
257    if stream.len() < *pos+num_tof_bytes {
258      error!("Not enough bytes for TOF packet with {} bytes! Only {} bytes remaining in input", num_tof_bytes, stream.len() - *pos);
259      return Err(SerializationError::StreamTooShort); 
260    }
261    let pos_before = *pos;
262    if num_tof_bytes != 0 {
263      let tof_pack   = TofPacket::from_bytestream(stream, pos)?;
264      let ts         = tof_pack.unpack::<TofEvent>()?;
265    // sanity check - is tofpacket as long as num_tof_bytes lets us believe?
266      me.tof_event = ts;
267    }
268    if pos_before + num_tof_bytes != *pos {
269      error!("Byte misalignment. Expected {num_tof_bytes}, got {pos} - {pos_before}"); 
270      return Err(SerializationError::WrongByteSize);
271    }
272    let trk_delim    = parse_u8(stream, pos);
273
274    //println!("TRK delim {}", trk_delim);
275    if trk_delim != 0xbb {
276      return Err(SerializationError::HeadInvalid);
277    }
278    if version == 1 {
279      let num_trk_hits = parse_u16(stream, pos);
280      if (*pos + (num_trk_hits as usize)*4 ) > stream.len() {
281        let expected_s = *pos + (num_trk_hits as usize)*4 - stream.len();
282        let actual = stream.len() - *pos;
283        error!("We expect {} more bytes, but see only {}", expected_s, actual);
284        error!("Not enough bytes ({}) for {} tracker hits!", stream.len(), num_trk_hits);
285        //println!("{}", &me);
286        return Err(SerializationError::StreamTooShort);
287      }
288      for _ in 0..num_trk_hits { 
289        let mut hit  = TrackerHit::new();
290        let strip_id = parse_u16(stream, pos);
291        let adc      = parse_u16(stream, pos);
292        hit.channel  = (strip_id & 0b11111) as u8;
293        hit.module   = ((strip_id >> 5) & 0b111) as u8;
294        hit.row      = ((strip_id >> 8) & 0b111) as u8;
295        hit.layer    = ((strip_id >> 11) & 0b1111) as u8;
296        hit.adc      = adc;
297        me.tracker_hits.push(hit);
298      }
299      // oscillators
300      let oscillators_delimiter = parse_u8(stream, pos);
301      if oscillators_delimiter != 0xcc {
302        return Err(SerializationError::HeadInvalid);
303      }
304      me.osc_flags = parse_u8(stream, pos);
305      let mut oscillator_idx = Vec::<u8>::new();
306      for j in 0..8 {
307        if (me.osc_flags >> j & 0b1) > 0 {
308          oscillator_idx.push(j)
309        }
310      }
311      if (*pos + oscillator_idx.len()*6) > stream.len() {
312        error!("Not enough bytes to parse tracker oscillators!");
313        return Err(SerializationError::StreamTooShort);
314      }
315      for idx in oscillator_idx.iter() {
316        let lower = parse_u32(stream, pos);
317        let upper = parse_u16(stream, pos);
318        let osc : u64 = (upper as u64) << 32 | (lower as u64);
319        //println!("FROM: IDX {}, LOWER {}, UPPER {}, OSC {}", idx, lower, upper, osc);
320        me.tracker_oscillators[*idx as usize] = osc;
321      }
322      me.oscillator_idx = oscillator_idx;
323    } else if version == 0 {
324      error!("Unsupported {version}!");
325      return Err(SerializationError::UnsupportedVersion);
326    } else {
327      error!("Unsuported version {version}!");
328      return Err(SerializationError::UnsupportedVersion);
329    } 
330    me.expected_tr_hits = me.tracker_hits.len() as u8;
331    Ok(me)
332  }
333  
334  fn to_bytestream(&self) -> Vec<u8> {
335    let mut stream = Vec::<u8>::new();
336    stream.push(self.version);
337    stream.push(self.flags0);
338    if self.version == 0 {
339      stream.push(self.flags1);
340    } else {
341      stream.extend_from_slice(&[0u8;8]);
342    }
343    stream.extend_from_slice(&self.event_id.to_le_bytes());
344    stream.push(0xaa); // tof delimiter
345    let tof           = self.tof_event.pack();
346    let tof_bytes     = tof.to_bytestream();
347    let num_tof_bytes = tof_bytes.len() as u16;
348    debug!("Will write {} bytes for tef event to stream!", num_tof_bytes);
349    stream.extend_from_slice(&num_tof_bytes.to_le_bytes());
350    stream.extend_from_slice(&tof_bytes);
351    stream.push(0xbb);
352    stream.extend_from_slice(&(self.tracker_hits.len() as u16).to_le_bytes());
353    //println!("Will add bytes for {} tracker hits!", self.tracker_hits.len());
354    for h in &self.tracker_hits { 
355      let mut strip_id: u16 = 0;
356      // Shift each value to its respective position and OR them together
357      strip_id |= (h.channel as u16) & 0b11111;       // Bits 0-4
358      strip_id |= ((h.module as u16) & 0b111) << 5;   // Bits 5-7
359      strip_id |= ((h.row as u16) & 0b111) << 8;      // Bits 8-10
360      strip_id |= ((h.layer as u16) & 0b1111) << 11; 
361      stream.extend_from_slice(&strip_id.to_le_bytes());
362      stream.extend_from_slice(&h.adc.to_le_bytes());
363    }
364    stream.push(0xcc); // oscillators delimiter
365    stream.push(self.osc_flags);
366    for idx in &self.oscillator_idx { 
367    //for osc in &self.tracker_oscillators {
368      //deconstruct the timestamps    
369      let osc   = self.tracker_oscillators[*idx as usize];
370      let upper = ((osc >> 32) & (u16::MAX as u64)) as u16;
371      let lower = (osc & (u32::MAX as u64)) as u32; 
372      //println!("TO: OSC {}, upper {}, lower {}", osc, upper, lower);
373      stream.extend_from_slice(&lower.to_le_bytes());
374      stream.extend_from_slice(&upper.to_le_bytes());
375    }
376    return stream;
377  }
378}
379
380impl fmt::Display for TelemetryEvent {
381  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
382    let mut repr     = String::from("<TelemetryEvent:");
383    let mut te = self.tof_event.clone();
384    te.calc_gcu_variables();
385    let tof_str  = format!("\n  {}", self.tof_event);
386    let mut good_hits = 0;
387    
388    if self.version == 0 {
389      repr += "\n VERSION 0 NOT SUPPORTED!!";
390    } else if self.version == 1 {
391      for _ in &self.tracker_hits {
392        good_hits += 1;
393      }
394    }
395    repr += &(format!("  {}", self.header));
396    repr += "\n  ** ** ** MERGED  ** ** **";
397    repr += &(format!("\n  version      {}", self.version));
398    repr += &(format!("\n  event ID     {}", self.event_id));  
399    repr += "\n  ** ** ** TOF GCU VARIABLES  ** ** **";
400    repr += &(format!("\n  n_hits_umb   {}", te.n_hits_umb));
401    repr += &(format!("\n  n_hits_cbe   {}", te.n_hits_cbe));
402    repr += &(format!("\n  n_hits_cor   {}", te.n_hits_cor));
403    repr += &(format!("\n  tot_edep_umb {}", te.tot_edep_umb));  
404    repr += &(format!("\n  tot_edep_cbe {}", te.tot_edep_cbe));  
405    repr += &(format!("\n  tot_edep_cor {}", te.tot_edep_cor));  
406    if self.version == 0 {
407      repr += "\n VERSION 0 NOT SUPPORTED!!"; 
408    }
409    repr += "\n  ** ** ** TRACKER ** ** **";
410    if self.version == 0 {
411      repr += "\n VERSION 0 NOT SUPPORTED!!"; 
412    } else if self.version == 1 {
413      repr += &(format!("\n  Trk oscillators {:?}", self.tracker_oscillators)); 
414    }
415    repr += &(format!("\n  N Good Trk Hits {}", good_hits));
416    repr += &tof_str;
417    write!(f,"{}", repr)
418  }
419}
420
421//----------------------------------------
422
423#[cfg(feature="pybindings")]
424#[pymethods]
425impl TelemetryEvent {
426
427  #[getter]
428  #[pyo3(name="exptected_tracker_hits")]
429  fn excpected_trk_hits_py(&self) -> u8 {
430    self.expected_tr_hits
431  }
432
433  #[getter]
434  #[pyo3(name="has_at_least_expected_trk_hits")]
435  fn has_at_least_expected_trk_hits(&self) -> bool {
436    return self.tracker_hits.len() as u8 >= self.expected_tr_hits
437  }
438
439  #[getter]
440  #[pyo3(name="version")]
441  fn version_py(&self) -> u8 {
442    self.version
443  }
444 
445  #[getter] 
446  #[pyo3(name="flags0")]
447  fn flags0_py(&self) -> u8 {
448    self.flags0
449  }
450  
451  #[getter] 
452  #[pyo3(name="flags1")]
453  fn flags1_py(&self) -> u8 {
454    self.flags1
455  }
456
457  //#[staticmethod]
458  //#[pyo3(name = "get_trk_energy")]
459  //fn get_trk_energy_py(adc : f32, tf : &TrackerStripTransferFunction) -> f32 {
460  //  Self::get_trk_energy(adc, tf)
461  //}
462
463  #[getter]
464  fn get_header(&self) -> TelemetryPacketHeader {
465    self.header
466  }
467
468  #[getter]
469  fn tracker(&self) -> PyResult<Vec<TrackerHit>> {
470    Ok(self.tracker_hits.clone())
471  }
472
473  #[getter]
474  fn get_event_id(&self) -> u32 {
475    self.event_id
476  }
477  
478  /// Remove all TOF hits from the event's hit series which 
479  /// do NOT obey causality. that is where the timings
480  /// measured at ends A and B can not be correlated
481  /// by the assumed speed of light in the paddle
482  #[pyo3(name="tof_remove_non_causal_hits")]
483  fn tof_remove_non_causal_hits_py(&mut self) -> Vec<u16> {
484    // return Vec<u16> here so that python does not 
485    // interpret it as a byte
486    let mut pids = Vec::<u16>::new();
487    for pid in self.tof_event.remove_non_causal_hits() {
488      pids.push(pid as u16);
489    }
490    pids
491  }
492  
493  /// Remove TOF hits from the hitseries which can not 
494  /// be caused by the same particle, which means 
495  /// that for these two specific hits beta with 
496  /// respect to the first hit in the event is 
497  /// larger than one
498  /// That this works, first hits need to be 
499  /// "normalized" by calling normalize_hit_times
500  #[pyo3(name="tof_lightspeed_cleaning")]
501  pub fn tof_lightspeed_cleaning_py(&mut self, t_err : f32) -> (Vec<u16>, Vec<f32>) {
502    // return Vec<u16> here so that python does not 
503    // interpret it as a byte
504    let mut pids = Vec::<u16>::new();
505    let (pids_rm, twindows) = self.tof_event.lightspeed_cleaning(t_err);
506    for pid in pids_rm {
507      pids.push(pid as u16);
508    }
509    (pids, twindows)
510  }
511 
512  /// Set per-paddle timing constant offsets 
513  /// for the TOF
514  ///
515  /// # Arguments:
516  ///   * timing_offsetes : A map of paddle id to timin
517  ///                       timing offset constant
518  #[pyo3(name="set_tof_timing_offsets")]
519  pub fn set_tof_timing_offsets_py(&mut self, timing_offsets : HashMap<u8, f32>) {
520    self.tof_event.set_timing_offsets(&timing_offsets);
521  }
522  
523  /// Normalize the hit times so that the first 
524  /// hit is at 0. This includes application 
525  /// of the phase correction
526  #[pyo3(name="tof_normalize_hit_times")]
527  pub fn tof_normalize_hit_times_py(&mut self) {
528    self.tof_event.normalize_hit_times();
529  }
530
531  /// Returns a COPY of the TofEvent associated 
532  /// with this event id
533  #[getter]
534  fn get_tof(&self) -> PyResult<TofEvent> {
535    Ok(self.tof_event.clone())
536  }
537  //#[getter]
538  //fn get_tof(&self, py: Python<'_>) -> Py<TofEvent> {
539  //    // This returns a reference-counted pointer to the same object
540  //    // Python can now call .timestamp = 123 on it
541  //  self.tof_event.clone_ref(py)
542  //}
543
544 // #[getter]``
545 // fn get_tof<'_py>(&self, py: Python<'_py>) -> PyResult<Bound<'_py, PyArray1<f32>>> {
546 // fn get_tof(slf: Bound<'_, Self>) -> PyResult<Bound<'_, TofEvent>> {
547      // 1. Borrow the parent (slf)
548      // 2. Map the borrow to the internal field
549      // 3. Return a Bound that points to that specific memory
550      
551  //    let py = slf.py();
552  //    let py_ref = slf.borrow_mut(); // This is a PyRef<'_, MyLibrary>
553  //    
554  //    // Use PyRef::map to project the reference to the internal field
555  //    // In PyO3 0.23+, this is often used via 'into_bound'
556  //    //let mapped = pyo3::PyRef::map(py_ref, |s| &s.tof_event);
557  //    
558  //    //Ok(mapped.into_bound(py))
559  //    Ok(py_ref)
560  //}
561
562  #[getter]
563  fn tracker_pointcloud(&self) -> Vec<(f32, f32, f32, f32, f32)> {
564    let mut pts = Vec::<(f32,f32,f32,f32,f32)>::new();
565    for h in &self.tracker_hits {
566      // uses adc
567      // FIXME - factor 10!
568      let pt = (10.0*h.x, 10.0*h.y, 10.0*h.z, f32::NAN, h.adc as f32);
569      pts.push(pt);
570    }
571    pts
572  }
573  
574  /// Add tracker hits to the merged event (e.g. with hits from packet type 80
575  #[pyo3(name="add_tracker_hits")]
576  pub fn add_tracker_hits_py(&mut self, hits:  Vec<TrackerHit>) {
577    // FIXME - use the above function here! 
578    self.tracker_hits.extend_from_slice(&hits);
579    // for each tracker hits, the packet length extends by 4 bytes 
580    self.header.length += (4*hits.len()) as u16;
581  }
582  
583  /// Delete tracker hits (e.g. in case they are supposed to 
584  /// be replaced by packet type 80 tracker hits)
585  #[pyo3(name="delete_all_tracker_hits")]
586  pub fn delete_all_tracker_hits_py(&mut self) {
587    self.tracker_hits.clear();
588  }
589
590  /// Populate a merged event from a TelemetryPacket.
591  ///
592  /// Telemetry packet type should be 90 (MergedEvent)
593  #[staticmethod]
594  fn from_telemetrypacket(packet : TelemetryPacket) -> PyResult<Self> {
595    match Self::from_bytestream(&packet.payload, &mut 0) {
596      Ok(mut event) => {
597        event.header = packet.header.clone();
598        #[cfg(feature="database")]
599        event.hydrate(&packet.tof_paddles, &packet.trk_strips);
600        return Ok(event);
601      }
602      Err(err) => {
603        return Err(PyValueError::new_err(err.to_string()));
604      }  
605    }
606  }
607
608  #[pyo3(name="pack")]
609  fn pack_py(&self) -> TelemetryPacket {
610    self.pack()
611  }
612
613  #[pyo3(name="calibrate_trk_hits")]
614  fn calibrate_trk_hits_py(&mut self, cali : Bound<'_, TrackerOfflineCalibration> ) -> PyResult<()> {
615    let cali_ref = cali.borrow();
616    self.calibrate_trk_hits(&*cali_ref)?;
617    Ok(())
618  }
619
620//  #[cfg(feature="database")]
621//  #[pyo3(name="mask_strips")]
622//  pub fn mask_strips_py(&mut self, masks : &HashMap<u32, TrackerStripMask>) {
623//  }
624//
625//  #[cfg(feature="database")]
626//  #[pyo3(name="remove_cmn_noise")]
627//  pub fn remove_cmn_noise_py(&mut self, cmn_noise : &HashMap<u32, TrackerStripCmnNoise>) {
628//  }
629//
630//  #[cfg(feature="database")]
631//  #[pyo3(name="calibrate_tracker")]
632//  pub fn calibrate_tracker_py(&mut self, 
633//                              pedestals   : &HashMap<u32, TrackerStripPedestal>,
634//                              transfer_fn : &HashMap<u32, TrackerStripTransferFunction>) {
635//  }
636}
637
638#[cfg(feature="random")]
639impl FromRandom for TelemetryEvent {
640
641  fn from_random() -> Self {
642    let mut rng    = rand::rng();
643    let mut ev     = Self::new();
644    ev.header      = TelemetryPacketHeader::from_random();
645    ev.tof_event   = TofEvent::from_random();
646    //let n_trk_hits = rng.random::<u8>();
647    let n_trk_hits = 1u8;
648    for _ in 0..n_trk_hits {
649      let mut h = TrackerHit::from_random();
650      h.oscillator = 0;
651      h.asic_event_code = 0;
652      ev.tracker_hits.push(h);
653    }
654    //ev.creation_time      = rng.random::<u64>();
655    ev.creation_time      = 0;
656    ev.event_id           = rng.random::<u32>();
657    ev.flags0             = 3;
658    ev.flags1             = 1;
659    ev.version            = 1;
660    // tracker oscillators are one by layer - set 
661    // layers active by random choice 
662    for idx in 0..10usize {
663      let active = rng.random::<bool>();
664      if idx < 8 && active {
665        ev.osc_flags = ev.osc_flags | (0b1 << idx);
666        let osc  = rng.random::<u64>() & 0x0000FFFFFFFFFFFF;
667        ev.tracker_oscillators[idx] = osc;
668        ev.oscillator_idx.push(idx as u8);
669        //ev.tracker_oscillators[idx] = u32::MAX as u64 + 1;
670      }
671    }
672    return ev;
673  }
674}
675
676#[test]
677#[cfg(feature="random")]
678fn serialize_deserialize_telemetryevent() {
679  for _ in 0..10 {
680    let mut ev        = TelemetryEvent::from_random();  
681    let bytes         = ev.to_bytestream();
682    let mut test      = TelemetryEvent::from_bytestream(&bytes, &mut 0).unwrap();
683    let creation_time = Instant::now();
684    ev.tof_event.creation_time   = creation_time;
685    test.tof_event.creation_time = creation_time;
686    ev.tof_event.rb_events.clear();
687    test.tof_event.rb_events.clear();
688    for h in &mut ev.tof_event.hits {
689      h.coax_cable_time = 0.0;
690      h.hart_cable_time = 0.0;
691      h.event_t0 = 0.0;
692      h.paddle_len = 0.0;
693      h.x = 0.0; 
694      h.y = 0.0; 
695      h.z = 0.0; 
696    }
697    for h in &mut test.tof_event.hits {
698      h.coax_cable_time = 0.0;
699      h.hart_cable_time = 0.0;
700      h.event_t0 = 0.0;
701      h.paddle_len = 0.0;
702      h.x = 0.0; 
703      h.y = 0.0; 
704      h.z = 0.0; 
705    }
706    // the header will not be the same, since it is not  
707    // deserialized with from_bytestream, but just attached 
708    assert_eq!(ev.creation_time      , test.creation_time); 
709    assert_eq!(ev.event_id           , test.event_id); 
710    assert_eq!(ev.osc_flags          , test.osc_flags); 
711    assert_eq!(ev.tracker_oscillators, test.tracker_oscillators); 
712    assert_eq!(ev.raw_data           , test.raw_data); 
713    assert_eq!(ev.flags0             , test.flags0); 
714    assert_eq!(ev.flags1             , test.flags1); 
715    assert_eq!(ev.version            , test.version); 
716    assert_eq!(ev.oscillator_idx     , test.oscillator_idx); 
717    assert_eq!(ev.tracker_hits       , test.tracker_hits); 
718    assert_eq!(ev.tof_event          , test.tof_event); 
719    //assert_eq!(ev, test);
720  }
721}
722
723#[cfg(feature="pybindings")]
724pythonize!(TelemetryEvent);
725