Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion mhctools/parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,15 @@ def split_stdout_lines(stdout):
# beginning of headers in NetMHC
if any(line.startswith(word) for word in NETMHC_TOKENS):
continue
yield line.split()
fields = line.split()
# Data rows always start with an integer position. Anything else is
# a header or summary line interleaved with the result blocks —
# e.g. "HLA-A24:02 : Distance to training data ..." in multi-allele
# NetMHCpan output, which appears after the first `---` separator
# and would otherwise be misparsed.
if not fields or not fields[0].lstrip("-").isdigit():
continue
yield fields


def clean_fields(fields, ignored_value_indices, transforms):
Expand Down
46 changes: 46 additions & 0 deletions tests/test_mhc_formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -527,3 +527,49 @@ def test_netmhciipan4_with_bind_level():
results = parse_netmhciipan4_stdout(output, mode="elution_score")
assert len(results) == 2
assert abs(results[1].score - 0.800000) < 0.0001


# ---------- Multi-allele runs (openvax/mhctools#195) ----------

def test_multi_allele_netmhcpan41_skips_interblock_headers():
"""Regression for #195: NetMHCpan 4.1 with multiple alleles in one
invocation emits a "Distance to training data" header line between
per-allele result blocks (after the first `---` separator). The
parser must skip these non-data rows, not crash on them."""
output = """
# NetMHCpan version 4.1b

# Input is in PEPTIDE format

# Make both EL and BA predictions

HLA-A02:01 : Distance to training data 0.000 (using nearest neighbor HLA-A02:01)

# Rank Threshold for Strong binding peptides 0.500
# Rank Threshold for Weak binding peptides 2.000
---------------------------------------------------------------------------------------------------------------------------
Pos MHC Peptide Core Of Gp Gl Ip Il Icore Identity Score_EL %Rank_EL Score_BA %Rank_BA Aff(nM) BindLevel
---------------------------------------------------------------------------------------------------------------------------
1 HLA-A*02:01 SIINFEKL SII-NFEKL 0 0 0 3 1 SIINFEKL PEPLIST 0.0100620 6.723 0.110414 20.171 15140.42
---------------------------------------------------------------------------------------------------------------------------

Protein PEPLIST. Allele HLA-A*02:01. Number of high binders 0. Number of weak binders 0. Number of peptides 1

HLA-A24:02 : Distance to training data 0.000 (using nearest neighbor HLA-A24:02)

# Rank Threshold for Strong binding peptides 0.500
# Rank Threshold for Weak binding peptides 2.000
---------------------------------------------------------------------------------------------------------------------------
Pos MHC Peptide Core Of Gp Gl Ip Il Icore Identity Score_EL %Rank_EL Score_BA %Rank_BA Aff(nM) BindLevel
---------------------------------------------------------------------------------------------------------------------------
1 HLA-A*24:02 SIINFEKL SII-NFEKL 0 0 0 3 1 SIINFEKL PEPLIST 0.0200000 5.000 0.220000 10.000 8000.00
---------------------------------------------------------------------------------------------------------------------------

Protein PEPLIST. Allele HLA-A*24:02. Number of high binders 0. Number of weak binders 0. Number of peptides 1
"""
results = parse_netmhcpan_stdout(output, mode="binding_affinity")
assert len(results) == 2, (
"Expected one row per allele after skipping inter-block header lines"
)
alleles = {r.allele for r in results}
assert alleles == {"HLA-A*02:01", "HLA-A*24:02"}
Loading