Skip to content

Mzml Reader

mzmlpy.run.Mzml

Mzml(
    file: str | Path | BinaryIO,
    *,
    build_index_from_scratch: bool = False,
    gzip_mode: Literal[
        "auto", "indexed", "stream"
    ] = "auto",
    in_memory: bool = False,
    spectrum_id_regex: str | None = None,
    chromatogram_id_regex: str | None = None
)

Reader for mzML files.

Data is lazily loaded, so only the specific sections of the XML file are parsed. The actual data and properties of objects are only parsed when accessed. Use the context manager to ensure proper file handling. The spectra and chromatograms properties return lookup objects that support iteration, indexing, and ID-based access.

Note

A reader is not thread-safe: random access shares a single underlying file handle, so concurrent access from multiple threads on the same Mzml instance will interleave seeks and reads and return corrupt or wrong data. Use one reader per thread.

Parameters:

Name Type Description Default
file str | Path | BinaryIO

Path to the mzML file (str or Path) or a file-like object.

required
build_index_from_scratch bool

Build the index from scratch instead of using an existing index.

False
gzip_mode Literal['auto', 'indexed', 'stream']

Strategy for reading gzip-compressed (.mzML.gz) files. Only used with in_memory=False; with in_memory=True the whole file is decompressed into memory and "indexed" or "stream" warn that they are ignored.

Self-indexed files created by :func:mzmlpy.write_indexed_gzip are detected automatically when in_memory=False. They use their embedded index regardless of this setting.

  • "auto" (default): Use the embedded index if the file has one. Otherwise use rapidgzip if it is installed, reading current sidecar indexes if present and otherwise indexing in memory. Otherwise decompress the whole file into memory. Never writes files. For fast re-opens of large files, run :func:mzmlpy.write_indexed_gzip once or open once with "indexed".
  • "indexed": Use the rapidgzip library for seekable access to the compressed file without decompressing it all, saving sidecar indexes next to it for fast re-opens. Requires pip install mzmlpy[rapidgzip].
  • "stream": Stream the file sequentially without building an index. Individual spectrum access re-scans the file from the beginning each time.

"extract" and extract_dir were removed in 0.10.

'auto'
in_memory bool

Load the entire (decompressed) file into memory. Defaults to False: plain files are read from disk through their index and gzip files follow gzip_mode. Pass True to buffer a small file, or a gzip file you will read many times.

False
spectrum_id_regex str | None

Optional regex applied to spectrum IDs to create a secondary lookup key. The first capture group (or full match if no groups) becomes the simplified key. For example, r"scan=(\d+)" lets you look up spectra by scan number (reader.spectra["19"]) instead of the full native ID ("scan=19").

None
chromatogram_id_regex str | None

Optional regex applied to chromatogram IDs to create a secondary lookup key. Works identically to spectrum_id_regex but for chromatograms.

None

Initialize Mzml and parse metadata.

Source code in src/mzmlpy/run.py
def __init__(
    self,
    file: str | Path | BinaryIO,
    *,
    build_index_from_scratch: bool = False,
    gzip_mode: Literal["auto", "indexed", "stream"] = "auto",
    in_memory: bool = False,
    spectrum_id_regex: str | None = None,
    chromatogram_id_regex: str | None = None,
) -> None:
    """Initialize Mzml and parse metadata."""
    self._spectrum_id_regex = spectrum_id_regex
    self._chromatogram_id_regex = chromatogram_id_regex
    self._spectra_lookup: SpectrumLookup | None = None
    self._chromatograms_lookup: ChromatogramLookup | None = None
    self._path: Path | None = None
    file_interface_arg: Any

    if isinstance(file, str | Path):
        self._path = Path(file)
        # Use string representation for internal helpers that expect paths
        path_str = str(self._path)
        self._encoding = _determine_file_encoding(path_str)
        file_interface_arg = path_str
    else:
        # File-like object — must be a readable binary stream. Validate up front so an
        # unsupported input (e.g. an int) raises a clear TypeError instead of an opaque
        # AttributeError from encoding sniffing below.
        if not (hasattr(file, "read") and hasattr(file, "readline")):
            raise TypeError(
                f"Unsupported input type {type(file).__name__!r}: expected a path (str/Path) "
                "or a readable binary file-like object."
            )
        if hasattr(file, "name") and isinstance(file.name, str):
            self._path = Path(file.name)
        self._encoding = _guess_encoding(file)
        file_interface_arg = file

    source = str(self._path) if self._path is not None else "in-memory-stream"
    # Open file
    with _parse_errors(source):
        self._file_object: FileInterface = FileInterface(
            path=file_interface_arg,
            encoding=self._encoding,
            build_index_from_scratch=build_index_from_scratch,
            gzip_mode=gzip_mode,
            in_memory=in_memory,
        )

    # Parse metadata. If parsing fails, close the file object so a half-constructed
    # reader does not leak file handles or rapidgzip worker threads — the caller
    # never receives the object, so it can never call close() itself.
    try:
        with _parse_errors(source):
            builder = self._parse_metadata()
        # Extract parsed content
        self._content: _MzMLContent = builder.build()
        self._obo_version: str | None = builder.obo_version
    except BaseException:
        self._file_object.close()
        raise

access_strategy property

access_strategy: AccessStrategy

Concrete storage strategy selected when the file was opened.

file_path property

file_path: Path | None

Access the file path as a Path object if available.

file_name property

file_name: str

Access the file name as a string.

spectra property

spectra: SpectrumLookup

Access spectra lookup.

Returns the same lookup instance across calls, so the regex id map built for spectrum_id_regex persists and id lookups don't re-scan the file on every access.

chromatograms property

chromatograms: ChromatogramLookup

Access chromatograms lookup.

Returns the same lookup instance across calls (see :meth:spectra).

total_ion_chromatogram property

total_ion_chromatogram: Chromatogram | None

The total ion chromatogram, or None if the file has none.

Found by the id "TIC" or, failing that, by the MS:1000235 CV term.

obo_version property

obo_version: str | None

Version of the PSI-MS controlled vocabulary declared in the cvList.

id property

id: str

Access mzML id.

version property

version: str

Access mzML version.

cvs property

cvs: dict[str, CVElement]

Access controlled vocabularies.

file_description property

file_description: FileDescription | None

Access file description.

referenceable_param_groups property

referenceable_param_groups: dict[
    str, ReferenceableParamGroup
]

Access referenceable parameter groups (a new dict on every call).

softwares property

softwares: dict[str, Software]

Access software list.

instrument_configurations property

instrument_configurations: dict[
    str, InstrumentConfiguration
]

Access instrument configurations (a new dict on every call).

data_processes property

data_processes: dict[str, DataProcessing]

Access data processing steps (a new dict on every call).

samples property

samples: dict[str, Sample]

Access sample list.

scan_settings property

scan_settings: dict[str, ScanSetting]

Access scan settings (a new dict on every call).

run property

run: Run | None

Access run information.

validate

validate(
    *,
    decode_binary: bool = False,
    check_index: bool = False
) -> ValidationReport

Validate this reader's XML through a fresh handle.

See :func:mzmlpy.validate for check scope. This checks the representation selected by the reader. Use the standalone function to validate the original file directly.

Source code in src/mzmlpy/run.py
def validate(self, *, decode_binary: bool = False, check_index: bool = False) -> ValidationReport:
    """Validate this reader's XML through a fresh handle.

    See :func:`mzmlpy.validate` for check scope. This checks the representation selected
    by the reader. Use the standalone function to validate the original file directly.
    """
    with self._file_object.file_handler.get_file_handler(self._encoding) as handle:
        binary = cast(BinaryIO, getattr(handle, "buffer", handle))
        return _validate_stream(binary, decode_binary=decode_binary, check_index=check_index)

Utilities

mzmlpy.run.peek_spectrum_count

peek_spectrum_count(file: str | Path) -> int | None

Return a file's spectrum count without building a random-access index.

Unlike len(Mzml(file).spectra), this does not construct a reader or index every spectrum's byte offset — it streams forward just far enough to read the <spectrumList count="N"> opening tag's count attribute (just after the header metadata, before the first spectrum) and stops. Files ending in .gz or .igz are decompressed on the fly. Useful for cheaply checking many files (e.g. before deciding which to open fully). Returns None if the file has no spectrumList or the tag has no count attribute.

Note

There is no equally cheap peek_chromatogram_count: per the mzML schema, chromatogramList follows spectrumList, so reaching its opening tag requires streaming past the entire spectrum list first — at that point building the full index via :class:Mzml is a better fit than a "peek."

Source code in src/mzmlpy/run.py
def peek_spectrum_count(file: str | Path) -> int | None:
    """Return a file's spectrum count without building a random-access index.

    Unlike ``len(Mzml(file).spectra)``, this does not construct a reader or index every
    spectrum's byte offset — it streams forward just far enough to read the
    ``<spectrumList count="N">`` opening tag's ``count`` attribute (just after the header
    metadata, before the first spectrum) and stops. Files ending in ``.gz`` or ``.igz`` are
    decompressed on the fly. Useful for cheaply checking many
    files (e.g. before deciding which to open fully). Returns ``None`` if the file has no
    ``spectrumList`` or the tag has no ``count`` attribute.

    Note:
        There is no equally cheap ``peek_chromatogram_count``: per the mzML schema,
        ``chromatogramList`` follows ``spectrumList``, so reaching its opening tag requires
        streaming past the entire spectrum list first — at that point building the full index
        via :class:`Mzml` is a better fit than a "peek."
    """
    path_str = str(file)
    is_gz = path_str.endswith(".gz") or path_str.endswith(".igz")
    file_handle = gzip_open_binary(path_str) if is_gz else open(path_str, "rb")
    try:
        with _parse_errors(path_str):
            # Read the count off the spectrumList *start* tag, but clear completed elements on their
            # *end* events so that a file with no spectrumList doesn't accumulate the whole tree in
            # memory before returning None.
            for event, element in ElementTree.iterparse(file_handle, events=("start", "end")):
                if event == "start":
                    if get_tag(element) == MzMLElement.SPECTRUM_LIST:
                        count = element.attrib.get("count")
                        return int(count) if count is not None else None
                else:
                    element.clear()
            return None
    finally:
        file_handle.close()