Files
wfdb_corrosion/src/headproc.rs
2025-09-30 16:53:19 +02:00

316 lines
9.5 KiB
Rust

use std::{u64, vec};
use crate::SignalFormat;
/// Record information obtained from the header.
///
/// Only the `name`, `signal_count`, and `sampling_freq` values are required by default.
#[derive(Debug, Clone)]
struct Record {
name: String,
seg_num: Option<u64>,
signal_count: u64,
sampling_freq: u64,
counter_freq: Option<u64>,
base_counter_val: Option<u64>,
sample_num: Option<u64>,
basetime: Option<String>, // I dont thing we really need to care much about these
basedate: Option<String>
}
impl Record {
/// Attempts to generate the record information from a string of the argument line
pub fn from_str(argument_line: &str) -> Result<Record, &str> {
let args: Vec<&str> = argument_line.split(' ').collect();
if args.len() < 2 {
return Err("error: header file provided lacks sufficient record arguments.");
}
let name = args[0].to_string();
let seg_num: Option<u64>;
let sig_count: u64;
let sampling_freq: u64;
// Everything else is initialized with None, this is for sake of me not getting a stroke
let mut counter_freq: Option<u64> = None;
let mut base_counter_val: Option<u64> = None;
let mut sample_num: Option<u64> = None;
let mut basetime: Option<String> = None;
let mut basedate: Option<String> = None;
// Signals and segments are kept in a single argument organized as signal/segment
// segments are optional and we need to check this too
{
let seg_sig: Vec<&str> = args[1].split('/').collect();
if seg_sig.len() == 2 {
match seg_sig[1].parse::<u64>() {
Ok(value) => {
seg_num = Some(value);
}
Err(_) => {
return Err("error: failed to parse segment number from header.");
}
}
} else {
seg_num = None;
}
match seg_sig[0].parse::<u64>() {
Ok(value) => {
sig_count = value;
}
Err(_) => {
return Err("error: failed to parse signal number from header.");
}
}
}
// Parse everything else, if present
loop { // This is apparently the way to get safe correct goto-like behaviour
if args.len() <= 2 { // Sampling frequency and counter frequency
sampling_freq = 250; // Default value
break;
}
{
let freq_dual: Vec<&str> = args[2].split('/').collect();
if freq_dual.len() == 2 {
match freq_dual[1].parse::<u64>() {
Ok(value) => {
counter_freq = Some(value);
}
Err(_) => {
return Err("error: failed to parse counter frequency from header.");
}
}
}
match freq_dual[0].parse::<u64>() {
Ok(value) => {
sampling_freq = value;
}
Err(_) => {
return Err("error: failed to parse sampling frequency from header.");
}
}
}
if args.len() <= 3 {break;}
match args[3].parse::<u64>() {
Ok(value) => {
base_counter_val = Some(value);
}
Err(_) => {}
}
if args.len() <= 4 {break;}
match args[4].parse::<u64>() {
Ok(value) => {
sample_num = Some(value);
}
Err(_) => {}
}
if args.len() <= 5 {break;}
basetime = Some(args[5].to_string());
if args.len() <= 6 {break;}
basedate = Some(args[6].to_string());
break;
}
Ok(Record {
name: name,
seg_num: seg_num,
signal_count: sig_count,
sampling_freq: sampling_freq,
counter_freq: counter_freq,
base_counter_val: base_counter_val,
sample_num: sample_num,
basetime: basetime,
basedate: basedate
})
}
}
#[derive(Debug, Clone)]
struct SignalSpec {
filename: String,
format: SignalFormat,
samples_frame: Option<u64>,
skew: Option<u64>,
offset: Option<u64>,
adc_gain: Option<f64>,
baseline: Option<u64>,
units: Option<String>,
adc_resolution: Option<u64>,
adc_zero: Option<u64>,
initial_val: Option<u64>,
checksum: Option<u64>,
blocksize: Option<u64>,
desc: Option<String>
}
impl SignalSpec {
/// Attempts to generate a valid signal specification struct from a supplied string.
/// Returns a `Result<SignalSpec, &str>` containing possible error information.
///
/// ## Arguments
/// - `argument_line` - argument line for the signal specification, taken from header file
pub fn from_str(argument_line: &str) -> Result<SignalSpec, &str> {
let args: Vec<&str> = argument_line.split(' ').collect();
if args.len() < 2 {
return Err("error: signal provided by header file lacks sufficient arguments.");
}
let name = args[0].to_string();
let sigformat: SignalFormat;
// Optional args
let samples_frame: Option<u64> = None;
let skew: Option<u64> = None;
let offset: Option<u64> = None;
let adc_gain: Option<f64> = None;
let baseline: Option<u64> = None;
let units: Option<String> = None;
let adc_resolution: Option<u64> = None;
let adc_zero: Option<u64> = None;
let initial_val: Option<u64> = None;
let checksum: Option<u64> = None;
let blocksize: Option<u64> = None;
let desc: Option<String> = None;
// TODO: implement samples per frame, skew, and offset
match args[1].parse::<u64>() {
Ok(value) => {
sigformat = SignalSpec::parse_format(value);
},
Err(_) => {
return Err("error: unable to parse format from signal");
}
}
loop {
if args.len() <= 2 {
break;
}
{
let bracket_split: Vec<&str> = args[2].split("(").collect();
}
if args.len() <= 3 {
}
break;
}
Ok(
SignalSpec { filename: name,
format: sigformat,
samples_frame: samples_frame,
skew: skew, offset: offset,
adc_gain: adc_gain,
baseline: baseline,
units: units,
adc_resolution: adc_resolution,
adc_zero: adc_zero,
initial_val: initial_val,
checksum: checksum,
blocksize: blocksize,
desc: desc }
)
}
fn parse_format(formatnum: u64) -> SignalFormat {
match formatnum {
16 => SignalFormat::Format16,
212 => SignalFormat::Format212,
0..=u64::MAX => SignalFormat::Unimpl
}
}
}
#[derive(Debug, Clone)]
pub struct Header {
record: Option<Record>,
signal_specs: Vec<SignalSpec>
}
impl Header {
/// Creates a completely empty, not fully initialized header
///
/// This is a workaround because I couldn't figure out how to make the compiler
/// accept non-initializing the value for the first pass of a for loop in
/// `parse_header()`
fn new() -> Header {
Header { record: None, signal_specs: vec![] }
}
fn from_record(record: Record) -> Header {
Header { record: Some(record), signal_specs: vec![] }
}
fn add_signal_spec(&mut self, spec: SignalSpec) {
self.signal_specs.push(spec);
}
pub fn is_empty(&self) -> bool {
match self.record {
Some(_) => false,
None => true
}
}
}
/// Attempts to parse the header file
pub fn parse_header(header_data: &str) -> Result<Header, &str> {
let header_lines: Vec<&str> = header_data.split("\n").collect();
let mut found_record: bool = false;
let mut header: Header = Header::new();
let mut specs_read: u64 = 0;
let mut specs_max: u64 = 0;
for line in header_lines {
// Ignore commented lines
if line.starts_with("#") {
continue;
}
if !found_record {
let possible_record = Record::from_str(line);
match possible_record {
Ok(rec) => {
specs_max = rec.signal_count;
header = Header::from_record(rec);
found_record = true;
continue;
}
Err(e) => {
return Err(e)
}
}
}
let possible_spec = SignalSpec::from_str(line);
match possible_spec {
Ok(spec) => {
header.add_signal_spec(spec);
}
Err(e) => {
return Err(e);
}
}
specs_read += 1;
if specs_read >= specs_max {
break;
}
}
if header.is_empty() {
return Err("Unable to parse valid header information");
}
Ok(header)
}