1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
//! A Rust library for reading LabVIEW TDMS files.
//! `tdms` is a LabVIEW TDMS file parser library written in Rust. This is meant to be a general purpose library for reading and performing any calculation work on data contained in those files.
//!
//! **Note:** This library is a work in progress. While I do not expect the current function signatures and library structure to change, you could experience difficulties due to early adoption.
//!
//! ### Current Features
//! - Read both standard and big endian encoded files
//! - Read files with DAQmx data and data indices
//! - Read all segments in file, along with their groups and channels
//!
//! ### Planned Features
//! - Iterators for each channel type, return native Rust values from encoded data channels
//! - DAQmx data channel iterator support
//! - Searching on string channels
//!
//!
//! ## Usage
//!
//! ```rust
//!extern crate tdms;
//!
//! use std::path::Path;
//! use tdms::data_type::TdmsDataType;
//! use tdms::TDMSFile;
//!
//! fn main() {
//! // open and parse the TDMS file, passing in metadata false will mean the entire file is
//! // read into memory, not just the metadata
//! let file = match TDMSFile::from_path(Path::new("data/standard.tdms")) {
//! Ok(f) => f,
//! Err(e) => panic!("{:?}", e),
//! };
//!
//! // fetch groups
//! let groups = file.groups();
//!
//! for group in groups {
//! // fetch an IndexSet of the group's channels
//! let channels = file.channels(&group);
//!
//! let mut i = 0;
//! for (_, channel) in channels {
//! // once you know the channel's full path (group + channel) you can ask for the full
//! // channel object. In order to fetch a channel you must call the proper channel func
//! // depending on your data type. Currently this feature is unimplemented but the method
//! // of calling this is set down for future changes
//! let full_channel = match channel.data_type {
//! // the returned full channel is an iterator over raw data
//! TdmsDataType::DoubleFloat(_) => file.channel_data_double_float(channel),
//! _ => {
//! panic!("{}", "channel for data type unimplemented")
//! }
//! };
//!
//! let mut full_channel_iterator = match full_channel {
//! Ok(i) => i,
//! Err(e) => {
//! panic!("{:?}", e)
//! }
//! };
//!
//! println!("{:?}", full_channel_iterator.count());
//!
//! i += 1;
//! }
//! }
//! }
//!
//! ```
//!
//! More information about the TDMS file format can be found here: <https://www.ni.com/en-us/support/documentation/supplemental/07/tdms-file-format-internal-structure.html>
//!
//! ## Contributing
//! Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
//!
//! Please make sure to update tests as appropriate.
//!
//! ## License
//! [MIT](https://choosealicense.com/licenses/mit/)
//!
use indexmap::{IndexMap, IndexSet};
use std::fs;
use std::fs::File;
use std::io::{BufReader, Seek, SeekFrom};
use std::path::Path;
pub mod error;
use crate::channel_iter::ChannelDataIter;
use crate::TdmsError::{
General, InvalidDAQmxDataIndex, InvalidSegment, StringConversionError, UnknownDataType,
};
pub use error::TdmsError;
use segment::Endianness::{Big, Little};
use segment::{Channel, Endianness, Segment};
pub mod channel_iter;
pub mod data_type;
pub mod segment;
#[cfg(test)]
mod tests;
#[derive(Debug)]
/// `TDDMSFile` represents all `segments` of a TDMS file in the order in which they were read.
pub struct TDMSFile<'a> {
pub segments: Vec<Segment>,
path: &'a Path,
}
impl<'a> TDMSFile<'a> {
/// `from_path` expects a path and whether or not to read only the metadata of each segment vs
/// the entire file into working memory.
pub fn from_path(path: &'a Path) -> Result<Self, TdmsError> {
let metadata = fs::metadata(path)?;
let file = File::open(path)?;
let mut reader = BufReader::with_capacity(4096, file);
let mut segments: Vec<Segment> = vec![];
let mut i = 0;
loop {
let previous_segment = if i == 0 { None } else { segments.get(i - 1) };
let segment = Segment::new(&mut reader, previous_segment)?;
if segment.end_pos == metadata.len() {
segments.push(segment);
break;
}
reader.seek(SeekFrom::Start(segment.end_pos))?;
segments.push(segment);
i += 1;
}
return Ok(TDMSFile { segments, path });
}
/// groups returns all possible groups throughout the file
pub fn groups(&self) -> Vec<String> {
let mut map: IndexSet<String> = IndexSet::new();
for segment in &self.segments {
for (group, _) in &segment.groups {
map.insert(String::from(group));
}
}
return Vec::from_iter(map);
}
pub fn channels(&self, group_path: &str) -> IndexMap<String, &Channel> {
let mut map: IndexMap<String, &Channel> = IndexMap::new();
for segment in &self.segments {
let channel_map = match segment.groups.get(group_path) {
Some(m) => m,
None => &None,
};
let channel_map = match channel_map {
None => continue,
Some(m) => m,
};
for (channel_path, channel) in channel_map {
map.insert(String::from(channel_path), channel);
}
}
return map;
}
/// returns a channel who's type is the native rust type equivalent to TdmsDoubleFloat, in this
/// case `f64` - the channel implements Iterator and using said iterator will let you move through
/// the channel's raw data if any exists
pub fn channel_data_double_float(
&self,
channel: &'a Channel,
) -> Result<ChannelDataIter<f64, File>, TdmsError> {
let vec = self.load_segments(channel.group_path.as_str(), channel.path.as_str());
let reader = BufReader::with_capacity(8192, File::open(self.path)?);
return ChannelDataIter::new(vec, channel, reader);
}
fn load_segments(&self, group_path: &str, path: &str) -> Vec<&Segment> {
let mut vec: Vec<&Segment> = vec![];
let mut channel_in_segment: bool = false;
for segment in &self.segments {
match segment.groups.get(group_path) {
None => {
if !segment.has_new_obj_list() && channel_in_segment {
vec.push(&segment)
} else {
channel_in_segment = false
}
}
Some(channels) => match channels {
None => {
if !segment.has_new_obj_list() && channel_in_segment {
vec.push(&segment)
} else {
channel_in_segment = false
}
}
Some(channels) => {
let channel = channels.get(path);
match channel {
None => {
if !segment.has_new_obj_list() && channel_in_segment {
vec.push(&segment)
} else {
channel_in_segment = false
}
}
Some(_) => {
vec.push(&segment);
channel_in_segment = true;
}
}
}
},
}
}
return vec;
}
}