tof_dataclasses/
calibrations.rs

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