# src/mzML.jl # This file is responsible for parsing metadata from .mzML files. # It has been refactored to produce a unified MSIData object. # Constants for CV parameter accessions - defined once for performance const MZ_AXIS_ACCESSION = "MS:1000514" const INTENSITY_AXIS_ACCESSION = "MS:1000515" const COMPRESSION_ACCESSION = "MS:1000574" const NO_COMPRESSION_ACCESSION = "MS:1000576" # Data format accessions as constants const DATA_FORMAT_ACCESSIONS = Dict{String, DataType}( "MS:1000518" => Int16, "MS:1000519" => Int32, "MS:1000520" => Float64, "MS:1000521" => Float32, "MS:1000522" => Int64, "MS:1000523" => Float64 ) """ get_spectrum_asset_metadata(stream::IO) Parses a `` block within an mzML file to extract metadata for a single data array (e.g., m/z or intensity). It reads CV parameters to determine the data type, compression, and axis type. # Arguments - `stream`: An IO stream positioned at the beginning of a `` block. # Returns - A `SpectrumAsset` struct containing the parsed metadata, including the binary data offset, encoded length, format, and compression status. """ function get_spectrum_asset_metadata(stream::IO) start_pos = position(stream) bda_tag = find_tag(stream, r"", line) break end if occursin("") binary_offset = position(stream) # Move stream to the end of the binary data array for the next iteration readuntil(stream, "") # Create SpectrumAsset directly from the variables return SpectrumAsset(data_format, compression_flag, binary_offset, encoded_length, axis) end # This function is updated to return the generic SpectrumMetadata struct """ parse_spectrum_metadata(stream::IO, offset::Int64) Parses an entire `` block from an mzML file, given a starting offset. It extracts the spectrum ID and calls `get_spectrum_asset_metadata` to parse the m/z and intensity array metadata. # Arguments - `stream`: An IO stream for the mzML file. - `offset`: The byte offset where the `` block begins. # Returns - A `SpectrumMetadata` struct containing the parsed metadata for one spectrum. """ function parse_spectrum_metadata(stream::IO, offset::Int64) seek(stream, offset) # Read the whole spectrum block to parse mode spectrum_start_pos = position(stream) line = "" spectrum_buffer = IOBuffer() while !eof(stream) line = readline(stream) write(spectrum_buffer, line) if occursin("", line) break end end spectrum_xml = String(take!(spectrum_buffer)) seek(stream, spectrum_start_pos) # Reset for other parsing id_match = match(r"` block in an indexed mzML file to extract the byte offsets for each spectrum. # Arguments - `stream`: An IO stream positioned at the start of the `` block. # Returns - A `Vector{Int64}` containing the byte offsets for all spectra. """ function parse_offset_list(stream::IO) offsets = Int64[] offset_regex = r"]*>(\d+)" # Actively search for offset tags, ignoring other lines until the end of the index is found. while !eof(stream) line = readline(stream) # First, check for the end condition. if occursin("", line) || occursin("", line) break end # If it's not the end, see if it's an offset tag. m = match(offset_regex, line) if m !== nothing push!(offsets, parse(Int64, m.captures[1])) end # If it's neither, ignore the line and continue the loop. end return offsets end """ find_index_offset(stream::IO)::Int64 Finds the index offset in an mzML file by reading from the end. Optimized version with better buffer management. """ function find_index_offset(stream::IO)::Int64 file_size = filesize(stream) seekend(stream) # Read larger chunk for better chance of finding the offset chunk_size = min(8192, file_size) seek(stream, file_size - chunk_size) footer = read(stream, String) index_offset_match = match(r"(\d+)", footer) if index_offset_match === nothing error("Could not find . File may not be an indexed mzML.") end return parse(Int64, index_offset_match.captures[1]) end # This is the main lazy-loading function for mzML, now returning an MSIData object. """ load_mzml_lazy(file_path::String; cache_size::Int=100) Lazily loads an indexed `.mzML` file by parsing only the metadata. It reads the spectrum index from the end of the file to get the offsets of each spectrum, then parses the metadata for each spectrum without loading the binary data. # Arguments - `file_path`: The path to the `.mzML` file. - `cache_size`: The number of spectra to hold in an LRU cache for faster access. # Returns - An `MSIData` object ready for lazy data access. """ function load_mzml_lazy(file_path::String; cache_size::Int=100) println("DEBUG: Opening file stream for $file_path") stream = open(file_path, "r") try println("DEBUG: Finding index offset...") index_offset = find_index_offset(stream) println("DEBUG: Seeking to index list at offset $index_offset.") seek(stream, index_offset) println("DEBUG: Searching for ''.") if find_tag(stream, r"]*>(\d+)", index_list = r"(\d+)" )