matfile/
lib.rs

1#![doc(html_root_url = "https://docs.rs/matfile/0.5.0")]
2
3//! Matfile is a library for reading (and in the future writing) Matlab ".mat" files.
4//!
5//! __Please note__: This library is still alpha quality software and only implements a subset of the features supported by .mat files.
6//!
7//! ## Feature Status
8//!
9//! Matfile currently allows you to load numeric arrays from .mat files (all floating point and integer types, including complex numbers). All other types are currently ignored.
10//!
11//! * [ ] Loading .mat files
12//!   * [x] Numeric arrays
13//!   * [ ] Cell arrays
14//!   * [ ] Structure arrays
15//!   * [ ] Object arrays
16//!   * [ ] Character arrays
17//!   * [ ] Sparse arrays
18//! * [ ] Writing .mat files
19//!
20//! ## Examples
21//!
22//! Loading a .mat file from disk and accessing one of its arrays by name:
23//!
24//! ```rust
25//! # pub fn main() -> Result<(), Box<dyn std::error::Error>> {
26//! let file = std::fs::File::open("tests/double.mat")?;
27//! let mat_file = matfile::MatFile::parse(file)?;
28//! let pos = mat_file.find_by_name("pos");
29//! println!("{:#?}", pos);
30//! # Ok(())
31//! # }
32//! ```
33//! Might output something like:
34//! ```text
35//! Some(
36//!     Array {
37//!         name: "pos",
38//!         size: [
39//!             2,
40//!             3
41//!         ],
42//!         data: Double {
43//!             real: [
44//!                 -5.0,
45//!                 8.0,
46//!                 6.0,
47//!                 9.0,
48//!                 7.0,
49//!                 10.0
50//!             ],
51//!             imag: None
52//!         }
53//!     }
54//! )
55//! ```
56//!
57//! ## Crate Feature Flags
58//! The following crate feature flags can be enabled in your Cargo.toml:
59//! * `ndarray`
60//!   * Enable conversions between Matfile and `ndarray` array types
61
62#[macro_use]
63extern crate enum_primitive_derive;
64
65#[cfg(feature = "ndarray")]
66pub mod ndarray;
67mod parse;
68
69/// MatFile is a collection of named arrays.
70///
71/// You can load a ".mat" file from disk like this:
72/// ```rust
73/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
74/// let file = std::fs::File::open("tests/double.mat")?;
75/// let mat_file = matfile::MatFile::parse(file)?;
76/// # Ok(())
77/// # }
78/// ```
79#[derive(Clone, Debug)]
80pub struct MatFile {
81    arrays: Vec<Array>,
82}
83
84/// A numeric array (the only type supported at the moment).
85///
86/// You can access the arrays of a MatFile either by name or by iterating
87/// through all of them:
88/// ```rust
89/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
90/// # let file = std::fs::File::open("tests/double.mat")?;
91/// # let mat_file = matfile::MatFile::parse(file)?;
92/// if let Some(array_a) = mat_file.find_by_name("A") {
93///     println!("Array \"A\": {:#?}", array_a);
94/// }
95///
96/// for array in mat_file.arrays() {
97///     println!("Found array named {} of size {:?}", array.name(), array.size());
98/// }
99/// # Ok(())
100/// # }
101/// ```
102#[derive(Clone, Debug)]
103pub struct Array {
104    name: String,
105    size: Vec<usize>,
106    data: NumericData,
107}
108
109/// Stores the data of a numerical array and abstracts over the actual data
110/// type used. Real and imaginary parts are stored in separate vectors with the
111/// imaginary part being optional.
112///
113/// Numerical data is stored in column-major order. When talking about higher
114/// dimensional arrays this means that the index of the first dimension varies
115/// fastest.
116#[derive(Clone, Debug)]
117pub enum NumericData {
118    Int8 {
119        real: Vec<i8>,
120        imag: Option<Vec<i8>>,
121    },
122    UInt8 {
123        real: Vec<u8>,
124        imag: Option<Vec<u8>>,
125    },
126    Int16 {
127        real: Vec<i16>,
128        imag: Option<Vec<i16>>,
129    },
130    UInt16 {
131        real: Vec<u16>,
132        imag: Option<Vec<u16>>,
133    },
134    Int32 {
135        real: Vec<i32>,
136        imag: Option<Vec<i32>>,
137    },
138    UInt32 {
139        real: Vec<u32>,
140        imag: Option<Vec<u32>>,
141    },
142    Int64 {
143        real: Vec<i64>,
144        imag: Option<Vec<i64>>,
145    },
146    UInt64 {
147        real: Vec<u64>,
148        imag: Option<Vec<u64>>,
149    },
150    Single {
151        real: Vec<f32>,
152        imag: Option<Vec<f32>>,
153    },
154    Double {
155        real: Vec<f64>,
156        imag: Option<Vec<f64>>,
157    },
158}
159
160fn try_convert_number_format(
161    target_type: parse::ArrayType,
162    data: parse::NumericData,
163) -> Result<parse::NumericData, Error> {
164    match target_type {
165        parse::ArrayType::Double => match data {
166            parse::NumericData::UInt8(data) => Ok(parse::NumericData::Double(
167                data.into_iter().map(|x| x as f64).collect(),
168            )),
169            parse::NumericData::Int16(data) => Ok(parse::NumericData::Double(
170                data.into_iter().map(|x| x as f64).collect(),
171            )),
172            parse::NumericData::UInt16(data) => Ok(parse::NumericData::Double(
173                data.into_iter().map(|x| x as f64).collect(),
174            )),
175            parse::NumericData::Int32(data) => Ok(parse::NumericData::Double(
176                data.into_iter().map(|x| x as f64).collect(),
177            )),
178            parse::NumericData::Double(data) => Ok(parse::NumericData::Double(data)),
179            _ => Err(Error::ConversionError),
180        },
181        parse::ArrayType::Single => match data {
182            parse::NumericData::UInt8(data) => Ok(parse::NumericData::Single(
183                data.into_iter().map(|x| x as f32).collect(),
184            )),
185            parse::NumericData::Int16(data) => Ok(parse::NumericData::Single(
186                data.into_iter().map(|x| x as f32).collect(),
187            )),
188            parse::NumericData::UInt16(data) => Ok(parse::NumericData::Single(
189                data.into_iter().map(|x| x as f32).collect(),
190            )),
191            parse::NumericData::Int32(data) => Ok(parse::NumericData::Single(
192                data.into_iter().map(|x| x as f32).collect(),
193            )),
194            parse::NumericData::Single(data) => Ok(parse::NumericData::Single(data)),
195            _ => Err(Error::ConversionError),
196        },
197        parse::ArrayType::UInt64 => match data {
198            parse::NumericData::UInt8(data) => Ok(parse::NumericData::UInt64(
199                data.into_iter().map(|x| x as u64).collect(),
200            )),
201            parse::NumericData::Int16(data) => Ok(parse::NumericData::UInt64(
202                data.into_iter().map(|x| x as u64).collect(),
203            )),
204            parse::NumericData::UInt16(data) => Ok(parse::NumericData::UInt64(
205                data.into_iter().map(|x| x as u64).collect(),
206            )),
207            parse::NumericData::Int32(data) => Ok(parse::NumericData::UInt64(
208                data.into_iter().map(|x| x as u64).collect(),
209            )),
210            parse::NumericData::UInt64(data) => Ok(parse::NumericData::UInt64(data)),
211            _ => Err(Error::ConversionError),
212        },
213        parse::ArrayType::Int64 => match data {
214            parse::NumericData::UInt8(data) => Ok(parse::NumericData::Int64(
215                data.into_iter().map(|x| x as i64).collect(),
216            )),
217            parse::NumericData::Int16(data) => Ok(parse::NumericData::Int64(
218                data.into_iter().map(|x| x as i64).collect(),
219            )),
220            parse::NumericData::UInt16(data) => Ok(parse::NumericData::Int64(
221                data.into_iter().map(|x| x as i64).collect(),
222            )),
223            parse::NumericData::Int32(data) => Ok(parse::NumericData::Int64(
224                data.into_iter().map(|x| x as i64).collect(),
225            )),
226            parse::NumericData::Int64(data) => Ok(parse::NumericData::Int64(data)),
227            _ => Err(Error::ConversionError),
228        },
229        parse::ArrayType::UInt32 => match data {
230            parse::NumericData::UInt8(data) => Ok(parse::NumericData::UInt32(
231                data.into_iter().map(|x| x as u32).collect(),
232            )),
233            parse::NumericData::Int16(data) => Ok(parse::NumericData::UInt32(
234                data.into_iter().map(|x| x as u32).collect(),
235            )),
236            parse::NumericData::UInt16(data) => Ok(parse::NumericData::UInt32(
237                data.into_iter().map(|x| x as u32).collect(),
238            )),
239            parse::NumericData::UInt32(data) => Ok(parse::NumericData::UInt32(data)),
240            _ => Err(Error::ConversionError),
241        },
242        parse::ArrayType::Int32 => match data {
243            parse::NumericData::UInt8(data) => Ok(parse::NumericData::Int32(
244                data.into_iter().map(|x| x as i32).collect(),
245            )),
246            parse::NumericData::Int16(data) => Ok(parse::NumericData::Int32(
247                data.into_iter().map(|x| x as i32).collect(),
248            )),
249            parse::NumericData::UInt16(data) => Ok(parse::NumericData::Int32(
250                data.into_iter().map(|x| x as i32).collect(),
251            )),
252            parse::NumericData::Int32(data) => Ok(parse::NumericData::Int32(data)),
253            _ => Err(Error::ConversionError),
254        },
255        parse::ArrayType::UInt16 => match data {
256            parse::NumericData::UInt8(data) => Ok(parse::NumericData::UInt16(
257                data.into_iter().map(|x| x as u16).collect(),
258            )),
259            parse::NumericData::UInt16(data) => Ok(parse::NumericData::UInt16(data)),
260            _ => Err(Error::ConversionError),
261        },
262        parse::ArrayType::Int16 => match data {
263            parse::NumericData::UInt8(data) => Ok(parse::NumericData::Int16(
264                data.into_iter().map(|x| x as i16).collect(),
265            )),
266            parse::NumericData::Int16(data) => Ok(parse::NumericData::Int16(data)),
267            _ => Err(Error::ConversionError),
268        },
269        parse::ArrayType::UInt8 => match data {
270            parse::NumericData::UInt8(data) => Ok(parse::NumericData::UInt8(data)),
271            _ => Err(Error::ConversionError),
272        },
273        parse::ArrayType::Int8 => match data {
274            parse::NumericData::Int8(data) => Ok(parse::NumericData::Int8(data)),
275            _ => Err(Error::ConversionError),
276        },
277        _ => Err(Error::ConversionError),
278    }
279}
280
281impl NumericData {
282    fn try_from(
283        target_type: parse::ArrayType,
284        real: parse::NumericData,
285        imag: Option<parse::NumericData>,
286    ) -> Result<Self, Error> {
287        let real = try_convert_number_format(target_type, real)?;
288        let imag = match imag {
289            Some(imag) => Some(try_convert_number_format(target_type, imag)?),
290            None => None,
291        };
292        // The next step should never fail unless there is a bug in the code
293        match (real, imag) {
294            (parse::NumericData::Double(real), None) => Ok(NumericData::Double {
295                real: real,
296                imag: None,
297            }),
298            (parse::NumericData::Double(real), Some(parse::NumericData::Double(imag))) => {
299                Ok(NumericData::Double {
300                    real: real,
301                    imag: Some(imag),
302                })
303            }
304            (parse::NumericData::Single(real), None) => Ok(NumericData::Single {
305                real: real,
306                imag: None,
307            }),
308            (parse::NumericData::Single(real), Some(parse::NumericData::Single(imag))) => {
309                Ok(NumericData::Single {
310                    real: real,
311                    imag: Some(imag),
312                })
313            }
314            (parse::NumericData::UInt64(real), None) => Ok(NumericData::UInt64 {
315                real: real,
316                imag: None,
317            }),
318            (parse::NumericData::UInt64(real), Some(parse::NumericData::UInt64(imag))) => {
319                Ok(NumericData::UInt64 {
320                    real: real,
321                    imag: Some(imag),
322                })
323            }
324            (parse::NumericData::Int64(real), None) => Ok(NumericData::Int64 {
325                real: real,
326                imag: None,
327            }),
328            (parse::NumericData::Int64(real), Some(parse::NumericData::Int64(imag))) => {
329                Ok(NumericData::Int64 {
330                    real: real,
331                    imag: Some(imag),
332                })
333            }
334            (parse::NumericData::UInt32(real), None) => Ok(NumericData::UInt32 {
335                real: real,
336                imag: None,
337            }),
338            (parse::NumericData::UInt32(real), Some(parse::NumericData::UInt32(imag))) => {
339                Ok(NumericData::UInt32 {
340                    real: real,
341                    imag: Some(imag),
342                })
343            }
344            (parse::NumericData::Int32(real), None) => Ok(NumericData::Int32 {
345                real: real,
346                imag: None,
347            }),
348            (parse::NumericData::Int32(real), Some(parse::NumericData::Int32(imag))) => {
349                Ok(NumericData::Int32 {
350                    real: real,
351                    imag: Some(imag),
352                })
353            }
354            (parse::NumericData::UInt16(real), None) => Ok(NumericData::UInt16 {
355                real: real,
356                imag: None,
357            }),
358            (parse::NumericData::UInt16(real), Some(parse::NumericData::UInt16(imag))) => {
359                Ok(NumericData::UInt16 {
360                    real: real,
361                    imag: Some(imag),
362                })
363            }
364            (parse::NumericData::Int16(real), None) => Ok(NumericData::Int16 {
365                real: real,
366                imag: None,
367            }),
368            (parse::NumericData::Int16(real), Some(parse::NumericData::Int16(imag))) => {
369                Ok(NumericData::Int16 {
370                    real: real,
371                    imag: Some(imag),
372                })
373            }
374            (parse::NumericData::UInt8(real), None) => Ok(NumericData::UInt8 {
375                real: real,
376                imag: None,
377            }),
378            (parse::NumericData::UInt8(real), Some(parse::NumericData::UInt8(imag))) => {
379                Ok(NumericData::UInt8 {
380                    real: real,
381                    imag: Some(imag),
382                })
383            }
384            (parse::NumericData::Int8(real), None) => Ok(NumericData::Int8 {
385                real: real,
386                imag: None,
387            }),
388            (parse::NumericData::Int8(real), Some(parse::NumericData::Int8(imag))) => {
389                Ok(NumericData::Int8 {
390                    real: real,
391                    imag: Some(imag),
392                })
393            }
394            _ => return Err(Error::InternalError),
395        }
396    }
397}
398
399#[derive(Debug)]
400pub enum Error {
401    IOError(std::io::Error),
402    ParseError(nom::Err<nom::error::Error<&'static [u8]>>),
403    ConversionError,
404    InternalError,
405}
406
407impl std::fmt::Display for Error {
408    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
409        match self {
410            Error::IOError(_) => write!(f, "An I/O error occurred"),
411            Error::ParseError(_) => write!(f, "An error occurred while parsing the file"),
412            Error::ConversionError => {
413                write!(f, "An error occurred while converting number formats")
414            }
415            Error::InternalError => write!(f, "An internal error occurred, this is a bug"),
416        }
417    }
418}
419
420impl std::error::Error for Error {
421    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
422        match self {
423            Error::IOError(ref err) => Some(err),
424            _ => None,
425        }
426    }
427}
428
429impl Array {
430    /// The name of this array.
431    pub fn name(&self) -> &str {
432        &self.name
433    }
434
435    /// The size of this array.
436    ///
437    /// The number of entries in this vector is equal to the number of
438    /// dimensions of this array. Each array has at least two dimensions.
439    /// For two-dimensional arrays the first dimension is the number of rows
440    /// while the second dimension is the number of columns.
441    pub fn size(&self) -> &Vec<usize> {
442        &self.size
443    }
444
445    /// The number of dimensions of this array. Is at least two.
446    pub fn ndims(&self) -> usize {
447        self.size.len()
448    }
449
450    /// The actual numerical data stored in this array.
451    ///
452    /// ```rust
453    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
454    /// # let file = std::fs::File::open("tests/double.mat")?;
455    /// # let mat_file = matfile::MatFile::parse(file)?;
456    /// # let array = &mat_file.arrays()[0];
457    /// if let matfile::NumericData::Double { real: real, imag: _ } = array.data() {
458    ///     println!("Real part of the data: {:?}", real);
459    /// }
460    /// # Ok(())
461    /// # }
462    /// ```
463    ///
464    /// For a more convenient access to the data, consider using the
465    /// `matfile-ndarray` crate.
466    pub fn data(&self) -> &NumericData {
467        &self.data
468    }
469}
470
471impl MatFile {
472    /// Tries to parse a byte sequence as a ".mat" file.
473    pub fn parse<R: std::io::Read>(mut reader: R) -> Result<Self, Error> {
474        let mut buf = Vec::new();
475        reader
476            .read_to_end(&mut buf)
477            .map_err(|err| Error::IOError(err))?;
478        let (_remaining, parse_result) = parse::parse_all(&buf)
479            .map_err(|err| Error::ParseError(parse::replace_err_slice(err, &[])))?;
480        let arrays: Result<Vec<Array>, Error> = parse_result
481            .data_elements
482            .into_iter()
483            .filter_map(|data_element| match data_element {
484                parse::DataElement::NumericMatrix(flags, dims, name, real, imag) => {
485                    let size = dims.into_iter().map(|d| d as usize).collect();
486                    let numeric_data = match NumericData::try_from(flags.class, real, imag) {
487                        Ok(numeric_data) => numeric_data,
488                        Err(err) => return Some(Err(err)),
489                    };
490                    Some(Ok(Array {
491                        size: size,
492                        name: name,
493                        data: numeric_data,
494                    }))
495                }
496                _ => None,
497            })
498            .collect();
499        let arrays = arrays?;
500        Ok(MatFile { arrays: arrays })
501    }
502
503    /// List of all arrays in this .mat file.
504    ///
505    /// When parsing a .mat file all arrays of unsupported type (currently all
506    /// non-numerical and sparse arrays) will be ignored and will thus not be
507    /// part of this list.
508    pub fn arrays(&self) -> &Vec<Array> {
509        &self.arrays
510    }
511
512    /// Returns an array with the given name if it exists. Case sensitive.
513    ///
514    /// When parsing a .mat file all arrays of unsupported type (currently all
515    /// non-numerical and sparse arrays) will be ignored and will thus not be
516    /// returned by this function.
517    pub fn find_by_name<'me>(&'me self, name: &'_ str) -> Option<&'me Array> {
518        for array in &self.arrays {
519            if array.name == name {
520                return Some(array);
521            }
522        }
523        None
524    }
525}
526
527// TODO: improve tests.
528// The tests are not very comprehensive yet and they only test whether
529// the files can be loaded without error, but not whether the result
530// is actually correct.
531#[cfg(test)]
532mod tests {
533    use super::*;
534
535    #[test]
536    fn double_array() {
537        let data = include_bytes!("../tests/double.mat");
538        let _mat_file = MatFile::parse(data.as_ref()).unwrap();
539    }
540
541    #[test]
542    fn double_as_int16_array() {
543        let data = include_bytes!("../tests/double_as_int16.mat");
544        let _mat_file = MatFile::parse(data.as_ref()).unwrap();
545    }
546
547    #[test]
548    fn double_as_uint8_array() {
549        let data = include_bytes!("../tests/double_as_uint8.mat");
550        let _mat_file = MatFile::parse(data.as_ref()).unwrap();
551    }
552
553    #[test]
554    fn single_complex_array() {
555        let data = include_bytes!("../tests/single_complex.mat");
556        let _mat_file = MatFile::parse(data.as_ref()).unwrap();
557    }
558
559    #[test]
560    fn two_arrays() {
561        let data = include_bytes!("../tests/two_arrays.mat");
562        let _mat_file = MatFile::parse(data.as_ref()).unwrap();
563    }
564
565    #[test]
566    fn multidimensional_array() {
567        let data = include_bytes!("../tests/multidimensional.mat");
568        let _mat_file = MatFile::parse(data.as_ref()).unwrap();
569    }
570
571    #[test]
572    fn long_name() {
573        let data = include_bytes!("../tests/long_name.mat");
574        let _mat_file = MatFile::parse(data.as_ref()).unwrap();
575    }
576}