use byteordered::{ByteOrdered, Endianness};
use flate2::bufread::GzDecoder;
use std::fs::File;
use std::io::{BufReader, BufRead, BufWriter};
use std::path::{Path};
use std::fmt;
use crate::util::{is_gz_file, vec32minmax, validate_finite_vertex_values};
use crate::error::{NeuroformatsError, Result};
use crate::config;
pub const CURV_MAGIC_CODE_U8: u8 = 255;
#[derive(Debug, Clone, PartialEq)]
pub struct FsCurvHeader {
pub curv_magic: [u8; 3],
pub num_vertices: i32,
pub num_faces: i32,
pub num_values_per_vertex: i32,
}
impl Default for FsCurvHeader {
fn default() -> FsCurvHeader {
FsCurvHeader {
curv_magic: [255; 3],
num_vertices: 0,
num_faces: 0,
num_values_per_vertex: 1,
}
}
}
impl FsCurvHeader {
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<FsCurvHeader> {
let gz = is_gz_file(&path);
let file = BufReader::new(File::open(path)?);
if gz {
FsCurvHeader::from_reader(BufReader::new(GzDecoder::new(file)))
} else {
FsCurvHeader::from_reader(file)
}
}
pub fn from_reader<S>(input: S) -> Result<FsCurvHeader>
where
S: BufRead,
{
let mut hdr = FsCurvHeader::default();
let mut input = ByteOrdered::be(input);
for v in &mut hdr.curv_magic {
*v = input.read_u8()?;
}
hdr.num_vertices = input.read_i32()?;
hdr.num_faces = input.read_i32()?;
hdr.num_values_per_vertex = input.read_i32()?;
if !(hdr.curv_magic[0] == CURV_MAGIC_CODE_U8 && hdr.curv_magic[1] == CURV_MAGIC_CODE_U8 && hdr.curv_magic[2] == CURV_MAGIC_CODE_U8) {
Err(NeuroformatsError::InvalidCurvFormat)
} else {
Ok(hdr)
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct FsCurv {
pub header: FsCurvHeader,
pub data: Vec<f32>,
}
impl fmt::Display for FsCurv {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let (min, max) = vec32minmax(self.data.iter().copied(), false);
write!(f, "Per-vertex data for {} vertices, with values in range {} to {}.", self.data.len(), min, max)
}
}
pub fn read_curv<P: AsRef<Path> + Copy>(path: P) -> Result<FsCurv> {
FsCurv::from_file(path)
}
pub fn write_curv<P: AsRef<Path> + Copy>(path: P, curv : &FsCurv) {
let f = File::create(path).expect("Unable to create curv file");
let f = BufWriter::new(f);
let mut f = ByteOrdered::runtime(f, Endianness::Big);
f.write_u8(CURV_MAGIC_CODE_U8).unwrap();
f.write_u8(CURV_MAGIC_CODE_U8).unwrap();
f.write_u8(CURV_MAGIC_CODE_U8).unwrap();
f.write_i32(curv.header.num_vertices).unwrap();
f.write_i32(curv.header.num_faces).unwrap();
f.write_i32(curv.header.num_values_per_vertex).unwrap();
for v in &curv.data {
f.write_f32(*v).unwrap();
}
}
impl FsCurv {
pub fn from_file<P: AsRef<Path> + Copy>(path: P) -> Result<FsCurv> {
let gz = is_gz_file(&path);
let hdr = FsCurvHeader::from_file(path)?;
let file = BufReader::new(File::open(path)?);
let data: Vec<f32> = if gz {
FsCurv::curv_data_from_reader(BufReader::new(GzDecoder::new(file)), &hdr)?
} else {
FsCurv::curv_data_from_reader(file, &hdr)?
};
let curv = FsCurv {
header : hdr,
data: data,
};
Ok(curv)
}
pub fn curv_data_from_reader<S>(input: S, hdr: &FsCurvHeader) -> Result<Vec<f32>>
where
S: BufRead,
{
if hdr.num_vertices < 0 {
return Err(NeuroformatsError::InvalidHeaderValue(format!(
"Negative vertex count: {}",
hdr.num_vertices
)));
}
let num_vertices = hdr.num_vertices as usize;
if num_vertices > config::max_vertices() {
return Err(NeuroformatsError::AllocationTooLarge);
}
let data_bytes = num_vertices
.checked_mul(4) .ok_or(NeuroformatsError::IntegerOverflow)?;
if data_bytes > config::max_bytes_per_file() {
return Err(NeuroformatsError::AllocationTooLarge);
}
let mut input = ByteOrdered::be(input);
let hdr_size = 15;
let mut hdr_data: Vec<u8> = Vec::with_capacity(hdr_size as usize);
for _ in 0..hdr_size {
hdr_data.push(input.read_u8().map_err(|e| NeuroformatsError::Io(e))?);
}
let mut data: Vec<f32> = Vec::with_capacity(num_vertices);
for _ in 0..num_vertices {
data.push(input.read_f32().map_err(|e| NeuroformatsError::Io(e))?);
}
validate_finite_vertex_values(&data, "curv value")?;
Ok(data)
}
}
#[cfg(test)]
mod test {
use super::*;
use approx::assert_abs_diff_eq;
use tempfile::{tempdir};
#[test]
fn the_demo_curv_file_can_be_read() {
const CURV_FILE: &str = "resources/subjects_dir/subject1/surf/lh.thickness";
let curv = read_curv(CURV_FILE).unwrap();
assert_eq!(149244, curv.header.num_vertices);
assert_eq!(298484, curv.header.num_faces);
assert_eq!(1, curv.header.num_values_per_vertex);
assert_eq!(149244, curv.data.len());
use crate::util::vec32minmax;
let (min, max) = vec32minmax(curv.data.into_iter(), false);
assert_abs_diff_eq!(0.0, min, epsilon = 1e-10);
assert_abs_diff_eq!(5.0, max, epsilon = 1e-10);
}
#[test]
fn a_curv_file_can_be_written_and_reread() {
const CURV_FILE: &str = "resources/subjects_dir/subject1/surf/lh.thickness";
let curv = read_curv(CURV_FILE).unwrap();
let dir = tempdir().unwrap();
let tfile_path = dir.path().join("temp-curv-file.curv");
let tfile_path = tfile_path.to_str().unwrap();
write_curv(tfile_path, &curv);
let curv_re = read_curv(tfile_path).unwrap();
assert_eq!(149244, curv_re.header.num_vertices);
assert_eq!(298484, curv_re.header.num_faces);
assert_eq!(1, curv_re.header.num_values_per_vertex);
assert_eq!(149244, curv_re.data.len());
use crate::util::vec32minmax;
let (min, max) = vec32minmax(curv_re.data.into_iter(), false);
assert_abs_diff_eq!(0.0, min, epsilon = 1e-10);
assert_abs_diff_eq!(5.0, max, epsilon = 1e-10);
}
#[test]
fn curv_rejects_negative_vertex_count() {
use std::io::{Cursor, Write};
use byteordered::byteorder::WriteBytesExt;
let mut buf = Cursor::new(Vec::new());
buf.write_all(&[255, 255, 255]).unwrap();
buf.write_i32::<byteordered::byteorder::BigEndian>(-1).unwrap();
buf.write_i32::<byteordered::byteorder::BigEndian>(10).unwrap(); buf.write_i32::<byteordered::byteorder::BigEndian>(1).unwrap(); buf.set_position(0);
let hdr = FsCurvHeader::from_reader(buf).unwrap();
let mut buf2 = Cursor::new(Vec::new());
buf2.write_all(&[255, 255, 255]).unwrap();
buf2.write_i32::<byteordered::byteorder::BigEndian>(-1).unwrap();
buf2.write_i32::<byteordered::byteorder::BigEndian>(10).unwrap();
buf2.write_i32::<byteordered::byteorder::BigEndian>(1).unwrap();
buf2.set_position(0);
let result = FsCurv::curv_data_from_reader(buf2, &hdr);
assert!(result.is_err());
}
#[test]
fn curv_rejects_nan_data_values() {
use std::io::{Cursor, Write};
use byteordered::byteorder::WriteBytesExt;
let mut buf = Cursor::new(Vec::new());
buf.write_all(&[255, 255, 255]).unwrap();
buf.write_i32::<byteordered::byteorder::BigEndian>(1).unwrap(); buf.write_i32::<byteordered::byteorder::BigEndian>(0).unwrap(); buf.write_i32::<byteordered::byteorder::BigEndian>(1).unwrap(); buf.write_f32::<byteordered::byteorder::BigEndian>(f32::NAN).unwrap();
buf.set_position(0);
let hdr = FsCurvHeader::from_reader(buf).unwrap();
let mut buf2 = Cursor::new(Vec::new());
buf2.write_all(&[255, 255, 255]).unwrap();
buf2.write_i32::<byteordered::byteorder::BigEndian>(1).unwrap();
buf2.write_i32::<byteordered::byteorder::BigEndian>(0).unwrap();
buf2.write_i32::<byteordered::byteorder::BigEndian>(1).unwrap();
buf2.write_f32::<byteordered::byteorder::BigEndian>(f32::NAN).unwrap();
buf2.set_position(0);
let result = FsCurv::curv_data_from_reader(buf2, &hdr);
assert!(result.is_err());
}
}