pub mod enhanced;
pub mod v73_enhanced;
pub mod write_extended;
mod write_impl;
use byteorder::{ByteOrder, LittleEndian};
use scirs2_core::ndarray::{Array, ArrayD, IxDyn};
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufReader, BufWriter, Read, Write};
use std::path::Path;
use crate::error::{IoError, Result};
#[derive(Debug, Clone)]
pub enum MatType {
Double(ArrayD<f64>),
Single(ArrayD<f32>),
Int8(ArrayD<i8>),
Int16(ArrayD<i16>),
Int32(ArrayD<i32>),
Int64(ArrayD<i64>),
UInt8(ArrayD<u8>),
UInt16(ArrayD<u16>),
UInt32(ArrayD<u32>),
UInt64(ArrayD<u64>),
Logical(ArrayD<bool>),
Char(String),
Cell(Vec<MatType>),
Struct(HashMap<String, MatType>),
SparseDouble(crate::sparse::SparseMatrix<f64>),
SparseSingle(crate::sparse::SparseMatrix<f32>),
SparseLogical(crate::sparse::SparseMatrix<bool>),
}
#[derive(Debug, Clone)]
struct MatHeader {
_version: u16,
endian_indicator: u16,
}
const MI_INT8: i32 = 1;
const _MI_UINT8: i32 = 2;
const _MI_INT16: i32 = 3;
const _MI_UINT16: i32 = 4;
const MI_INT32: i32 = 5;
const MI_UINT32: i32 = 6;
const _MI_SINGLE: i32 = 7;
const _MI_DOUBLE: i32 = 9;
const _MI_INT64: i32 = 12;
const _MI_UINT64: i32 = 13;
const MI_MATRIX: i32 = 14;
const _MI_COMPRESSED: i32 = 15;
const MI_UTF8: i32 = 16;
const _MI_UTF16: i32 = 17;
const _MI_UTF32: i32 = 18;
const _MX_CELL_CLASS: i32 = 1;
const _MX_STRUCT_CLASS: i32 = 2;
const _MX_OBJECT_CLASS: i32 = 3;
const MX_CHAR_CLASS: i32 = 4;
const _MX_SPARSE_CLASS: i32 = 5;
const MX_DOUBLE_CLASS: i32 = 6;
const MX_SINGLE_CLASS: i32 = 7;
const MX_INT8_CLASS: i32 = 8;
const MX_UINT8_CLASS: i32 = 9;
const MX_INT16_CLASS: i32 = 10;
const MX_UINT16_CLASS: i32 = 11;
const MX_INT32_CLASS: i32 = 12;
const MX_UINT32_CLASS: i32 = 13;
const MX_INT64_CLASS: i32 = 14;
const MX_UINT64_CLASS: i32 = 15;
#[derive(Debug, Clone)]
struct MatrixFlags {
class_type: i32,
is_complex: bool,
_is_global: bool,
is_logical: bool,
}
impl MatrixFlags {
fn from_u32(flags: u32) -> Self {
let class_type = (flags & 0xFF) as i32;
let is_complex = (flags & 0x800) != 0;
let is_global = (flags & 0x400) != 0;
let is_logical = (flags & 0x200) != 0;
MatrixFlags {
class_type,
is_complex,
is_logical,
_is_global: is_global,
}
}
fn _to_u32(&self) -> u32 {
let mut flags = self.class_type as u32;
if self.is_complex {
flags |= 0x800;
}
if self._is_global {
flags |= 0x400;
}
if self.is_logical {
flags |= 0x200;
}
flags
}
}
#[derive(Debug, Clone)]
struct _DataElement {
data_type: i32,
data: Vec<u8>,
}
#[derive(Debug, Clone)]
struct _MatrixArray {
flags: MatrixFlags,
dims: Vec<i32>,
name: String,
realdata: Vec<u8>,
imagdata: Option<Vec<u8>>,
}
#[allow(dead_code)]
pub fn read_mat<P: AsRef<Path>>(path: P) -> Result<HashMap<String, MatType>> {
let file = File::open(path).map_err(|e| IoError::FileError(e.to_string()))?;
let mut reader = BufReader::new(file);
let mut headerbytes = [0u8; 128];
reader
.read_exact(&mut headerbytes)
.map_err(|e| IoError::FileError(format!("Failed to read MAT header: {e}")))?;
let magic = std::str::from_utf8(&headerbytes[0..6])
.map_err(|_| IoError::FormatError("Invalid MAT file header".to_string()))?;
if magic != "MATLAB" {
return Err(IoError::FormatError("Not a valid MATLAB file".to_string()));
}
let subsystemdata_offset = &headerbytes[124..128];
let version = LittleEndian::read_u16(&subsystemdata_offset[0..2]);
let endian_indicator = LittleEndian::read_u16(&subsystemdata_offset[2..4]);
let header = MatHeader {
_version: version,
endian_indicator,
};
if header.endian_indicator != 0x4D49 && header.endian_indicator != 0x494D {
return Err(IoError::FormatError(
"Invalid endianness indicator".to_string(),
));
}
let mut variables = HashMap::<String, MatType>::new();
while let Ok(element_type) = read_i32(&mut reader) {
if element_type == 0 {
break;
}
let element_size = read_i32(&mut reader)?;
match element_type {
MI_MATRIX => {
let mut matrixdata = vec![0u8; element_size as usize];
reader
.read_exact(&mut matrixdata)
.map_err(|e| IoError::FileError(format!("Failed to read matrix data: {e}")))?;
if let Ok((name, mat_type)) = parse_matrixdata(&matrixdata) {
variables.insert(name, mat_type);
}
}
_ => {
reader
.by_ref()
.take(element_size as u64)
.read_to_end(&mut vec![])
.map_err(|e| IoError::FileError(format!("Failed to skip element: {e}")))?;
}
}
}
Ok(variables)
}
#[allow(dead_code)]
fn parse_matrixdata(data: &[u8]) -> Result<(String, MatType)> {
let mut cursor = 0;
let array_flags_type = LittleEndian::read_i32(&data[cursor..cursor + 4]);
cursor += 4;
let array_flags_size = LittleEndian::read_i32(&data[cursor..cursor + 4]);
cursor += 4;
if array_flags_type != MI_UINT32 || array_flags_size != 8 {
return Err(IoError::FormatError("Invalid array flags".to_string()));
}
let flags = MatrixFlags::from_u32(LittleEndian::read_u32(&data[cursor..cursor + 4]));
cursor += 8;
let dimensions_type = LittleEndian::read_i32(&data[cursor..cursor + 4]);
cursor += 4;
let dimensions_size = LittleEndian::read_i32(&data[cursor..cursor + 4]);
cursor += 4;
if dimensions_type != MI_INT32 {
return Err(IoError::FormatError("Invalid dimensions type".to_string()));
}
let num_dims = dimensions_size / 4;
let mut dims = Vec::with_capacity(num_dims as usize);
for i in 0..num_dims {
dims.push(LittleEndian::read_i32(
&data[cursor + (i * 4) as usize..cursor + ((i + 1) * 4) as usize],
));
}
cursor += dimensions_size as usize;
if cursor % 8 != 0 {
cursor += 8 - (cursor % 8);
}
let name_type = LittleEndian::read_i32(&data[cursor..cursor + 4]);
cursor += 4;
let name_size = LittleEndian::read_i32(&data[cursor..cursor + 4]);
cursor += 4;
if name_type != MI_INT8 && name_type != MI_UTF8 {
return Err(IoError::FormatError("Invalid name type".to_string()));
}
let name = std::str::from_utf8(&data[cursor..cursor + name_size as usize])
.map_err(|_| IoError::FormatError("Invalid name encoding".to_string()))?
.to_string();
cursor += name_size as usize;
if cursor % 8 != 0 {
cursor += 8 - (cursor % 8);
}
let data_type = LittleEndian::read_i32(&data[cursor..cursor + 4]);
cursor += 4;
let data_size = LittleEndian::read_i32(&data[cursor..cursor + 4]);
cursor += 4;
let realdata = &data[cursor..cursor + data_size as usize];
cursor += data_size as usize;
if cursor % 8 != 0 {
cursor += 8 - (cursor % 8);
}
let _imagdata = if flags.is_complex {
let imag_type = LittleEndian::read_i32(&data[cursor..cursor + 4]);
cursor += 4;
let imag_size = LittleEndian::read_i32(&data[cursor..cursor + 4]);
cursor += 4;
if imag_type != data_type {
return Err(IoError::FormatError(
"Mismatched imaginary type".to_string(),
));
}
Some(&data[cursor..cursor + imag_size as usize])
} else {
None
};
let mat_type = match flags.class_type {
MX_DOUBLE_CLASS => {
let data_vec = bytes_to_f64_vec(realdata);
let ndarray = Array::from_shape_vec(IxDyn(&convert_dims(&dims)), data_vec)
.map_err(|e| IoError::FormatError(format!("Failed to create array: {e}")))?;
MatType::Double(ndarray)
}
MX_SINGLE_CLASS => {
let data_vec = bytes_to_f32_vec(realdata);
let ndarray = Array::from_shape_vec(IxDyn(&convert_dims(&dims)), data_vec)
.map_err(|e| IoError::FormatError(format!("Failed to create array: {e}")))?;
MatType::Single(ndarray)
}
MX_INT8_CLASS => {
let data_vec = realdata.to_vec();
let ndarray = Array::from_shape_vec(
IxDyn(&convert_dims(&dims)),
data_vec.into_iter().map(|b| b as i8).collect(),
)
.map_err(|e| IoError::FormatError(format!("Failed to create array: {e}")))?;
MatType::Int8(ndarray)
}
MX_UINT8_CLASS => {
if flags.is_logical {
let data_vec: Vec<bool> = realdata.iter().map(|&b| b != 0).collect();
let ndarray = Array::from_shape_vec(IxDyn(&convert_dims(&dims)), data_vec)
.map_err(|e| IoError::FormatError(format!("Failed to create array: {e}")))?;
MatType::Logical(ndarray)
} else {
let data_vec = realdata.to_vec();
let ndarray = Array::from_shape_vec(IxDyn(&convert_dims(&dims)), data_vec)
.map_err(|e| IoError::FormatError(format!("Failed to create array: {e}")))?;
MatType::UInt8(ndarray)
}
}
MX_INT16_CLASS => {
let data_vec = bytes_to_i16_vec(realdata);
let ndarray = Array::from_shape_vec(IxDyn(&convert_dims(&dims)), data_vec)
.map_err(|e| IoError::FormatError(format!("Failed to create array: {e}")))?;
MatType::Int16(ndarray)
}
MX_UINT16_CLASS => {
let data_vec = bytes_to_u16_vec(realdata);
let ndarray = Array::from_shape_vec(IxDyn(&convert_dims(&dims)), data_vec)
.map_err(|e| IoError::FormatError(format!("Failed to create array: {e}")))?;
MatType::UInt16(ndarray)
}
MX_INT32_CLASS => {
let data_vec = bytes_to_i32_vec(realdata);
let ndarray = Array::from_shape_vec(IxDyn(&convert_dims(&dims)), data_vec)
.map_err(|e| IoError::FormatError(format!("Failed to create array: {e}")))?;
MatType::Int32(ndarray)
}
MX_UINT32_CLASS => {
let data_vec = bytes_to_u32_vec(realdata);
let ndarray = Array::from_shape_vec(IxDyn(&convert_dims(&dims)), data_vec)
.map_err(|e| IoError::FormatError(format!("Failed to create array: {e}")))?;
MatType::UInt32(ndarray)
}
MX_INT64_CLASS => {
let data_vec = bytes_to_i64_vec(realdata);
let ndarray = Array::from_shape_vec(IxDyn(&convert_dims(&dims)), data_vec)
.map_err(|e| IoError::FormatError(format!("Failed to create array: {e}")))?;
MatType::Int64(ndarray)
}
MX_UINT64_CLASS => {
let data_vec = bytes_to_u64_vec(realdata);
let ndarray = Array::from_shape_vec(IxDyn(&convert_dims(&dims)), data_vec)
.map_err(|e| IoError::FormatError(format!("Failed to create array: {e}")))?;
MatType::UInt64(ndarray)
}
MX_CHAR_CLASS => {
let chars: Vec<u16> = bytes_to_u16_vec(realdata);
let utf16_chars: Vec<u16> = chars.into_iter().collect();
let string = String::from_utf16_lossy(&utf16_chars);
MatType::Char(string)
}
_ => {
return Err(IoError::FormatError(format!(
"Unsupported class type: {}",
flags.class_type
)));
}
};
Ok((name, mat_type))
}
#[allow(dead_code)]
fn convert_dims(dims: &[i32]) -> Vec<usize> {
dims.iter().rev().map(|&d| d as usize).collect()
}
#[allow(dead_code)]
fn read_i32<R: Read>(reader: &mut R) -> Result<i32> {
let mut buffer = [0u8; 4];
match reader.read_exact(&mut buffer) {
Ok(_) => Ok(LittleEndian::read_i32(&buffer)),
Err(_) => Ok(0), }
}
#[allow(dead_code)]
fn bytes_to_f64_vec(bytes: &[u8]) -> Vec<f64> {
let mut result = Vec::with_capacity(bytes.len() / 8);
for i in (0..bytes.len()).step_by(8) {
if i + 8 <= bytes.len() {
result.push(LittleEndian::read_f64(&bytes[i..i + 8]));
}
}
result
}
#[allow(dead_code)]
fn bytes_to_f32_vec(bytes: &[u8]) -> Vec<f32> {
let mut result = Vec::with_capacity(bytes.len() / 4);
for i in (0..bytes.len()).step_by(4) {
if i + 4 <= bytes.len() {
result.push(LittleEndian::read_f32(&bytes[i..i + 4]));
}
}
result
}
#[allow(dead_code)]
fn bytes_to_i16_vec(bytes: &[u8]) -> Vec<i16> {
let mut result = Vec::with_capacity(bytes.len() / 2);
for i in (0..bytes.len()).step_by(2) {
if i + 2 <= bytes.len() {
result.push(LittleEndian::read_i16(&bytes[i..i + 2]));
}
}
result
}
#[allow(dead_code)]
fn bytes_to_u16_vec(bytes: &[u8]) -> Vec<u16> {
let mut result = Vec::with_capacity(bytes.len() / 2);
for i in (0..bytes.len()).step_by(2) {
if i + 2 <= bytes.len() {
result.push(LittleEndian::read_u16(&bytes[i..i + 2]));
}
}
result
}
#[allow(dead_code)]
fn bytes_to_i32_vec(bytes: &[u8]) -> Vec<i32> {
let mut result = Vec::with_capacity(bytes.len() / 4);
for i in (0..bytes.len()).step_by(4) {
if i + 4 <= bytes.len() {
result.push(LittleEndian::read_i32(&bytes[i..i + 4]));
}
}
result
}
#[allow(dead_code)]
fn bytes_to_u32_vec(bytes: &[u8]) -> Vec<u32> {
let mut result = Vec::with_capacity(bytes.len() / 4);
for i in (0..bytes.len()).step_by(4) {
if i + 4 <= bytes.len() {
result.push(LittleEndian::read_u32(&bytes[i..i + 4]));
}
}
result
}
#[allow(dead_code)]
fn bytes_to_i64_vec(bytes: &[u8]) -> Vec<i64> {
let mut result = Vec::with_capacity(bytes.len() / 8);
for i in (0..bytes.len()).step_by(8) {
if i + 8 <= bytes.len() {
result.push(LittleEndian::read_i64(&bytes[i..i + 8]));
}
}
result
}
#[allow(dead_code)]
fn bytes_to_u64_vec(bytes: &[u8]) -> Vec<u64> {
let mut result = Vec::with_capacity(bytes.len() / 8);
for i in (0..bytes.len()).step_by(8) {
if i + 8 <= bytes.len() {
result.push(LittleEndian::read_u64(&bytes[i..i + 8]));
}
}
result
}
#[allow(dead_code)]
pub fn write_mat<P: AsRef<Path>>(path: P, vars: &HashMap<String, MatType>) -> Result<()> {
let file = File::create(path).map_err(|e| IoError::FileError(e.to_string()))?;
let mut writer = BufWriter::new(file);
write_impl::write_mat_header(&mut writer)?;
for (name, mat_type) in vars {
write_impl::write_variable(&mut writer, name, mat_type)?;
}
writer
.flush()
.map_err(|e| IoError::FileError(format!("Failed to flush writer: {e}")))?;
Ok(())
}