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