gondola_core/
lib.rs

1//! Dataclasses provides structures to facilitate the work with data drom 
2//! the GAPS experiment. Most noticeably, there are
3//!
4//! * events       - TOF/Tracker data, TOF events on disk, MergedEvents send over telemetry
5//!
6//! * packets      - containers to serialize/deserialize the described structures so that 
7//!                  these can be stored on disk or send over the network
8//!
9//! * calibration  - TOF/Tracker related calibration routines and containers to hold results
10//! 
11//! * io           - read/write packets to/from disk or receive them over the network
12//! 
13//! * random       - random numbers for software tests
14//! 
15//! * tof          - Very specific TOF related code which does not fall under a different 
16//!                  category
17//! 
18//! * tracker      - Very specific TRK related code which does not fall under any other 
19//!                  category
20//!
21//! # features:
22//!
23//! * random - allow random number generated data classes for 
24//!            testing
25//!
26//! * database - access a data base for advanced paddle
27//!              mapping, readoutboard and ltb information etc.
28//!              This will introduce a dependency on sqlite and 
29//!              diesel
30//!
31//!
32// This file is part of gaps-online-software and published 
33// under the GPLv3 license
34
35#[macro_use] extern crate log; 
36
37pub mod prelude;
38#[cfg(feature="random")]
39pub mod random;
40pub mod constants;
41pub mod events;
42pub mod packets;
43pub mod version;
44pub mod io;
45pub mod calibration;
46pub mod errors;
47pub mod tof;
48pub mod tracker;
49pub mod monitoring;
50pub mod stats;
51#[cfg(feature="pybindings")]
52pub mod python;
53#[cfg(feature="database")]
54pub mod database;
55
56// python convention
57pub const VERSION: &str = env!("CARGO_PKG_VERSION"); 
58
59/// A simple helper macro adding an as_str function 
60/// as well as the Display method to any enum.
61///
62/// Avoids writing boilerplate
63#[macro_export]
64macro_rules! expand_and_test_enum {
65  ($name:ident, $test_name:ident) => {
66    impl fmt::Display for $name {
67      fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
68        write!(f, "<{}: {}>",stringify!($name), self.as_ref())
69      }
70    }
71
72    impl From<u8> for $name {
73      fn from(value: u8) -> Self {
74        match Self::from_repr(value)  {
75          None => {
76            return Self::Unknown;
77          }
78          Some(variant) => {
79            return variant;
80          }
81        }
82      }
83    }
84
85    #[cfg(feature="random")]
86    impl FromRandom for $name {
87      fn from_random() -> Self {
88        let mut choices = Vec::<Self>::new();
89        for k in Self::iter() {
90          choices.push(k);
91        }
92        let mut rng  = rand::rng();
93        let idx = rng.random_range(0..choices.len());
94        choices[idx]
95      }
96    }
97
98    #[test]
99    fn $test_name() {
100      for _ in 0..100 {
101        let data = $name::from_random();
102        assert_eq!($name::from(data as u8), data);
103      }
104    }
105  };
106}
107
108//---------------------------------------
109// helpers to init the logging system
110//
111
112use colored::{
113    Colorize,
114    ColoredString
115};
116use chrono::Utc;
117use log::Level;
118use std::io::Write;
119
120/// Make sure that the loglevel is in color, even though not using pretty_env logger
121pub fn color_log(level : &Level) -> ColoredString {
122  match level {
123    Level::Error    => String::from(" ERROR!").red(),
124    Level::Warn     => String::from(" WARN  ").yellow(),
125    Level::Info     => String::from(" Info  ").green(),
126    Level::Debug    => String::from(" debug ").blue(),
127    Level::Trace    => String::from(" trace ").cyan(),
128  }
129}
130
131/// Set up the environmental (env) logger
132/// with our format
133///
134/// Ensure that the lines and module paths
135/// are printed in the logging output
136pub fn init_env_logger() {
137  env_logger::builder()
138    .format(|buf, record| {
139    writeln!( buf, "[{ts} - {level}][{module_path}:{line}] {args}",
140      ts    = Utc::now().format("%Y/%m/%d-%H:%M:%SUTC"), 
141      level = color_log(&record.level()),
142      module_path = record.module_path().unwrap_or("<unknown>"),
143      line  = record.line().unwrap_or(0),
144      args  = record.args()
145      )
146    }).init();
147}
148
149//-------------------------------------------------
150// Build the python library
151
152#[cfg(feature="pybindings")]
153pub use pyo3::prelude::*; 
154#[cfg(feature="pybindings")]
155pub use pyo3::wrap_pymodule; 
156#[cfg(feature="pybindings")]
157pub use pyo3::wrap_pyfunction; 
158
159#[cfg(feature="pybindings")]
160#[pymodule]
161#[pyo3(name = "tof")]
162fn tof_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
163  use crate::tof::*;
164  m.add_class::<RBPaddleID>()?;
165  m.add_class::<TofDetectorStatus>()?;
166  m.add_class::<TofCommandCode>()?;
167  m.add_class::<TofCommand>()?;
168  m.add_class::<TofOperationMode>()?;
169  m.add_class::<BuildStrategy>()?;
170  m.add_class::<PreampBiasConfig>()?;
171  m.add_class::<RBChannelMaskConfig>()?;
172  m.add_class::<TriggerConfig>()?;
173  m.add_class::<TofRunConfig>()?;
174  m.add_class::<TofCuts>()?;
175  m.add_class::<PyMasterTrigger>()?;
176  m.add_function(wrap_pyfunction!(to_board_id_string, m)?)?;
177  Ok(())
178}
179
180#[cfg(feature="pybindings")]
181#[pymodule]
182#[pyo3(name = "tracker")]
183fn tracker_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
184  use crate::tracker::*;
185  //m.add_function(wrap_pyfunction!(mt_event_get_timestamp_abs48,m)?)?;
186  m.add_function(wrap_pyfunction!(strip_lines, m)?)?;
187  Ok(())
188}
189
190#[cfg(feature="pybindings")]
191#[pymodule]
192#[pyo3(name = "calibration")]
193fn calibration_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
194  use crate::calibration::tof::*;
195  m.add_class::<RBCalibrations>()?;
196  Ok(())
197}
198
199#[cfg(feature="pybindings")]
200#[pymodule]
201#[pyo3(name = "events")]
202fn events_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
203  use crate::events::*;
204  m.add_class::<TofHit>()?;
205  m.add_class::<TrackerHit>()?;
206  m.add_class::<RBEventHeader>()?;
207  m.add_class::<RBEvent>()?;
208  m.add_class::<RBWaveform>()?;
209  m.add_class::<EventStatus>()?;
210  m.add_class::<DataType>()?;
211  m.add_class::<TofEvent>()?;
212  m.add_class::<TelemetryEvent>()?;
213  m.add_function(wrap_pyfunction!(strip_id, m)?)?;
214  m.add_class::<EventQuality>()?;
215  m.add_class::<TriggerType>()?;
216  m.add_class::<LTBThreshold>()?;
217  m.add_function(wrap_pyfunction!(mt_event_get_timestamp_abs48,m)?)?;
218  Ok(())
219}
220
221#[cfg(feature="pybindings")]
222#[pymodule]
223#[pyo3(name = "packets")]
224fn packets_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
225  use crate::packets::*;
226  m.add_class::<TofPacketType>()?;
227  m.add_class::<TofPacket>()?;
228  m.add_class::<TelemetryPacketType>()?;
229  m.add_class::<TelemetryPacket>()?;
230  m.add_class::<TelemetryPacketHeader>()?;
231  m.add_class::<TrackerHeader>()?;
232  m.add_function(wrap_pyfunction!(make_systime,m)?)?;
233  Ok(())
234}
235
236#[cfg(feature="pybindings")]
237#[pymodule]
238#[pyo3(name = "io")]
239fn io_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
240  use crate::io::*;
241  use crate::io::caraspace::*;
242  #[cfg(feature="root")]
243  use crate::io::root_reader::read_example;
244  use crate::io::caraspace::frame::get_all_telemetry_event_names;
245  #[cfg(feature="root")]
246  m.add_function(wrap_pyfunction!(read_example, m)?)?;
247  m.add_function(wrap_pyfunction!(get_all_telemetry_event_names, m)?)?;
248  m.add_function(wrap_pyfunction!(get_runfilename, m)?)?;
249  m.add_function(wrap_pyfunction!(get_califilename, m)?)?;
250  m.add_function(wrap_pyfunction!(list_path_contents_sorted_py, m)?)?;
251  m.add_function(wrap_pyfunction!(get_utc_timestamp, m)?)?;
252  m.add_function(wrap_pyfunction!(get_utc_date, m)?)?;
253  m.add_function(wrap_pyfunction!(get_rundata_from_file, m)?)?;
254  m.add_function(wrap_pyfunction!(get_datetime, m)?)?;
255  m.add_function(wrap_pyfunction!(get_unix_timestamp, m)?)?;
256  m.add_function(wrap_pyfunction!(get_unix_timestamp_from_telemetry, m)?)?;
257  m.add_class::<CRFrameObject>()?;
258  m.add_class::<CRFrameObjectType>()?;
259  m.add_class::<CRFrame>()?;
260  m.add_class::<DataSourceKind>()?;
261  m.add_class::<CRReader>()?;
262  m.add_class::<CRWriter>()?;
263  m.add_class::<TofPacketReader>()?;
264  m.add_class::<TelemetryPacketReader>()?;
265  //m.add_class::<PyDataSource>()?;
266  Ok(())
267}
268
269#[cfg(feature="pybindings")]
270#[pymodule]
271#[pyo3(name = "monitoring")]
272fn monitoring_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
273  use crate::monitoring::*;
274  m.add_class::<EventBuilderHB>()?;
275  m.add_class::<EventBuilderHBSeries>()?;
276  m.add_class::<DataSinkHB>()?;
277  m.add_class::<DataSinkHBSeries>()?;
278  m.add_class::<MasterTriggerHB>()?;
279  m.add_class::<MasterTriggerHBSeries>()?;
280  m.add_class::<PAMoniData>()?;
281  m.add_class::<PAMoniDataSeries>()?;
282  m.add_class::<PBMoniData>()?;
283  m.add_class::<PBMoniDataSeries>()?;
284  m.add_class::<MtbMoniData>()?;
285  m.add_class::<MtbMoniDataSeries>()?;
286  m.add_class::<LTBMoniData>()?;
287  m.add_class::<LTBMoniDataSeries>()?;
288  m.add_class::<RBMoniData>()?;
289  m.add_class::<RBMoniDataSeries>()?;
290  m.add_class::<CPUMoniData>()?;
291  m.add_class::<CPUMoniDataSeries>()?;
292  m.add_class::<RunStatistics>()?;
293  Ok(())
294}
295
296#[cfg(feature="pybindings")]
297#[pymodule]
298#[pyo3(name = "stats")]
299fn stats_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
300  //use crate::io::*;
301  use crate::stats::py_gamma_pdf;
302  m.add_function(wrap_pyfunction!(py_gamma_pdf, m)?)?;
303  Ok(())
304}
305
306#[cfg(feature="pybindings")]
307#[pymodule]
308#[pyo3(name = "algo")]
309fn algo_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
310  //use crate::io::*;
311  use crate::tof::algorithms::*;
312  m.add_function(wrap_pyfunction!(get_max_value_idx_py, m)?)?;
313  m.add_function(wrap_pyfunction!(interpolate_time_py, m)?)?;
314  m.add_function(wrap_pyfunction!(fit_sine_simple_py, m)?)?;
315  Ok(())
316}
317
318#[cfg(feature="database")]
319#[cfg(feature="pybindings")]
320#[pymodule]
321#[pyo3(name = "db")]
322fn db_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
323  use crate::database::*;
324  m.add_class::<TofPaddle>()?;
325  m.add_class::<ReadoutBoard>()?;
326  m.add_class::<TrackerStrip>()?;
327  m.add_class::<TrackerStripMask>()?;
328  m.add_class::<TrackerStripPedestal>()?;
329  m.add_class::<TrackerStripTransferFunction>()?;
330  m.add_class::<TrackerStripCmnNoise>()?;
331  m.add_function(wrap_pyfunction!(get_all_rbids_in_db, m)?)?;
332  m.add_function(wrap_pyfunction!(get_hid_vid_map, m)?)?;
333  m.add_function(wrap_pyfunction!(get_vid_hid_map, m)?)?;
334  m.add_function(wrap_pyfunction!(get_dsi_j_ch_pid_map_py, m)?)?;
335  Ok(())
336}
337
338#[cfg(feature="pybindings")]
339#[pyfunction]
340fn get_version() -> &'static str {
341  return VERSION;
342}
343
344/// Python API to rust version of tof-dataclasses.
345///
346/// Currently, this contains only the analysis 
347/// functions
348#[cfg(feature="pybindings")]
349#[pymodule]
350#[pyo3(name = "gondola_core")]
351fn gondola_core_py<'_py>(m : &Bound<'_py, PyModule>) -> PyResult<()> { //: Python<'_>, m: &PyModule) -> PyResult<()> {
352  pyo3_log::init();
353  m.add_function(wrap_pyfunction!(get_version, m)?)?;
354  m.add_wrapped(wrap_pymodule!(events_py))?;
355  m.add_wrapped(wrap_pymodule!(monitoring_py))?;
356  m.add_wrapped(wrap_pymodule!(packets_py))?;
357  m.add_wrapped(wrap_pymodule!(tof_py))?;
358  m.add_wrapped(wrap_pymodule!(tracker_py))?;
359  m.add_wrapped(wrap_pymodule!(io_py))?;
360  m.add_wrapped(wrap_pymodule!(db_py))?;
361  m.add_wrapped(wrap_pymodule!(stats_py))?;
362  m.add_wrapped(wrap_pymodule!(algo_py))?;
363  m.add_wrapped(wrap_pymodule!(calibration_py))?;
364  Ok(())
365}