Skip to content

Improve mzML reader parsing for filter strings and missing fields - #122

Open
falconsmilie wants to merge 8 commits into
MannLabs:mainfrom
falconsmilie:fix-mzml-reader-parsing
Open

Improve mzML reader parsing for filter strings and missing fields#122
falconsmilie wants to merge 8 commits into
MannLabs:mainfrom
falconsmilie:fix-mzml-reader-parsing

Conversation

@falconsmilie

Copy link
Copy Markdown

This PR improves mzML reader robustness in alpharaw/mzml.py.

The main fix is for NCE parsing from mzML scan filter strings. The previous implementation attempted to access dict.get using subscript syntax, which meant valid filter strings could silently fall back to np.nan because the error was caught by a broad exception handler.

This PR also removes duplicated charge-state parsing and adds safer handling for missing or empty mzML fields.

What changed

Fixed filter string NCE parsing

Previously, the filter string was accessed like this:

item_dict.get("scanList").get("scan")[0].get["filter string"]

Since get is a method, this raises a TypeError.

The PR changes this to normal .get("filter string") access and routes the result through a small helper:

def _parse_nce_from_filter_string(filter_string) -> float:
    """Parse NCE from Thermo-like filter strings."""
    if not filter_string:
        return np.nan

    try:
        if "@hcd" in filter_string:
            return float(filter_string.split("@hcd")[1].split(" ")[0])

        if "@cid" in filter_string:
            return float(filter_string.split("@cid")[1].split(" ")[0])

        return np.nan
    except (TypeError, ValueError, IndexError):
        return np.nan

This keeps the behaviour close to the original intended logic while fixing the parsing bug.

Removed duplicated charge state parsing

Charge state is now parsed in one place.

Expected behaviour:

  • valid charge state becomes an int
  • missing charge state returns 0
  • invalid charge state returns 0

Improved missing-field handling

The parser now handles several optional mzML fields more safely, especially around precursor data.

This includes safer handling for:

  • missing filter strings
  • missing charge state
  • invalid charge state
  • missing selected ion fields
  • missing selected ion m/z
  • empty scan payloads

Added empty import guard

_import() now avoids calling np.concatenate(...) on empty lists.

When no peak arrays are available, the reader returns empty arrays with the expected dtypes instead of failing with a NumPy error.

Tests added

This PR adds mzML reader tests covering:

  • MS1 entries without precursor fields
  • MS2 entries with HCD filter strings
  • MS2 entries with CID filter strings
  • MS2 entries with missing filter strings
  • missing charge state
  • invalid charge state
  • missing selected ion m/z
  • empty scan payloads
  • empty mzML reader input

Behaviour note

This PR does not intentionally change the public meaning of precursor_mz.

There is a related question around whether precursor_mz should use selected ion m/z or the isolation-window target m/z. I have kept that separate from this PR because it may affect downstream behaviour and seems worth confirming with maintainers first.

Validation

Suggested local checks:

python -m pytest tests/unit/test_mzml_reader.py
python -m pytest tests/unit/test_mzml_writer.py
python -m pytest

Checklist

  • NCE parsing works for valid @hcd filter strings.
  • NCE parsing works for valid @cid filter strings.
  • Missing filter strings return np.nan.
  • Malformed filter strings return np.nan.
  • Charge-state parsing is no longer duplicated.
  • Missing charge state defaults to 0.
  • Invalid charge state defaults to 0.
  • Missing precursor fields are handled safely.
  • Empty scan payloads are handled safely.
  • Empty mzML imports no longer fail because of np.concatenate([]).
  • New reader tests pass.
  • Existing writer tests pass.
  • Full test suite passes.

To-do list (outside contributers only)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant