Skip to main content

gondola_core/tracker/
online_calibration.rs

1// This file is part of gaps-online-software and published 
2// under the GPLv3 license
3
4use crate::prelude::*;
5
6use std::io::BufRead;
7#[cfg(feature="pybindings")]
8use std::path::PathBuf;
9
10/// The tracker online (in-flight) calibration. This converts 
11/// adc to energy,  according to a transfer function. 
12/// There is only a single transfer function for all the strips
13#[cfg_attr(feature="pybindings", pyclass)] 
14pub struct TrackerOnlineCalibration {
15  pub tf_map     : HashMap<u32,TrackerStripTransferFunction>, 
16  pub ped_map    : HashMap<u32,f32>,
17  pub pulser_map : HashMap<u32,bool>
18}
19
20impl TrackerOnlineCalibration {
21
22  pub fn new() -> Self {
23    Self {
24      tf_map     : HashMap::<u32,TrackerStripTransferFunction>::new(), 
25      ped_map    : HashMap::<u32,f32>::new(),
26      pulser_map : HashMap::<u32,bool>::new()
27    }
28  }
29
30  /// Load the online tracker calibration from the file 
31  /// as it was used in the GAPSI flight.
32  pub fn from_default() -> Self {
33    let cali_path  = env::var("GONDOLA_TRK_ONLINE_CAL").unwrap_or_else(|_| "".to_string());
34    Self::from_file(&cali_path)
35  }
36
37  //pub fn from_file(fname : &str) -> Self {
38  pub fn from_file<P: AsRef<Path>>(path: P) -> Self {
39    let mut cali = Self::new();
40    let file_res = File::open(path);
41    match file_res {
42      Err(_err) => {
43        error!("Unable to open file!");
44        return cali; 
45      }
46      Ok(file) => {
47        let reader = BufReader::new(file);
48        //let mut data = Vec::new();
49
50        // Use .skip(5) to bypass the header lines
51        for line in reader.lines().skip(5) {
52          let line = line.unwrap();
53          let trimmed = line.trim();
54          if trimmed.is_empty() {
55            continue;
56          }
57          let row: Vec<f32> = trimmed
58            .split(',')
59            .map(|s| s.trim().parse::<f32>().unwrap_or(0.0))
60            .collect();
61          let mut strip = TrackerStrip::new();
62          strip.layer   = row[0] as i32;
63          strip.row     = row[1] as i32;
64          strip.module  = row[2] as i32;
65          strip.channel = row[3] as i32;
66          let mut tf    = TrackerStripTransferFunction::new();
67          let strip_id  = strip.get_stripid();
68          tf.strip_id   = strip_id as i32;
69          tf.pol_a2_0   = row[4]; 
70          tf.pol_a2_1   = row[5];    
71          tf.pol_a2_2   = row[6]; 
72          tf.pol_b3_0   = row[7]; 
73          tf.pol_b3_1   = row[8]; 
74          tf.pol_b3_2   = row[9]; 
75          tf.pol_b3_3   = row[10]; 
76          tf.pol_c3_0   = row[11]; 
77          tf.pol_c3_1   = row[12]; 
78          tf.pol_c3_2   = row[13]; 
79          tf.pol_c3_3   = row[14]; 
80          tf.pol_d3_0   = row[15];     
81          tf.pol_d3_1   = row[16]; 
82          tf.pol_d3_2   = row[17]; 
83          tf.pol_d3_3   = row[18]; 
84          let pedestal  = row[19];
85          let is_puls   = row[20] > 0.0;
86          cali.tf_map.insert(strip_id, tf);
87          cali.pulser_map.insert(strip_id, is_puls);
88          cali.ped_map.insert(strip_id, pedestal as f32);
89          //data.push(row);
90        }
91      }
92    }
93    cali
94  }
95
96  /// Fill out the energy field in the tracker hit
97  /// 
98  /// FIXME - this panics if the calibration is not 
99  /// loaded properly
100  pub fn calibrate(&self, hit : &mut TrackerHit) {
101    let strip_id = hit.get_stripid();
102    let scale   : f32 = 0.841/1000.0;
103    let mut adc : f32 = hit.adc as f32 - self.ped_map[&strip_id];
104    if adc > 1500.0 {
105      adc = 1500.0;
106    } 
107    hit.energy = scale*self.tf_map[&strip_id].evaluate(adc);
108  }
109
110  pub fn is_pulsed(&self, hit : &TrackerHit) -> bool {
111    self.pulser_map[&hit.get_stripid()] 
112  }
113} 
114
115#[cfg(feature="pybindings")]
116#[pymethods]
117impl TrackerOnlineCalibration {
118
119  #[new]
120  fn new_py() -> Self {
121    Self::new()
122  }
123 
124  /// Fill out the energy field in the tracker hit
125  /// 
126  /// FIXME - this panics if the calibration is not 
127  /// loaded properly
128  ///
129  /// # Arguments: 
130  ///   * hit : A tracker hit. This hit will have its 
131  ///           .energy field populated
132  #[pyo3(name="calibrate")]
133  fn calibrate_py(&self, hit : &mut TrackerHit) {
134    self.calibrate(hit);
135  }
136
137  #[pyo3(name="is_pulsed")]
138  fn is_pulsed_py(&self, hit : &TrackerHit) -> bool {
139    self.is_pulsed(hit)
140  }
141  
142  /// Load the online tracker calibration from the file 
143  /// as it was used in the GAPSI flight.
144  ///
145  /// FIXME - if this fails, we won't know
146  #[staticmethod]
147  #[pyo3(name="from_default")]
148  pub fn from_default_py() -> PyResult<Self> {
149    let cali_path  = env::var("GONDOLA_TRK_ONLINE_CAL").unwrap_or_else(|_| "".to_string());
150    Ok(Self::from_file(&cali_path))
151  }
152
153  #[staticmethod]
154  #[pyo3(name="from_file")]
155  fn from_file_py(path: String) -> PyResult<Self> {
156    // Convert the Python String to a PathBuf
157    let path_buf = PathBuf::from(path);
158    Ok(Self::from_file(path_buf))
159  }
160}
161