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//! * tof-control         - allows to control LTB & Powerboard from the RBs 
31//!                         over i2c. Since the i2c protocoll is not supported 
32//!                         on Mac, code with this feature enable won't build 
33//!                         on Apple systems. 
34//! * advanced-algorithms - allows to use a different algorithm for pulse extraction 
35//! * pybindings          - build the python library "gondola". Most of the entitities 
36//!                         within "gondola-core" have pybindings, e.g. events, hits.
37//! * tof-liftof          - build the code which is required to build liftof, the flight
38//!                         code utiilizing this library 
39// This file is part of gaps-online-software and published 
40// under the GPLv3 license
41
42#[macro_use] extern crate log; 
43
44pub mod prelude;
45#[cfg(feature="random")]
46pub mod random;
47pub mod constants;
48pub mod events;
49pub mod packets;
50pub mod version;
51pub mod io;
52pub mod calibration;
53pub mod errors;
54pub mod tof;
55pub mod tracker;
56pub mod monitoring;
57pub mod stats;
58#[cfg(feature="pybindings")]
59pub mod python;
60#[cfg(feature="database")]
61pub mod database;
62
63// python convention
64pub const VERSION: &str = env!("CARGO_PKG_VERSION"); 
65
66#[cfg(feature="pybindings")]
67use crate::errors::*;
68
69/// A simple helper macro adding an as_str function 
70/// as well as the Display method to any enum.
71///
72/// Avoids writing boilerplate
73#[macro_export]
74macro_rules! expand_and_test_enum {
75  ($name:ident, $test_name:ident) => {
76    impl fmt::Display for $name {
77      fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
78        write!(f, "<{}: {}>",stringify!($name), self.as_ref())
79      }
80    }
81
82    impl From<u8> for $name {
83      fn from(value: u8) -> Self {
84        match Self::from_repr(value)  {
85          None => {
86            return Self::Unknown;
87          }
88          Some(variant) => {
89            return variant;
90          }
91        }
92      }
93    }
94
95    #[cfg(feature="random")]
96    impl FromRandom for $name {
97      fn from_random() -> Self {
98        let mut choices = Vec::<Self>::new();
99        for k in Self::iter() {
100          choices.push(k);
101        }
102        let mut rng  = rand::rng();
103        let idx = rng.random_range(0..choices.len());
104        choices[idx]
105      }
106    }
107
108    #[test]
109    fn $test_name() {
110      for _ in 0..100 {
111        let data = $name::from_random();
112        assert_eq!($name::from(data as u8), data);
113      }
114    }
115
116    #[cfg(feature="pybindings")]
117    #[pymethods]
118    impl $name {
119    
120      #[staticmethod]  
121      #[pyo3(name = "from_u8")]
122      fn from_py(byte : u8) -> Self {
123        Self::from(byte) 
124      }
125    }
126  };
127}
128
129//---------------------------------------
130// helpers to init the logging system
131//
132
133use colored::{
134    Colorize,
135    ColoredString
136};
137use chrono::Utc;
138use log::Level;
139use std::io::Write;
140
141/// Make sure that the loglevel is in color, even though not using pretty_env logger
142pub fn color_log(level : &Level) -> ColoredString {
143  match level {
144    Level::Error    => String::from(" ERROR!").red(),
145    Level::Warn     => String::from(" WARN  ").yellow(),
146    Level::Info     => String::from(" Info  ").green(),
147    Level::Debug    => String::from(" debug ").blue(),
148    Level::Trace    => String::from(" trace ").cyan(),
149  }
150}
151
152/// Set up the environmental (env) logger
153/// with our format
154///
155/// Ensure that the lines and module paths
156/// are printed in the logging output
157pub fn init_env_logger() {
158  env_logger::builder()
159    .format(|buf, record| {
160    writeln!( buf, "[{ts} - {level}][{module_path}:{line}] {args}",
161      ts    = Utc::now().format("%Y/%m/%d-%H:%M:%SUTC"), 
162      level = color_log(&record.level()),
163      module_path = record.module_path().unwrap_or("<unknown>"),
164      line  = record.line().unwrap_or(0),
165      args  = record.args()
166      )
167    }).init();
168}
169
170//-------------------------------------------------
171// Build the python library
172
173#[cfg(feature="pybindings")]
174pub use pyo3::prelude::*; 
175#[cfg(feature="pybindings")]
176pub use pyo3::wrap_pymodule; 
177#[cfg(feature="pybindings")]
178pub use pyo3::wrap_pyfunction; 
179
180#[cfg(feature="pybindings")]
181#[pymodule]
182#[pyo3(name = "tof")]
183fn tof_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
184  use crate::tof::*;
185  m.add_class::<RBPaddleID>()?;
186  m.add_class::<TofDetectorStatus>()?;
187  m.add_class::<TofCommandCode>()?;
188  m.add_class::<TofCommand>()?;
189  m.add_class::<TofOperationMode>()?;
190  m.add_class::<BuildStrategy>()?;
191  m.add_class::<PreampBiasConfig>()?;
192  m.add_class::<RBChannelMaskConfig>()?;
193  m.add_class::<TriggerConfig>()?;
194  m.add_class::<TofRunConfig>()?;
195  m.add_class::<TofCuts>()?;
196  m.add_class::<TofAnalysis>()?;
197  m.add_class::<TofAnalysisCache>()?;
198  m.add_class::<TofAnalysisPaddleCache>()?;
199  m.add_class::<AnalysisEngineSettings>()?;
200  #[cfg(feature="tof-liftof")]
201  m.add_class::<PyMasterTrigger>()?;
202  m.add_function(wrap_pyfunction!(waveform_analysis, m)?)?;
203  m.add_function(wrap_pyfunction!(to_board_id_string, m)?)?;
204  // the commands
205  m.add_function(wrap_pyfunction!(start_run, m)?)?;
206  m.add_function(wrap_pyfunction!(stop_run, m)?)?;
207  m.add_function(wrap_pyfunction!(restart_liftofrb, m)?)?;
208  m.add_function(wrap_pyfunction!(enable_verification_run, m)?)?;
209  m.add_function(wrap_pyfunction!(shutdown_all_rbs, m)?)?;
210  m.add_function(wrap_pyfunction!(shutdown_rat, m)?)?;
211  m.add_function(wrap_pyfunction!(shutdown_ratpair, m)?)?;
212  m.add_function(wrap_pyfunction!(shutdown_rb, m)?)?;
213  m.add_function(wrap_pyfunction!(shutdown_tofcpu, m)?)?;
214  m.add_function(wrap_pyfunction!(run_action_alfa, m)?)?;
215  m.add_function(wrap_pyfunction!(run_action_bravo, m)?)?;
216  m.add_function(wrap_pyfunction!(run_action_charlie, m)?)?;
217  m.add_function(wrap_pyfunction!(run_action_whiskey, m)?)?;
218  m.add_function(wrap_pyfunction!(run_action_tango, m)?)?;
219  m.add_function(wrap_pyfunction!(run_action_foxtrott, m)?)?;
220  m.add_function(wrap_pyfunction!(request_liftof_settings, m)?)?;
221  m.add_function(wrap_pyfunction!(restore_default_config, m)?)?;
222  m.add_function(wrap_pyfunction!(apply_settings_diff, m)?)?;
223  Ok(())
224}
225
226#[cfg(feature="pybindings")]
227#[pymodule]
228#[pyo3(name = "tracker")]
229fn tracker_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
230  use crate::tracker::*;
231  //m.add_function(wrap_pyfunction!(mt_event_get_timestamp_abs48,m)?)?;
232  m.add_function(wrap_pyfunction!(strip_lines, m)?)?;
233  Ok(())
234}
235
236#[cfg(feature="pybindings")]
237#[pymodule]
238#[pyo3(name = "calibration")]
239fn calibration_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
240  use crate::calibration::tof::*;
241  m.add_class::<RBCalibrations>()?;
242  Ok(())
243}
244
245#[cfg(feature="pybindings")]
246#[pymodule]
247#[pyo3(name = "events")]
248fn events_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
249  use crate::events::*;
250  m.add_class::<TofHit>()?;
251  m.add_class::<TrackerHit>()?;
252  m.add_class::<RBEventHeader>()?;
253  m.add_class::<RBEvent>()?;
254  m.add_class::<RBWaveform>()?;
255  m.add_class::<EventStatus>()?;
256  m.add_class::<DataType>()?;
257  m.add_class::<TofEvent>()?;
258  m.add_class::<TelemetryEvent>()?;
259  m.add_function(wrap_pyfunction!(strip_id, m)?)?;
260  m.add_class::<EventQuality>()?;
261  m.add_class::<TriggerType>()?;
262  m.add_class::<LTBThreshold>()?;
263  m.add_function(wrap_pyfunction!(mt_event_get_timestamp_abs48,m)?)?;
264  Ok(())
265}
266
267#[cfg(feature="pybindings")]
268#[pymodule]
269#[pyo3(name = "packets")]
270fn packets_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
271  use crate::packets::*;
272  m.add_class::<TofPacketType>()?;
273  m.add_class::<TofPacket>()?;
274  m.add_class::<TelemetryPacketType>()?;
275  m.add_class::<TelemetryPacket>()?;
276  m.add_class::<TelemetryPacketHeader>()?;
277  m.add_class::<TrackerHeader>()?;
278  m.add_class::<PduChannel>()?;
279  m.add_class::<Pac1934>()?;
280  m.add_class::<PduHKPacket>()?;
281  m.add_function(wrap_pyfunction!(make_systime,m)?)?;
282  Ok(())
283}
284
285#[cfg(feature="pybindings")]
286#[pymodule]
287#[pyo3(name = "io")]
288fn io_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
289  use crate::io::*;
290  use crate::io::caraspace::*;
291  #[cfg(feature="root")]
292  use crate::io::root_reader::read_example;
293  use crate::io::caraspace::frame::get_all_telemetry_event_names;
294  #[cfg(feature="root")]
295  m.add_function(wrap_pyfunction!(read_example, m)?)?;
296  m.add_function(wrap_pyfunction!(get_all_telemetry_event_names, m)?)?;
297  m.add_function(wrap_pyfunction!(get_runfilename, m)?)?;
298  m.add_function(wrap_pyfunction!(get_califilename, m)?)?;
299  m.add_function(wrap_pyfunction!(list_path_contents_sorted_py, m)?)?;
300  m.add_function(wrap_pyfunction!(get_utc_timestamp, m)?)?;
301  m.add_function(wrap_pyfunction!(get_utc_date, m)?)?;
302  m.add_function(wrap_pyfunction!(get_rundata_from_file, m)?)?;
303  m.add_function(wrap_pyfunction!(get_datetime, m)?)?;
304  m.add_function(wrap_pyfunction!(get_unix_timestamp, m)?)?;
305  m.add_function(wrap_pyfunction!(get_unix_timestamp_from_telemetry, m)?)?;
306  // these are the config file manipulators
307  m.add_function(wrap_pyfunction!(apply_diff_to_file_py, m)?)?;
308  m.add_function(wrap_pyfunction!(compress_toml_py, m)?)?;
309  m.add_function(wrap_pyfunction!(decompress_toml_py, m)?)?;
310  m.add_function(wrap_pyfunction!(create_compressed_diff_py, m)?)?;
311
312  m.add_class::<CRFrameObject>()?;
313  m.add_class::<CRFrameObjectType>()?;
314  m.add_class::<CRFrame>()?;
315  m.add_class::<DataSourceKind>()?;
316  m.add_class::<CRReader>()?;
317  m.add_class::<CRWriter>()?;
318  m.add_class::<TofPacketReader>()?;
319  m.add_class::<TofPacketWriter>()?;
320  m.add_class::<TelemetryPacketReader>()?;
321  //m.add_class::<PyDataSource>()?;
322  Ok(())
323}
324
325#[cfg(feature="pybindings")]
326#[pymodule]
327#[pyo3(name = "monitoring")]
328fn monitoring_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
329  use crate::monitoring::*;
330  m.add_class::<EventBuilderHB>()?;
331  m.add_class::<EventBuilderHBSeries>()?;
332  m.add_class::<DataSinkHB>()?;
333  m.add_class::<DataSinkHBSeries>()?;
334  m.add_class::<MasterTriggerHB>()?;
335  m.add_class::<MasterTriggerHBSeries>()?;
336  m.add_class::<PAMoniData>()?;
337  m.add_class::<PAMoniDataSeries>()?;
338  m.add_class::<PBMoniData>()?;
339  m.add_class::<PBMoniDataSeries>()?;
340  m.add_class::<MtbMoniData>()?;
341  m.add_class::<MtbMoniDataSeries>()?;
342  m.add_class::<LTBMoniData>()?;
343  m.add_class::<LTBMoniDataSeries>()?;
344  m.add_class::<RBMoniData>()?;
345  m.add_class::<RBMoniDataSeries>()?;
346  m.add_class::<CPUMoniData>()?;
347  m.add_class::<CPUMoniDataSeries>()?;
348  m.add_class::<RunStatistics>()?;
349  Ok(())
350}
351
352#[cfg(feature="pybindings")]
353#[pymodule]
354#[pyo3(name = "stats")]
355fn stats_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
356  //use crate::io::*;
357  use crate::stats::py_gamma_pdf;
358  m.add_function(wrap_pyfunction!(py_gamma_pdf, m)?)?;
359  Ok(())
360}
361
362#[cfg(feature="pybindings")]
363#[pymodule]
364#[pyo3(name = "algo")]
365fn algo_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
366  //use crate::io::*;
367  use crate::tof::algorithms::*;
368  m.add_function(wrap_pyfunction!(get_max_value_idx_py, m)?)?;
369  m.add_function(wrap_pyfunction!(interpolate_time_py, m)?)?;
370  m.add_function(wrap_pyfunction!(fit_sine_simple_py, m)?)?;
371  Ok(())
372}
373
374#[cfg(feature="database")]
375#[cfg(feature="pybindings")]
376#[pymodule]
377#[pyo3(name = "db")]
378fn db_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
379  use crate::database::*;
380  m.add_class::<TofPaddle>()?;
381  m.add_class::<ReadoutBoard>()?;
382  m.add_class::<TrackerStrip>()?;
383  m.add_class::<TrackerStripMask>()?;
384  m.add_class::<TrackerStripPedestal>()?;
385  m.add_class::<TrackerStripTransferFunction>()?;
386  m.add_class::<TrackerStripCmnNoise>()?;
387  m.add_class::<TofPaddleTimingConstant>()?;
388  m.add_function(wrap_pyfunction!(get_all_rbids_in_db, m)?)?;
389  m.add_function(wrap_pyfunction!(get_hid_vid_map, m)?)?;
390  m.add_function(wrap_pyfunction!(get_vid_hid_map, m)?)?;
391  m.add_function(wrap_pyfunction!(get_dsi_j_ch_pid_map_py, m)?)?;
392  Ok(())
393}
394
395#[cfg(feature="pybindings")]
396#[pyfunction]
397fn get_version() -> &'static str {
398  return VERSION;
399}
400
401// add exceptions for the custom Error types
402//#[cfg(feature="pybindings")]
403//pyo3::create_exception!(gondola_core_py, MasterTriggerError, pyo3::exceptions::PyException);
404//#[cfg(feature="pybindings")]
405//pyo3::create_exception!(gondola_core_py, RunError, pyo3::exceptions::PyException);
406//#[cfg(feature="pybindings")]
407//pyo3::create_exception!(gondola_core_py, TofError, pyo3::exceptions::PyException);
408//#[cfg(feature="pybindings")]
409//pyo3::create_exception!(gondola_core_py, StagingError, pyo3::exceptions::PyException);
410//#[cfg(feature="pybindings")]
411//pyo3::create_exception!(gondola_core_py, SensorError, pyo3::exceptions::PyException);
412//#[cfg(feature="pybindings")]
413//pyo3::create_exception!(gondola_core_py, UserError, pyo3::exceptions::PyException);
414//#[cfg(feature="pybindings")]
415//pyo3::create_exception!(gondola_core_py, CalibrationError, pyo3::exceptions::PyException);
416//#[cfg(feature="pybindings")]
417//pyo3::create_exception!(gondola_core_py, WaveformError, pyo3::exceptions::PyException);
418//#[cfg(feature="pybindings")]
419//pyo3::create_exception!(gondola_core_py, IPBusError, pyo3::exceptions::PyException);
420//#[cfg(feature="pybindings")]
421//pyo3::create_exception!(gondola_core_py, SerializationError, pyo3::exceptions::PyException);
422//#[cfg(feature="pybindings")]
423//pyo3::create_exception!(gondola_core_py, AnalysisError, pyo3::exceptions::PyException); 
424#[cfg(feature="pybindings")]
425pyo3::create_exception!(gondola_core_py, PyMasterTriggerError, pyo3::exceptions::PyException);
426#[cfg(feature="pybindings")]
427pyo3::create_exception!(gondola_core_py, PyRunError, pyo3::exceptions::PyException);
428#[cfg(feature="pybindings")]
429pyo3::create_exception!(gondola_core_py, PyTofError, pyo3::exceptions::PyException);
430#[cfg(feature="pybindings")]
431pyo3::create_exception!(gondola_core_py, PyStagingError, pyo3::exceptions::PyException);
432#[cfg(feature="pybindings")]
433pyo3::create_exception!(gondola_core_py, PySensorError, pyo3::exceptions::PyException);
434#[cfg(feature="pybindings")]
435pyo3::create_exception!(gondola_core_py, PyUserError, pyo3::exceptions::PyException);
436#[cfg(feature="pybindings")]
437pyo3::create_exception!(gondola_core_py, PyCalibrationError, pyo3::exceptions::PyException);
438#[cfg(feature="pybindings")]
439pyo3::create_exception!(gondola_core_py, PyWaveformError, pyo3::exceptions::PyException);
440#[cfg(feature="pybindings")]
441pyo3::create_exception!(gondola_core_py, PyIPBusError, pyo3::exceptions::PyException);
442#[cfg(feature="pybindings")]
443pyo3::create_exception!(gondola_core_py, PySerializationError, pyo3::exceptions::PyException);
444#[cfg(feature="pybindings")]
445pyo3::create_exception!(gondola_core_py, PyAnalysisError, pyo3::exceptions::PyException); 
446
447#[macro_export]
448macro_rules! pythonize_error {
449  ($name:ident, $pyname:ident) => {
450
451    impl From<$name> for PyErr {
452      fn from(err: $name) -> PyErr {
453        // You can use a standard Python exception if you prefer, 
454        // e.g., pyo3::exceptions::PyValueError::new_err(...)
455        // But mapping to your custom PyAnalysisError is usually better. 
456        $pyname::new_err(format!("<GondolaCoreException: {}>", err))
457      }
458    }
459  }
460}
461
462#[cfg(feature="pybindings")]
463pythonize_error!(SerializationError, PySerializationError);
464#[cfg(feature="pybindings")]
465pythonize_error!(AnalysisError, PyAnalysisError);
466
467
468/// Python API to rust version of tof-dataclasses.
469///
470/// Currently, this contains only the analysis 
471/// functions
472#[cfg(feature="pybindings")]
473#[pymodule]
474#[pyo3(name = "gondola_core")]
475fn gondola_core_py<'_py>(m : &Bound<'_py, PyModule>) -> PyResult<()> { //: Python<'_>, m: &PyModule) -> PyResult<()> {
476  pyo3_log::init();
477  m.add_function(wrap_pyfunction!(get_version, m)?)?;
478  m.add_wrapped(wrap_pymodule!(events_py))?;
479  m.add_wrapped(wrap_pymodule!(monitoring_py))?;
480  m.add_wrapped(wrap_pymodule!(packets_py))?;
481  m.add_wrapped(wrap_pymodule!(tof_py))?;
482  m.add_wrapped(wrap_pymodule!(tracker_py))?;
483  m.add_wrapped(wrap_pymodule!(io_py))?;
484  m.add_wrapped(wrap_pymodule!(db_py))?;
485  m.add_wrapped(wrap_pymodule!(stats_py))?;
486  m.add_wrapped(wrap_pymodule!(algo_py))?;
487  m.add_wrapped(wrap_pymodule!(calibration_py))?;
488  //m.add("SerializationError",  pyo3::types::PyAnyMethods::<PySerializationError>::get_type(m))?;
489  //m.add("AnalysisError",  pyo3::types::PyAnyMethods::<PyAnalysisError>::get_type(m))?;
490  Ok(())
491}
492
493
494
495