use std::io::BufRead;
use std::path::Path;
use crate::error::{NeuroformatsError, Result};
use byteordered::byteorder::ReadBytesExt;
use colorgrad::Gradient;
pub fn values_to_colors(values: &[f32], min_val: f32, max_val: f32) -> Vec<u8> {
let grad = colorgrad::preset::viridis();
let mut colors = Vec::with_capacity(values.len() * 3);
for &value in values {
let t = (value - min_val) / (max_val - min_val);
let t = t.clamp(0.0, 1.0);
let color = grad.at(t as f32);
colors.push((color.r * 255.0) as u8);
colors.push((color.g * 255.0) as u8);
colors.push((color.b * 255.0) as u8);
}
colors
}
pub fn is_gz_file<P>(path: P) -> bool
where
P: AsRef<Path>,
{
path.as_ref()
.file_name()
.map(|a| a.to_string_lossy().ends_with(".gz"))
.unwrap_or(false)
}
pub fn read_fs_variable_length_string<S>(input: &mut S, max_len: usize) -> Result<String>
where
S: BufRead,
{
let mut last_char;
let mut cur_char: char = '0';
let mut info_line = String::new();
loop {
last_char = cur_char;
cur_char = input.read_u8()? as char;
info_line.push(cur_char);
if info_line.len() > max_len {
return Err(NeuroformatsError::StringTooLong);
}
if last_char == '\x0A' && cur_char == '\x0A' {
break;
}
}
Ok(info_line)
}
pub fn read_fixed_length_string<S>(input: &mut S, len: usize) -> Result<String>
where
S: BufRead,
{
let mut info_line = String::with_capacity(len);
for char_idx in 0..len {
let cur_char = input.read_u8()? as char;
if char_idx == (len - 1) {
if cur_char != '\0' {
info_line.push(cur_char);
}
} else {
info_line.push(cur_char);
}
}
Ok(info_line)
}
pub fn vec32minmax<I>(data: I, remove_nan: bool) -> (f32, f32)
where
I: Iterator<Item = f32>,
{
let mut data = data.filter(|v| match (remove_nan, v.is_nan()) {
(_, false) => true,
(true, true) => false,
(false, true) => panic!("NaN values not allowed in input."),
});
let first = data.next().expect("Input data must not be empty.");
let mut min = first;
let mut max = first;
for value in data {
if value < min {
min = value;
} else if value > max {
max = value;
}
}
(min, max)
}
pub fn checked_mul_dims(dims: &[i32]) -> Result<usize> {
let mut total: usize = 1;
for &dim in dims {
if dim < 0 {
return Err(NeuroformatsError::InvalidHeaderValue(format!(
"Negative dimension value: {}",
dim
)));
}
total = total
.checked_mul(dim as usize)
.ok_or(NeuroformatsError::IntegerOverflow)?;
}
Ok(total)
}
pub fn validate_finite_f32_slice(values: &[f32], field_name: &str) -> Result<()> {
for (i, &v) in values.iter().enumerate() {
if !v.is_finite() {
return Err(NeuroformatsError::InvalidHeaderValue(format!(
"Field '{}' at index {} is not finite (value: {})",
field_name, i, v
)));
}
}
Ok(())
}
pub fn validate_finite_vertex_values(values: &[f32], label: &str) -> Result<()> {
for (i, &v) in values.iter().enumerate() {
if !v.is_finite() {
return Err(NeuroformatsError::InvalidVertexValue(format!(
"{} at index {} is not finite (value: {})",
label, i, v
)));
}
}
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn the_min_and_max_of_an_f32_vector_without_nan_values_can_be_computed() {
let v: Vec<f32> = vec![0.4, 0.5, 0.9, 0.01];
let (min, max) = vec32minmax(v.into_iter(), true);
assert_abs_diff_eq!(min, 0.01, epsilon = 1e-8);
assert_abs_diff_eq!(max, 0.9, epsilon = 1e-8);
}
#[test]
fn the_min_and_max_of_an_f32_vector_with_nan_values_can_be_computed() {
let v: Vec<f32> = vec![0.4, 0.5, 0.9, f32::NAN, 0.01];
let (min, max) = vec32minmax(v.into_iter(), true);
assert_abs_diff_eq!(min, 0.01, epsilon = 1e-8);
assert_abs_diff_eq!(max, 0.9, epsilon = 1e-8);
}
#[test]
fn a_variable_length_fs_string_can_be_read() {
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
let mut c = Cursor::new(Vec::<u8>::new());
c.write(b"test\x0A\x0A").unwrap();
c.write(&[166 as u8]).unwrap();
c.seek(SeekFrom::Start(0)).unwrap();
let s = read_fs_variable_length_string(&mut c, 1024).unwrap();
let mut out = Vec::new();
c.read_to_end(&mut out).unwrap();
assert_eq!(s, "test\n\n");
assert_eq!(out, &[166]);
assert_eq!(7, c.position());
}
#[test]
fn a_fixed_length_nul_terminated_string_can_be_read() {
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
let mut c = Cursor::new(Vec::<u8>::new());
c.write(b"test\x0A\x0Atest\x00").unwrap();
c.seek(SeekFrom::Start(0)).unwrap();
let s = read_fixed_length_string(&mut c, 11 as usize).unwrap();
let mut out: Vec<u8> = Vec::new();
c.read_to_end(&mut out).unwrap();
let empty: Vec<u8> = [].to_vec();
assert_eq!(s, "test\n\ntest");
assert_eq!(out, empty);
assert_eq!(11, c.position());
}
#[test]
fn a_fixed_length_without_termination_char_can_be_read() {
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
let mut c = Cursor::new(Vec::<u8>::new());
c.write(b"test\x0A\x0Atestdonotreadthis").unwrap();
c.seek(SeekFrom::Start(0)).unwrap();
let s = read_fixed_length_string(&mut c, 10 as usize).unwrap();
assert_eq!(s, "test\n\ntest");
assert_eq!(10, c.position());
let mut out: Vec<u8> = Vec::new();
c.read_to_end(&mut out).unwrap();
assert_eq!(23, c.position());
}
#[test]
fn float_per_vertex_data_can_be_converted_to_rgb_uint8_colors() {
let values: Vec<f32> = vec![0.0, 0.5, 1.0];
let min_val: f32 = 0.0;
let max_val: f32 = 1.0;
let colors: Vec<u8> = values_to_colors(&values, min_val, max_val);
assert_eq!(colors, vec![68, 1, 84, 38, 130, 142, 254, 232, 37]);
}
#[test]
fn checked_mul_dims_rejects_negative_values() {
let result = checked_mul_dims(&[10, -1, 10]);
assert!(result.is_err());
match result {
Err(NeuroformatsError::InvalidHeaderValue(msg)) => {
assert!(msg.contains("Negative"));
}
_ => panic!("Expected InvalidHeaderValue error"),
}
}
#[test]
fn checked_mul_dims_detects_overflow() {
let result = checked_mul_dims(&[i32::MAX, i32::MAX, i32::MAX]);
assert!(result.is_err());
}
#[test]
fn checked_mul_dims_works_for_valid_input() {
assert_eq!(checked_mul_dims(&[256, 256, 256, 1]).unwrap(), 16_777_216);
assert_eq!(checked_mul_dims(&[1]).unwrap(), 1);
assert_eq!(checked_mul_dims(&[0, 100]).unwrap(), 0);
}
#[test]
fn validate_finite_f32_slice_rejects_nan() {
let values = [1.0_f32, f32::NAN, 3.0];
let result = validate_finite_f32_slice(&values, "test_field");
assert!(result.is_err());
}
#[test]
fn validate_finite_f32_slice_rejects_inf() {
let values = [1.0_f32, f32::INFINITY, 3.0];
let result = validate_finite_f32_slice(&values, "test_field");
assert!(result.is_err());
}
#[test]
fn validate_finite_f32_slice_rejects_neg_inf() {
let values = [f32::NEG_INFINITY, 0.0];
let result = validate_finite_f32_slice(&values, "test_field");
assert!(result.is_err());
}
#[test]
fn validate_finite_f32_slice_accepts_valid_values() {
let values = [1.0_f32, -2.5, 0.0, 3.14];
assert!(validate_finite_f32_slice(&values, "test_field").is_ok());
}
#[test]
fn validate_finite_vertex_values_rejects_nan() {
let values = [0.0_f32, f32::NAN, 1.0];
let result = validate_finite_vertex_values(&values, "vertex");
assert!(result.is_err());
}
#[test]
fn variable_length_string_exceeding_max_is_rejected() {
use std::io::{Cursor, Seek, Write};
let mut c = Cursor::new(Vec::<u8>::new());
let long_string = vec![b'A'; 200];
c.write(&long_string).unwrap();
c.seek(std::io::SeekFrom::Start(0)).unwrap();
let result = read_fs_variable_length_string(&mut c, 100);
assert!(result.is_err());
match result {
Err(NeuroformatsError::StringTooLong) => {} other => panic!("Expected StringTooLong, got {:?}", other),
}
}
}