tof_dataclasses/
analysis.rs

1//! Implementations of analysis engine
2//! This is based on the original code 
3//! by J.Zweerink
4//!
5  
6use crate::errors::WaveformError;
7use crate::constants::{
8    NWORDS,
9    C_LIGHT_PADDLE,
10};
11
12#[cfg(feature="advanced-algorithms")]
13extern crate smoothed_z_score;
14#[cfg(feature="advanced-algorithms")]
15use smoothed_z_score::{Peak, PeaksDetector, PeaksFilter};
16
17/// Return the bin with the maximum ADC value
18pub fn get_max_bin(voltages    : &Vec<f32>,
19                   lower_bound : usize,
20                   window      : usize) -> Result<usize, WaveformError> {
21  if lower_bound >= voltages.len() {
22    error!("Invalid value for lower_bound {}", lower_bound);
23    return Err(WaveformError::OutOfRangeLowerBound);
24  }
25  if lower_bound + window >= voltages.len() {
26    error!("Lower bound {} + window {} is too large!", lower_bound, window); 
27    return Err(WaveformError::OutOfRangeUpperBound);
28  }
29  let mut maxval = voltages[lower_bound];
30  let mut maxbin = lower_bound;
31  for n in lower_bound..lower_bound + window {
32    if voltages[n] > maxval {
33      maxval  = voltages[n];
34      maxbin  = n;
35    }
36  } // end for
37  trace!("Got maxbin {} with a value of {}", maxbin, maxval);
38  Ok(maxbin)
39} // end fn
40
41///
42///
43///
44///
45pub fn interpolate_time (voltages      : &Vec<f32>,
46                         nanoseconds   : &Vec<f32>, 
47                         mut threshold : f32,
48                         mut idx       : usize,
49                         size          : usize) -> Result<f32, WaveformError> {
50  if idx + 1 > nanoseconds.len() {
51    return Err(WaveformError::OutOfRangeUpperBound);
52  }
53  threshold     = threshold.abs();
54  let mut lval  = (voltages[idx]).abs();
55  let mut hval : f32 = 0.0; 
56  if size == 1 {
57    hval = (voltages[idx+1]).abs();
58  } else {
59    for n in idx+1..idx+size {
60      hval = voltages[n].abs();
61      if (hval>=threshold) && (threshold<=lval) { // Threshold crossing?
62        idx = n-1; // Reset idx to point before crossing
63        break;
64      }
65      lval = hval;
66    }
67  }
68  if ((lval > threshold) && (size != 1)) || lval == hval {
69    return Ok(nanoseconds[idx]);
70  } else {
71    return Ok(nanoseconds[idx] 
72          + (threshold-lval)/(hval-lval) * (nanoseconds[idx+1]
73          - nanoseconds[idx]));
74  }
75}
76
77
78
79/// Integrate a waveform
80///
81/// That this works right, prior to the 
82/// integration we should subtract the 
83/// baseline.
84///
85/// # Arguments:
86///
87/// * impedance : typically this is 
88pub fn integrate(voltages     : &Vec<f32>,
89                 nanoseconds  : &Vec<f32>,
90                 lo_bin       : usize,
91                 upper_bin    : usize,
92                 impedance    : f32) -> Result<f32, WaveformError>  {
93  //if lower_bound < 0.0 { 
94  //  return Err(WaveformError::NegativeLowerBound);
95  //}
96  //let lo_bin          = time2bin(nanoseconds,lower_bound)?;
97  //let mut size_bin    = time2bin(nanoseconds,lower_bound + size)?;
98  //println!("lower bound {}, lo bin {}, size bin {}", lower_bound, lo_bin, size_bin);
99  //size_bin = size_bin - lo_bin;
100  //if lo_bin + size_bin > voltages.len() {
101  //  warn!("Limiting integration range to waveform size!");
102  //  size_bin = voltages.len() - lo_bin;
103  //}
104  if upper_bin > voltages.len() {
105    return Err(WaveformError::OutOfRangeUpperBound);
106  }
107  if lo_bin < 1 {
108    return Err(WaveformError::OutOfRangeLowerBound);
109  }
110  let mut sum = 0f32;
111  //let upper_bin = lo_bin + size_bin;
112  for n in lo_bin..upper_bin {
113    sum += voltages[n] * (nanoseconds[n] - nanoseconds[n-1]) ;
114  }
115  sum /= impedance;
116  Ok(sum)
117}
118
119/// Given a time in ns, find the bin most closely corresponding to that time
120/// # Arguments
121/// 
122pub fn time2bin(nanoseconds : &Vec<f32>,
123                t_ns        : f32) -> Result<usize, WaveformError> {
124  for n in 0..nanoseconds.len() {
125    if nanoseconds[n] > t_ns {
126      return Ok(n-1);
127    }
128  }
129  debug!("Did not find a bin corresponding to the given time {}!", t_ns);
130  return Err(WaveformError::TimesTooSmall);
131}
132
133/// The pedestal is the baseline of the waveform
134///
135/// # Arguments
136///
137/// * voltages      : calibrated waveform
138/// * threshold     : consider everything below threshold
139///                   the pedestal (typical 10mV)
140/// * ped_begin_bin : beginning of the window for pedestal
141///                   calculation (bin)
142/// * ped_range_bin : length of the window for pedestal
143///                   calculation (in bins)
144///
145/// # Return
146/// pedestal value with error (quadratic error)
147pub fn calculate_pedestal(voltages      : &Vec<f32>,
148                          threshold     : f32,
149                          ped_begin_bin : usize,
150                          ped_range_bin : usize) -> (f32,f32) {
151  let mut sum  = 0f32;
152  let mut sum2 = 0f32;
153  for k in ped_begin_bin..ped_begin_bin + ped_range_bin {
154    if f32::abs(voltages[k]) < threshold {
155      sum  += voltages[k];
156      sum2 += voltages[k]*voltages[k];
157    }
158  }
159  let average = sum/(ped_range_bin as f32);
160  let sigma   = f32::sqrt(sum2/(ped_range_bin as f32 - (average*average)));
161  (average, sigma)
162}
163
164/// Find the onset time of a peak with a 
165/// constant fraction discrimination method.
166///
167/// The peaks have to be sane
168/// FIXME: Maybe introduce a separate check?
169pub fn cfd_simple(voltages    : &Vec<f32>,
170                  nanoseconds : &Vec<f32>,
171                  cfd_frac    : f32,
172                  start_peak  : usize,
173                  end_peak    : usize) -> Result<f32, WaveformError> {
174
175  let idx = get_max_bin(voltages, start_peak, end_peak-start_peak)?;
176  let mut sum : f32 = 0.0;
177  for n in idx-1..idx+1{
178    sum += voltages[n];
179  }
180  let tmp_thresh : f32 = f32::abs(cfd_frac * (sum / 3.0));
181  trace!("Calculated tmp threshold of {}", tmp_thresh);
182  // Now scan through the waveform around the peak to find the bin
183  // crossing the calculated threshold. Bin idx is the peak so it is
184  // definitely above threshold. So let's walk backwards through the
185  // trace until we find a bin value less than the threshold.
186  let mut lo_bin : usize = voltages.len();
187  let mut n = idx;
188  if idx < start_peak {
189    error!("The index {} is smaller than the beginning of the peak {}!", idx, start_peak);
190    return Err(WaveformError::OutOfRangeLowerBound);
191  }
192  if start_peak >= 10 {
193    while n > start_peak - 10 {
194    //for n in (idx..start_peak - 10).rev() {
195      if f32::abs(voltages[n]) < tmp_thresh {
196        lo_bin = n;
197        break;
198      }
199      n -= 1;
200    }  
201  } else {
202    debug!("We require that the peak is at least 10 bins away from the start!");
203    return Err(WaveformError::OutOfRangeLowerBound);
204  }
205
206  trace!("Lo bin {} , start peak {}", lo_bin, start_peak);
207  let cfd_time : f32;
208  if lo_bin < nanoseconds.len() -1 {
209    cfd_time = interpolate_time(voltages, nanoseconds, tmp_thresh, lo_bin, 1)?;  
210  } else {
211    cfd_time = nanoseconds[nanoseconds.len() - 1];
212  } 
213  Ok(cfd_time)
214}
215
216/// Find peaks in a given time window (in ns) by 
217/// comparing the waveform voltages with the 
218/// given threshold. 
219///
220/// #Arguments:
221/// * start_time     : begin to look for peaks after 
222///                    this (local) waveform time 
223/// * window_size    : (in ns)
224/// * min_peak_width : minimum number of consequtive bins
225///                    which have to be over threshold
226///                    so that it is considered a peak
227/// * threshold      : peaks are found when voltages go
228///                    over threshold for at leas
229///                    min_peak_width bins
230/// * max_peaks      : stop algorithm after max_peaks are
231///                    found, the rest will be ignored
232/// #Returns:
233/// 
234/// Vec<(peak_begin_bin, peak_end_bin)>
235///
236pub fn find_peaks(voltages       : &Vec<f32>,
237                  nanoseconds    : &Vec<f32>,
238                  start_time     : f32,
239                  window_size    : f32,
240                  min_peak_width : usize,
241                  threshold      : f32,
242                  max_peaks      : usize)
243-> Result<Vec<(usize,usize)>, WaveformError> {
244  let mut peaks      = Vec::<(usize,usize)>::new();
245  let mut start_bin  = time2bin(nanoseconds, start_time)?;
246  if start_bin <= 10 {
247    debug!("We deliberatly do not search for peaks within the first 10 bins! Correcting..");
248    start_bin = 10;
249  }
250  let window_bin = time2bin(nanoseconds, start_time + window_size)? - start_bin;
251  if start_bin + window_bin > voltages.len () {
252    return Err(WaveformError::OutOfRangeUpperBound);
253  }
254
255  let mut pos = 0usize;
256  // find the first bin when voltage
257  // goes over threshold
258  for k in start_bin..start_bin + window_bin {
259    if voltages[k] >= threshold {
260      pos = k;
261      break;
262    }
263  }
264  if pos == 0 && start_bin == 0 && voltages[pos] < threshold {
265    // waveform did not cross threshold
266    return Err(WaveformError::DidNotCrossThreshold)
267  }
268  // actual peak finding
269  let mut nbins_peak   = 0usize;
270  let mut begin_peak   = pos;
271  let mut end_peak  : usize;
272  if (pos + window_bin) > voltages.len() {
273    return Err(WaveformError::OutOfRangeUpperBound);
274  }
275  for k in pos..(pos + window_bin) {
276    if voltages[k] >= threshold {
277      nbins_peak += 1;
278      let mut slope = 0i16; // slope can be positive (1)
279                            // or negative (-1)
280                            // as soon as the slope turns, 
281                            // we declare the peak over, 
282                            // if it is still positive, we
283                            // continue to count the bins
284      if nbins_peak == min_peak_width {
285        // in this case, we don't care about the slope
286        begin_peak  = k - min_peak_width -1;
287      } else if nbins_peak > min_peak_width {
288        for j in 0..min_peak_width {
289          if voltages[k -j] > voltages[k-j-1] {
290            slope = 1; // still ascending
291          }
292        }
293        if slope == 1 {
294          // we consider this the same peak
295          continue;
296        } 
297        if slope == 0 {
298          // each bump counts as separate peak
299          end_peak = k;
300          nbins_peak = 0; // peak is done
301          peaks.push((begin_peak, end_peak));
302          if peaks.len() == max_peaks {
303            break;
304          }
305        }
306      } // if nbins_peak < min_peak_width, we just 
307        // continue going to check if it is still 
308        // over threshold
309    } else {
310      if nbins_peak > min_peak_width {
311        end_peak = k;
312        peaks.push((begin_peak, end_peak));
313        if peaks.len() == max_peaks {
314          break;
315        }
316      }
317      nbins_peak = 0;
318    }
319  }
320  // FIXME - remove invalid peaks
321  let len_pks_dirty = peaks.len();
322  peaks.retain(|&x| {(x.0 < NWORDS - 1) & (x.1 <= NWORDS - 1)});
323  let len_pks_clean = peaks.len();
324  if len_pks_clean != len_pks_dirty {
325    debug!("We removed {} pks because they had values outside of 0-{}!", len_pks_dirty - len_pks_clean, NWORDS);
326  }
327  Ok(peaks)
328}
329
330/// An approximation to calculate the energy deposition as used by
331/// Philip/Jamie/Jeff
332pub fn calc_edep_simple(peak_voltage : f32) -> f32 {
333  (-1000.0 * peak_voltage) / (21.0 * peak_voltage - 35260.0)
334}
335
336
337/// Calculate the interaction time based on the peak timings measured 
338/// at the paddle ends A and B
339///
340/// # Arguments
341///
342/// * t_a           : (absolute) timing for the peak measured at A side
343/// * t_b           : (absolute) timing for the peak measured at B side
344/// * paddle_length : the length of the paddle in cm
345pub fn get_paddle_t0(t_a : f32, t_b : f32, paddle_length : f32) -> f32 {
346  0.5*(t_a + t_b - (paddle_length/(10.0*C_LIGHT_PADDLE)))
347}
348
349/// Calculate the distance from the A side
350/// We will Always use the A side to measure
351/// "pos_accross"
352///
353/// Returns:
354///   Distance from "A" side (in mm)
355pub fn pos_across(t_a : f32, t0 : f32) -> f32 {
356  (t_a - t0)*C_LIGHT_PADDLE*10.0 // 10 for cm->mm 
357}
358
359#[cfg(feature = "advanced-algorithms")]
360fn find_sequence_ranges(vec: Vec<usize>) -> Vec<(usize, usize)> {
361  let mut ranges = Vec::new();
362  let mut start = vec[0];
363  let mut end   = vec[0];
364
365  for &value in vec.iter().skip(1) {
366    if value == end + 1 {
367      // Extend the current sequence
368      end = value;
369    } else {
370      // End of current sequence, start of a new one
371      ranges.push((start, end));
372      start = value;
373      end = value;
374    }
375  }
376
377  // Add the last sequence
378  ranges.push((start, end));
379  ranges
380}
381
382#[cfg(feature = "advanced-algorithms")]
383/// Z-scores peak finding algorithm
384///
385/// Brakel, J.P.G. van (2014).
386/// "Robust peak detection algorithm using z-scores". 
387/// Stack Overflow.
388/// Available at: <https://stackoverflow.com/questions/22583391/peak-signal-detection-in-realtime-timeseries-data/i22640362#22640362> (version: 2020-11-08).
389///
390/// Robust peak detection algorithm (using z-scores)
391///
392/// [..] algorithm that works very well for these types of datasets.
393/// It is based on the principle of dispersion:
394/// if a new datapoint is a given x number of standard deviations away
395/// from a moving mean, the algorithm gives a signal.
396/// The algorithm is very robust because it constructs a separate moving mean
397/// and deviation, such that previous signals do not corrupt
398/// the signalling threshold for future signals.
399/// The sensitivity of the algorithm is therefore robust to previous signals.
400///
401/// # Arguments:
402///
403/// * nanoseconds   : calibrated waveform times
404/// * voltages      : calibrated waveform voltages
405/// * start_time    : restrict the algorithm on a 
406///                   certain time window, start 
407///                   at start_time
408/// * window_size   : in ns
409/// * lag           : The lag of the moving window that calculates the mean
410///                   and standard deviation of historical data.
411///                   A longer window takes more historical data in account.
412///                   A shorter window is more adaptive,
413///                   such that the algorithm will adapt to new information
414///                   more quickly.
415///                   For example, a lag of 5 will use the last 5 observations
416///                   to smooth the data.
417/// * threshold     : The "z-score" at which the algorithm signals.
418///                   Simply put, if the distance between a new datapoint
419///                   and the moving mean is larger than the threshold
420///                   multiplied with the moving standard deviation of the data,
421///                   the algorithm provides a signal.
422///                   For example, a threshold of 3.5 will signal if a datapoint
423///                   is 3.5 standard deviations away from the moving mean. 
424/// * influence     : The influence (between 0 and 1) of new signals on
425///                   the calculation of the moving mean and moving standard deviation.
426///                   For example, an influence parameter of 0.5 gives new signals
427///                   half of the influence that normal datapoints have.
428///                   Likewise, an influence of 0 ignores signals completely
429///                   for recalculating the new threshold.
430///                   An influence of 0 is therefore the most robust option 
431///                   (but assumes stationarity);
432///                   putting the influence option at 1 is least robust.
433///                   For non-stationary data, the influence option should
434///                   therefore be put between 0 and 1.
435pub fn find_peaks_zscore(nanoseconds    : &Vec<f32>,
436                         voltages       : &Vec<f32>,
437                         start_time     : f32,
438                         window_size    : f32,
439                         lag            : usize,
440                         threshold      : f64,
441                         influence      : f64)
442-> Result<Vec<(usize,usize)>, WaveformError> {
443  let mut peaks = Vec::<(usize, usize)>::new();
444  let start_bin = time2bin(nanoseconds, start_time)?;
445  let end_bin   = time2bin(nanoseconds, start_time + window_size)?;
446  let mut ranged_voltage = Vec::<f32>::with_capacity(end_bin - start_bin);
447  ranged_voltage.extend_from_slice(&voltages[start_bin..=end_bin]);
448  //30, 5.0, 0.0
449
450  let output: Vec<_> = voltages
451            .into_iter()
452            .enumerate()
453            .peaks(PeaksDetector::new(lag, threshold, influence), |e| *e.1 as f64)
454            .map(|((i, _), p)| (i, p))
455            .collect();
456  // we ignore low peaks
457  if output.len() == 0 {
458    return Ok(peaks);
459  }
460  let mut peak_high = Vec::<usize>::new();
461  for k in output.iter() {
462    if matches!(k.1, Peak::High) {
463      peak_high.push(k.0);
464    }
465  }
466  if peaks.len() > 0 {
467    peaks = find_sequence_ranges(peak_high); 
468  }
469  Ok(peaks)
470}
471