Compare commits

...

5 Commits

Author SHA1 Message Date
Misha Vicha
87c95833cf Adds parsing of Format16 bytedata 2025-10-14 12:01:21 +02:00
Misha Vicha
f9777b124b Hashmap for file:signals pairs, signal_data file 2025-10-13 08:20:42 +02:00
Misha Vicha
81f768a12a Updates launch json with test file argument 2025-10-13 08:19:05 +02:00
Misha Vicha
cf57876e67 SignalData parsing pt.1 - tb squashed 2025-10-01 14:24:25 +02:00
Misha Vicha
2becccd064 Improved naming of Header constructor
`from_record` is now called `with_record` as the library standard
suggests
2025-10-01 14:04:45 +02:00
4 changed files with 195 additions and 15 deletions

4
.vscode/launch.json vendored
View File

@@ -19,7 +19,9 @@
"kind": "bin" "kind": "bin"
} }
}, },
"args": [], "args": [
"./test_data/s1_high_resistance_bike.hea"
],
"cwd": "${workspaceFolder}" "cwd": "${workspaceFolder}"
}, },
{ {

View File

@@ -1,4 +1,4 @@
use std::{collections::HashMap, u64, vec}; use std::{collections::HashMap, hash::Hash, u64, vec};
use crate::SignalFormat; use crate::SignalFormat;
@@ -153,9 +153,9 @@ enum AdcBlockKeys {
/// ///
/// By minimum, only carries the `filename`, `format`, and `adc_zero` attributes /// By minimum, only carries the `filename`, `format`, and `adc_zero` attributes
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct SignalSpec { pub struct SignalSpec {
filename: String, filename: String,
format: SignalFormat, pub format: SignalFormat,
samples_frame: Option<u64>, samples_frame: Option<u64>,
skew: Option<u64>, skew: Option<u64>,
offset: Option<u64>, offset: Option<u64>,
@@ -410,7 +410,7 @@ impl Header {
/// ///
/// Initializes the signal_specs vector with the correct capacity provided /// Initializes the signal_specs vector with the correct capacity provided
/// by the record. /// by the record.
fn from_record(record: Record) -> Header { fn with_record(record: Record) -> Header {
let capacity = record.signal_count; let capacity = record.signal_count;
Header { record: Some(record), signal_specs: Vec::with_capacity(capacity) } Header { record: Some(record), signal_specs: Vec::with_capacity(capacity) }
} }
@@ -425,6 +425,24 @@ impl Header {
None => true None => true
} }
} }
pub fn signals_per_file(&self) -> HashMap<String, u64> {
let mut map: HashMap<String, u64> = HashMap::new();
for signal in &self.signal_specs {
map.entry(signal.filename.clone()).and_modify(|val: &mut u64| *val += 1 ).or_insert(1);
}
map
}
pub fn signals_in_file(&self, filename: String) -> Vec<&SignalSpec> {
let mut buffer: Vec<&SignalSpec> = Vec::new();
for signal in &self.signal_specs {
if signal.filename == filename {
buffer.push(signal);
}
}
buffer
}
} }
/// Attempts to parse the header file. /// Attempts to parse the header file.
@@ -448,7 +466,7 @@ pub fn parse_header(header_data: &str) -> Result<Header, &str> {
match possible_record { match possible_record {
Ok(rec) => { Ok(rec) => {
specs_max = rec.signal_count; specs_max = rec.signal_count;
header = Header::from_record(rec); header = Header::with_record(rec);
found_record = true; found_record = true;
continue; continue;
} }

View File

@@ -1,17 +1,21 @@
use std::{env::{self}, io::Error, path::Path}; use std::{env::{self}, os::linux::raw, path::Path, str::FromStr};
use std::fs; use std::fs;
pub mod headparse; // The HEAder parsing pub mod headparse; // The HEAder parsing
pub mod signal_data;
use crate::{headparse::{Header, SignalSpec}, signal_data::SignalData};
/// Use for handling possible formats of the WFDB data /// Use for handling possible formats of the WFDB data
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy, PartialEq)]
enum SignalFormat { enum SignalFormat {
Format16 = 16, Format16 = 16,
Format212 = 212, Format212 = 212,
Invalid = -1,
Unimpl = 0 Unimpl = 0
} }
fn main() -> Result<(), Error>{ fn main() -> Result<(), String>{
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();
if args.len() <= 1 || args.contains(&"help".to_string()) { if args.len() <= 1 || args.contains(&"help".to_string()) {
@@ -29,14 +33,40 @@ fn main() -> Result<(), Error>{
return Ok(()); return Ok(());
} }
// Parse the header information
let header: Header;
{
let hea_file_result = fs::read_to_string(filepath); let hea_file_result = fs::read_to_string(filepath);
match hea_file_result { match hea_file_result {
Ok(file_data) => { Ok(file_data) => {
let header = headparse::parse_header(file_data.as_str()); match headparse::parse_header(file_data.as_str()) {
dbg!(header); Ok(h) => {header = h}
Err(e) => {return Err(e.to_string())}
}
}
Err(e) => {return Err("error: Provided file cannot be opened".to_string())}
} }
Err(e) => return Err(e)
} }
let path_parent: String;
{
// This is cursed lol
match filepath.parent() {
Some(p) => {
match p.to_str() {
Some(str) => {
path_parent = str.to_string();
}
None => {path_parent = "".to_string()}
}
}
None => {path_parent = "".to_string()}
}
}
let parsed_signal_result = get_all_data(header, path_parent);
println!("Hello, world!"); println!("Hello, world!");
Ok(()) Ok(())
} }
@@ -44,4 +74,81 @@ fn main() -> Result<(), Error>{
fn help() { fn help() {
println!("Conversion of WFDB files to a more human readable format. By default to a CSV."); println!("Conversion of WFDB files to a more human readable format. By default to a CSV.");
println!("\nUse in the format \"wfdb_corrosion (.hea filename)\"") println!("\nUse in the format \"wfdb_corrosion (.hea filename)\"")
}
fn get_signal_data(spec: &SignalSpec, offset: u64, data_length: usize, raw: &Vec<u64>) -> SignalData {
todo!()
}
fn get_all_data(header: Header, header_root: String) -> Result<Vec<SignalData>, String> {
let signal_counts = header.signals_per_file();
for (fname, sigcount) in signal_counts {
let mut full = header_root.to_owned(); // Consider refactoring these to avoid this massive amount of ownership bs
full.push('/');
let fname = fname.to_owned();
full.push_str(&fname);
let filepath = Path::new(&full);
if !filepath.is_file() {
return Err("Error: Could not open signal .dat file. Check if the
header points to the right .dat file and if files are present".to_string());
}
if filepath.extension().unwrap() != "dat" {
println!("warn: Header file defines signal files with an extension
other than .dat.
Check if the header points to the right files, reading will continue
but correct data is not guaranteed");
}
// Time to actually do the work
let signals = header.signals_in_file(fname);
let format = file_signal_format_check(signals);
if format == SignalFormat::Invalid {
return Err("Error: Signals in single file use multiple formats.
Is your header file correctly formatted?".to_string());
}
if format == SignalFormat::Unimpl {
println!("Warn: Signals in single file use an unimplemented format,
and will be ignored");
continue;
}
let raw_bytes = fs::read(filepath).unwrap();
match format {
SignalFormat::Format16 => {
let mut buffer: u64 = 0;
let mut do_append = false;
let mut raw_converted: Vec<u64> = Vec::with_capacity(raw_bytes.len() / 2);
for byte in raw_bytes {
if do_append {
buffer = (buffer & 0xFF) + (byte as u64) << 8;
raw_converted.push(buffer);
do_append = false;
} else {
buffer = byte as u64;
do_append = true;
}
}
dbg!(raw_converted);
},
_ => {
panic!("Somehow reached actual file and format processing with
unimplemented format. Please make a bug report")
}
}
}
todo!()
}
fn file_signal_format_check(signals: Vec<&SignalSpec>) -> SignalFormat {
let first_sig = signals[0];
let format = first_sig.format;
for sig in signals {
if sig.format != format {
return SignalFormat::Invalid;
}
}
return format;
} }

53
src/signal_data.rs Normal file
View File

@@ -0,0 +1,53 @@
/// Holds fully processed signal data already parsed from the .dat file, ready
/// to be outputted in a practical way
#[derive(Debug, Clone)]
pub struct SignalData {
name: String,
values: Vec<f64>,
unit: String
}
impl SignalData {
/// Creates a [`SignalData`](crate::SignalData) with a name and an empty
/// `values` vector and `unit` string.
///
/// The `values` vector is initialized with no capacity, which causes this
/// struct to be quite a bit slower. This method is thus not suggested, use
/// [`SignalData::with_name_and_capacity`](crate::SignalData::with_name_and_capacity)
/// when you know how much you want to allocate
///
/// # Arguments
/// * `name` - The name of the Signal
pub fn with_name(name: String) -> SignalData {
SignalData { name: name, values: vec![], unit: String::new() }
}
/// Creates a [`SignalData`](crate::SignalData) with a name and a
/// `values` vector of a certain capacity and an empty `unit` string.
///
/// # Arguments
///
/// * `name` - The name of the Signal
/// * `capacity` - The capacity with which the `values` vector will be
/// initialized
pub fn with_name_and_capacity(name: String, capacity: usize) -> SignalData {
SignalData { name: name, values: Vec::with_capacity(capacity), unit: String::new() }
}
/// Appends a value to `values` inside the struct
pub fn push(&mut self, value: f64) {
self.values.push(value);
}
/// Appends data copied from the supplied `data` argument to the end of the
/// `values` vector inside the struct
pub fn append_copy(&mut self, data: Vec<f64>) {
let mut to_add: Vec<f64> = data;
to_add.append(&mut self.values);
}
}
pub struct SignalDataRaw {
name: String,
raw_data: Vec<u8>
}