Skip to main content

gondola_core/
events.rs

1// This file is part of gaps-online-software and published 
2// under the GPLv3 license
3
4pub mod tof_hit;
5pub use tof_hit::TofHit;
6
7pub mod rb_waveform;
8pub use rb_waveform::RBWaveform;
9
10pub mod rb_event_header;
11pub use rb_event_header::RBEventHeader;
12
13pub mod tof_event;
14pub use tof_event::TofEvent;
15
16pub mod rb_event;
17pub use rb_event::{
18  RBEvent,
19  unpack_traces
20};
21
22pub mod tracker_hit;
23pub use tracker_hit::TrackerHit;
24
25pub mod tracker_daq_event;
26pub use tracker_daq_event::TrackerDAQEvent;
27
28pub mod tracker_daq_event_packet;
29pub use tracker_daq_event_packet::TrackerDAQEventPacket;
30
31pub mod telemetry_event;
32pub use telemetry_event::TelemetryEvent;
33
34pub mod mc_hit;
35pub use mc_hit::McHit;
36
37pub mod mc_track;
38pub use mc_track::McTrack;
39
40pub mod mc_event;
41pub use mc_event::McEvent;
42
43pub mod mc_tree;
44pub use mc_tree::McTree;
45
46use std::fmt;
47
48use strum_macros::{
49  AsRefStr,
50  FromRepr,
51  EnumIter
52};
53// needed for enum macro
54// FIXME 
55#[cfg(feature="random")]
56use strum::IntoEnumIterator;
57use crate::expand_and_test_enum;
58
59#[cfg(feature="pybindings")]
60use pyo3::prelude::*;
61
62#[cfg(feature="random")]
63use crate::random::FromRandom;
64#[cfg(feature="random")]
65use rand::Rng;
66
67/// mask to decode LTB hit masks
68pub const LTB_CH0 : u16 = 0x3   ;
69/// mask to decode LTB hit masks
70pub const LTB_CH1 : u16 = 0xc   ;
71/// mask to decode LTB hit masks
72pub const LTB_CH2 : u16 = 0x30  ; 
73/// mask to decode LTB hit masks
74pub const LTB_CH3 : u16 = 0xc0  ;
75/// mask to decode LTB hit masks
76pub const LTB_CH4 : u16 = 0x300 ;
77/// mask to decode LTB hit masks
78pub const LTB_CH5 : u16 = 0xc00 ;
79/// mask to decode LTB hit masks
80pub const LTB_CH6 : u16 = 0x3000;
81/// mask to decode LTB hit masks
82pub const LTB_CH7 : u16 = 0xc000;
83/// mask to decode LTB channels from bitmask
84pub const LTB_CHANNELS : [u16;8] = [
85  LTB_CH0,
86  LTB_CH1,
87  LTB_CH2,
88  LTB_CH3,
89  LTB_CH4,
90  LTB_CH5,
91  LTB_CH6,
92  LTB_CH7
93];
94
95/// An array of the channel numbers as they come in pairs on the LTB
96pub const PHYSICAL_CHANNELS : [(u8, u8); 8] = [(1u8,  2u8), (3u8,4u8), (5u8, 6u8), (7u8, 8u8),
97                                               (9u8, 10u8), (11u8,12u8), (13u8, 14u8), (15u8, 16u8)];
98
99
100/// Calculate an unique identifier for 
101/// tracker strips from the position in 
102/// the tracker stack
103///
104/// # Arguments:
105///   * layer   : tracker layer (0-9)
106///   * row     : row in layer  (0-6)
107///   * module  : module in row (0-6)
108///   * channel : channel in module (0-32) 
109///
110#[cfg_attr(feature="pybindings", pyfunction)]
111pub fn strip_id(layer : u8, row :u8, module : u8, channel : u8) -> u32 {
112  channel as u32 + (module as u32)*100 + (row as u32)*10000 + (layer as u32)*100000
113}
114  
115/// Get absolute timestamp as sent by the GPS and 
116/// as seen by the MTB
117#[cfg_attr(feature="pybindings", pyfunction)]
118pub fn mt_event_get_timestamp_abs48(mtb_timestamp : u32, gps_timestamp : u32, tiu_timestamp : u32) -> u64 {
119  let gps = gps_timestamp as u64;
120  let mut timestamp = mtb_timestamp as u64;
121  if timestamp < tiu_timestamp as u64 {
122    // it has wrapped
123    timestamp += u32::MAX as u64 + 1;
124  }
125  let gps_mult = match 100_000_000u64.checked_mul(gps) {
126  //let gps_mult = match 100_000u64.checked_mul(gps) {
127    Some(result) => result,
128    None => {
129        // Handle overflow case here
130        // Example: log an error, return a default value, etc.
131        0 // Example fallback value
132    }
133  };
134
135  let ts = gps_mult + (timestamp - tiu_timestamp as u64);
136  ts
137}
138
139#[derive(Debug, Copy, Clone, PartialEq,FromRepr, AsRefStr, EnumIter)]
140#[repr(u8)]
141#[cfg_attr(feature = "pybindings", pyclass(eq, eq_int))]
142pub enum EventQuality {
143  Unknown        =  0u8,
144  Silver         = 10u8,
145  Gold           = 20u8,
146  Diamond        = 30u8,
147  FourLeafClover = 40u8,
148}
149
150expand_and_test_enum!(EventQuality, test_eventquality_repr);
151
152//--------------------------------------------
153  
154#[derive(Debug, Copy, Clone, PartialEq,FromRepr, AsRefStr, EnumIter)]
155#[repr(u8)]
156#[cfg_attr(feature = "pybindings", pyclass(eq, eq_int))]
157pub enum HitQuality {
158  // hit has not been classified (yet)
159  Unknown           =  0,
160  //$$$$$ Reserved for future use, nothing sets these yet.
161  Mangled           =  1,
162  MissingHit        =  2,
163  //$$$$$
164  LowPedPlusPedRMS  =  3, // abs(ped) > 1 and pedRMS > 2 (jeff's cut)
165  LowRMSgrThree     =  4, // rms > 3 (box2 cut)
166  LowElenaCuts      =  5, // cuts where really the analysis failed.
167  Low               = 10, // general Low status
168
169  MidPedRMS         = 11, // middle triangular region in peak vs rms for coincidence pulses (box1 cut)
170  MidPos            = 12, // hit position too close to a paddle end
171  MidPedRMSandPos   = 13, // MidPedRMS and MidPos both apply
172  MidPosSat         = 14, // position issue on an otherwise-saturated pulse; ped cuts don't apply to saturated pulses
173  MidPosSatReprocess= 15,
174  MidPosReprocess   = 16,
175  Mid               = 20, // general Mid status
176
177  HighSat           = 21, // High, but the pulse is saturated (> 600 mV)
178  High              = 30, // general High status
179  // between mid and high, really just techincally high hits
180  // but could use some sort of recalibration...
181  // like a noisy saturated pulse for example
182  ReprocessHighSat  = 31,
183  ReprocessHigh     = 40,
184  // same as ReprocessHigh, but the pulse is also saturated (tot725 filled)
185}
186
187expand_and_test_enum!(HitQuality, test_hitquality_repr);
188
189//--------------------------------------------
190
191/// The type of trigger which has fired at time of 
192/// recoring a specific event. 
193///
194/// The GAPS main trigger system allows to record 
195/// events based on different conditions
196/// simulataneously. 
197#[derive(Debug, Copy, Clone, PartialEq,FromRepr, AsRefStr, EnumIter, serde::Serialize, serde::Deserialize)]
198#[repr(u8)]
199#[cfg_attr(feature = "pybindings", pyclass(eq, eq_int))]
200pub enum TriggerType {
201  Unknown         = 0u8,
202  /// -> 1-10 "pysics" triggers
203  Any             = 1u8,
204  /// 1 hit on outer (UMB + COR) and inner (CBE) TOF
205  Track           = 2u8,
206  TrackCentral    = 3u8,
207  /// The "default" antiparticle trigger. It requires 
208  /// 8 hits in total, 4 on the inner, 4 on the outer TOF.
209  /// 2 of the hits have to cross
210  /// the second LTB threshold "beta"
211  Gaps            = 4u8,
212  Gaps633         = 5u8, 
213  Gaps422         = 6u8,
214  Gaps211         = 7u8,
215  TrackUmbCentral = 8u8,
216  Gaps1044        = 9u8,
217  /// -> 20+ "Philip's triggers"
218  /// Any paddle HIT in UMB  + any paddle HIT in CUB
219  UmbCube         = 21u8,
220  /// Any paddle HIT in UMB + any paddle HIT in CUB top
221  UmbCubeZ        = 22u8,
222  /// Any paddle HIT in UMB + any paddle hit in COR + any paddle hit in CUB 
223  UmbCorCube      = 23u8,
224  /// Any paddle HIT in COR + any paddle HIT in CUB SIDES
225  CorCubeSide     = 24u8,
226  /// Any paddle hit in UMB + any three paddles HIT in CUB
227  Umb3Cube        = 25u8,
228  /// > 100 -> Debug triggers
229  Poisson         = 100u8,
230  Forced          = 101u8,
231  FixedRate       = 102u8,
232  /// > 200 -> These triggers can not be set, they are merely
233  /// the result of what we read out from the trigger mask of 
234  /// the ltb
235  ConfigurableTrigger = 200u8,
236}
237
238impl TriggerType {
239
240  /// In the serialized data, trigger sources are represented by 2bytes. 
241  /// This will regenerate a vector of trigger sources from these bytes
242  pub fn transcode_trigger_sources(trigger_sources : u16) -> Vec<Self> {
243    let mut t_types    = Vec::<Self>::new();
244    let gaps_trigger   = trigger_sources >> 5 & 0x1 == 1;
245    if gaps_trigger {
246      t_types.push(TriggerType::Gaps);
247    }
248    let any_trigger    = trigger_sources >> 6 & 0x1 == 1;
249    if any_trigger {
250      t_types.push(TriggerType::Any);
251    }
252    let forced_trigger = trigger_sources >> 7 & 0x1 == 1;
253    if forced_trigger {
254      t_types.push(TriggerType::Forced);
255    }
256    let track_trigger  = trigger_sources >> 8 & 0x1 == 1;
257    if track_trigger {
258      t_types.push(TriggerType::Track);
259    }
260    let central_track_trigger
261                       = trigger_sources >> 9 & 0x1 == 1;
262    if central_track_trigger {
263      t_types.push(TriggerType::TrackCentral);
264    }
265    t_types
266  }
267 
268  pub fn to_u8(&self) -> u8 {
269    match self {
270      TriggerType::Unknown => {
271        return 0;
272      }
273      TriggerType::Poisson => {
274        return 100;
275      }
276      TriggerType::Forced => {
277        return 101;
278      }
279      TriggerType::FixedRate => {
280        return 102;
281      }
282      TriggerType::Any => {
283        return 1;
284      }
285      TriggerType::Track => {
286        return 2;
287      }
288      TriggerType::TrackCentral => {
289        return 3;
290      }
291      TriggerType::Gaps => {
292        return 4;
293      }
294      TriggerType::Gaps633 => {
295        return 5;
296      }
297      TriggerType::Gaps422 => {
298        return 6;
299      }
300      TriggerType::Gaps211 => {
301        return 7;
302      }
303      TriggerType::TrackUmbCentral => {
304        return 8;
305      }
306      TriggerType::Gaps1044 => {
307        return 9;
308      }
309      TriggerType::UmbCube => {
310        return 21;
311      }
312      TriggerType::UmbCubeZ => {
313        return 22; 
314      }
315      TriggerType::UmbCorCube => {
316        return 23;
317      }
318      TriggerType::CorCubeSide => {
319        return 24;
320      }
321      TriggerType::Umb3Cube => {
322        return 25;
323      }
324      TriggerType::ConfigurableTrigger => {
325        return 200;  
326      }
327    }
328  }
329}
330
331#[cfg(feature="pybindings")]
332#[pymethods]
333impl TriggerType {
334  #[staticmethod]
335  #[pyo3(name="transcode_trigger_sources")]
336  fn transcode_trigger_sources_py(trigger_sources : u16) -> Vec<Self> {
337    TriggerType::transcode_trigger_sources(trigger_sources)
338  }
339}
340
341expand_and_test_enum!(TriggerType, test_triggertype_repr);
342
343//--------------------------------------------
344
345/// LTB Thresholds as passed on by the MTB
346/// [See also](https://gaps1.astro.ucla.edu/wiki/gaps/images/gaps/5/52/LTB_Data_Format.pdf)
347#[derive(Debug, Copy, Clone, PartialEq,FromRepr, AsRefStr, EnumIter)]
348#[cfg_attr(feature = "pybindings", pyclass(eq, eq_int))]
349#[repr(u8)]
350pub enum LTBThreshold {
351  NoHit = 0u8,
352  /// First threshold, 40mV, about 0.75 minI
353  Hit   = 1u8,
354  /// Second threshold, 32mV (? error in doc ?, about 2.5 minI
355  Beta  = 2u8,
356  /// Third threshold, 375mV about 30 minI
357  Veto  = 3u8,
358  /// Use u8::MAX for Unknown, since 0 is pre-determined for 
359  /// "NoHit, 
360  Unknown = 255u8
361}
362
363expand_and_test_enum!(LTBThreshold, test_ltbthreshold_repr);
364
365//--------------------------------------------
366
367#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq, FromRepr, AsRefStr, EnumIter)]
368#[repr(u8)]
369#[cfg_attr(feature = "pybindings", pyclass(eq, eq_int))]
370pub enum EventStatus {
371  Unknown                = 0u8,
372  CRC32Wrong             = 10u8,
373  TailWrong              = 11u8,
374  ChannelIDWrong         = 12u8,
375  /// one of the channels cells CellSyncError bits 
376  /// has been set (RB)
377  CellSyncErrors         = 13u8,
378  /// one of the channels ChannelSyncError bits 
379  /// has been set (RB)
380  ChnSyncErrors          = 14u8,
381  /// Both of the bits (at least one for the cell sync errors)
382  /// have been set
383  CellAndChnSyncErrors   = 15u8,
384  /// If any of the RBEvents have Sync erros, we flag the tof 
385  /// event summary to indicate there were issues
386  AnyDataMangling        = 16u8,
387  /// RB is missing, but it is expected that is missing
388  /// when we compare the trigger information with the 
389  /// list of known dead rbs
390  KnownDeadRB            = 17u8,
391  IncompleteReadout      = 21u8,
392  /// This can be used if there is a version
393  /// missmatch and we have to hack something
394  IncompatibleData       = 22u8,
395  /// The TofEvent timed out while waiting for more Readoutboards
396  EventTimeOut           = 23u8,
397  /// A RB misses Ch9 data
398  NoChannel9             = 24u8,
399  /// A RBReceives a strange event id 
400  RBEventWacky           = 25u8,
401  GoodNoCRCOrErrBitCheck = 39u8,
402  /// The event status is good, but we did not 
403  /// perform any CRC32 check
404  GoodNoCRCCheck         = 40u8,
405  /// The event is good, but we did not perform
406  /// error checks
407  GoodNoErrBitCheck      = 41u8,
408  Perfect                = 42u8
409}
410
411// in case we have pybindings for this type, 
412// expand it so that it can be used as keys
413// in dictionaries
414#[cfg(feature = "pybindings")]
415#[pymethods]
416impl EventStatus {
417
418  #[getter]
419  fn __hash__(&self) -> usize {
420    (*self as u8) as usize
421  } 
422}
423
424
425expand_and_test_enum!(EventStatus, test_eventstatus_repr);
426
427//--------------------------------------------
428
429/// A generic data type
430///
431/// Describe the purpose of the data. This
432/// is the semantics behind it.
433#[derive(Debug, Copy, Clone, PartialEq,FromRepr, AsRefStr, EnumIter, serde::Deserialize, serde::Serialize)]
434#[cfg_attr(feature = "pybindings", pyclass(eq, eq_int))]
435#[repr(u8)]
436pub enum DataType {
437  Unknown            = 0u8,
438  VoltageCalibration = 10u8,
439  TimingCalibration  = 20u8,
440  Noi                = 30u8,
441  Physics            = 40u8,
442  RBTriggerPeriodic  = 50u8,
443  RBTriggerPoisson   = 60u8,
444  MTBTriggerPoisson  = 70u8,
445  // future extension for different trigger settings!
446}
447
448expand_and_test_enum!(DataType, test_datatype_repr);
449
450//--------------------------------------------
451