use byteorder::{LittleEndian, ReadBytesExt};
use ndarray::{Array1, Array2, s};
use std::f64::consts::PI;
use std::fs::File;
use std::io::{BufReader, Read, Seek, SeekFrom};
use std::path::Path;
use std::time::Instant;
use crate::types::*;
const RHS_MAGIC_NUMBER: u32 = 0xd69127ac;
const SAMPLES_PER_DATA_BLOCK: usize = 128;
const PRINT_PROGRESS_STEP: usize = 10;
const AMPLIFIER_SCALE_FACTOR: f64 = 0.195; const DC_AMPLIFIER_SCALE_FACTOR: f64 = 19.23; const ADC_DAC_SCALE_FACTOR: f64 = 0.0003125; const DC_AMPLIFIER_OFFSET: f64 = 512.0;
const ADC_DAC_OFFSET: f64 = 32768.0;
pub fn load_file<P: AsRef<Path>>(file_path: P) -> Result<RhsFile, Box<dyn std::error::Error>> {
let tic = Instant::now();
let file = File::open(file_path.as_ref())?;
let file_size = file.metadata()?.len();
let mut reader = BufReader::with_capacity(65536, file);
let header = read_header(&mut reader)?;
let (data_present, num_blocks, num_samples) =
calculate_data_size(&header, file_size, &mut reader)?;
let data = if data_present {
let data = read_all_data_blocks(&header, num_samples, num_blocks, &mut reader)?;
check_end_of_file(file_size, &mut reader)?;
let data = process_data(&header, data)?;
Some(data)
} else {
None
};
println!(
"Done! Elapsed time: {:.1} seconds",
tic.elapsed().as_secs_f64()
);
Ok(RhsFile {
header,
data,
data_present,
source_files: None, })
}
fn read_header<R: Read + Seek>(reader: &mut R) -> Result<RhsHeader, Box<dyn std::error::Error>> {
let mut header = RhsHeader {
version: Version { major: 0, minor: 0 },
sample_rate: 0.0,
num_samples_per_data_block: SAMPLES_PER_DATA_BLOCK as i32,
dsp_enabled: 0,
actual_dsp_cutoff_frequency: 0.0,
actual_lower_bandwidth: 0.0,
actual_lower_settle_bandwidth: 0.0,
actual_upper_bandwidth: 0.0,
desired_dsp_cutoff_frequency: 0.0,
desired_lower_bandwidth: 0.0,
desired_lower_settle_bandwidth: 0.0,
desired_upper_bandwidth: 0.0,
notch_filter_frequency: None,
desired_impedance_test_frequency: 0.0,
actual_impedance_test_frequency: 0.0,
amp_settle_mode: 0,
charge_recovery_mode: 0,
stim_step_size: 0.0,
recovery_current_limit: 0.0,
recovery_target_voltage: 0.0,
notes: Notes {
note1: String::new(),
note2: String::new(),
note3: String::new(),
},
dc_amplifier_data_saved: false,
eval_board_mode: 0,
reference_channel: String::new(),
amplifier_channels: Vec::new(),
spike_triggers: Vec::new(),
board_adc_channels: Vec::new(),
board_dac_channels: Vec::new(),
board_dig_in_channels: Vec::new(),
board_dig_out_channels: Vec::new(),
frequency_parameters: FrequencyParameters {
amplifier_sample_rate: 0.0,
board_adc_sample_rate: 0.0,
board_dig_in_sample_rate: 0.0,
desired_dsp_cutoff_frequency: 0.0,
actual_dsp_cutoff_frequency: 0.0,
dsp_enabled: 0,
desired_lower_bandwidth: 0.0,
desired_lower_settle_bandwidth: 0.0,
actual_lower_bandwidth: 0.0,
actual_lower_settle_bandwidth: 0.0,
desired_upper_bandwidth: 0.0,
actual_upper_bandwidth: 0.0,
notch_filter_frequency: None,
desired_impedance_test_frequency: 0.0,
actual_impedance_test_frequency: 0.0,
},
stim_parameters: StimParameters {
stim_step_size: 0.0,
charge_recovery_current_limit: 0.0,
charge_recovery_target_voltage: 0.0,
amp_settle_mode: 0,
charge_recovery_mode: 0,
},
};
check_magic_number(reader)?;
read_version_number(reader, &mut header)?;
header.sample_rate = reader.read_f32::<LittleEndian>()?;
header.frequency_parameters.amplifier_sample_rate = header.sample_rate;
header.frequency_parameters.board_adc_sample_rate = header.sample_rate;
header.frequency_parameters.board_dig_in_sample_rate = header.sample_rate;
read_freq_settings(reader, &mut header)?;
read_notch_filter_frequency(reader, &mut header)?;
read_impedance_test_frequencies(reader, &mut header)?;
header.amp_settle_mode = reader.read_i16::<LittleEndian>()? as i32;
header.stim_parameters.amp_settle_mode = header.amp_settle_mode;
header.charge_recovery_mode = reader.read_i16::<LittleEndian>()? as i32;
header.stim_parameters.charge_recovery_mode = header.charge_recovery_mode;
header.stim_step_size = reader.read_f32::<LittleEndian>()?;
header.stim_parameters.stim_step_size = header.stim_step_size;
header.recovery_current_limit = reader.read_f32::<LittleEndian>()?;
header.stim_parameters.charge_recovery_current_limit = header.recovery_current_limit;
header.recovery_target_voltage = reader.read_f32::<LittleEndian>()?;
header.stim_parameters.charge_recovery_target_voltage = header.recovery_target_voltage;
read_notes(reader, &mut header)?;
header.dc_amplifier_data_saved = reader.read_i16::<LittleEndian>()? != 0;
header.eval_board_mode = reader.read_i16::<LittleEndian>()? as i32;
header.reference_channel = read_qstring(reader)?;
read_signal_summary(reader, &mut header)?;
print_header_summary(&header);
Ok(header)
}
fn check_magic_number<R: Read>(reader: &mut R) -> Result<(), IntanError> {
let magic_number = reader.read_u32::<LittleEndian>()?;
if magic_number != RHS_MAGIC_NUMBER {
return Err(IntanError::UnrecognizedFileFormat);
}
Ok(())
}
fn read_version_number<R: Read>(reader: &mut R, header: &mut RhsHeader) -> Result<(), IntanError> {
let mut version_bytes = [0u8; 4];
reader.read_exact(&mut version_bytes)?;
header.version.major = i16::from_le_bytes([version_bytes[0], version_bytes[1]]) as i32;
header.version.minor = i16::from_le_bytes([version_bytes[2], version_bytes[3]]) as i32;
println!(
"\nReading Intan Technologies RHS Data File, Version {}.{}\n",
header.version.major, header.version.minor
);
Ok(())
}
fn read_freq_settings<R: Read>(reader: &mut R, header: &mut RhsHeader) -> Result<(), IntanError> {
header.dsp_enabled = reader.read_i16::<LittleEndian>()? as i32;
header.frequency_parameters.dsp_enabled = header.dsp_enabled;
header.actual_dsp_cutoff_frequency = reader.read_f32::<LittleEndian>()?;
header.frequency_parameters.actual_dsp_cutoff_frequency = header.actual_dsp_cutoff_frequency;
header.actual_lower_bandwidth = reader.read_f32::<LittleEndian>()?;
header.frequency_parameters.actual_lower_bandwidth = header.actual_lower_bandwidth;
header.actual_lower_settle_bandwidth = reader.read_f32::<LittleEndian>()?;
header.frequency_parameters.actual_lower_settle_bandwidth =
header.actual_lower_settle_bandwidth;
header.actual_upper_bandwidth = reader.read_f32::<LittleEndian>()?;
header.frequency_parameters.actual_upper_bandwidth = header.actual_upper_bandwidth;
header.desired_dsp_cutoff_frequency = reader.read_f32::<LittleEndian>()?;
header.frequency_parameters.desired_dsp_cutoff_frequency = header.desired_dsp_cutoff_frequency;
header.desired_lower_bandwidth = reader.read_f32::<LittleEndian>()?;
header.frequency_parameters.desired_lower_bandwidth = header.desired_lower_bandwidth;
header.desired_lower_settle_bandwidth = reader.read_f32::<LittleEndian>()?;
header.frequency_parameters.desired_lower_settle_bandwidth =
header.desired_lower_settle_bandwidth;
header.desired_upper_bandwidth = reader.read_f32::<LittleEndian>()?;
header.frequency_parameters.desired_upper_bandwidth = header.desired_upper_bandwidth;
Ok(())
}
fn read_notch_filter_frequency<R: Read>(reader: &mut R, header: &mut RhsHeader) -> Result<(), IntanError> {
let notch_filter_mode = reader.read_i16::<LittleEndian>()? as i32;
header.notch_filter_frequency = match notch_filter_mode {
1 => Some(50),
2 => Some(60),
_ => None,
};
header.frequency_parameters.notch_filter_frequency = header.notch_filter_frequency;
Ok(())
}
fn read_impedance_test_frequencies<R: Read>(
reader: &mut R,
header: &mut RhsHeader,
) -> Result<(), IntanError> {
header.desired_impedance_test_frequency = reader.read_f32::<LittleEndian>()?;
header.actual_impedance_test_frequency = reader.read_f32::<LittleEndian>()?;
header.frequency_parameters.desired_impedance_test_frequency =
header.desired_impedance_test_frequency;
header.frequency_parameters.actual_impedance_test_frequency =
header.actual_impedance_test_frequency;
Ok(())
}
fn read_notes<R: Read + Seek>(reader: &mut R, header: &mut RhsHeader) -> Result<(), IntanError> {
header.notes.note1 = read_qstring(reader)?;
header.notes.note2 = read_qstring(reader)?;
header.notes.note3 = read_qstring(reader)?;
Ok(())
}
fn read_signal_summary<R: Read + Seek>(reader: &mut R, header: &mut RhsHeader) -> Result<(), IntanError> {
let number_of_signal_groups = reader.read_i16::<LittleEndian>()?;
for _ in 1..=number_of_signal_groups {
add_signal_group_information(header, reader)?;
}
Ok(())
}
fn add_signal_group_information<R: Read + Seek>(header: &mut RhsHeader, reader: &mut R) -> Result<(), IntanError> {
let signal_group_name = read_qstring(reader)?;
let signal_group_prefix = read_qstring(reader)?;
let signal_group_enabled = reader.read_i16::<LittleEndian>()?;
let signal_group_num_channels = reader.read_i16::<LittleEndian>()?;
let _ = reader.read_i16::<LittleEndian>()?;
if signal_group_num_channels > 0 && signal_group_enabled > 0 {
for _ in 0..signal_group_num_channels {
add_channel_information(header, reader, &signal_group_name, &signal_group_prefix)?;
}
}
Ok(())
}
fn add_channel_information<R: Read + Seek>(
header: &mut RhsHeader,
reader: &mut R,
signal_group_name: &str,
signal_group_prefix: &str,
) -> Result<(), IntanError> {
let mut new_channel = ChannelInfo {
port_name: signal_group_name.to_string(),
port_prefix: signal_group_prefix.to_string(),
port_number: 0,
native_channel_name: String::new(),
custom_channel_name: String::new(),
native_order: 0,
custom_order: 0,
chip_channel: 0,
board_stream: 0,
electrode_impedance_magnitude: 0.0,
electrode_impedance_phase: 0.0,
};
let mut new_trigger = SpikeTrigger {
voltage_trigger_mode: 0,
voltage_threshold: 0,
digital_trigger_channel: 0,
digital_edge_polarity: 0,
};
new_channel.native_channel_name = read_qstring(reader)?;
new_channel.custom_channel_name = read_qstring(reader)?;
new_channel.native_order = reader.read_i16::<LittleEndian>()? as i32;
new_channel.custom_order = reader.read_i16::<LittleEndian>()? as i32;
let signal_type = reader.read_i16::<LittleEndian>()? as i32;
let channel_enabled = reader.read_i16::<LittleEndian>()? as i32;
new_channel.chip_channel = reader.read_i16::<LittleEndian>()? as i32;
let _ = reader.read_i16::<LittleEndian>()?; new_channel.board_stream = reader.read_i16::<LittleEndian>()? as i32;
new_trigger.voltage_trigger_mode = reader.read_i16::<LittleEndian>()? as i32;
new_trigger.voltage_threshold = reader.read_i16::<LittleEndian>()? as i32;
new_trigger.digital_trigger_channel = reader.read_i16::<LittleEndian>()? as i32;
new_trigger.digital_edge_polarity = reader.read_i16::<LittleEndian>()? as i32;
new_channel.electrode_impedance_magnitude = reader.read_f32::<LittleEndian>()?;
new_channel.electrode_impedance_phase = reader.read_f32::<LittleEndian>()?;
if channel_enabled == 0 {
return Ok(());
}
match signal_type {
0 => {
header.amplifier_channels.push(new_channel);
header.spike_triggers.push(new_trigger);
}
1 => return Err(IntanError::InvalidChannelType), 2 => return Err(IntanError::InvalidChannelType), 3 => header.board_adc_channels.push(new_channel),
4 => header.board_dac_channels.push(new_channel),
5 => header.board_dig_in_channels.push(new_channel),
6 => header.board_dig_out_channels.push(new_channel),
_ => return Err(IntanError::InvalidChannelType),
}
Ok(())
}
fn print_header_summary(header: &RhsHeader) {
println!(
"Found {} amplifier channel{}.",
header.amplifier_channels.len(),
if header.amplifier_channels.len() != 1 {
"s"
} else {
""
}
);
if header.dc_amplifier_data_saved {
println!(
"Found {} DC amplifier channel{}.",
header.amplifier_channels.len(),
if header.amplifier_channels.len() != 1 {
"s"
} else {
""
}
);
}
println!(
"Found {} board ADC channel{}.",
header.board_adc_channels.len(),
if header.board_adc_channels.len() != 1 {
"s"
} else {
""
}
);
println!(
"Found {} board DAC channel{}.",
header.board_dac_channels.len(),
if header.board_dac_channels.len() != 1 {
"s"
} else {
""
}
);
println!(
"Found {} board digital input channel{}.",
header.board_dig_in_channels.len(),
if header.board_dig_in_channels.len() != 1 {
"s"
} else {
""
}
);
println!(
"Found {} board digital output channel{}.",
header.board_dig_out_channels.len(),
if header.board_dig_out_channels.len() != 1 {
"s"
} else {
""
}
);
println!();
}
fn read_qstring<R: Read + Seek>(reader: &mut R) -> Result<String, IntanError> {
let length = reader.read_u32::<LittleEndian>()?;
if length == 0xFFFFFFFF {
return Ok(String::new());
}
let current_position = reader.stream_position()?;
let file_length = reader.seek(SeekFrom::End(0))?;
reader.seek(SeekFrom::Start(current_position))?;
if length as u64 > file_length - current_position + 1 {
return Err(IntanError::StringReadError);
}
let length = (length as usize) / 2;
let mut data = Vec::with_capacity(length);
for _ in 0..length {
let c = reader.read_u16::<LittleEndian>()?;
data.push(c);
}
let mut result = String::with_capacity(length);
for &c in &data {
match char::from_u32(c as u32) {
Some(ch) => result.push(ch),
None => return Err(IntanError::StringReadError),
}
}
Ok(result)
}
fn calculate_data_size<R: Read + Seek>(
header: &RhsHeader,
file_size: u64,
reader: &mut R,
) -> Result<(bool, u64, u64), Box<dyn std::error::Error>> {
let bytes_per_block = get_bytes_per_data_block(header)?;
let current_position = reader.stream_position()?;
let bytes_remaining = file_size - current_position;
let data_present = bytes_remaining > 0;
if bytes_remaining % bytes_per_block as u64 != 0 {
return Err(Box::new(IntanError::FileSizeError));
}
let num_blocks = bytes_remaining / bytes_per_block as u64;
let num_samples = num_blocks * header.num_samples_per_data_block as u64;
print_record_time_summary(num_samples, header.sample_rate, data_present);
Ok((data_present, num_blocks, num_samples))
}
fn print_record_time_summary(num_amp_samples: u64, sample_rate: f32, data_present: bool) {
let record_time = num_amp_samples as f32 / sample_rate;
if data_present {
println!(
"File contains {:.3} seconds of data. Amplifiers were sampled at {:.2} kS/s.",
record_time,
sample_rate / 1000.0
);
} else {
println!(
"Header file contains no data. Amplifiers were sampled at {:.2} kS/s.",
sample_rate / 1000.0
);
}
}
fn get_bytes_per_data_block(header: &RhsHeader) -> Result<usize, Box<dyn std::error::Error>> {
let num_samples_per_data_block = 128;
let mut bytes_per_block = bytes_per_signal_type(num_samples_per_data_block, 1, 4);
bytes_per_block += bytes_per_signal_type(
num_samples_per_data_block,
header.amplifier_channels.len(),
2,
);
if header.dc_amplifier_data_saved {
bytes_per_block += bytes_per_signal_type(
num_samples_per_data_block,
header.amplifier_channels.len(),
2,
);
}
bytes_per_block += bytes_per_signal_type(
num_samples_per_data_block,
header.amplifier_channels.len(),
2,
);
bytes_per_block += bytes_per_signal_type(
num_samples_per_data_block,
header.board_adc_channels.len(),
2,
);
bytes_per_block += bytes_per_signal_type(
num_samples_per_data_block,
header.board_dac_channels.len(),
2,
);
if !header.board_dig_in_channels.is_empty() {
bytes_per_block += bytes_per_signal_type(num_samples_per_data_block, 1, 2);
}
if !header.board_dig_out_channels.is_empty() {
bytes_per_block += bytes_per_signal_type(num_samples_per_data_block, 1, 2);
}
Ok(bytes_per_block)
}
fn bytes_per_signal_type(
num_samples: usize,
num_channels: usize,
bytes_per_sample: usize,
) -> usize {
num_samples * num_channels * bytes_per_sample
}
struct RawData {
timestamps: Array1<i32>,
amplifier_data_raw: Option<Array2<i32>>,
dc_amplifier_data_raw: Option<Array2<i32>>,
stim_data_raw: Option<Array2<i32>>,
board_adc_data_raw: Option<Array2<i32>>,
board_dac_data_raw: Option<Array2<i32>>,
board_dig_in_raw: Option<Array2<i32>>,
board_dig_out_raw: Option<Array2<i32>>,
}
fn read_all_data_blocks<R: Read + Seek>(
header: &RhsHeader,
num_samples: u64,
num_blocks: u64,
reader: &mut R,
) -> Result<RawData, Box<dyn std::error::Error>> {
println!("Reading data from file...");
let mut raw_data = RawData {
timestamps: Array1::zeros(num_samples as usize),
amplifier_data_raw: if !header.amplifier_channels.is_empty() {
Some(Array2::zeros((
header.amplifier_channels.len(),
num_samples as usize,
)))
} else {
None
},
dc_amplifier_data_raw: if !header.amplifier_channels.is_empty()
&& header.dc_amplifier_data_saved
{
Some(Array2::zeros((
header.amplifier_channels.len(),
num_samples as usize,
)))
} else {
None
},
stim_data_raw: if !header.amplifier_channels.is_empty() {
Some(Array2::zeros((
header.amplifier_channels.len(),
num_samples as usize,
)))
} else {
None
},
board_adc_data_raw: if !header.board_adc_channels.is_empty() {
Some(Array2::zeros((
header.board_adc_channels.len(),
num_samples as usize,
)))
} else {
None
},
board_dac_data_raw: if !header.board_dac_channels.is_empty() {
Some(Array2::zeros((
header.board_dac_channels.len(),
num_samples as usize,
)))
} else {
None
},
board_dig_in_raw: if !header.board_dig_in_channels.is_empty() {
Some(Array2::zeros((
header.board_dig_in_channels.len(),
num_samples as usize,
)))
} else {
None
},
board_dig_out_raw: if !header.board_dig_out_channels.is_empty() {
Some(Array2::zeros((
header.board_dig_out_channels.len(),
num_samples as usize,
)))
} else {
None
},
};
let print_step = PRINT_PROGRESS_STEP;
let mut percent_done = print_step;
let num_blocks = num_blocks as usize;
for i in 0..num_blocks {
let index = i * SAMPLES_PER_DATA_BLOCK;
read_one_data_block(&mut raw_data, header, index, reader)?;
let progress = (i as f64 / num_blocks as f64) * 100.0;
if progress >= percent_done as f64 {
println!("{}% done...", percent_done);
percent_done += print_step;
}
}
Ok(raw_data)
}
fn read_one_data_block<R: Read + Seek>(
data: &mut RawData,
header: &RhsHeader,
index: usize,
reader: &mut R,
) -> Result<(), Box<dyn std::error::Error>> {
let samples_per_block = SAMPLES_PER_DATA_BLOCK;
read_timestamps(reader, &mut data.timestamps, index, samples_per_block)?;
read_analog_signals(reader, data, header, index, samples_per_block)?;
read_digital_signals(reader, data, header, index, samples_per_block)?;
Ok(())
}
fn read_timestamps<R: Read>(
reader: &mut R,
timestamps: &mut Array1<i32>,
index: usize,
num_samples: usize,
) -> Result<(), Box<dyn std::error::Error>> {
let start = index;
let end = start + num_samples;
let mut buffer = vec![0u8; num_samples * 4];
reader.read_exact(&mut buffer)?;
let mut timestamps_slice = timestamps.slice_mut(s![start..end]);
for i in 0..num_samples {
let ts = i32::from_le_bytes([
buffer[i * 4],
buffer[i * 4 + 1],
buffer[i * 4 + 2],
buffer[i * 4 + 3],
]);
timestamps_slice[i] = ts;
}
Ok(())
}
fn read_analog_signals<R: Read>(
reader: &mut R,
data: &mut RawData,
header: &RhsHeader,
index: usize,
samples_per_block: usize,
) -> Result<(), Box<dyn std::error::Error>> {
let num_amplifier_channels = header.amplifier_channels.len();
if num_amplifier_channels > 0 {
if let Some(ref mut amp_data) = data.amplifier_data_raw {
read_analog_signal_type(
reader,
amp_data,
index,
samples_per_block,
num_amplifier_channels,
)?;
}
}
if num_amplifier_channels > 0 && header.dc_amplifier_data_saved {
if let Some(ref mut dc_amp_data) = data.dc_amplifier_data_raw {
read_analog_signal_type(
reader,
dc_amp_data,
index,
samples_per_block,
num_amplifier_channels,
)?;
}
}
if num_amplifier_channels > 0 {
if let Some(ref mut stim_data) = data.stim_data_raw {
read_analog_signal_type(
reader,
stim_data,
index,
samples_per_block,
num_amplifier_channels,
)?;
}
}
let num_board_adc_channels = header.board_adc_channels.len();
if num_board_adc_channels > 0 {
if let Some(ref mut adc_data) = data.board_adc_data_raw {
read_analog_signal_type(
reader,
adc_data,
index,
samples_per_block,
num_board_adc_channels,
)?;
}
}
let num_board_dac_channels = header.board_dac_channels.len();
if num_board_dac_channels > 0 {
if let Some(ref mut dac_data) = data.board_dac_data_raw {
read_analog_signal_type(
reader,
dac_data,
index,
samples_per_block,
num_board_dac_channels,
)?;
}
}
Ok(())
}
fn read_analog_signal_type<R: Read>(
reader: &mut R,
dest: &mut Array2<i32>,
start: usize,
num_samples: usize,
num_channels: usize,
) -> Result<(), Box<dyn std::error::Error>> {
if num_channels < 1 {
return Ok(());
}
let end = start + num_samples;
let mut buffer = vec![0u8; num_samples * num_channels * 2];
reader.read_exact(&mut buffer)?;
let mut t_slice = dest.slice_mut(s![.., start..end]);
for ch in 0..num_channels {
for s in 0..num_samples {
let idx = 2 * (s * num_channels + ch);
let sample = i16::from_le_bytes([buffer[idx], buffer[idx + 1]]) as i32;
t_slice[[ch, s]] = sample;
}
}
Ok(())
}
fn read_digital_signals<R: Read>(
reader: &mut R,
data: &mut RawData,
header: &RhsHeader,
index: usize,
samples_per_block: usize,
) -> Result<(), Box<dyn std::error::Error>> {
let num_board_dig_in_channels = header.board_dig_in_channels.len();
if num_board_dig_in_channels > 0 {
read_digital_signal_type(reader, &mut data.board_dig_in_raw, index, samples_per_block)?;
}
let num_board_dig_out_channels = header.board_dig_out_channels.len();
if num_board_dig_out_channels > 0 {
read_digital_signal_type(reader, &mut data.board_dig_out_raw, index, samples_per_block)?;
}
Ok(())
}
fn read_digital_signal_type<R: Read>(
reader: &mut R,
dest: &mut Option<Array2<i32>>,
start: usize,
num_samples: usize,
) -> Result<(), Box<dyn std::error::Error>> {
if let Some(dest_array) = dest.as_mut() {
let num_channels = dest_array.shape()[0];
if num_channels < 1 {
return Ok(());
}
let end = start + num_samples;
let mut buffer = vec![0u8; num_samples * 2];
reader.read_exact(&mut buffer)?;
let mut t_slice = dest_array.slice_mut(s![.., start..end]);
for s in 0..num_samples {
let value = u16::from_le_bytes([buffer[s * 2], buffer[s * 2 + 1]]) as i32;
for ch in 0..num_channels {
t_slice[[ch, s]] = value;
}
}
}
Ok(())
}
fn check_end_of_file<R: Read + Seek>(filesize: u64, reader: &mut R) -> Result<(), Box<dyn std::error::Error>> {
let current_position = reader.stream_position()?;
let bytes_remaining = filesize - current_position;
if bytes_remaining != 0 {
return Err(Box::new(IntanError::FileSizeError));
}
Ok(())
}
fn process_data(
header: &RhsHeader,
raw_data: RawData,
) -> Result<RhsData, Box<dyn std::error::Error>> {
println!("Processing data...");
let mut data = RhsData {
timestamps: raw_data.timestamps.clone(),
amplifier_data: None,
dc_amplifier_data: None,
stim_data: None,
compliance_limit_data: None,
charge_recovery_data: None,
amp_settle_data: None,
board_adc_data: None,
board_dac_data: None,
board_dig_in_data: None,
board_dig_out_data: None,
};
check_timestamps(&data.timestamps);
if let Some(amp_data_raw) = raw_data.amplifier_data_raw {
let mut amp_data = scale_amplifier_data(&_data_raw);
apply_notch_filter(header, &mut amp_data);
data.amplifier_data = Some(amp_data);
}
if let Some(dc_amp_data_raw) = raw_data.dc_amplifier_data_raw {
let dc_amp_data = scale_dc_amplifier_data(&dc_amp_data_raw);
data.dc_amplifier_data = Some(dc_amp_data);
}
if let Some(stim_data_raw) = raw_data.stim_data_raw {
let (stim_data, compliance_limit_data, charge_recovery_data, amp_settle_data) =
extract_stim_data(&stim_data_raw, header.stim_step_size);
data.stim_data = Some(stim_data);
data.compliance_limit_data = Some(compliance_limit_data);
data.charge_recovery_data = Some(charge_recovery_data);
data.amp_settle_data = Some(amp_settle_data);
}
if let Some(adc_data_raw) = raw_data.board_adc_data_raw {
let adc_data = scale_adc_data(&adc_data_raw);
data.board_adc_data = Some(adc_data);
}
if let Some(dac_data_raw) = raw_data.board_dac_data_raw {
let dac_data = scale_dac_data(&dac_data_raw);
data.board_dac_data = Some(dac_data);
}
if let Some(dig_in_raw) = raw_data.board_dig_in_raw {
data.board_dig_in_data = Some(extract_digital_data(
&dig_in_raw,
&header.board_dig_in_channels,
)?);
}
if let Some(dig_out_raw) = raw_data.board_dig_out_raw {
data.board_dig_out_data = Some(extract_digital_data(
&dig_out_raw,
&header.board_dig_out_channels,
)?);
}
Ok(data)
}
fn check_timestamps(timestamps: &Array1<i32>) {
let num_gaps = timestamps
.windows(2)
.into_iter()
.filter(|window| window[1] - window[0] != 1)
.count();
if num_gaps == 0 {
println!("No missing timestamps in data.");
} else {
println!(
"Warning: {} gaps in timestamp data found. Time scale will not be uniform!",
num_gaps
);
}
}
fn scale_amplifier_data(data_raw: &Array2<i32>) -> Array2<f64> {
data_raw.mapv(|x| {
let unsigned_val = if x < 0 {
(x + 65536) as f64
} else {
x as f64
};
(unsigned_val - ADC_DAC_OFFSET) * AMPLIFIER_SCALE_FACTOR
})
}
fn scale_dc_amplifier_data(data_raw: &Array2<i32>) -> Array2<f64> {
data_raw.mapv(|x| {
let unsigned_val = if x < 0 {
(x + 65536) as f64
} else {
x as f64
};
((unsigned_val - DC_AMPLIFIER_OFFSET) * DC_AMPLIFIER_SCALE_FACTOR) / 1000.0
})
}
fn scale_adc_data(data_raw: &Array2<i32>) -> Array2<f64> {
data_raw.mapv(|x| {
let unsigned_val = if x < 0 {
(x + 65536) as f64
} else {
x as f64
};
(unsigned_val - ADC_DAC_OFFSET) * ADC_DAC_SCALE_FACTOR
})
}
fn scale_dac_data(data_raw: &Array2<i32>) -> Array2<f64> {
data_raw.mapv(|x| {
let unsigned_val = if x < 0 {
(x + 65536) as f64
} else {
x as f64
};
(unsigned_val - ADC_DAC_OFFSET) * ADC_DAC_SCALE_FACTOR
})
}
fn extract_stim_data(
stim_data_raw: &Array2<i32>,
stim_step_size: f32,
) -> (Array2<i32>, Array2<bool>, Array2<bool>, Array2<bool>) {
let shape = stim_data_raw.shape();
let num_channels = shape[0];
let num_samples = shape[1];
let mut stim_data = Array2::<i32>::zeros((num_channels, num_samples));
let mut compliance_limit_data = Array2::<bool>::from_elem((num_channels, num_samples), false);
let mut charge_recovery_data = Array2::<bool>::from_elem((num_channels, num_samples), false);
let mut amp_settle_data = Array2::<bool>::from_elem((num_channels, num_samples), false);
for i in 0..num_channels {
for j in 0..num_samples {
let value = stim_data_raw[[i, j]];
compliance_limit_data[[i, j]] = (value & 32768) != 0;
charge_recovery_data[[i, j]] = (value & 16384) != 0;
amp_settle_data[[i, j]] = (value & 8192) != 0;
let stim_polarity = 1 - 2 * ((value & 256) >> 8);
let curr_amp = value & 255;
stim_data[[i, j]] = ((curr_amp * stim_polarity) as f32 * stim_step_size) as i32;
}
}
(
stim_data,
compliance_limit_data,
charge_recovery_data,
amp_settle_data,
)
}
fn extract_digital_data(
digital_data_raw: &Array2<i32>,
channels: &[ChannelInfo],
) -> Result<Array2<i32>, Box<dyn std::error::Error>> {
let shape = digital_data_raw.shape();
let num_channels = channels.len();
let num_samples = shape[1];
let mut digital_data = Array2::<i32>::zeros((num_channels, num_samples));
for (i, channel) in channels.iter().enumerate() {
let mask = 1 << channel.native_order;
for j in 0..num_samples {
digital_data[[i, j]] = if (digital_data_raw[[0, j]] & mask) != 0 {
1
} else {
0
};
}
}
Ok(digital_data)
}
fn apply_notch_filter(header: &RhsHeader, data: &mut Array2<f64>) {
if header.notch_filter_frequency.is_none() {
return;
}
if header.version.major >= 3 {
return;
}
let notch_freq = header.notch_filter_frequency.unwrap() as f32;
println!("Applying notch filter...");
let print_step = 10;
let mut percent_done = print_step;
let num_channels = data.shape()[0];
for i in 0..num_channels {
let channel_data: Vec<f64> = data.slice(s![i, ..]).to_vec();
let filtered_data = notch_filter(&channel_data, header.sample_rate, notch_freq, 10);
let mut slice = data.slice_mut(s![i, ..]);
for (j, &value) in filtered_data.iter().enumerate() {
slice[j] = value;
}
let progress = (i as f64 / num_channels as f64) * 100.0;
if progress >= percent_done as f64 {
println!("{}% done...", percent_done);
percent_done += print_step;
}
}
}
fn notch_filter(signal_in: &[f64], f_sample: f32, f_notch: f32, bandwidth: i32) -> Vec<f64> {
let t_step = 1.0 / f_sample as f64;
let f_c = f_notch as f64 * t_step;
let signal_length = signal_in.len();
let d = (-2.0 * PI * (bandwidth as f64 / 2.0) * t_step).exp();
let b = (1.0 + d * d) * (2.0 * PI * f_c).cos();
let a0 = 1.0;
let a1 = -b;
let a2 = d * d;
let a = (1.0 + d * d) / 2.0;
let b0 = 1.0;
let b1 = -2.0 * (2.0 * PI * f_c).cos();
let b2 = 1.0;
let mut signal_out = vec![0.0; signal_length];
signal_out[0] = signal_in[0];
signal_out[1] = signal_in[1];
for i in 2..signal_length {
signal_out[i] =
(a * b0 * signal_in[i] + a * b1 * signal_in[i - 1] + a * b2 * signal_in[i - 2]
- a2 * signal_out[i - 2]
- a1 * signal_out[i - 1])
/ a0;
}
signal_out
}
pub fn load_and_combine_files(file_paths: &[std::path::PathBuf]) -> Result<RhsFile, Box<dyn std::error::Error>> {
if file_paths.is_empty() {
return Err(Box::new(IntanError::Other("No files to load".to_string())));
}
println!("\nLoading file 1/{}: {}", file_paths.len(), file_paths[0].display());
let mut combined_file = load_file(&file_paths[0])?;
if file_paths.len() == 1 {
return Ok(combined_file);
}
combined_file.source_files = Some(vec![file_paths[0].to_string_lossy().to_string()]);
for (i, file_path) in file_paths[1..].iter().enumerate() {
println!("\nLoading file {}/{}: {}", i + 2, file_paths.len(), file_path.display());
let next_file = load_file(file_path)?;
verify_header_compatibility(&combined_file.header, &next_file.header)?;
if combined_file.data_present && next_file.data_present {
combine_data(&mut combined_file, next_file)?;
}
if let Some(ref mut sources) = combined_file.source_files {
sources.push(file_path.to_string_lossy().to_string());
}
}
println!("\nSuccessfully combined {} files", file_paths.len());
println!("Total duration: {:.2} seconds", combined_file.duration());
Ok(combined_file)
}
fn verify_header_compatibility(header1: &RhsHeader, header2: &RhsHeader) -> Result<(), Box<dyn std::error::Error>> {
if (header1.sample_rate - header2.sample_rate).abs() > 0.01 {
return Err(Box::new(IntanError::Other(format!(
"Sample rates don't match: {} Hz vs {} Hz",
header1.sample_rate, header2.sample_rate
))));
}
if header1.amplifier_channels.len() != header2.amplifier_channels.len() {
return Err(Box::new(IntanError::Other(format!(
"Number of amplifier channels don't match: {} vs {}",
header1.amplifier_channels.len(), header2.amplifier_channels.len()
))));
}
if header1.board_adc_channels.len() != header2.board_adc_channels.len() {
return Err(Box::new(IntanError::Other(format!(
"Number of board ADC channels don't match: {} vs {}",
header1.board_adc_channels.len(), header2.board_adc_channels.len()
))));
}
if header1.board_dig_in_channels.len() != header2.board_dig_in_channels.len() {
return Err(Box::new(IntanError::Other(format!(
"Number of digital input channels don't match: {} vs {}",
header1.board_dig_in_channels.len(), header2.board_dig_in_channels.len()
))));
}
for (i, (ch1, ch2)) in header1.amplifier_channels.iter().zip(&header2.amplifier_channels).enumerate() {
if ch1.native_channel_name != ch2.native_channel_name {
return Err(Box::new(IntanError::Other(format!(
"Amplifier channel {} names don't match: '{}' vs '{}'",
i, ch1.native_channel_name, ch2.native_channel_name
))));
}
}
Ok(())
}
fn combine_data(combined: &mut RhsFile, next: RhsFile) -> Result<(), Box<dyn std::error::Error>> {
use ndarray::{Axis, concatenate};
if let (Some(combined_data), Some(next_data)) = (combined.data.as_mut(), next.data) {
combined_data.timestamps = concatenate![Axis(0), combined_data.timestamps.view(), next_data.timestamps.view()];
if let (Some(combined_amp), Some(next_amp)) =
(&mut combined_data.amplifier_data, next_data.amplifier_data) {
*combined_amp = concatenate![Axis(1), combined_amp.view(), next_amp.view()];
}
if let (Some(combined_dc), Some(next_dc)) =
(&mut combined_data.dc_amplifier_data, next_data.dc_amplifier_data) {
*combined_dc = concatenate![Axis(1), combined_dc.view(), next_dc.view()];
}
if let (Some(combined_stim), Some(next_stim)) =
(&mut combined_data.stim_data, next_data.stim_data) {
*combined_stim = concatenate![Axis(1), combined_stim.view(), next_stim.view()];
}
if let (Some(combined_comp), Some(next_comp)) =
(&mut combined_data.compliance_limit_data, next_data.compliance_limit_data) {
*combined_comp = concatenate![Axis(1), combined_comp.view(), next_comp.view()];
}
if let (Some(combined_charge), Some(next_charge)) =
(&mut combined_data.charge_recovery_data, next_data.charge_recovery_data) {
*combined_charge = concatenate![Axis(1), combined_charge.view(), next_charge.view()];
}
if let (Some(combined_settle), Some(next_settle)) =
(&mut combined_data.amp_settle_data, next_data.amp_settle_data) {
*combined_settle = concatenate![Axis(1), combined_settle.view(), next_settle.view()];
}
if let (Some(combined_adc), Some(next_adc)) =
(&mut combined_data.board_adc_data, next_data.board_adc_data) {
*combined_adc = concatenate![Axis(1), combined_adc.view(), next_adc.view()];
}
if let (Some(combined_dac), Some(next_dac)) =
(&mut combined_data.board_dac_data, next_data.board_dac_data) {
*combined_dac = concatenate![Axis(1), combined_dac.view(), next_dac.view()];
}
if let (Some(combined_din), Some(next_din)) =
(&mut combined_data.board_dig_in_data, next_data.board_dig_in_data) {
*combined_din = concatenate![Axis(1), combined_din.view(), next_din.view()];
}
if let (Some(combined_dout), Some(next_dout)) =
(&mut combined_data.board_dig_out_data, next_data.board_dig_out_data) {
*combined_dout = concatenate![Axis(1), combined_dout.view(), next_dout.view()];
}
}
Ok(())
}