go_pybindings/
caraspace.rs

1//! The following file is part of gaps-online-software and published 
2//! under the GPLv3 license
3//!
4//! This file contains the source for pybindings with pyO3 for the 
5//! caraspace i/o system
6
7use std::collections::HashMap;
8
9use std::sync::Arc;
10
11use pyo3::prelude::*;
12use pyo3::types::{
13  PyBytes,
14};
15
16//use log::error;
17use caraspace::prelude::*;
18use tof_dataclasses::database::{
19  Paddle,
20  TrackerStrip,
21  get_tofpaddles,
22  get_trackerstrips
23};
24use tof_dataclasses::packets::TofPacket;
25//use tof_dataclasses::events::TofEventSummary;
26use tof_dataclasses::events::{
27  TofEvent,
28  TofEventSummary
29};
30use telemetry_dataclasses::packets::{
31  TelemetryPacket,
32  MergedEvent
33};
34
35use crate::dataclasses::{
36  PyTofPacket,
37  PyTofEvent,
38  PyTofEventSummary,
39};
40
41use crate::telemetry::{
42  PyTelemetryPacket,
43  PyMergedEvent,
44};
45
46use pyo3::exceptions::PyValueError;
47
48/// Parse an u8 from python bytes. 
49///
50/// # Arguments:
51///
52/// * stream (bytes)  : parse the number from this stream
53/// * start_pos (int) : begin parsing at this position 
54#[pyfunction]
55#[pyo3(name="parse_u8")]
56pub fn py_parse_u8<'_py>(stream: Bound<'_py, PyBytes>, start_pos : usize) -> (u8, usize) {
57  let bs : Vec<u8> = stream.extract().expect("Don't understand input!");
58  let mut pos = start_pos;
59  let value = parse_u8(&bs, &mut pos);
60  (value, pos)
61}
62
63/// Parse an u16 from python bytes. 
64///
65/// # Arguments:
66///
67/// * stream (bytes)  : parse the number from this stream
68/// * start_pos (int) : begin parsing at this position 
69#[pyfunction]
70#[pyo3(name="parse_u16")]
71pub fn py_parse_u16<'_py>(stream: Bound<'_py, PyBytes>, start_pos : usize) -> (u16, usize) {
72  let bs : Vec<u8> = stream.extract().expect("Don't understand input!");
73  let mut pos = start_pos;
74  let value = parse_u16(&bs, &mut pos);
75  (value, pos)
76}
77
78/// Parse an u32 from python bytes. 
79///
80/// # Arguments:
81///
82/// * stream (bytes)  : parse the number from this stream
83/// * start_pos (int) : begin parsing at this position 
84#[pyfunction]
85#[pyo3(name="parse_u32")]
86pub fn py_parse_u32<'_py>(stream: Bound<'_py, PyBytes>, start_pos : usize) -> (u32, usize) {
87  let bs : Vec<u8> = stream.extract().expect("Don't understand input!");
88  let mut pos = start_pos;
89  let value = parse_u32(&bs, &mut pos);
90  (value, pos)
91}
92
93/// Parse an u64 from python bytes. 
94///
95/// # Arguments:
96///
97/// * stream (bytes)  : parse the number from this stream
98/// * start_pos (int) : begin parsing at this position 
99#[pyfunction]
100#[pyo3(name="parse_u64")]
101pub fn py_parse_u64<'_py>(stream: Bound<'_py, PyBytes>, start_pos : usize) -> (u64, usize) {
102  let bs : Vec<u8> = stream.extract().expect("Don't understand input!");
103  let mut pos = start_pos;
104  let value = parse_u64(&bs, &mut pos);
105  (value, pos)
106}
107
108/// The building blocks of the caraspace serialization 
109/// library
110///
111/// A CRFrame is capable of storing multiple packets of 
112/// any type.
113#[pyclass]
114#[pyo3(name="CRFrameObject")]
115#[derive(Clone, Debug)]
116pub struct PyCRFrameObject {
117  frame_object : CRFrameObject
118}
119
120#[pymethods]
121impl PyCRFrameObject {
122  #[new]
123  fn new() -> Self {
124    Self {
125      frame_object : CRFrameObject::new(),
126    }
127  }
128    
129  fn __repr__(&self) -> PyResult<String> {
130    Ok(format!("<PyO3Wrapper: {}>", self.frame_object)) 
131  }
132}
133
134/// The building blocks of the caraspace serialization 
135/// library
136///
137/// A CRFrame is capable of storing multiple packets of 
138/// any type.
139#[pyclass]
140#[pyo3(name="CRFrame")]
141#[derive(Clone, Debug)]
142pub struct PyCRFrame{
143  frame   : CRFrame,
144  paddles : Arc<HashMap<u8, Paddle>>, 
145  strips  : Arc<HashMap<u32, TrackerStrip>>
146}
147
148//impl Default for PyCRFrame {
149//  fn default() -> Self {
150//    Self {
151//      frame   : CRFrame::new(),
152//      paddles : HashMap::<u8, Paddle>::new(),
153//      strips  : HashMap::<u32, TrackerStrip>::new(),
154//    }
155//  }
156//}
157
158#[pymethods]
159impl PyCRFrame {
160  #[new]
161  fn new() -> Self {
162    Self {
163      frame   : CRFrame::new(),
164      paddles : Arc::new(HashMap::<u8, Paddle>::new()),
165      strips  : Arc::new(HashMap::<u32, TrackerStrip>::new()),
166    }
167  }
168
169  /// Delete a CRFrameObject by this name from the frame
170  ///
171  /// To delete multiple objects, delete calls can be 
172  /// chained
173  /// 
174  /// # Arguments:
175  ///   * name : The name of the FrameObject to delte 
176  ///            (must be in index)
177  ///
178  /// # Returns:
179  ///   A complete copy of self, without the given object.
180  pub fn delete(&self, name : &str) -> PyResult<PyCRFrame> {
181    match self.frame.delete(name) {
182      Ok(new_frame) => {
183        let mut new_pyframe = PyCRFrame::new();
184        new_pyframe.frame   = new_frame;
185        new_pyframe.paddles = Arc::clone(&self.paddles);
186        new_pyframe.strips  = Arc::clone(&self.strips);
187        Ok(new_pyframe)
188      }
189      Err(err) => {
190        return Err(PyValueError::new_err(err.to_string()));
191      }
192    }
193  }
194
195  //#[pyo3(signature = (packet, name = None))]
196  fn put_telemetrypacket(&mut self, packet : PyTelemetryPacket, name : String) {
197    let packet = packet.packet;
198    self.frame.put(packet, name)
199      //let packet = packet.p;
200  }
201
202  /// Add a TofPacket to the frame. 
203  ///
204  /// # Arguments:
205  ///   name : The name under which we store the TofPacket within the index.
206  ///          If None given, use the default name, which is
207  ///          "PacketType.<ValueOf(TofPacketType)". This should be used in 
208  ///          all cases for which there is only a single TofPacket within 
209  ///          the frame.
210  #[pyo3(signature = (packet, name = None))]
211  fn put_tofpacket(&mut self, packet : PyTofPacket, name : Option<String>) -> PyResult<()> {
212    let packet = packet.packet;
213    if let Some(p_name) = name {
214      self.frame.put(packet, p_name);
215      Ok(())
216    } else {
217      return Err(PyValueError::new_err("Currently this is not yet implemented. Please specify a name for the TofPacket to be registered within the frame's index!"));
218    }
219  }
220
221  fn get_telemetrypacket(&mut self, name : String) -> PyResult<PyTelemetryPacket> {
222    let mut py_packet = PyTelemetryPacket::new();
223    let packet    = self.frame.get::<TelemetryPacket>(name).unwrap();
224    py_packet.packet = packet;
225    Ok(py_packet)
226  }
227  
228  fn get_mergedevent(&mut self, name : String) -> PyResult<PyMergedEvent> {
229    let mut py_event    = PyMergedEvent::new();
230    let packet        = self.frame.get::<TelemetryPacket>(name).map_err(|_| pyo3::exceptions::PyValueError::new_err("Merged Event not found"))?;
231    match MergedEvent::from_bytestream(&packet.payload, &mut 0) {
232      Ok(mut event) => {
233        event.tof_event.set_paddles(&self.paddles);
234        event.tof_event.normalize_hit_times();
235        py_event.event        = event;
236        py_event.event.header = packet.header.clone();
237      }
238      Err(err) => {
239        return Err(PyValueError::new_err(err.to_string()));
240      }
241    }
242    for h in &mut py_event.event.tracker_hitsv2 {
243      h.set_coordinates(&self.strips);
244    }
245    Ok(py_event)
246  }
247  
248  fn get_tofevent(&mut self, name : String) -> PyResult<PyTofEvent> {
249    let mut py_event  = PyTofEvent::new();
250    // FIXME
251    let packet    = self.frame.get::<TofPacket>(name).unwrap();
252    let mut event = packet.unpack::<TofEvent>().unwrap();
253    event.set_paddles(&self.paddles);
254    py_event.event  = event;
255    Ok(py_event)
256  }
257  
258  fn get_tofeventsummary(&mut self, name : String) -> PyResult<PyTofEventSummary> {
259    let mut py_event  = PyTofEventSummary::new();
260    // FIXME
261    let packet    = self.frame.get::<TofPacket>(name).unwrap();
262    let mut event = packet.unpack::<TofEventSummary>().unwrap();
263    event.set_paddles(&self.paddles);
264    py_event.event  = event;
265    Ok(py_event)
266  }
267
268  fn get_tofpacket(&mut self, name : String) -> PyResult<PyTofPacket> {
269    let mut py_packet = PyTofPacket::new();
270    // FIXME
271    let packet    = self.frame.get::<TofPacket>(name).unwrap();
272    py_packet.packet = packet;
273    Ok(py_packet)
274  }
275  //fn put(&mut self, stream :  Vec<u8>, name : String) {
276  //  let mut bs = stream.clone();
277  //  self.frame.put_stream(&mut bs, name);
278  //}
279
280  /// Check if the frame contains an object with the given name
281  ///
282  /// # Arguments:
283  ///   * name : The name of the object as it appears in the index
284  fn has(&self, name : &str) -> bool {
285    self.frame.has(name)
286  }
287
288  #[getter]
289  fn index(&self) -> HashMap<String, (u64, CRFrameObjectType)> {
290    self.frame.index.clone()
291  }
292  
293  fn __repr__(&self) -> PyResult<String> {
294    Ok(format!("<PyO3Wrapper: {}>", self.frame)) 
295  }
296}
297
298/// Read caraspace files. Caraspace files are an aggregate filetype
299/// which allows to hold information from multiple sources in an 
300/// efficient binary format. 
301/// For the use within the GAPS experiment, L0 files are caraspace files
302/// and contain the TOF waveforms as sourced by the files written by the 
303/// TOFComputer to disk as well as the files emitted by the flight 
304/// computer (telemetry).
305///
306/// To create a new CRReader, simply call
307/// CRReader(filename_or_directory : path/string) where the argument can be either a name
308/// of an existing file or a directory with caraspace files.
309#[pyclass]
310#[pyo3(name="CRReader")]
311pub struct PyCRReader {
312  reader  : CRReader,
313  paddles : Arc<HashMap<u8,Paddle>>,
314  strips  : Arc<HashMap<u32, TrackerStrip>>
315}
316
317#[pymethods]
318impl PyCRReader {
319  #[new]
320  fn new(filename_or_directory : &Bound<'_,PyAny>) -> PyResult<Self> {
321    let mut string_value = String::from("foo");
322    if let Ok(s) = filename_or_directory.extract::<String>() {
323       string_value = s;
324    } //else if let Ok(p) = filename_or_directory.extract::<&Path>() {
325    if let Ok(fspath_method) = filename_or_directory.getattr("__fspath__") {
326      if let Ok(fspath_result) = fspath_method.call0() {
327        if let Ok(py_string) = fspath_result.extract::<String>() {
328          string_value = py_string;
329        }
330      }
331    }
332
333    //   string_value = p.display().to_string();
334    //} else {
335    //   return Err(pyo3::exceptions::PyTypeError::new_err(
336    //     "Expected a string or a path-like object",
337    //   ));
338    //}
339    let mut paddles = HashMap::<u8, Paddle>::new();
340    let mut strips  = HashMap::<u32, TrackerStrip>::new();
341    match get_tofpaddles() {
342      Ok(pdls) => {
343        paddles = pdls;
344      }
345      Err(_err) => {
346        // FIXME!
347        //error!("Unable to get paddles from database. Maybe the 'DATABASE_URL' path is not set. Are you sure you loaded the setup-env.sh shell?");
348      }
349    }
350    match get_trackerstrips() {
351      Ok(_strips) => {
352        strips = _strips;
353      }
354      Err(_err) => {
355        // FIXME!
356        //error!("Unable to get paddles from database. Maybe the 'DATABASE_URL' path is not set. Are you sure you loaded the setup-env.sh shell?");
357      }
358    }
359    Ok(Self {
360      reader  : CRReader::new(string_value)?,
361      paddles : Arc::new(paddles),
362      strips  : Arc::new(strips),
363    })
364  }
365
366  /// This is the filename we are currently 
367  /// extracting frames from 
368  #[getter]
369  fn get_current_filename(&self) -> Option<String> {
370    self.reader.get_current_filename()
371  }
372
373  /// Start the reader from the beginning
374  /// This is equivalent to a re-initialization
375  /// of that reader.
376  fn rewind(&mut self) -> PyResult<()> {
377    match self.reader.rewind() {
378      Err(err) => {
379        return Err(PyValueError::new_err(err.to_string()));
380      }
381      Ok(_) => Ok(())
382    }
383  }
384
385  fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
386    slf 
387  }
388  
389  fn __next__(mut slf: PyRefMut<'_, Self>) -> Option<PyCRFrame> {
390    match slf.reader.next() { 
391      Some(frame) => {
392        let mut pyframe = PyCRFrame::new();
393        pyframe.frame = frame;
394        //FIXME - these are huge! This needs to be solved
395        //by some other method
396        pyframe.paddles = Arc::clone(&slf.paddles);
397        pyframe.strips  = Arc::clone(&slf.strips);
398        return Some(pyframe)
399      }   
400      None => {
401        return None;
402      }   
403    }   
404  }
405
406  /// Get the number of frames this reader can walkthrough.
407  /// Since this is going through all files, it might take
408  /// a long time
409  fn count_frames(&mut self) -> usize {
410    self.reader.get_n_frames()
411  }
412
413  #[getter]
414  fn get_first_frame(&mut self) -> Option<PyCRFrame> {
415    match self.reader.first_frame() {
416      Some(frame) => {
417        let mut pyframe = PyCRFrame::new();
418        pyframe.frame = frame;
419        return Some(pyframe);
420      }
421      None => {
422        return None;
423      }
424    }
425  }
426  
427  #[getter]
428  fn get_last_frame(&mut self) -> Option<PyCRFrame> {
429    match self.reader.last_frame() {
430      Some(frame) => {
431        let mut pyframe = PyCRFrame::new();
432        pyframe.frame = frame;
433        return Some(pyframe);
434      }
435      None => {
436        return None;
437      }
438    }
439  }
440
441  fn __repr__(&self) -> PyResult<String> {
442    Ok(format!("<PyO3Wrapper: {}>", self.reader)) 
443  }
444}
445
446#[pyclass]
447#[pyo3(name="CRWriter")]
448pub struct PyCRWriter {
449  writer : CRWriter
450}
451
452#[pymethods]
453impl PyCRWriter {
454  #[new]
455  #[pyo3(signature = (filename, run_id, subrun_id = None, timestamp = None))]
456  fn new(filename : String, run_id : u32, subrun_id : Option<u64>, timestamp : Option<String>) -> Self {
457    Self {
458      writer : CRWriter::new(filename, run_id, subrun_id, timestamp ),
459    }
460  }
461 
462  fn set_mbytes_per_file(&mut self, fsize : usize) {
463    self.writer.mbytes_per_file = fsize;
464  }
465
466  fn set_file_timestamp(&mut self, timestamp : String) {
467    self.writer.file_timestamp = Some(timestamp);
468  }
469  
470  fn add_frame(&mut self, frame : PyCRFrame) {
471    self.writer.add_frame(&frame.frame);  
472  }
473}
474
475