Skip to main content

gondola_core/calibration/
tof.rs

1//! Calibration routines for the GAPS TOF system
2//!
3// This file is part of gaps-online-software and published 
4// under the GPLv3 license
5
6use crate::prelude::*;
7
8/// Roll over the entries from the end of a vector 
9/// to the beginning by a given offset.
10/// 
11/// This is similar to 
12/// <https://numpy.org/doc/2.2/reference/generated/numpy.roll.html>
13///
14/// # Arguments:
15///   * `vec`   : The vector to be rolled over. It will 
16///               be changed in place 
17///   * `offset`: The signed number to shift elements by (can be to 
18///               the left or right)
19pub fn roll<T: Clone>(vec: &mut Vec<T>, offset: isize) {
20  let len = vec.len() as isize;
21  if len <= 1 {
22      return;
23  }
24  let offset = offset % len;
25  if offset == 0 {
26      return;
27  }
28  let split_point = if offset > 0 {
29      len - offset
30  } else {
31      -offset
32  } as usize;
33
34  let mut temp = Vec::with_capacity(len as usize);
35  temp.extend_from_slice(&vec[split_point..]);
36  temp.extend_from_slice(&vec[..split_point]);
37
38  vec.clear();
39  vec.extend_from_slice(&temp);
40}
41
42//-----------------------------------------------
43
44/// Simplified version of spike cleaning 
45///
46/// Taken over from Jamie's python code
47pub fn clean_spikes(trace : &mut Vec<f32>, vcaldone : bool) {
48  //# TODO: make robust (symmetric, doubles, fixed/estimated spike height)
49  let mut thresh : f32 = 360.0;
50  if vcaldone {
51    thresh = 16.0;
52  }
53
54  let mut spf_allch = vec![0usize;1023];
55  let mut spf_sum   = vec![0f32;1024];
56  let tracelen      = trace.len();
57  let spikefilter0 = &trace[0..tracelen-3];
58  let spikefilter1 = &trace[1..tracelen-2];
59  let spikefilter2 = &trace[2..tracelen-1];
60  let spikefilter3 = &trace[3..tracelen];
61  let spf_len      = spikefilter0.len();
62  for k in 0..spf_len {
63    spf_sum[k] += spikefilter1[k] - spikefilter0[k] + spikefilter2[k] - spikefilter3[k];
64  }
65  for k in 0..spf_len {
66    if spf_sum[k] > thresh {
67      spf_allch[k] += 1;
68    }
69  }
70  let mut spikes = Vec::<usize>::new();
71  for k in 0..spf_allch.len() {
72    if spf_allch[k] >= 2 {
73      spikes.push(k);
74    }
75  }
76  for spike in spikes.iter() {
77    let d_v : f32 = (trace[spike+3] - trace[*spike])/3.0;
78    trace[spike+1] = trace[*spike] + d_v;
79    trace[spike+2] = trace[*spike] + 2.0*d_v;
80  }
81}
82
83//-----------------------------------------------
84
85//#[cfg(feature="pybindings")]
86//#[pyfunction]
87//#[pyo3(name="clean_spikes")]
88//pub fn clean_spikes_pyx<'_py>(value : Bound<'_py,PyArray1<f32>>, vcal_done : bool) {
89//  unsafe {
90//    match clean_spikes(value.as_slice().unwrap(), vcal_done) {
91//      Err(err) => {
92//        return Err(PyValueError::new_err(err.to_string()));
93//      }
94//      Ok(max_val) => {
95//        return Ok(max_val);
96//      }
97//    }
98//  }
99//}
100
101
102//-----------------------------------------------
103
104#[derive(Debug, Copy, Clone)]
105#[repr(u8)]
106pub enum Edge {
107  Unknown = 0u8,
108  Rising  = 10u8,
109  Falling = 20u8,
110  Average = 30u8,
111  None    = 40u8
112}
113
114impl fmt::Display for Edge {
115  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
116    let r : &str;
117    match self {
118      Edge::Unknown => {r = "Unknown"} 
119      Edge::Rising  => {r = "Rising"},
120      Edge::Falling => {r = "Falling"},
121      Edge::Average => {r = "Average"},
122      Edge::None    => {r = "None"}
123    }
124    write!(f, "<Edge: {}>", r)
125  }
126}
127
128impl From<u8> for Edge {
129  fn from(value: u8) -> Self {
130    match value {
131      0u8  => Edge::Unknown,
132      10u8 => Edge::Rising,
133      20u8 => Edge::Falling,
134      30u8 => Edge::Average,
135      40u8 => Edge::None,
136      _    => Edge::Unknown
137    }
138  }
139}
140
141#[cfg(feature = "random")]
142impl FromRandom for Edge {
143  
144  fn from_random() -> Self {
145    let choices = [
146      Edge::Rising,
147      Edge::Falling,
148      Edge::Average,
149      Edge::None,
150    ];
151    let mut rng  = rand::rng();
152    let idx = rng.random_range(0..choices.len());
153    choices[idx]
154  }
155}
156
157
158/// smaller packet to bring through the gcu
159#[derive(Debug, Clone, PartialEq)]
160#[cfg_attr(feature="pybindings", pyclass)]
161pub struct RBCalibrationFlightT {
162  pub rb_id      : u8,
163  pub timestamp  : u32, // time the calibration was taken
164  pub tbins      : [[f16;NWORDS];NCHN], // cell width (ns)
165}
166
167//-----------------------------------------------
168
169/// Get the periods of a sine wave
170///
171/// # Arguments:
172///   * dts : some version of tcal data? FIXME
173pub fn get_periods(trace   : &Vec<f32>,
174                   dts     : &Vec<f32>,
175                   nperiod : f32,
176                   nskip   : f32,
177                   edge    : &Edge) -> (Vec<usize>, Vec<f32>) {
178  let mut trace_c = trace.clone();
179  let mut periods = Vec::<f32>::new();
180  if trace_c.len() == 0 {
181    let foo = Vec::<usize>::new();
182    return (foo, periods);
183  }
184  let firstbin : usize = 20;
185  let lastbin = firstbin + (nperiod * (900.0/nperiod).floor()).floor() as usize;
186  //info!("firstbin {} lastbin {}", firstbin, lastbin);
187  let mut vec_for_median = Vec::<f32>::new();
188  for bin in firstbin..lastbin {
189    if trace[bin].is_nan() {
190      continue;
191    }
192    vec_for_median.push(trace[bin]);
193  }
194  let median_val = median(&vec_for_median);
195  debug!("median val {median_val}");
196  for k in 0..trace_c.len() {
197    trace_c[k] -= median_val;
198  }
199  let mut zcs = find_zero_crossings(&trace_c);
200  //trace!("Found {} zero crossings!", zcs.len());
201  let mut zcs_nskip = Vec::<usize>::with_capacity(zcs.len());
202  for k in 0..zcs.len() {
203    if zcs[k] > nskip as usize {
204      zcs_nskip.push(zcs[k]);
205    }
206  }
207  zcs = zcs_nskip;
208  let mut zcs_filter = Vec::<usize>::with_capacity(zcs.len());
209  for k in 0..zcs.len() {
210    match edge {
211      Edge::Rising => {
212        if trace_c[zcs[k]] < 0.0 {
213          zcs_filter.push(zcs[k]);
214        }
215      }
216      Edge::Falling => {
217        // What about the equal case?
218        if trace_c[zcs[k]] > 0.0 {
219          zcs_filter.push(zcs[k]);
220        }
221      },
222      Edge::None => {
223        warn!("Unsure what to do for Edge::None");
224      },
225      Edge::Average => {
226        warn!("Unsure what to do for Edge::Average");
227      },
228      Edge::Unknown => {
229        warn!("Unsure what to do for Edge::Unknown");
230      }
231    }
232  }
233  debug!("Found {} zero crossings!", zcs_filter.len()); 
234  zcs = zcs_filter;
235  if zcs.len() < 3 {
236    return (zcs, periods);
237  }
238 
239  for k in 0..zcs.len() -1 {
240    let zcs_a  = &zcs[k];
241    let zcs_b  = &zcs[k+1];
242    // FIXME - there is an issue with the last
243    // zero crossings
244    if zcs_a + 2 > trace_c.len() || zcs_b + 2 > trace_c.len() {
245      continue;
246    }
247    let tr_a   = &trace_c[*zcs_a..*zcs_a+2];
248    let tr_b   = &trace_c[*zcs_b..*zcs_b+2];
249    let mut period : f32 = 0.0;
250    for n in zcs_a+1..*zcs_b {
251      period += dts[n];
252    }
253    period += dts[*zcs_a]*f32::abs(tr_a[1]/(tr_a[1] - tr_a[0])); // first semi bin
254    period += dts[*zcs_b]*f32::abs(tr_b[0]/(tr_b[1] - tr_b[0])); // first semi bin
255    if period.is_nan() {
256      error!("NAN in period found!");
257      continue;
258    }
259    
260    if f32::abs(*zcs_b as f32 - *zcs_a as f32 - nperiod) > 5.0 {
261      let mut zcs_tmp = Vec::<usize>::new();
262      zcs_tmp.extend_from_slice(&zcs[0..k+1]);
263      zcs = zcs_tmp;
264      break;
265    }
266    trace!("zcs_a, zcs_b, period, nperiod {} {} {} {}", zcs_a, zcs_b, period, nperiod);
267    periods.push(period);
268  }
269  debug!("Calculated {} zero-crossings and {} periods!", zcs.len(), periods.len());
270  (zcs, periods)
271}
272
273//-----------------------------------------------
274
275/// Designed to match np.where(np.diff(np.signbit(trace)))\[0\] 
276/// FIXME -> This needs to be moved to analysis!
277pub fn find_zero_crossings(trace: &Vec<f32>) -> Vec<usize> {
278  let mut zero_crossings = Vec::new();
279  for i in 1..trace.len() {
280    // acccount for the fact that sometimes the second/first point can be 0
281    if (trace[i - 1] > 0.0 && trace[i] < 0.0) || (trace[i - 1] < 0.0 && trace[i] > 0.0) {
282      zero_crossings.push(i - 1);
283    }
284    if i < trace.len() - 1 {
285      if trace[i - 1] > 0.0 && trace[i] == 0.0 && trace[i+1] < 0.0 {
286        zero_crossings.push(1);
287      }
288      if trace[i - 1] < 0.0 && trace[i] == 0.0 && trace[i+1] > 0.0 {
289        zero_crossings.push(i);
290      }
291    }
292  }
293  zero_crossings
294}
295
296//-----------------------------------------------
297
298impl RBCalibrationFlightT {
299  pub fn new() -> Self {
300    Self {
301      rb_id     : 0,
302      timestamp : 0,
303      tbins     : [[f16::from_f32(0.0);NWORDS];NCHN],
304    }
305  }
306}
307
308impl Serialization for RBCalibrationFlightT {
309  const SIZE            : usize = NCHN*NWORDS*2 + 4 + 1; 
310  const HEAD            : u16   = 0xAAAA; // 43690 
311  const TAIL            : u16   = 0x5555; // 21845 
312  
313  fn from_bytestream(bytestream : &Vec<u8>, 
314                     pos        : &mut usize)
315    -> Result<Self, SerializationError> { 
316    let mut rb_cal = Self::new();
317    if parse_u16(bytestream, pos) != Self::HEAD {
318      return Err(SerializationError::HeadInvalid {});
319    }
320    rb_cal.rb_id                = parse_u8(bytestream, pos);
321    rb_cal.timestamp            = parse_u32(bytestream, pos);
322    for ch in 0..NCHN {
323      for k in 0..NWORDS {
324        let value         = parse_f16(bytestream, pos);
325        rb_cal.tbins[ch][k]      = value;
326      }
327    }
328    *pos += 2;
329    Ok(rb_cal)
330  }
331
332  fn to_bytestream(&self) -> Vec<u8> {
333    
334    let mut bs = Vec::<u8>::with_capacity(Self::SIZE);
335    bs.extend_from_slice(&Self::HEAD.to_le_bytes());
336    bs.extend_from_slice(&self.rb_id.to_le_bytes());
337    bs.extend_from_slice(&self.timestamp.to_le_bytes());
338    for ch in 0..NCHN {
339      for k in 0..NWORDS {
340        bs.extend_from_slice(&self.tbins[ch][k]     .to_le_bytes());
341      }
342    }
343    bs.extend_from_slice(&Self::TAIL.to_le_bytes());
344    bs
345  }
346}
347
348impl TofPackable for RBCalibrationFlightT {
349  const TOF_PACKET_TYPE : TofPacketType = TofPacketType::RBCalibrationFlightT;
350}
351
352impl fmt::Display for RBCalibrationFlightT {
353  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
354    let mut timestamp_str = String::from("?");
355    match Utc.timestamp_opt(self.timestamp.into(), 0) {
356      LocalResult::Single(datetime_utc) => {
357        timestamp_str = datetime_utc.format("%Y/%m/%d %H:%M:%S").to_string();
358      },
359      LocalResult::Ambiguous(_, _) => {
360        error!("The given timestamp is ambiguous.");
361      },
362      LocalResult::None => {
363        error!("The given timestamp is not valid.");
364      },
365    }
366
367    //let datetime_utc: DateTime<Utc> = Utc.timestamp_opt(self.timestamp as i64, 0).earliest().unwrap_or(DateTime::<Utc>::from_timestamp_millis(0).unwrap());
368    write!(f, 
369  "<RBCalibrationFlightT [{} UTC]:
370      RB             : {}
371      T Bins    [ch0]: .. {:?} {:?} ..>",
372      timestamp_str,
373      self.rb_id,
374      self.tbins[0][98],
375      self.tbins[0][99]
376    )
377  } 
378}
379
380#[cfg(feature = "random")]
381impl FromRandom for RBCalibrationFlightT {
382    
383  fn from_random() -> Self {
384    let mut cali   = Self::new();
385    let mut rng    = rand::rng();
386    cali.rb_id     = rng.random::<u8>();
387    cali.timestamp = rng.random::<u32>();
388    for ch in 0..NCHN {
389      for n in 0..NWORDS { 
390        cali.tbins     [ch][n] = f16::from_f32(rng.random::<f32>());
391      }
392    }
393    cali
394  }
395}
396
397//-----------------------------------------------
398
399#[cfg(feature="pybindings")]
400#[pymethods]
401impl RBCalibrationFlightT {
402  #[getter]
403  fn get_rb_id(&self) -> u8 {
404    self.rb_id
405  }
406  
407  #[getter]
408  fn get_timestamp(&self) -> u32 {
409    self.timestamp
410  }
411
412  fn get_tbins<'_py>(&self, py: Python<'_py>, channel : u8 ) -> PyResult<Bound<'_py, PyArray1<f32>>> {
413    if channel < 1 || channel > 9 {
414      return Err(PyValueError::new_err("Channel must be > 0 and < 9"));
415    }
416    let mut py_tbins = [0.0f32;NWORDS];
417    for k in 0..NWORDS {
418      py_tbins[k] = self.tbins[channel as usize][k].to_f32();
419    };
420    let py_array = py_tbins.to_pyarray(py);
421    Ok(py_array)
422  }
423}
424
425#[cfg(feature="pybindings")]
426pythonize_packable!(RBCalibrationFlightT);
427
428//-----------------------------------------------
429
430#[derive(Debug, Clone, PartialEq)]
431#[cfg_attr(feature="pybindings", pyclass)]
432pub struct RBCalibrationFlightV {
433  pub rb_id      : u8,
434  pub d_v        : f32, // input voltage difference between 
435                        // vcal_data and noi data
436  pub timestamp  : u32, // time the calibration was taken
437  pub v_offsets : [[f16;NWORDS];NCHN], // voltage offset
438  pub v_dips    : [[f16;NWORDS];NCHN], // voltage "dip" (time-dependent correction)
439  pub v_inc     : [[f16;NWORDS];NCHN], // voltage increment (mV/ADC unit)
440}
441
442impl RBCalibrationFlightV {
443  pub fn new() -> Self {
444    Self {
445      rb_id     : 0,
446      d_v       : 0.9,
447      timestamp : 0,
448      v_offsets : [[f16::from_f32(0.0);NWORDS];NCHN],
449      v_dips    : [[f16::from_f32(0.0);NWORDS];NCHN],
450      v_inc     : [[f16::from_f32(0.0);NWORDS];NCHN],
451    }
452  }
453}
454
455impl Serialization for RBCalibrationFlightV {
456  const SIZE            : usize = NCHN*NWORDS*2*3 + 4 + 4 + 1; 
457  const HEAD            : u16   = 0xAAAA; // 43690 
458  const TAIL            : u16   = 0x5555; // 21845 
459
460  fn from_bytestream(bytestream : &Vec<u8>, 
461                     pos        : &mut usize)
462    -> Result<Self, SerializationError> { 
463    let mut rb_cal = Self::new();
464    if parse_u16(bytestream, pos) != Self::HEAD {
465      return Err(SerializationError::HeadInvalid {});
466    }
467    rb_cal.rb_id                = parse_u8(bytestream, pos);
468    rb_cal.d_v                  = parse_f32(bytestream, pos);
469    rb_cal.timestamp            = parse_u32(bytestream, pos);
470    for ch in 0..NCHN {
471      for k in 0..NWORDS {
472        let mut value = parse_f16(bytestream, pos);
473        rb_cal.v_offsets[ch][k] = value;
474        value         = parse_f16(bytestream, pos);
475        rb_cal.v_dips[ch][k]    = value;
476        value         = parse_f16(bytestream, pos);
477        rb_cal.v_inc[ch][k]     = value;
478      }
479    }
480    *pos += 2;
481    Ok(rb_cal)
482  }
483  
484  fn to_bytestream(&self) -> Vec<u8> {
485    let mut bs = Vec::<u8>::with_capacity(Self::SIZE);
486    bs.extend_from_slice(&Self::HEAD.to_le_bytes());
487    bs.extend_from_slice(&self.rb_id.to_le_bytes());
488    bs.extend_from_slice(&self.d_v.to_le_bytes());
489    bs.extend_from_slice(&self.timestamp.to_le_bytes());
490    for ch in 0..NCHN {
491      for k in 0..NWORDS {
492        bs.extend_from_slice(&self.v_offsets[ch][k].to_le_bytes());
493        bs.extend_from_slice(&self.v_dips[ch][k]   .to_le_bytes());
494        bs.extend_from_slice(&self.v_inc[ch][k]    .to_le_bytes());
495      }
496    }
497    bs.extend_from_slice(&Self::TAIL.to_le_bytes());
498    bs
499  }
500}
501
502impl TofPackable for RBCalibrationFlightV {
503  const TOF_PACKET_TYPE : TofPacketType = TofPacketType::RBCalibrationFlightV;
504}
505
506impl fmt::Display for RBCalibrationFlightV {
507  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
508    let mut timestamp_str = String::from("?");
509    match Utc.timestamp_opt(self.timestamp.into(), 0) {
510      LocalResult::Single(datetime_utc) => {
511        timestamp_str = datetime_utc.format("%Y/%m/%d %H:%M:%S").to_string();
512      },
513      LocalResult::Ambiguous(_, _) => {
514        error!("The given timestamp is ambiguous.");
515      },
516      LocalResult::None => {
517        error!("The given timestamp is not valid.");
518      },
519    }
520    write!(f, 
521  "<RBCalibrationFlightV [{} UTC]:
522      RB             : {}
523      V Offsets [ch0]: .. {:?} {:?} ..
524      V Incrmts [ch0]: .. {:?} {:?} ..
525      V Dips    [ch0]: .. {:?} {:?} ..>",
526      timestamp_str,
527      self.rb_id,
528      self.v_offsets[0][98],
529      self.v_offsets[0][99],
530      self.v_inc[0][98],
531      self.v_inc[0][99],
532      self.v_dips[0][98],
533      self.v_dips[0][99]
534    )
535  } 
536}
537
538#[cfg(feature = "random")]
539impl FromRandom for RBCalibrationFlightV {
540    
541  fn from_random() -> Self {
542    let mut cali     = Self::new();
543    let mut rng      = rand::rng();
544    cali.rb_id       = rng.random::<u8>();
545    cali.d_v         = rng.random::<f32>();
546    cali.timestamp   = rng.random::<u32>();
547    for ch in 0..NCHN {
548      for n in 0..NWORDS { 
549        cali.v_offsets  [ch][n] = f16::from_f32(rng.random::<f32>());
550        cali.v_dips     [ch][n] = f16::from_f32(rng.random::<f32>());
551        cali.v_inc      [ch][n] = f16::from_f32(rng.random::<f32>());
552      }
553    }
554    cali
555  }
556}
557
558//-----------------------------------------------
559
560#[cfg(feature="pybindings")]
561#[pymethods]
562impl RBCalibrationFlightV {
563  #[getter]
564  fn get_rb_id(&self) -> u8 {
565    self.rb_id
566  }
567 
568  #[getter]
569  fn get_d_v(&self) -> f32 {
570    self.d_v
571  }
572
573  #[getter]
574  fn get_timestamp(&self) -> u32 {
575    self.timestamp
576  }
577
578  fn get_v_offsets<'_py>(&self, py: Python<'_py>, channel : u8 ) -> PyResult<Bound<'_py, PyArray1<f32>>> {
579    if channel < 1 || channel > 9 {
580      return Err(PyValueError::new_err("Channel must be > 0 and < 9"));
581    }
582    let mut py_voffsets = [0.0f32;NWORDS];
583    for k in 0..NWORDS {
584      py_voffsets[k] = self.v_offsets[channel as usize][k].to_f32();
585    };
586    let py_array = py_voffsets.to_pyarray(py);
587    Ok(py_array)
588  }
589
590  fn get_v_dips<'_py>(&self, py: Python<'_py>, channel : u8 ) -> PyResult<Bound<'_py, PyArray1<f32>>> {
591    if channel < 1 || channel > 9 {
592      return Err(PyValueError::new_err("Channel must be > 0 and < 9"));
593    }
594    let mut py_vdips = [0.0f32;NWORDS];
595    for k in 0..NWORDS {
596      py_vdips[k] = self.v_dips[channel as usize][k].to_f32();
597    };
598    let py_array = py_vdips.to_pyarray(py);
599    Ok(py_array)
600  }
601
602  fn get_v_inc<'_py>(&self, py: Python<'_py>, channel : u8 ) -> PyResult<Bound<'_py, PyArray1<f32>>> {
603    if channel < 1 || channel > 9 {
604      return Err(PyValueError::new_err("Channel must be > 0 and < 9"));
605    }
606    let mut py_vinc = [0.0f32;NWORDS];
607    for k in 0..NWORDS {
608      py_vinc[k] = self.v_inc[channel as usize][k].to_f32();
609    };
610    let py_array = py_vinc.to_pyarray(py);
611    Ok(py_array)
612  }
613}
614
615#[cfg(feature="pybindings")]
616pythonize_packable!(RBCalibrationFlightV);
617
618//-----------------------------------------------
619
620// new way of property testing using quicktest!
621#[test]
622fn prop_roll_then_unroll_gives_original() {
623  fn prop(mut vec: Vec<u8>, offset: i8) -> bool {
624    let original = vec.clone();
625    let offset = offset as isize;
626
627    roll(&mut vec, offset);
628    roll(&mut vec, -offset);
629
630    vec == original
631  }
632  quickcheck::QuickCheck::new().tests(100).quickcheck(prop as fn(Vec<u8>, i8) -> bool);
633}
634
635//-----------------------------------------------
636
637/// DRS4 calibration routine for the TOF readoutboards.
638///
639/// Translate adc per bin to voltage (mV) over time 
640/// (nanoseconds) 
641///
642/// This bascially calibrates the capacitor array of 
643/// the DRS4 chip and produces a number of calibration 
644/// constants.
645#[derive(Debug, Clone, PartialEq)]
646#[cfg_attr(feature="pybindings", pyclass)]
647pub struct RBCalibrations {
648  pub rb_id      : u8,
649  pub d_v        : f32, // input voltage difference between 
650                        // vcal_data and noi data
651  pub timestamp  : u32, // time the calibration was taken
652  pub serialize_event_data : bool,
653  // calibration constants
654  pub v_offsets : [[f32;NWORDS];NCHN], // voltage offset
655  pub v_dips    : [[f32;NWORDS];NCHN], // voltage "dip" (time-dependent correction)
656  pub v_inc     : [[f32;NWORDS];NCHN], // voltage increment (mV/ADC unit)
657  pub tbin      : [[f32;NWORDS];NCHN], // cell width (ns)
658
659  // calibration data
660  pub vcal_data : Vec::<RBEvent>,
661  pub tcal_data : Vec::<RBEvent>,
662  pub noi_data  : Vec::<RBEvent>,
663}
664
665impl RBCalibrations {
666  // skip the first n cells for the 
667  // voltage calibration. Historically,
668  // this had been 2.
669  pub const NSKIP       : usize = 2;
670  pub const SINMAX      : usize = 60; // ~1000 ADC units
671  pub const DVCUT       : f32   = 15.0; // ns away that should be considered
672  pub const NOMINALFREQ : f32   = 2.0; // nominal sampling frequency,
673                                       // GHz
674  pub const CALFREQ     : f32   = 0.025; // calibration sine wave frequency (25MHz)
675 
676  /// Re-assemble a RBCalibration from chopped up parts
677  pub fn assemble_from_flightcal(fcal_t : RBCalibrationFlightT,
678                                 fcal_v : RBCalibrationFlightV)
679    -> Result<Self, CalibrationError> {
680    if (fcal_t.timestamp != fcal_v.timestamp) || (fcal_t.rb_id != fcal_v.rb_id) {
681      error!("These calibrations do not match! {} , {}", fcal_t, fcal_v);
682      return Err(CalibrationError::IncompatibleFlightCalibrations);
683    }
684    let mut cal              = Self::new(fcal_t.rb_id);
685    cal.rb_id                = fcal_t.rb_id;
686    cal.timestamp            = fcal_t.timestamp;
687    cal.d_v                  = fcal_v.d_v;
688    cal.serialize_event_data = false;
689    for ch in 0..NCHN {
690      for k in 0..NWORDS {
691        cal.tbin     [ch][k] = f16::to_f32(fcal_t.tbins[ch][k]);
692        cal.v_offsets[ch][k] = f16::to_f32(fcal_v.v_offsets[ch][k]); 
693        cal.v_dips   [ch][k] = f16::to_f32(fcal_v.v_dips[ch][k]);
694        cal.v_inc    [ch][k] = f16::to_f32(fcal_v.v_inc[ch][k]); 
695      }
696    }
697    Ok(cal)
698  }
699
700  /// Return the timing part of the calibration in a 
701  /// package digestable by the flight computer.
702  ///
703  /// Additonal compression by using f16
704  pub fn emit_flighttcal(&self) -> RBCalibrationFlightT {
705    let mut cal = RBCalibrationFlightT::new();
706    cal.rb_id = self.rb_id;
707    cal.timestamp = self.timestamp;
708    for ch in 0..NCHN {
709      for n in 0..NWORDS { 
710        cal.tbins  [ch][n] = f16::from_f32(self.tbin[ch][n]);
711      }
712    }
713    cal
714  }
715  
716  /// Return the voltage part of the calibration in a 
717  /// package digestable by the flight computer.
718  ///
719  /// Additional compression by using f16
720  pub fn emit_flightvcal(&self) -> RBCalibrationFlightV {
721    let mut cal = RBCalibrationFlightV::new();
722    cal.rb_id = self.rb_id;
723    cal.timestamp = self.timestamp;
724    cal.d_v   = self.d_v;
725    for ch in 0..NCHN {
726      for n in 0..NWORDS { 
727        cal.v_offsets  [ch][n] = f16::from_f32(self.v_offsets[ch][n]);
728        cal.v_dips     [ch][n] = f16::from_f32(self.v_dips[ch][n]);
729        cal.v_inc      [ch][n] = f16::from_f32(self.v_inc[ch][n]);
730      }
731    }
732    cal
733  }
734
735  /// Remove events with invalid traces or event fragment bits set
736  pub fn clean_input_data(&mut self) {
737    self.vcal_data.retain(|x|  !x.header.drs_lost_trigger()
738        && !x.header.is_event_fragment()
739        && x.trace_check()); 
740    self.tcal_data.retain(|x|  !x.header.drs_lost_trigger()
741        && !x.header.is_event_fragment()
742        && x.trace_check()); 
743    self.noi_data.retain(|x|   !x.header.drs_lost_trigger() 
744        && !x.header.is_event_fragment()
745        && x.trace_check()); 
746    self.noi_data.sort_by(|a, b| a.header.event_id.cmp(&b.header.event_id));
747    self.vcal_data.sort_by(|a, b| a.header.event_id.cmp(&b.header.event_id));
748    self.tcal_data.sort_by(|a, b| a.header.event_id.cmp(&b.header.event_id));
749  }
750
751  // apply the vcal to a dataset of the calibration
752  // (e.g. timing calibration)
753  fn apply_vcal(&self, 
754                data      : &Vec<RBEvent>)
755      -> (Vec<Vec<Vec<f32>>>,Vec<isize>) {
756    let nevents          = data.len();
757    let mut traces       = Vec::<Vec::<Vec::<f32>>>::new();
758    let mut trace        = Vec::<f32>::with_capacity(NWORDS);
759    let mut stop_cells   = Vec::<isize>::new();
760    let mut empty_events = Vec::<Vec::<f32>>::new();
761    for _ in 0..nevents {
762        empty_events.push(trace.clone());
763    }
764    for ch in 0..NCHN {
765      traces.push(empty_events.clone());
766      for (k,ev) in data.iter().enumerate() {
767        trace.clear();
768        stop_cells.push(ev.header.stop_cell as isize);
769        for k in 0..NWORDS {
770          trace.push(ev.adc[ch][k] as f32);
771        }
772        self.voltages(ch + 1, ev.header.stop_cell as usize,
773                      &ev.adc[ch], &mut trace);
774        traces[ch][k] = trace.clone();
775      }
776    }
777    (traces, stop_cells)
778  }
779
780  // channel is from 0-8
781  pub fn apply_vcal_constants(&self,
782                              adc       : &Vec<f32>,
783                              channel   : usize,  
784                              stop_cell : usize) -> Vec<f32> {
785    let mut waveform = Vec::<f32>::with_capacity(adc.len());
786    let mut value : f32;
787    for k in 0..adc.len() {
788      value  = adc[k] as f32;
789      value -= self.v_offsets[channel][(k + (stop_cell)) %NWORDS];
790      value -= self.v_dips   [channel][k];
791      value *= self.v_inc    [channel][(k + (stop_cell)) %NWORDS];
792      waveform.push(value);
793    } 
794    waveform
795  }
796
797  /// Calculate the offset and dips calibration constants 
798  /// for input data. 
799  ///
800  /// # Return:
801  ///
802  /// offsets, dips
803  fn voltage_offset_and_dips(input_vcal_data : &Vec<RBEvent>) 
804  -> Result<(Vec<Vec<f32>>, Vec<Vec<f32>>), CalibrationError> {
805    if input_vcal_data.len() == 0 {
806      return Err(CalibrationError::EmptyInputData);
807    }
808    let mut all_v_offsets = Vec::<Vec::<f32>>::new();
809    let mut all_v_dips    = Vec::<Vec::<f32>>::new();
810    let nchn = input_vcal_data[0].header.get_nchan();
811
812    debug!("Found {nchn} channels!");
813    for _ in 0..nchn {
814      let empty_vec_off : Vec<f32> = vec![0.0;NWORDS];
815      all_v_offsets.push(empty_vec_off);  
816      let empty_vec_dip : Vec<f32> = vec![0.0;NWORDS];
817      all_v_dips.push(empty_vec_dip);  
818    }
819
820    // we temporarily get the adc traces
821    // traces are [channel][event][adc_cell]
822    let mut traces        = unpack_traces(&input_vcal_data);
823    let mut rolled_traces = traces.clone();
824    for ch in 0..nchn {
825      for n in 0..input_vcal_data.len() {
826        for k in 0..Self::NSKIP {
827          traces[ch][n][k] = f32::NAN;
828          rolled_traces[ch][n][k] = f32::NAN;
829        }
830        // the traces are filled and the first 2 bins
831        // marked with nan, now need to get "rolled over",
832        // so that they start with the stop cell
833        roll(&mut rolled_traces[ch][n],
834             input_vcal_data[n].header.stop_cell as isize); 
835      }// first loop over events done
836      all_v_offsets[ch] = calculate_column_stat(&rolled_traces[ch], mean);
837      //let v_offsets = calculate_column_medians(&rolled_traces[ch]);
838      debug!("We calculated {} voltage offset values for ch {}", all_v_offsets[ch].len(), ch);
839      // fill these in the prepared array structure
840      //for k in 0..v_offsets.len() {
841      //  all_v_offsets[ch][k] = v_offsets[k];
842      //}
843      for (n, ev) in input_vcal_data.iter().enumerate() {
844        // now we roll the v_offsets back
845        let mut v_offsets_rolled = all_v_offsets[ch].clone();
846        roll(&mut v_offsets_rolled, -1*ev.header.stop_cell as isize);
847        for k in 0..traces[ch][n].len() {
848          traces[ch][n][k] -= v_offsets_rolled[k];
849        }
850      }
851      let v_dips = calculate_column_stat(&traces[ch], median);
852      for k in 0..v_dips.len() {
853        if k < Self::NSKIP {
854          all_v_dips[ch][k] = 0.0;
855        } else {
856          all_v_dips[ch][k] = v_dips[k];
857        }
858      }
859    }
860    Ok((all_v_offsets, all_v_dips))
861  }
862
863
864  /// Voltage calibration has to be applied
865  /// 
866  /// # Returns
867  ///
868  ///   vec\[ch\[9\], tbin\[1024\]\]
869  ///
870  pub fn timing_calibration(&self,
871                            edge       : &Edge,
872                            apply_vcal : bool) 
873  -> Result<Vec<Vec<f32>>, CalibrationError> {
874    if self.tcal_data.len() == 0 {
875      error!("Input data for timing calibration is empty!");
876      return Err(CalibrationError::EmptyInputData);
877    }
878    // tcal values are [channel][adc_cell] 
879    let mut all_tcal = Vec::<Vec::<f32>>::new();
880    for _ in 0..NCHN {
881      all_tcal.push(Vec::<f32>::new());
882    }
883    // traces are [channel][event][adc_cell]
884    let mut traces       : Vec<Vec<Vec<f32>>>;
885    let mut stop_cells   = Vec::<isize>::new();
886    if apply_vcal {
887      let result = self.apply_vcal(&self.tcal_data);
888      traces     = result.0;
889      stop_cells = result.1;
890    } else {
891      warn!("Not applying voltage calibration to tcal data. This most likely makes no sense!");
892      traces  = unpack_traces(&self.tcal_data);
893      for ev in self.tcal_data.iter() {
894        stop_cells.push(ev.header.event_id as isize);
895      }
896    }
897    let do_spike_cleaning = true;
898    if do_spike_cleaning {
899      for k_ch in 0..traces.len() {
900        for k_ev in 0..traces[k_ch].len() {
901          clean_spikes(&mut traces[k_ch][k_ev], true);
902        }
903      }
904    }
905    let nwords = traces[0][0].len();
906    for ch in 0..NCHN {
907      for ev in 0..traces[ch].len() {
908        for k in 0..nwords {
909          if k < Self::NSKIP {
910            traces[ch][ev][k]  = f32::NAN;
911          }
912          if f32::abs(traces[ch][ev][k]) > Self::SINMAX as f32 { 
913            traces[ch][ev][k]  = f32::NAN;
914          }// the traces are filled and the first 2 bins
915        }
916      }  
917    }
918
919    let mut rolled_traces = traces.clone();
920    let mut drolled_traces = traces.clone();
921    for ch in 0..NCHN {
922      for ev in 0..traces[ch].len() {
923        roll(&mut rolled_traces[ch][ev],
924             stop_cells[ev]); 
925      }
926    }
927    for ch in 0..NCHN {
928      for ev in 0..traces[ch].len() {
929        for n in 0..traces[ch][ev].len() {
930          let mut dval : f32;
931          if n == traces[ch][ev].len() - 1 {
932            dval = rolled_traces[ch][ev][0] - rolled_traces[ch][ev][traces[ch][ev].len() -1];
933          } else {
934            dval = rolled_traces[ch][ev][n + 1] - rolled_traces[ch][ev][n];      
935          }
936          match edge {
937            Edge::Rising | Edge::Average => {
938              if dval < 0.0 {
939                dval = f32::NAN;
940              }
941            },
942            Edge::Falling => {
943              if dval > 0.0 {
944                dval = f32::NAN;
945              }
946            },
947            _ => {
948              // FIXME - better error handling
949              error!("Only average, rising or falling edge supported!");
950            }
951          } // end match
952          dval = f32::abs(dval); 
953          if f32::abs(dval - 15.0) > Self::DVCUT {
954            dval = f32::NAN;
955          }
956          drolled_traces[ch][ev][n] = dval;
957        } // end loop over adc bins
958      } // end loop over events
959      let col_means = calculate_column_stat(&drolled_traces[ch], mean);
960      let nfreq_vec : Vec<f32> = vec![1.0/Self::NOMINALFREQ;NWORDS];
961      all_tcal[ch]  = nfreq_vec;
962      let ch_mean   = mean(&col_means);
963      for n in 0..all_tcal[ch].len() {
964        all_tcal[ch][n] *= col_means[n]/ch_mean;  
965      }
966    } // end loop over channels
967    Ok(all_tcal)
968  }
969
970  /// Call to the calibration routine, using
971  /// the set input data
972  pub fn calibrate(&mut self) -> Result<(), CalibrationError>{
973    if self.vcal_data.len() == 0
974    || self.tcal_data.len() == 0 
975    || self.noi_data.len() == 0 {
976      return Err(CalibrationError::EmptyInputData);
977    }
978    info!("Starting calculating voltage calibration constants!");
979    let (v_offsets_high, _v_dips_high) 
980        = Self::voltage_offset_and_dips(&self.vcal_data)?;
981    let (v_offsets_low, v_dips_low) 
982        = Self::voltage_offset_and_dips(&self.noi_data)?;
983    // which of the v_offsets do we actually use?
984    for ch in 0..NCHN {
985      for k in 0..NWORDS {
986        self.v_offsets[ch][k] = v_offsets_low[ch][k];
987        self.v_dips[ch][k]    = v_dips_low[ch][k];
988        self.v_inc[ch][k]     = self.d_v/(v_offsets_high[ch][k] - v_offsets_low[ch][k]);
989      }
990    }
991    // at this point, the voltage calibration is complete
992    info!("Filnished calculating voltage calibration constants!");
993    info!("Starting calculating timing calibration constants!");
994    warn!("Calibration only supported for Edge::Average!");
995    // this just suppresses a warning
996    // We have to think if edge will be
997    // a parameter or a constant.
998    let mut edge    = Edge::None;
999    if matches!(edge, Edge::None) {
1000      edge = Edge::Average;
1001    }
1002
1003    let mut tcal_av = self.timing_calibration( &edge, true)?;
1004    if matches!(edge, Edge::Average) {
1005      edge = Edge::Falling;
1006      let tcal_falling = self.timing_calibration(&edge, true)?;
1007      for ch in 0..NCHN {
1008        for k in 0..tcal_av.len() {
1009          tcal_av[ch][k] += tcal_falling[ch][k];
1010          tcal_av[ch][k] /= 2.0;
1011        }
1012      } 
1013      // for further calibration procedure
1014      edge = Edge::Rising;
1015    } else {
1016      error!("This is not implemented for any other case yet!");
1017      todo!();
1018    }
1019    
1020    // another set of constants
1021    //nevents,nchan,tracelen = gbf.traces.shape
1022    let mut damping   : f32 = 0.1;
1023    let corr_limit    : f32 = 0.05;
1024    //let n_iter_period : f32 = 1000; //#500 or nevents #
1025
1026    let nperiod = Self::NOMINALFREQ/Self::CALFREQ; 
1027    let global = true;
1028    if global {
1029      
1030      //let mut tcal_traces   = Vec::<Vec::<Vec::<f32>>>::new();
1031      //let mut stop_cells    = Vec::<isize>::new();
1032      
1033      let result  = self.apply_vcal(&self.tcal_data);
1034      let tcal_traces = result.0;
1035      let stop_cells  = result.1;
1036
1037      //for n in 0..1000 {
1038      for ch in 0..NCHN {
1039        for ev in 0..tcal_traces[ch].len() {
1040          let tracelen = tcal_traces[ch][ev].len();
1041          let stop_cell = stop_cells[ev];
1042          let mut tcal_av_cp = tcal_av.clone();
1043          roll(&mut tcal_av_cp[ch], -1* (stop_cell as isize));
1044          
1045          let (zcs, periods) = get_periods(&tcal_traces[ch][ev],
1046                                           &tcal_av_cp[ch],
1047                                           nperiod,
1048                                           Self::NSKIP as f32,
1049                                           &edge);
1050          debug!("Will iterate over {} periods!", periods.len());
1051          for (n_p,period) in periods.iter().enumerate() {
1052            if *period == 0.0 {
1053              warn!("period is 0 {:?}", periods);
1054            }
1055            if period.is_nan() {
1056              warn!("period is nan! {:?}", periods);
1057            }
1058            let zcs_a = zcs[n_p]     + stop_cell as usize;
1059            let zcs_b = zcs[n_p + 1] + stop_cell as usize;
1060            let mut corr = (1.0/Self::CALFREQ)/period;
1061            if matches!(edge, Edge::None) {
1062              corr *= 0.5;
1063            }
1064            if f32::abs(corr - 1.0) > corr_limit {
1065              continue;
1066            }
1067            corr = (corr-1.0)*damping + 1.0;
1068            let zcs_a_tl = zcs_a%tracelen;
1069            let zcs_b_tl = zcs_b%tracelen;
1070            if zcs_a < tracelen && zcs_b > tracelen {
1071              for m in zcs_a..tcal_av[ch].len() {
1072                tcal_av[ch][m] *= corr;
1073              }
1074              for m in 0..zcs_b_tl {
1075                tcal_av[ch][m] *= corr;
1076              }
1077            } else {
1078              for m in zcs_a_tl..zcs_b_tl {
1079                tcal_av[ch][m] *= corr;
1080              }
1081            }
1082          }
1083          //n_correct[ch] += 1.0;
1084        } // end over nchannel
1085        damping *= 0.99;
1086      } // end loop over n_iter_period
1087   
1088    } // end global
1089    for ch in 0..NCHN {
1090      for k in 0..NWORDS {
1091        self.tbin[ch][k] = tcal_av[ch][k];
1092      }
1093    }
1094    Ok(())
1095  }
1096
1097  /// Apply the spike cleaning to all channels
1098  pub fn spike_cleaning(voltages  : &mut Vec<Vec<f32>>,
1099                        stop_cell : u16) 
1100    -> Result<(), WaveformError> {
1101
1102    let mut spikes      : [i32;10] = [0;10];
1103    let mut filter      : f32;
1104    let mut dfilter     : f32;
1105    let mut n_neighbor  : usize;
1106    let mut n_rsp       = 0usize;
1107    let mut rsp : [i32;10]    = [-1;10];
1108    // to me, this seems that should be u32
1109    // the 10 is for a maximum of 10 spikes (Jeff)
1110    let mut sp   : [[usize;10];NCHN] = [[0;10];NCHN];
1111    let mut n_sp : [usize;10]        = [0;10];
1112
1113    for j in 0..NWORDS as usize {
1114      for i in 0..NCHN as usize {
1115        filter = -voltages[i][j] + voltages[i][(j + 1) % NWORDS] + voltages[i][(j + 2) % NWORDS] - voltages[i][(j + 3) % NWORDS];
1116        dfilter = filter + 2.0 * voltages[i][(j + 3) % NWORDS] + voltages[i][(j + 4) % NWORDS] - voltages[i][(j + 5) % NWORDS];
1117        if filter > 20.0  && filter < 100.0 {
1118          if n_sp[i] < 10 {   // record maximum of 10 spikes
1119            sp[i][n_sp[i] as usize] = (j + 1) % NWORDS ;
1120            n_sp[i] += 1;
1121          } else {
1122            return Err(WaveformError::TooSpiky);
1123          }            // too many spikes -> something wrong
1124        }// end of if
1125        else if dfilter > 40.0 && dfilter < 100.0 && filter > 10.0 {
1126          if n_sp[i] < 9 {  // record maximum of 10 spikes
1127            sp[i][n_sp[i] as usize] = (j + 1) % NWORDS ;
1128            sp[i][(n_sp[i] + 1) as usize] = (j + 3) % NWORDS ;
1129            n_sp[i] += 2;
1130          } else {
1131            return Err(WaveformError::TooSpiky);
1132          } // too many spikes -> something wrong
1133        } // end of else if
1134
1135      }// end loop over NCHN
1136    } // end loop over NWORDS
1137
1138    // go through all spikes and look for neighbors */
1139    for i in 0..NCHN {
1140      for j in 0..n_sp[i] as usize {
1141        //n_symmetric = 0;
1142        n_neighbor = 0;
1143        for k in 0..NCHN {
1144          for l in 0..n_sp[k] as usize {
1145          //check if this spike has a symmetric partner in any channel
1146            if (sp[i][j] as i32 + sp[k][l] as i32 - 2 * stop_cell as i32) as i32 % NWORDS as i32 == 1022 {
1147              //n_symmetric += 1;
1148              break;
1149            }
1150          }
1151        } // end loop over k
1152        // check if this spike has same spike is in any other channels */
1153        //for (k = 0; k < nChn; k++) {
1154        for k in 0..NCHN {
1155          if i != k {
1156            for l in 0..n_sp[k] {
1157              if sp[i][j] == sp[k][l] {
1158              n_neighbor += 1;
1159              break;
1160              }
1161            } // end loop over l   
1162          } // end if
1163        } // end loop over k
1164
1165        if n_neighbor >= 2 {
1166          for k in 0..n_rsp {
1167            if rsp[k] == sp[i][j] as i32 {break;} // ignore repeats
1168            if n_rsp < 10 && k == n_rsp {
1169              rsp[n_rsp] = sp[i][j] as i32;
1170              n_rsp += 1;
1171            }
1172          }  
1173        }
1174
1175      } // end loop over j
1176    } // end loop over i
1177
1178    // recognize spikes if at least one channel has it */
1179    //for (k = 0; k < n_rsp; k++)
1180    let magic_value : f32 = 14.8;
1181    let mut x       : f32;
1182    let mut y       : f32;
1183
1184    let mut skip_next : bool = false;
1185    for k in 0..n_rsp {
1186      if skip_next {
1187        skip_next = false;
1188        continue;
1189      }
1190      spikes[k] = rsp[k];
1191      //for (i = 0; i < nChn; i++)
1192      for i in 0..NCHN {
1193        if k < n_rsp && i32::abs(rsp[k] as i32 - rsp[k + 1] as i32 % NWORDS as i32) == 2
1194        {
1195          // remove double spike 
1196          let j = if rsp[k] > rsp[k + 1] {rsp[k + 1] as usize}  else {rsp[k] as usize};
1197          x = voltages[i][(j - 1) % NWORDS];
1198          y = voltages[i][(j + 4) % NWORDS];
1199          if f32::abs(x - y) < 15.0 {
1200            voltages[i][j % NWORDS] = x + 1.0 * (y - x) / 5.0;
1201            voltages[i][(j + 1) % NWORDS] = x + 2.0 * (y - x) / 5.0;
1202            voltages[i][(j + 2) % NWORDS] = x + 3.0 * (y - x) / 5.0;
1203            voltages[i][(j + 3) % NWORDS] = x + 4.0 * (y - x) / 5.0;
1204          } else {
1205            voltages[i][j % NWORDS] -= magic_value;
1206            voltages[i][(j + 1) % NWORDS] -= magic_value;
1207            voltages[i][(j + 2) % NWORDS] -= magic_value;
1208            voltages[i][(j + 3) % NWORDS] -= magic_value;
1209          }
1210        } else {
1211          // remove single spike 
1212          x = voltages[i][((rsp[k] - 1) % NWORDS as i32) as usize];
1213          y = voltages[i][(rsp[k] + 2) as usize % NWORDS];
1214          if f32::abs(x - y) < 15.0 {
1215            voltages[i][rsp[k] as usize] = x + 1.0 * (y - x) / 3.0;
1216            voltages[i][(rsp[k] + 1) as usize % NWORDS] = x + 2.0 * (y - x) / 3.0;
1217          } else {
1218            voltages[i][rsp[k] as usize] -= magic_value;
1219            voltages[i][(rsp[k] + 1) as usize % NWORDS] -= magic_value;
1220          }
1221        } // end loop over nchn
1222      } // end loop over n_rsp
1223      if k < n_rsp && i32::abs(rsp[k] - rsp[k + 1] % NWORDS as i32) == 2
1224        {skip_next = true;} // skip second half of double spike
1225    } // end loop over k
1226  Ok(())
1227  }
1228
1229  /// Apply the voltage calibration to a single channel 
1230  /// FIXME - mixing of naming conventions for the channels
1231  ///
1232  /// FIXME - make it return Result<(), CalibrationError>
1233  ///
1234  /// # Arguments
1235  ///
1236  /// * channel   : Channel id 1-9
1237  /// * stop_cell : This channels stop cell 
1238  /// * adc       : Uncalibrated channel data
1239  /// * waveform  : Pre-allocated array to hold 
1240  ///               calibrated waveform data.
1241  pub fn voltages(&self,
1242                  channel   : usize,
1243                  stop_cell : usize,
1244                  adc       : &Vec<u16>,
1245                  waveform  : &mut Vec<f32>) {
1246                  //waveform  : &mut [f32;NWORDS]) {
1247    if channel > 9 || channel == 0 {
1248      error!("There is no channel larger than 9 and no channel 0! Channel {channel} was requested. Can not perform voltage calibration!");
1249      return;
1250    }
1251    if adc.len() != waveform.len() {
1252      error!("Ch{} has {} adc values, however we are expecting {}!", channel,  adc.len(), waveform.len());
1253      return;
1254    }
1255
1256    let mut value : f32; 
1257    for k in 0..NWORDS {
1258      value  = adc[k] as f32;
1259      value -= self.v_offsets[channel -1][(k + (stop_cell)) %NWORDS];
1260      value -= self.v_dips   [channel -1][k];
1261      value *= self.v_inc    [channel -1][(k + (stop_cell)) %NWORDS];
1262      waveform[k] = value;
1263    }
1264  }
1265  
1266  /// Apply the timing calibration to a single channel 
1267  /// 
1268  /// This will allocate the array for the waveform 
1269  /// time bins (unit is ns)
1270  ///
1271  /// # Arguments
1272  ///
1273  /// * channel   : Channel id 1-9
1274  /// * stop_cell : This channels stop cell 
1275  pub fn nanoseconds(&self,
1276                     channel   : usize,
1277                     stop_cell : usize,
1278                     times     : &mut Vec<f32>) {
1279    if channel > 9 || channel == 0 {
1280      error!("There is no channel larger than 9 and no channel 0! Channel {channel} was requested. Can not perform timing calibration!");
1281    }
1282    for k in 1..NWORDS { 
1283      times[k] = times[k-1] + self.tbin[channel -1][(k-1+(stop_cell))%NWORDS];
1284    }
1285  }
1286
1287  pub fn new(rb_id : u8) -> Self {
1288    let timestamp = Utc::now().timestamp() as u32;
1289    Self {
1290      rb_id     : rb_id,
1291      d_v       : 182.0, // FIXME - this needs to be a constant
1292      timestamp : timestamp,
1293      serialize_event_data : false, // per default, don't serialize the data 
1294      v_offsets : [[0.0;NWORDS];NCHN], 
1295      v_dips    : [[0.0;NWORDS];NCHN], 
1296      v_inc     : [[0.0;NWORDS];NCHN], 
1297      tbin      : [[0.0;NWORDS];NCHN],
1298      vcal_data : Vec::<RBEvent>::new(),
1299      tcal_data : Vec::<RBEvent>::new(),
1300      noi_data  : Vec::<RBEvent>::new()
1301    }
1302  }
1303
1304  /// Discard the data to reduce the memory footprint
1305  pub fn discard_data(&mut self) {
1306    self.vcal_data = Vec::<RBEvent>::new();
1307    self.tcal_data = Vec::<RBEvent>::new();
1308    self.noi_data  = Vec::<RBEvent>::new();
1309  }
1310
1311  /// Gets the calibration from a file which 
1312  /// has the RBCalibration stored in a 
1313  /// TofPacket
1314  ///
1315  /// E.g. if it was written with TofPacketWriter
1316  pub fn from_file(filename : String, discard_data : bool) -> Result<Self, SerializationError> {
1317    let mut reader = TofPacketReader::new(&filename);
1318    loop {
1319      match reader.next() {
1320        None => {
1321          error!("Can't load calibration!");
1322          break;
1323        },
1324        Some(pack) => {
1325          if pack.packet_type == TofPacketType::RBCalibration { 
1326            let mut cali = RBCalibrations::from_bytestream(&pack.payload, &mut 0)?;
1327            if discard_data {
1328              cali.discard_data();
1329            }
1330            return Ok(cali);
1331          } else {
1332            continue;
1333          }
1334        }
1335      }
1336    }
1337    Err(SerializationError::StreamTooShort)
1338  }
1339
1340
1341  /// Infer the readoutboard id from the filename
1342  ///
1343  /// Assuming a certain naming scheme for the filename "rbXX_cal.txt"
1344  /// we extract the readoutboard id
1345  pub fn get_id_from_filename(&mut self, filename : &Path) -> u8 {
1346    let rb_id : u8;
1347    match filename.file_name() {
1348      None   => {
1349        error!("Path {} seems non-sensical!", filename.display());
1350        self.rb_id = 0;
1351        return 0;
1352      }
1353      Some(name) => {
1354        // TODO This might panic! Is it ok?
1355        let fname = name.to_os_string().into_string().unwrap();
1356        let id    = &fname[2..4];
1357        // TODO This might panic! Is it ok?
1358        rb_id     = id.parse::<u8>().unwrap();
1359        debug!("Extracted RB ID {} from filename {}", rb_id, fname);
1360      }
1361    }
1362  self.rb_id = rb_id;
1363  rb_id
1364  }
1365  
1366  /// Self check if the timing constants are sane 
1367  pub fn passes_timing_checks(&self) -> bool {
1368    for ch in 0..9 {
1369      let mut mean = 0.0;
1370      for k in 0..NWORDS {
1371        mean += self.tbin[ch][k];
1372      }
1373      mean /= NWORDS as f32;
1374      if mean < 0.48824 || mean > 0.48834 {
1375        error!("Timing calibration for ch {} / RB {} failed. Got mean of {}", ch + 1, self.rb_id, mean);
1376        return false;
1377      }
1378      let tbin_ch = self.tbin[ch].to_vec();
1379      let var = standard_deviation(&tbin_ch).unwrap_or(0.0);
1380      if var < 0.08 || var > 0.15 {
1381        error!("Timing calibration for ch {}/ RB {} failed. Got st dev of {}", ch + 1, self.rb_id, var);
1382        return false;
1383      }
1384    }
1385    debug!("Passed timing calibration sanity checks for RB {}!", self.rb_id);
1386    true
1387  }
1388
1389  /// Self check if the voltage constants are sane
1390  pub fn passes_voltage_checks(&self) -> bool {
1391    for ch in 0..9 {
1392      let mut mean = 0.0;
1393      for k in 0..NWORDS {
1394        mean += self.v_offsets[ch][k];
1395      }
1396      mean /= NWORDS as f32;
1397      if mean < 4200.0 || mean > 5200.0 {
1398        error!("Voltage offset calibration for ch {} / RB {} failed. Got mean of {}", ch + 1, self.rb_id, mean);
1399        return false;
1400      }
1401      let v_off = self.tbin[ch].to_vec();
1402      let var = standard_deviation(&v_off).unwrap_or(0.0);
1403      if var > 150.0 {
1404        error!("Voltage offset calibration for ch {} / RB {} failed. Got st dev of {}", ch + 1, self.rb_id, var);
1405        return false;
1406      }
1407      mean = 0.0;
1408      for k in 0..NWORDS {
1409        mean += self.v_dips[ch][k];
1410      }
1411      mean /= NWORDS as f32;
1412      if mean < -0.5 || mean > 0.5 {
1413        error!("Voltage droop calibration for ch {} / RB {} failed. Got mean of {}", ch + 1, self.rb_id, mean);
1414        return false;
1415      }
1416      mean = 0.0;
1417      for k in 0..NWORDS {
1418        mean += self.v_inc[ch][k];
1419      }
1420      mean /= NWORDS as f32;
1421      if mean < 0.06 || mean > 0.07 {
1422        error!("Voltage gain calibration for ch {} / RB {} failed. Got mean of {}", ch + 1, self.rb_id, mean);
1423        return false;
1424      }
1425      let v_inc = self.v_inc[ch].to_vec();
1426      let var = standard_deviation(&v_inc).unwrap_or(0.0);
1427      if var > 0.00025 {
1428        error!("Voltage gain calibration for ch {} / RB {} failed. Got st dev of {}", ch + 1, self.rb_id, var);
1429        //return false;
1430      }
1431    }
1432    debug!("Passed voltage calibration sanity checks for RB {}!", self.rb_id);
1433    true 
1434  }
1435
1436
1437  /// Check voltage and timing constants for sanity 
1438  ///
1439  /// for each RB channel
1440  /// take distribution of 1024 calibration constants and confirm
1441  /// 
1442  /// Tcal:
1443  ///     average value between 0.48824 and 0.48834
1444  ///     standard deviation between 0.08 and 0.15.
1445  ///     Most common problem: avg of tcal distribution is < 0.48824
1446  /// 
1447  /// Vcal1 (offsets):
1448  ///     average value between 4200 and 5200
1449  ///     standard deviation less than 150
1450  /// 
1451  /// Vcal2 (droop):
1452  ///     average value between -0.5 and 0.5
1453  ///     print out a warning if the standard deviation for any channel is > 5.0
1454  ///     Note that ch1 on any particular board has a more dramatic droop so if ch1 droop is the only channel on the board with this warning, it can be ignored
1455  /// 
1456  /// Vcal3 (gain):
1457  ///     average value between 0.06 and 0.07
1458  ///     standard deviation less than 0.00025
1459  pub fn check(&self) -> bool {
1460    self.passes_timing_checks() && self.passes_voltage_checks() 
1461  }
1462}
1463
1464impl TofPackable for RBCalibrations {
1465  const TOF_PACKET_TYPE : TofPacketType = TofPacketType::RBCalibration;
1466}
1467
1468impl Serialization for RBCalibrations {
1469  const SIZE            : usize = NCHN*NWORDS*4*8 + 4 + 1 + 1; 
1470  const HEAD            : u16   = 0xAAAA; // 43690 
1471  const TAIL            : u16   = 0x5555; // 21845 
1472  
1473  fn from_bytestream(bytestream : &Vec<u8>, 
1474                     pos        : &mut usize)
1475    -> Result<Self, SerializationError> { 
1476    let mut rb_cal = Self::new(0);
1477    if parse_u16(bytestream, pos) != Self::HEAD {
1478      return Err(SerializationError::HeadInvalid {});
1479    }
1480    rb_cal.rb_id                = parse_u8(bytestream, pos);
1481    rb_cal.d_v                  = parse_f32(bytestream, pos);
1482    rb_cal.timestamp            = parse_u32(bytestream, pos);
1483    rb_cal.serialize_event_data = parse_bool(bytestream, pos);
1484    for ch in 0..NCHN {
1485      for k in 0..NWORDS {
1486        let mut value = parse_f32(bytestream, pos);
1487        rb_cal.v_offsets[ch][k] = value;
1488        value         = parse_f32(bytestream, pos);
1489        rb_cal.v_dips[ch][k]    = value;
1490        value         = parse_f32(bytestream, pos);
1491        rb_cal.v_inc[ch][k]     = value;
1492        value         = parse_f32(bytestream, pos);
1493        rb_cal.tbin[ch][k]      = value;
1494      }
1495    }
1496    if rb_cal.serialize_event_data {
1497      let broken_event = RBEvent::new();
1498      let n_noi  = parse_u16(bytestream, pos);
1499      debug!("Found {n_noi} no input data events!");
1500      for _ in 0..n_noi {
1501        match RBEvent::from_bytestream(bytestream, pos) {
1502          Ok(ev) => {
1503            rb_cal.noi_data.push(ev);            
1504          }
1505          Err(err) => {
1506            error!("Unable to read RBEvent! {err}");
1507          }
1508        }
1509        // FIXME - broken event won't advance the pos marker
1510      }
1511      let n_vcal = parse_u16(bytestream, pos); 
1512      debug!("Found {n_vcal} VCal data events!");
1513      for _ in 0..n_vcal {
1514        match RBEvent::from_bytestream(bytestream, pos) {
1515          Err(err) => {
1516            error!("Found broken event {err}");
1517          },
1518          Ok(good_ev) => {
1519            rb_cal.vcal_data.push(good_ev);
1520          }
1521        }
1522      }
1523      let n_tcal = parse_u16(bytestream, pos); 
1524      debug!("Found {n_tcal} TCal data events!");
1525      for _ in 0..n_tcal {
1526        rb_cal.tcal_data.push(RBEvent::from_bytestream(bytestream, pos).unwrap_or(broken_event.clone()));
1527      }
1528    } else {
1529      // we can skip the next 6 bytes, 
1530      // which just contain 0
1531      *pos += 6;
1532    }
1533    if parse_u16(bytestream, pos) != Self::TAIL {
1534      return Err(SerializationError::TailInvalid {});
1535    }
1536    Ok(rb_cal)
1537  }
1538
1539  fn to_bytestream(&self) -> Vec<u8> {
1540    let mut bs = Vec::<u8>::with_capacity(Self::SIZE);
1541    bs.extend_from_slice(&Self::HEAD.to_le_bytes());
1542    bs.extend_from_slice(&self.rb_id.to_le_bytes());
1543    bs.extend_from_slice(&self.d_v.to_le_bytes());
1544    bs.extend_from_slice(&self.timestamp.to_le_bytes());
1545    let serialize_event_data = self.serialize_event_data as u8;
1546    bs.push(serialize_event_data);
1547    for ch in 0..NCHN {
1548      for k in 0..NWORDS {
1549        bs.extend_from_slice(&self.v_offsets[ch][k].to_le_bytes());
1550        bs.extend_from_slice(&self.v_dips[ch][k]   .to_le_bytes());
1551        bs.extend_from_slice(&self.v_inc[ch][k]    .to_le_bytes());
1552        bs.extend_from_slice(&self.tbin[ch][k]     .to_le_bytes());
1553      }
1554    }
1555    if self.serialize_event_data {
1556      info!("Serializing calibration event data!");
1557      let n_noi  = self.noi_data.len()  as u16;
1558      let n_vcal = self.vcal_data.len() as u16;
1559      let n_tcal = self.tcal_data.len() as u16;
1560      bs.extend_from_slice(&n_noi.to_le_bytes());
1561      for ev in &self.noi_data {
1562        bs.extend_from_slice(&ev.to_bytestream());
1563      }
1564      bs.extend_from_slice(&n_vcal.to_le_bytes());
1565      for ev in &self.vcal_data {
1566        bs.extend_from_slice(&ev.to_bytestream());
1567      }
1568      bs.extend_from_slice(&n_tcal.to_le_bytes());
1569      for ev in &self.tcal_data {
1570        bs.extend_from_slice(&ev.to_bytestream());
1571      }
1572    } else { // if we don't serialize event data, write 0 
1573             // for the empty data
1574      // (3 16bit 0s) for noi, vcal, tcal
1575      for _ in 0..6 {
1576        bs.push(0);
1577      }
1578      //bs.push(0); // noi data
1579      //bs.push(0); // vcal data
1580      //bs.push(0); // tcal data
1581    }
1582    bs.extend_from_slice(&Self::TAIL.to_le_bytes());
1583    bs
1584  }
1585}
1586
1587impl Default for RBCalibrations {
1588  fn default() -> Self {
1589    Self::new(0)
1590  }
1591}
1592
1593impl fmt::Display for RBCalibrations {
1594  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1595    let mut timestamp_str = String::from("?");
1596    match Utc.timestamp_opt(self.timestamp.into(), 0) {
1597      LocalResult::Single(datetime_utc) => {
1598        timestamp_str = datetime_utc.format("%Y/%m/%d %H:%M:%S").to_string();
1599      },
1600      LocalResult::Ambiguous(_, _) => {
1601        println!("The given timestamp is ambiguous.");
1602      },
1603      LocalResult::None => {
1604        println!("The given timestamp is not valid.");
1605      },
1606    }
1607
1608    //let datetime_utc: DateTime<Utc> = Utc.timestamp_opt(self.timestamp as i64, 0).earliest().unwrap_or(DateTime::<Utc>::from_timestamp_millis(0).unwrap());
1609    if !self.check() {
1610      return write!(f, "<RBCalibrations [{} UTC] for board {}:
1611   -- fails self checks!>", timestamp_str, self.rb_id);
1612    }  
1613    write!(f, 
1614  "<RBCalibrations [{} UTC]:
1615      ** all self checks passed! ** 
1616      RB             : {}
1617      VCalData       : {} (events)
1618      TCalData       : {} (events)
1619      NoInputData    : {} (events)
1620      V Offsets [ch0]: .. {:?} {:?} ..
1621      V Incrmts [ch0]: .. {:?} {:?} ..
1622      V Dips    [ch0]: .. {:?} {:?} ..
1623      T Bins    [ch0]: .. {:?} {:?} ..>",
1624      timestamp_str,
1625      self.rb_id,
1626      self.vcal_data.len(),
1627      self.tcal_data.len(),
1628      self.noi_data.len(),
1629      self.v_offsets[0][98],
1630      self.v_offsets[0][99],
1631      self.v_inc[0][98],
1632      self.v_inc[0][99],
1633      self.v_dips[0][98],
1634      self.v_dips[0][99],
1635      self.tbin[0][98],
1636      self.tbin[0][99])
1637  } 
1638}
1639
1640#[cfg(feature = "pybindings")] 
1641#[pymethods]
1642impl RBCalibrations {
1643  #[getter]
1644  fn rb_id(&self) -> u8 {
1645    self.rb_id
1646  }
1647
1648  #[getter]
1649  fn d_v(&self) -> f32 {
1650    self.d_v
1651  }
1652
1653  #[getter]
1654  fn vcal_data(&self) -> Vec<RBEvent> {
1655    self.vcal_data.clone()
1656  }
1657  
1658  #[getter]
1659  fn tcal_data(&self) -> Vec<RBEvent> {
1660    self.tcal_data.clone()
1661  }
1662  
1663  #[getter]
1664  fn noi_data(&self) -> Vec<RBEvent> {
1665    self.noi_data.clone()
1666  }
1667 
1668  #[getter]
1669  fn v_offsets<'_py>(&self, py: Python<'_py>) -> PyResult<Bound<'_py, PyArray2<f32>>> {  
1670    let mut data = Vec::<Vec<f32>>::with_capacity(9);
1671    for ch in 0..9 {
1672      data.push(self.v_offsets[ch].to_vec());
1673    }
1674    let pyarray = PyArray2::from_vec2(py, &data).unwrap();
1675    Ok(pyarray)
1676  }
1677  
1678  #[getter]
1679  fn v_dips<'_py>(&self, py: Python<'_py>) -> PyResult<Bound<'_py, PyArray2<f32>>> {  
1680    let mut data = Vec::<Vec<f32>>::with_capacity(9);
1681    for ch in 0..9 {
1682      data.push(self.v_dips[ch].to_vec());
1683    }
1684    let pyarray = PyArray2::from_vec2(py, &data).unwrap();
1685    Ok(pyarray)
1686  }
1687  
1688  #[getter]
1689  fn v_inc<'_py>(&self, py: Python<'_py>) -> PyResult<Bound<'_py, PyArray2<f32>>> {  
1690    let mut data = Vec::<Vec<f32>>::with_capacity(9);
1691    for ch in 0..9 {
1692      data.push(self.v_inc[ch].to_vec());
1693    }
1694    let pyarray = PyArray2::from_vec2(py, &data).unwrap();
1695    Ok(pyarray)
1696  }
1697  
1698  #[getter]
1699  fn tbin<'_py>(&self, py: Python<'_py>) -> PyResult<Bound<'_py, PyArray2<f32>>> {  
1700    let mut data = Vec::<Vec<f32>>::with_capacity(9);
1701    for ch in 0..9 {
1702      data.push(self.tbin[ch].to_vec());
1703    }
1704    let pyarray = PyArray2::from_vec2(py, &data).unwrap();
1705    Ok(pyarray)
1706  }
1707  
1708  /// Apply the voltage calibration to a single channel 
1709  /// FIXME - mixing of naming conventions for the channels
1710  ///
1711  /// FIXME - make it return Result<(), CalibrationError>
1712  ///
1713  /// # Arguments
1714  ///
1715  /// * channel   : Channel id 1-9
1716  /// * stop_cell : This channels stop cell 
1717  /// * adc       : Uncalibrated channel data
1718  /// * waveform  : Pre-allocated array to hold 
1719  ///               calibrated waveform data.
1720  #[pyo3(name="voltages")]
1721  pub fn voltages_py<'_py>(&self,
1722                           py        : Python<'_py>,
1723                           channel   : usize,
1724                           stop_cell : usize,
1725                           adc       : Bound<'_py, PyArray1<u16>>)
1726      -> PyResult<Bound<'_py, PyArray1<f32>>>{
1727                  //waveform  : &mut [f32;NWORDS]) {
1728    let mut voltages = vec![0.0f32; 1024];
1729    let adc_data = adc.to_vec().unwrap();
1730    self.voltages(channel, stop_cell, &adc_data, &mut voltages); 
1731    let pyarray = PyArray1::from_vec(py, voltages);
1732    Ok(pyarray)
1733  }
1734  
1735  /// Apply the timing calibration to a single channel 
1736  /// 
1737  /// This will allocate the array for the waveform 
1738  /// time bins (unit is ns)
1739  ///
1740  /// # Arguments
1741  ///
1742  /// * channel   : Channel id 1-9
1743  /// * stop_cell : This channels stop cell 
1744  #[pyo3(name="nanoseconds")]
1745  pub fn nanoseconds_py<'_py>(&self,
1746                              py        : Python<'_py>,                           
1747                              channel   : usize,
1748                              stop_cell : usize) 
1749      -> PyResult<Bound<'_py, PyArray1<f32>>> {
1750    let mut times = vec![0.0f32; 1024];
1751    self.nanoseconds(channel, stop_cell, &mut times); 
1752    let pyarray = PyArray1::from_vec(py, times);
1753    Ok(pyarray)
1754  }
1755
1756  /// Load the calibration from a file with a 
1757  /// TofPacket of type RBCalibration in it
1758  ///
1759  /// # Arguments:
1760  ///
1761  /// * filename     : File with a TofPacket of type RBCalibration in it
1762  /// * discard_data : Throw away event data after loading
1763  #[pyo3(name = "from_file", signature = (filename, discard_data = true))]
1764  #[staticmethod]
1765  fn from_file_py(filename : String, discard_data : bool) -> PyResult<Self> {
1766    let cali = RBCalibrations::from_file(filename, discard_data);
1767    match cali {
1768      Ok(c) => {
1769        return Ok(c);
1770      },
1771      Err(err) => {
1772        return Err(PyValueError::new_err(err.to_string()));
1773      }
1774    }
1775  }
1776
1777  #[pyo3(name="passes_voltage_checks")]
1778  fn passes_voltage_checks_py(&self) -> bool {
1779    self.passes_voltage_checks() 
1780  }
1781
1782  #[pyo3(name="passes_timing_checks")]
1783  fn passes_timing_checks_py(&self) -> bool {
1784    self.passes_timing_checks()
1785  }
1786
1787  #[pyo3(name="check")]
1788  fn check_py(&self) -> bool {
1789    self.check()
1790  }
1791
1792  #[pyo3(name="assemble_from_flightcal")]
1793  #[staticmethod]
1794  /// Re-assemble a RBCalibration from chopped up parts
1795  pub fn assemble_from_flightcal_py(fcal_t : RBCalibrationFlightT,
1796                                    fcal_v : RBCalibrationFlightV) -> Self {
1797    Self::assemble_from_flightcal(fcal_t, fcal_v).unwrap()
1798  }
1799}
1800
1801#[cfg(feature = "pybindings")]
1802pythonize_packable_no_new!(RBCalibrations);
1803
1804#[cfg(feature = "random")]
1805impl FromRandom for RBCalibrations {
1806    
1807  fn from_random() -> Self {
1808    let mut cali   = Self::new(0);
1809    let mut rng    = rand::rng();
1810    cali.rb_id     = rng.random::<u8>();
1811    cali.d_v       = rng.random::<f32>(); 
1812    cali.serialize_event_data = rng.random::<bool>();
1813    for ch in 0..NCHN {
1814      for n in 0..NWORDS { 
1815        cali.v_offsets[ch][n] = rng.random::<f32>();
1816        cali.v_dips   [ch][n] = rng.random::<f32>(); 
1817        cali.v_inc    [ch][n] = rng.random::<f32>(); 
1818        cali.tbin     [ch][n] = rng.random::<f32>();
1819      }
1820    }
1821    if cali.serialize_event_data {
1822      for _ in 0..1000 {
1823        let mut ev = RBEvent::from_random();
1824        cali.vcal_data.push(ev);
1825        ev = RBEvent::from_random();
1826        cali.noi_data.push(ev);
1827        ev = RBEvent::from_random();
1828        cali.tcal_data.push(ev);
1829      }
1830    }
1831    cali
1832  }
1833}
1834
1835//-----------------------------------------------
1836
1837#[cfg(feature = "random")]
1838#[test]
1839fn serialization_rbcalibration_noeventpayload() {
1840  let mut calis = Vec::<RBCalibrations>::new();
1841  for _ in 0..100 {
1842    let cali = RBCalibrations::from_random();
1843    if cali.serialize_event_data {
1844      continue;
1845    }
1846    calis.push(cali);
1847    break;
1848  }
1849  let test = RBCalibrations::from_bytestream(&calis[0].to_bytestream(), &mut 0).unwrap();
1850  assert_eq!(calis[0], test);
1851}
1852
1853#[cfg(feature = "random")]
1854#[test]
1855fn serialization_rbcalibration_witheventpayload() {
1856  loop {
1857    let cali = RBCalibrations::from_random();
1858    if !cali.serialize_event_data {
1859      continue;
1860    }
1861    let mut test = RBCalibrations::from_bytestream(&cali.to_bytestream(), &mut 0).unwrap();
1862    for k in &mut test.vcal_data {
1863      k.creation_time = None; 
1864    }
1865    for k in &mut test.tcal_data {
1866      k.creation_time = None; 
1867    }
1868    for k in &mut test.noi_data {
1869      k.creation_time = None; 
1870    }
1871    assert_eq!(cali, test);
1872    break;
1873  }
1874}
1875
1876#[cfg(feature = "random")]
1877#[test]
1878fn pack_rbcalibrationfilghtt() {
1879  for _ in 0..100 {
1880    let cfg  = RBCalibrationFlightT::from_random();
1881    let test : RBCalibrationFlightT = cfg.pack().unpack().unwrap();
1882    assert_eq!(cfg, test);
1883  }
1884}
1885
1886#[cfg(feature = "random")]
1887#[test]
1888fn pack_rbcalibfilghtv() {
1889  for _ in 0..100 {
1890    let cfg  = RBCalibrationFlightV::from_random();
1891    let test : RBCalibrationFlightV = cfg.pack().unpack().unwrap();
1892    assert_eq!(cfg, test);
1893  }
1894}
1895
1896#[cfg(feature = "random")]
1897#[test]
1898fn assemble_flightcal() {
1899  for _ in 0..10 {
1900    let cal  = RBCalibrations::from_random();
1901    let fct  = cal.emit_flighttcal();
1902    let fcv  = cal.emit_flightvcal();
1903    let test = RBCalibrations::assemble_from_flightcal(fct, fcv).unwrap();
1904    assert_eq!(cal.rb_id, test.rb_id);
1905    assert_eq!(cal.d_v  , test.d_v);
1906    assert_eq!(cal.timestamp, test.timestamp);
1907    assert_eq!(test.serialize_event_data, false);
1908    for ch in 0..NCHN {
1909      for k in 0..NWORDS {
1910        assert_eq!(f16::from_f32(cal.tbin[ch][k]),     f16::from_f32(test.tbin[ch][k])); 
1911        assert_eq!(f16::from_f32(cal.v_offsets[ch][k]),f16::from_f32(test.v_offsets[ch][k])); 
1912        assert_eq!(f16::from_f32(cal.v_dips[ch][k]),   f16::from_f32(test.v_dips[ch][k])); 
1913        assert_eq!(f16::from_f32(cal.v_inc[ch][k]),    f16::from_f32(test.v_inc[ch][k]));
1914      }
1915    }
1916  }
1917}
1918
1919