Skip to content

Commit

Permalink
added channel creation
Browse files Browse the repository at this point in the history
  • Loading branch information
DnOberon committed Jun 11, 2022
1 parent 0b3dc93 commit d8f396a
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 52 deletions.
4 changes: 1 addition & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
[package]
name = "tdms"
description = "A LabView TDMS file parser written in Rust"
version = "0.1.5"
version = "0.1.6"
edition = "2021"
license = "MIT"
repository = "https://github.com/DnOberon/tdms"
# no need to include the test data directory in the crate, in fact that makes it so we can't publish
exclude = ["/data"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
indexmap = "1.8.2"
thiserror = "1.0"
Expand Down
22 changes: 17 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,25 @@ fn main() {
Err(e) => panic!("{:?}", e),
};

// TDMS files record their data in segments - each can potentially contain metadata and/or raw
// data
for segment in file.segments {
println!("{:?}", segment.metadata)
// fetch groups
let groups = file.groups();

for group in groups {
// fetch an IndexSet of the group's channels
let channels = file.channels(&group);

for channel in channels {
// once you know the channel's full path (group + channel) you can ask for the full
// channel object. This contains all the segments that contain this data channel
// eventually this will also implement Iterator, allowing you to iterate through
// the channels raw data, currently planned
match file.channel(&group, &channel) {
Ok(c) => c,
Err(e) => panic!("{:?}", e),
};
}
}
}

```

## Contributing
Expand Down
21 changes: 17 additions & 4 deletions examples/segments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,22 @@ fn main() {
Err(e) => panic!("{:?}", e),
};

// TDMS files record their data in segments - each can potentially contain metadata and/or raw
// data
for segment in file.segments {
println!("{:?}", segment.metadata)
// fetch groups
let groups = file.groups();

for group in groups {
// fetch an IndexSet of the group's channels
let channels = file.channels(&group);

for channel in channels {
// once you know the channel's full path (group + channel) you can ask for the full
// channel object. This contains all the segments that contain this data channel
// eventually this will also implement Iterator, allowing you to iterate through
// the channels raw data, currently planned
match file.channel(&group, &channel) {
Ok(c) => c,
Err(e) => panic!("{:?}", e),
};
}
}
}
29 changes: 9 additions & 20 deletions src/channel.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
use crate::segment::{DAQmxDataIndex, Metadata, RawDataIndex};
use crate::segment::{ChannelPath, DAQmxDataIndex, GroupPath, RawDataIndex};
use crate::{General, Segment, TdmsError};

#[derive(Debug, Clone)]
pub struct Channel<'a> {
path: String,
group_path: GroupPath,
path: ChannelPath,
segments: Vec<&'a Segment>,
bytes_read: u64,
current_segment: &'a Segment,
raw_data_index: &'a Option<RawDataIndex>,
daqmx_data_index: &'a Option<DAQmxDataIndex>,
}

impl<'a> Channel<'a> {
pub fn new(segments: Vec<&'a Segment>, path: String) -> Result<Self, TdmsError> {
pub fn new(
segments: Vec<&'a Segment>,
group_path: String,
path: String,
) -> Result<Self, TdmsError> {
if segments.len() <= 0 {
return Err(General(String::from(
"no segments provided for channel creation",
Expand All @@ -21,26 +24,12 @@ impl<'a> Channel<'a> {

let current_segment = segments[0];

let mut raw_data_index: &Option<RawDataIndex> = &None;
let mut daqmx_data_index: &Option<DAQmxDataIndex> = &None;

match &current_segment.metadata {
None => {}
Some(metadata) => {
for object in &metadata.objects {
raw_data_index = &object.raw_data_index;
daqmx_data_index = &object.daqmx_data_index
}
}
}

return Ok(Channel {
group_path,
path,
segments,
bytes_read: 0,
current_segment,
raw_data_index,
daqmx_data_index,
});
}
}
81 changes: 77 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
//! A Rust library for reading LabVIEW TDMS files.
//!
//! 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>
use indexmap::IndexSet;
use std::collections::HashSet;
use std::fs;
use std::fs::File;
use std::io::{BufReader, Read, Seek, SeekFrom};
use std::path::Path;

pub mod error;
use crate::channel::Channel;
use crate::segment::ChannelPath;
use crate::TdmsError::{
General, InvalidDAQmxDataIndex, InvalidSegment, StringConversionError, UnknownDataType,
};
Expand Down Expand Up @@ -51,14 +54,84 @@ impl TDMSFile<File> {
return Ok(TDMSFile { segments, reader });
}

pub fn channel(&self, path: String) -> Result<Channel, TdmsError> {
/// groups returns all possible groups throughout the file
pub fn groups(&self) -> Vec<String> {
let mut map: HashSet<String> = HashSet::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) -> Vec<String> {
let mut map: HashSet<String> = HashSet::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 in channel_map {
map.insert(String::from(channel));
}
}

return Vec::from_iter(map);
}

pub fn channel(&self, group_path: &str, path: &str) -> Result<Channel, TdmsError> {
let mut vec: Vec<&Segment> = vec![];
let mut channel_in_segment: bool = false;

// TODO: actually iterate through and build a set of segments that match
for segment in &self.segments {
vec.push(&segment)
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 Channel::new(vec, path);

return Channel::new(vec, group_path.to_string(), path.to_string());
}
}

Expand Down
49 changes: 34 additions & 15 deletions src/segment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,23 +90,23 @@ impl Segment {
let paths: Vec<&str> = path.split("/").collect();

if paths.len() >= 2 && paths[1] != "" {
if !groups.contains_key(rem_first_and_last(paths[1])) {
let _ = groups.insert(rem_first_and_last(paths[1]).to_string(), None);
if !groups.contains_key(rem_quotes(paths[1])) {
let _ = groups.insert(rem_quotes(paths[1]).to_string(), None);
}
}

if paths.len() >= 3 && paths[2] != "" {
let map = groups.get_mut(rem_first_and_last(paths[1]));
let map = groups.get_mut(rem_quotes(paths[1]));

match map {
Some(map) => match map {
Some(map) => {
map.insert(rem_first_and_last(paths[2]).to_string());
map.insert(rem_quotes(paths[2]).to_string());
}
None => {
let _ = groups.insert(
rem_first_and_last(paths[1]).to_string(),
Some(indexset! {rem_first_and_last(paths[2]).to_string()}),
rem_quotes(paths[1]).to_string(),
Some(indexset! {rem_quotes(paths[2]).to_string()}),
);
}
},
Expand Down Expand Up @@ -156,15 +156,15 @@ impl Segment {
/// `all_data_reader` returns a Take containing the raw data for the segment. This function assumes
/// that the reader passed in is the ORIGINAL reader, or another instance thereof.
pub fn all_data_reader<R: Read + Seek>(&mut self, mut r: R) -> Result<Take<R>, TdmsError> {
match r.seek(SeekFrom::Start(
return match r.seek(SeekFrom::Start(
self.start_pos + self.lead_in.raw_data_offset,
)) {
Ok(_) => {
let take = r.take(self.lead_in.next_segment_offset - self.lead_in.raw_data_offset);
return Ok(take);
Ok(take)
}
Err(e) => return Err(ReadError(e)),
}
Err(e) => Err(ReadError(e)),
};
}

/// this function is not accurate unless the lead in portion of the segment has been read
Expand Down Expand Up @@ -594,11 +594,30 @@ impl MetadataProperty {
}
}

fn rem_first_and_last(value: &str) -> &str {
let mut chars = value.chars();
chars.next();
chars.next_back();
chars.as_str()
fn rem_quotes(value: &str) -> &str {
let mut original = value.chars();
let mut chars = value.clone().chars().peekable();

match chars.peek() {
None => (),
Some(first) => {
if first.to_string() == "'" {
original.next();
}
}
}

let mut reversed = chars.rev().peekable();
match reversed.peek() {
None => (),
Some(last) => {
if last.to_string() == "'" {
original.next_back();
}
}
}

original.as_str()
}

#[macro_export]
Expand Down
26 changes: 25 additions & 1 deletion src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::segment::Segment;
use crate::TDMSFile;
use crate::{Channel, TDMSFile, TdmsError};
use std::fs::File;
use std::path::Path;

Expand Down Expand Up @@ -100,3 +100,27 @@ fn can_read_all_segments_raw() {

assert_eq!(file.segments.len(), 3);
}

#[test]
fn can_read_groups_channels() {
let file = match TDMSFile::from_path(Path::new("data/standard.tdms"), false) {
Ok(f) => f,
Err(e) => panic!("{:?}", e),
};

let groups = file.groups();
assert!(groups.len() > 0);

for group in groups {
let channels = file.channels(&group);

for channel in channels {
match file.channel(&group, &channel) {
Ok(c) => c,
Err(e) => panic!("{:?}", e),
};
}
}

assert_eq!(file.segments.len(), 2);
}

0 comments on commit d8f396a

Please sign in to comment.