mzdata 0.67.3

A library to read mass spectrometry data formats and a data model for mass spectra
Documentation
mod inference;
mod dispatch;
mod pipeline;

pub use dispatch::{MZReader, MZReaderType, MZReaderBuilder, IMMZReaderType};
#[cfg(feature = "async_partial")]
pub use dispatch::{AsyncMZReaderType, AsyncMZReader, AsyncMZReaderBuilder};



pub use inference::{infer_from_path, infer_from_stream, infer_format, MassSpectrometryFormat, _SourceFileExt};

pub use pipeline::{MassSpectrometryReadWriteProcess, Source, Sink};

#[cfg(test)]
mod test {
    use std::{fs, io, path};

    use mzpeaks::{CentroidPeak, DeconvolutedPeak};

    use crate::{
        prelude::*,
        spectrum::{ArrayType, Spectrum},
        io::DetailLevel
    };

    use super::*;

    #[cfg(feature = "mzml")]
    #[test]
    fn infer_mzml() {
        let path = path::Path::new("./test/data/small.mzML");
        assert!(path.exists());
        let (fmt, zipped) = infer_from_path(path);
        assert_eq!(fmt, MassSpectrometryFormat::MzML);
        assert!(!zipped);
    }

    #[test]
    fn infer_mgf() {
        let path = path::Path::new("./test/data/small.mgf");
        assert!(path.exists());
        let (fmt, zipped) = infer_from_path(path);
        assert_eq!(fmt, MassSpectrometryFormat::MGF);
        assert!(!zipped);
    }

    #[cfg(feature = "thermo")]
    #[test]
    fn infer_thermo() {
        let path = path::Path::new("./test/data/small.RAW");
        let (fmt, zipped) = infer_from_path(path);
        assert_eq!(fmt, MassSpectrometryFormat::ThermoRaw);
        assert!(!zipped);
    }

    #[cfg(feature = "mzml")]
    #[test]
    fn infer_open() {
        let path = path::Path::new("./test/data/small.mzML");
        assert!(path.exists());
        if let Ok(mut reader) = MZReader::open_path(path) {
            assert_eq!(reader.len(), 48);
            assert_eq!(*reader.detail_level(), DetailLevel::Full);
            if let Some(spec) = reader.get_spectrum_by_index(10) {
                let spec: Spectrum = spec;
                assert!(spec.index() == 10);
                assert!(spec.id() == "controllerType=0 controllerNumber=1 scan=11");
                if let Some(data_arrays) = &spec.arrays {
                    assert!(data_arrays.has_array(&ArrayType::MZArray));
                    assert!(data_arrays.has_array(&ArrayType::IntensityArray));
                    let mzs = data_arrays.mzs().unwrap();
                    assert!(mzs.len() == 941);
                }
            } else {
                panic!("Failed to retrieve spectrum by index")
            }

            assert_eq!(reader.get_spectrum_by_id("controllerType=0 controllerNumber=1 scan=11").unwrap().index(), 10);

            if let Some(spec) = reader.get_spectrum_by_time(0.358558333333) {
                assert_eq!(spec.index(), 34);
            } else {
                panic!("Failed to retrieve spectrum by time")
            }

        } else {
            panic!("Failed to open file")
        }
    }

    #[cfg(feature = "thermo")]
    #[test]
    fn infer_open_thermo() {
        let path = path::Path::new("./test/data/small.RAW");
        assert!(path.exists());
        if let Ok(mut reader) = MZReader::open_path(path) {
            assert_eq!(reader.len(), 48);
            assert_eq!(*reader.detail_level(), DetailLevel::Full);
            if let Some(spec) = reader.get_spectrum_by_index(10) {
                let spec: Spectrum = spec;
                assert_eq!(spec.index(), 10);
                assert_eq!(spec.id(), "controllerType=0 controllerNumber=1 scan=11");
                assert_eq!(spec.peaks().len(), 941);
            } else {
                panic!("Failed to retrieve spectrum by index")
            }

            assert_eq!(reader.get_spectrum_by_id("controllerType=0 controllerNumber=1 scan=11").unwrap().index(), 10);

            if let Some(spec) = reader.get_spectrum_by_time(0.358558333333) {
                assert_eq!(spec.index(), 34);
            } else {
                panic!("Failed to retrieve spectrum by time")
            }

        } else {
            panic!("Failed to open file")
        }
    }

    #[test]
    fn test_source_conv() -> io::Result<()> {
        let s = Source::<CentroidPeak, DeconvolutedPeak>::from("text/path".as_ref());
        assert!(matches!(s, Source::PathLike(_)));

        let fh = Box::new(io::BufReader::new(fs::File::open("./test/data/small.mgf")?)) as Box<dyn SeekRead + Send>;
        let rs: Source<CentroidPeak, DeconvolutedPeak> = (fh, MassSpectrometryFormat::MGF).into();
        assert!(matches!(rs, Source::Reader(_, _)));

        Ok(())
    }

    #[cfg(feature = "mzml")]
    #[test]
    fn test_dispatch_mzreader() -> io::Result<()> {
        let mut reader = MZReader::open_path("./test/data/small.mzML")?;

        let n = reader.len();
        let n_ms1 = reader.iter().filter(|s| s.ms_level() == 1).count();
        let n_msn = reader.iter().filter(|s| s.ms_level() == 2).count();

        assert_eq!(n, 48);
        assert_eq!(n, n_ms1 + n_msn);
        Ok(())
    }

    #[cfg(feature = "mzml")]
    #[test]
    fn test_infer_stream() -> io::Result<()> {
        let mut mzml_file = fs::File::open("./test/data/small.mzML")?;
        let (form, gzip) = infer_from_stream(&mut mzml_file)?;
        assert_eq!(form, MassSpectrometryFormat::MzML);
        assert!(!gzip);

        mzml_file = fs::File::open("./test/data/20200204_BU_8B8egg_1ug_uL_7charges_60_min_Slot2-11_1_244.mzML.gz")?;
        let (form, gzip) = infer_from_stream(&mut mzml_file)?;
        assert_eq!(form, MassSpectrometryFormat::MzML);
        assert!(gzip);

        let mut mgf_file = fs::File::open("./test/data/small.mgf")?;
        let (form, gzip) = infer_from_stream(&mut mgf_file)?;
        assert_eq!(form, MassSpectrometryFormat::MGF);
        assert!(!gzip);
        Ok(())
    }

    #[cfg(feature = "mgf")]
    #[test]
    fn test_infer_short_prefixes() -> io::Result<()> {
        use io::{Read, Seek, Write};
        let input = b"BEGIN IONS\nEND IONS\n";
        let mut encoder = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default());
        encoder.write_all(input)?;
        let compressed = encoder.finish()?;
        for (bytes, gzipped) in [
            (input.to_vec(), false),
            (compressed.clone(), true),
            ([compressed.clone(), compressed].concat(), true),
        ] {
            let source = io::Cursor::new(&bytes[..1]).chain(io::Cursor::new(&bytes[1..]));
            let mut stream = crate::io::PreBufferedStream::new_with_buffer_size(source, 128)?;
            assert_eq!(
                infer_from_stream(&mut stream)?,
                (MassSpectrometryFormat::MGF, gzipped)
            );
            assert_eq!(stream.stream_position()?, 0);
            let mut remaining = Vec::new();
            stream.read_to_end(&mut remaining)?;
            assert_eq!(remaining, bytes);
        }
        #[cfg(feature = "mzml")]
        for prefix in [b"".as_slice(), b" \n", b"\xef\xbb\xbf"] {
            let bytes = [prefix, b"<!--BEGIN IONS--><mzML></mzML>"].concat();
            let split = prefix.len() + 14;
            let source = io::Cursor::new(&bytes[..1]).chain(io::Cursor::new(&bytes[1..split]));
            let source = source.chain(io::Cursor::new(&bytes[split..]));
            let mut stream = crate::io::PreBufferedStream::new(source)?;
            assert_eq!(
                infer_from_stream(&mut stream)?,
                (MassSpectrometryFormat::MzML, false)
            );
        }
        Ok(())
    }

    #[test]
    fn test_infer_restores_position() -> io::Result<()> {
        for bytes in [vec![], vec![b'x'], vec![0; 700]] {
            let mut stream = io::Cursor::new(bytes);
            stream.set_position(1);
            assert_eq!(
                infer_from_stream(&mut stream)?,
                (MassSpectrometryFormat::Unknown, false)
            );
            assert_eq!(stream.position(), 1);
        }
        for bytes in [vec![0x1f, 0x8b], vec![0x1f, 0x8b, 8, 0]] {
            let mut stream = io::Cursor::new([vec![b'x'], bytes].concat());
            stream.set_position(1);
            assert!(infer_from_stream(&mut stream).is_err());
            assert_eq!(stream.position(), 1);
        }
        Ok(())
    }

    #[cfg(feature = "mzml")]
    #[test]
    fn test_open_streams_preserve_spectra() -> io::Result<()> {
        use io::Read;
        fn check(
            actual: impl Iterator<Item = Spectrum> + MSDataFileMetadata,
            expected: impl Iterator<Item = Spectrum> + MSDataFileMetadata,
        ) {
            assert_eq!(actual.file_description(), expected.file_description());
            assert_eq!(
                actual.instrument_configurations(),
                expected.instrument_configurations()
            );
            let actual: Vec<_> = actual.collect();
            let expected: Vec<_> = expected.collect();
            assert_eq!(actual.len(), 48);
            assert_eq!(actual.len(), expected.len());
            for (actual, expected) in actual.iter().zip(&expected) {
                assert_eq!(actual.description, expected.description);
                let actual = actual.arrays.as_ref().unwrap();
                let expected = expected.arrays.as_ref().unwrap();
                assert_eq!(actual.len(), expected.len());
                for (name, expected) in expected.iter() {
                    let actual = actual.get(name).unwrap();
                    assert_eq!(actual.name, expected.name);
                    assert_eq!(actual.dtype, expected.dtype);
                    assert_eq!(actual.compression, expected.compression);
                    assert_eq!(actual.unit, expected.unit);
                    assert_eq!(actual.params, expected.params);
                    assert_eq!(actual.data, expected.data);
                }
            }
        }
        for (path, gzipped) in [
            ("test/data/small.mzML", false),
            ("test/data/small.mzML.gz", true),
        ] {
            let input = fs::read(path)?;
            let decoded = if gzipped {
                let mut decoded = Vec::new();
                flate2::read::GzDecoder::new(io::Cursor::new(&input)).read_to_end(&mut decoded)?;
                decoded
            } else {
                input.clone()
            };
            let expected = crate::MzMLReader::new(io::Cursor::new(decoded.clone()));
            let source = io::Cursor::new(&input[..2]).chain(io::Cursor::new(&input[2..]));
            if gzipped {
                check(MZReader::open_gzipped_read(source)?, expected);
            } else {
                check(MZReader::open_read(source)?, expected);
            }
            let source = Box::new(io::Cursor::new(input));
            check(
                MZReader::<crate::io::PreBufferedStream<fs::File>>::open_read_generic(source)?,
                crate::MzMLReader::new(io::Cursor::new(decoded)),
            );
        }
        Ok(())
    }

    #[cfg(feature = "imzml")]
    #[test]
    fn test_infer_short_imzml_prefix() -> io::Result<()> {
        use io::Read;
        let bytes = b"<mzML><cvList><cv id=\"IMS\"/></cvList></mzML>";
        let source = io::Cursor::new(&bytes[..6]).chain(io::Cursor::new(&bytes[6..]));
        let mut stream = crate::io::PreBufferedStream::new(source)?;
        assert_eq!(
            infer_from_stream(&mut stream)?,
            (MassSpectrometryFormat::IMzML, false)
        );
        Ok(())
    }
}