Skip to content

Commit e8a6155

Browse files
authored
Merge pull request #1118 from Proteobench/add-mods-to-entrapment
add modification support in entrapment
2 parents dd2fe64 + 451a392 commit e8a6155

8 files changed

Lines changed: 135 additions & 40 deletions

File tree

docs/available-modules/active-modules/13-entrapment-ion-dia-astral.md

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Each workflow submission is classified as:
2929
3030
> **Use the pre-digested entrapment FASTA — do not enable in-silico digestion.** The entrapment FASTA already contains peptide sequences (not full proteins). Enabling digestion in your search engine will lead to identified peptides without matched entrapments, which makes FDP calculation less accurate.
3131
32-
> **Do not add any variable modifications.**
32+
> **Only methionine oxidation is supported as a variable modification.** The entrapment mapping enumerates every combination of methionine oxidation and cysteine carbamidomethylation. Enabling any other modification (variable, or an additional fixed modification) causes metric calculation to fail with an error, because the target/entrapment pairing cannot be resolved for it.
3333
3434
## Data set
3535

@@ -53,6 +53,10 @@ ProteoBench reads the search engine output, maps runs to samples, and classifies
5353

5454
The three FDP estimates are computed from the resulting set and compared to the reported FDR threshold (inferred from the output file).
5555

56+
### Modification support
57+
58+
Precursor identifications are matched to the entrapment mapping file using their exact modified sequence, not the stripped peptide. The mapping file enumerates every combination of variable methionine oxidation and fixed cysteine carbamidomethylation, so an identification built only from those two modifications is paired correctly, down to the specific set of oxidized methionines. Any other modification present in the reported sequence is not recognized, and metric calculation fails with an error rather than silently ignoring it.
59+
5660
## How to use
5761

5862
### Suggested parameters
@@ -67,7 +71,7 @@ The module currently accepts DIA-NN, FragPipe, FragPipe with DIA-NN quantificati
6771
| Spectral library | Predicted from entrapment FASTA |
6872
| Digestion | **None** (FASTA is pre-digested) |
6973
| Fixed modifications | Carbamidomethylation (C) |
70-
| Variable modifications | **None** |
74+
| Variable modifications | Methionine oxidation (M), optional. No other variable modification is supported. |
7175
| Precursor charge range | 1–5 |
7276
| Precursor m/z range | 400–1000 |
7377
| Fragment m/z range | 100–1800 |
@@ -93,35 +97,22 @@ You will receive a link to a GitHub pull request. Save it — it contains your r
9397
1. Import the raw `.raw` files.
9498
2. Add the entrapment FASTA. Do not enable "Contaminants" — contaminants are already included in the FASTA.
9599
3. **Disable in-silico digestion.** The FASTA is pre-digested; use '--cut ' in the additional parameter fields to disable enzymatic cleavage.
96-
4. Enable library-free search / FASTA-based library generation (activates deep-learning prediction of spectra, RTs, and IMs).
97-
5. Do not set verbosity / log level higher than 1, otherwise parameter parsing will fail.
98-
6. Upload `report.tsv` or `report.parquet` for metric calculation, and `report.log.txt` for public submission.
100+
4. Variable modifications are limited to methionine oxidation (optional); disable all other variable modifications. Carbamidomethylation (C) can be used as a fixed modification.
101+
5. Enable library-free search / FASTA-based library generation (activates deep-learning prediction of spectra, RTs, and IMs).
102+
6. Do not set verbosity / log level higher than 1, otherwise parameter parsing will fail.
103+
7. Upload `report.tsv` or `report.parquet` for metric calculation, and `report.log.txt` for public submission.
99104

100105
### FragPipe with DIA-NN quantification
101106

102-
FragPipe workflows that produce a DIA-NN-style report are submitted as **FragPipe (DIA-NN quant)**. ProteoBench parses the precursor identifications from the DIA-NN report and extracts workflow metadata from the FragPipe `.workflow` file.
103-
104-
1. Use the ProteoBench entrapment FASTA as the sequence database. Do not add a second contaminant database.
105-
2. Configure the search without enzymatic digestion. The FASTA contains pre-digested peptide entries, so in-silico digestion must remain disabled throughout the FragPipe/DIA-NN workflow. MSFragger Protein Digestion settings:
106-
107-
```
108-
Load Rules: nocleavage
109-
Cuts 1: @
110-
No cuts 1: @
111-
```
112-
113-
To set these via the GUI: MSFragger tab -> Protein Digestion -> Load Rules = "nocleavage"; Cuts 1 = "@"; No cuts 1 = "@".
114-
3. Keep variable modifications disabled. Carbamidomethylation (C) may be used as the fixed modification.
115-
4. Use the DIA-NN report generated by FragPipe (`report.tsv` or `report.parquet`) for metric calculation.
116-
5. Upload the FragPipe `.workflow` file for public submission. Do not upload the DIA-NN log as the parameter file for this workflow type.
107+
FragPipe is currently not supported as the decoy generation is not compatible with the predigested module fasta (see [here](https://github.com/Nesvilab/FragPipe/issues/2847)).
117108

118109
### [AlphaDIA](https://github.com/MannLabs/alphadia)
119110

120111
AlphaDIA submissions are parsed from precursor-level output. The entrapment module currently expects AlphaDIA 2.x-style precursor output.
121112

122113
1. Use the ProteoBench entrapment FASTA and disable additional contaminants.
123114
2. Configure AlphaDIA for a no-enzyme / pre-digested FASTA search: set "no-cleave" as the enzyme parameter.
124-
3. Keep variable modifications disabled. Use Carbamidomethylation (C) as the fixed modification if alkylation was applied.
115+
3. Variable modifications are limited to methionine oxidation; keep all other variable modifications disabled. Use Carbamidomethylation (C) as the fixed modification if alkylation was applied.
125116
4. Upload `precursors.parquet` for metric calculation.
126117
5. Upload the AlphaDIA `log.txt` file for public submission.
127118

proteobench/io/parsing/io_parse_settings/entrapment/DIA/ion/Astral/parse_settings_diann.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"isalpha" = true
1414
"isupper" = true
1515
"pattern" = "\\(([^()]*)\\)"
16-
"modification_dict" = {"(unimod:35)" = "Oxidation", "(unimod:1)" = "Acetyl", "(unimod:4)" = "Carbamidomethyl"}
16+
"modification_dict" = {"(unimod:35)" = "Oxidation", "(unimod:4)" = "Carbamidomethyl"}
1717

1818
[general]
1919
"contaminant_flag" = "Cont_"

proteobench/io/parsing/io_parse_settings/parse_settings_files.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,4 @@
112112

113113
[entrapment_DIA_ion_Astral]
114114
"DIA-NN" = "parse_settings_diann.toml"
115-
"FragPipe (DIA-NN quant)" = "parse_settings_fragpipe_diann_quant.toml"
116-
"FragPipe" = "parse_settings_fragpipe_DIA.toml"
117115
"AlphaDIA" = "parse_settings_alphadia.toml"

proteobench/io/parsing/parse_ion.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,13 +305,17 @@ def get_proforma_bracketed(
305305
if idx in pos_mod_dict:
306306
if idx == 0:
307307
new_seq += f"[{pos_mod_dict[idx]}]-"
308-
elif idx == len(stripped_seq):
309-
new_seq += f"-[{pos_mod_dict[idx]}]"
310308
else:
311309
new_seq += f"[{pos_mod_dict[idx]}]"
312310
if not before_aa:
313311
new_seq += aa
314312

313+
# A modification on the last residue (before_aa=False, i.e. bracket after the
314+
# residue) is recorded at idx == len(stripped_seq), which the loop above never
315+
# reaches (idx only ranges 0..len(stripped_seq)-1), so it must be appended here.
316+
if len(stripped_seq) in pos_mod_dict:
317+
new_seq += f"[{pos_mod_dict[len(stripped_seq)]}]"
318+
315319
return new_seq
316320

317321

proteobench/io/parsing/parse_settings.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1023,6 +1023,43 @@ def add_modification_parser(self, parser: ParseModificationSettings):
10231023
"""
10241024
self.modification_parser = parser
10251025

1026+
def _process_modifications(self, df: pd.DataFrame) -> pd.DataFrame:
1027+
"""
1028+
Standardize the modified sequence column to ProForma-style bracket notation.
1029+
1030+
Tools report modifications differently (e.g. DIA-NN/FragPipe use
1031+
``M(UniMod:35)``, AlphaDIA already reports ``M[Oxidation]``). The
1032+
entrapment mapping file is keyed on the bracketed, human-readable form
1033+
(e.g. ``M[Oxidation]``, ``C[Carbamidomethyl]``), so tools with a
1034+
``[modifications_parser]`` section have their modified-sequence column
1035+
rewritten into that same notation before mapping. Tools without a
1036+
modification parser (i.e. already reporting that notation natively)
1037+
are left untouched.
1038+
1039+
Parameters
1040+
----------
1041+
df : pd.DataFrame
1042+
Input DataFrame containing the modified sequence column.
1043+
1044+
Returns
1045+
-------
1046+
pd.DataFrame
1047+
DataFrame with the modified sequence column standardized.
1048+
"""
1049+
if self.modification_parser is None:
1050+
return df
1051+
1052+
mp = self.modification_parser
1053+
df[mp.modifications_parse_column] = df[mp.modifications_parse_column].apply(
1054+
get_proforma_bracketed,
1055+
before_aa=mp.modifications_before_aa,
1056+
isalpha=mp.modifications_isalpha,
1057+
isupper=mp.modifications_isupper,
1058+
pattern=mp.modifications_pattern,
1059+
modification_dict=mp.modifications_mapper,
1060+
)
1061+
return df
1062+
10261063
def convert_to_standard_format(self, df: pd.DataFrame) -> pd.DataFrame:
10271064
"""
10281065
Convert a software tool output into a generic format supported by the module.
@@ -1054,7 +1091,7 @@ def convert_to_standard_format(self, df: pd.DataFrame) -> pd.DataFrame:
10541091
# df = self._fix_colnames(df)
10551092
# df = self._mark_contaminants(df)
10561093
# df = self._process_species_information(df)
1057-
# df = self._process_modifications(df)
1094+
df = self._process_modifications(df)
10581095
# df_melted = self._handle_data_format(df)
10591096
# df_melted = self._filter_zero_intensities(df_melted)
10601097
return df # self._format_by_analysis_level(df_melted), replicate_to_raw

proteobench/modules/entrapment/entrapment_base_module.py

Lines changed: 76 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -145,17 +145,31 @@ def _apply_mapping(
145145
Filter unmapped peptides, assign target/entrapment labels, and merge pair index.
146146
147147
Reads ``self.mapping_file`` (TSV with ``sequence``, ``peptide_pair_index``,
148-
and ``peptide_type`` columns). Peptides absent from the mapping are removed;
149-
if more than ``max_missing_fraction`` of unique peptides are absent an
150-
``EntrapmentError`` is raised. The caller receives a DataFrame with two extra
151-
columns: ``"Target or Entrapment"`` (from ``peptide_type``) and
152-
``"peptide_pair_index"``.
148+
and ``peptide_type`` columns). The mapping file may contain both bare peptide
149+
stems and modification-specific variants (methionine oxidation and/or cysteine
150+
carbamidomethylation, in any combination) that share their stem's identity but
151+
have their own ``sequence`` and ``peptide_pair_index`` values. Peptides absent
152+
from the mapping are removed; if more than ``max_missing_fraction`` of unique
153+
peptides are absent an ``EntrapmentError`` is raised. The caller receives a
154+
DataFrame with two extra columns: ``"Target or Entrapment"`` (from
155+
``peptide_type``) and ``"peptide_pair_index"``.
156+
157+
Matching is done exactly against the modified ``"Sequence"`` column - there is
158+
no fallback to the stripped ``"Peptide"`` stem. ``"Sequence"`` is expected to
159+
already be standardized to ProForma-style bracket notation (e.g.
160+
``"M[Oxidation]"``, ``"C[Carbamidomethyl]"``) by the parser's
161+
``[modifications_parser]`` step (see ``ParseSettingsEntrapment._process_modifications``),
162+
which normalizes each tool's native notation (e.g. DIA-NN's ``"M(UniMod:35)"``)
163+
to that common form. Any modification other than methionine oxidation
164+
(``Oxidation``) and/or cysteine carbamidomethylation (``Carbamidomethyl``) is
165+
therefore not supported and raises an ``EntrapmentError`` up front, rather than
166+
silently collapsing to a mod-agnostic stem match.
153167
154168
Parameters
155169
----------
156170
standard_format : pd.DataFrame
157171
Standardised input DataFrame (output of ``convert_to_standard_format``).
158-
Must contain a ``"Peptide"`` column.
172+
Must contain ``"Peptide"`` and ``"Sequence"`` columns.
159173
max_missing_fraction : float
160174
Maximum tolerated fraction of unmatched peptides. Defaults to 0.03.
161175
@@ -167,11 +181,27 @@ def _apply_mapping(
167181
Raises
168182
------
169183
EntrapmentError
170-
If the fraction of unmatched peptides exceeds ``max_missing_fraction``.
184+
If the fraction of unmatched peptides exceeds ``max_missing_fraction``, if
185+
any identified sequence carries a modification other than methionine
186+
oxidation or cysteine carbamidomethylation, or if a sequence built only from
187+
those two modifications is still absent from the mapping file.
171188
"""
189+
import re
190+
172191
from proteobench.exceptions import EntrapmentError
173192

193+
allowed_modifications = {"carbamidomethyl", "oxidation"}
194+
modification_pattern = re.compile(r"\[([^\[\]]*)\]")
195+
196+
def unsupported_modifications(sequence: str) -> List[str]:
197+
return [
198+
mod
199+
for mod in modification_pattern.findall(sequence)
200+
if mod.strip().lower() not in allowed_modifications
201+
]
202+
174203
mapping_df = pd.read_csv(self.mapping_file, sep="\t", index_col=False)
204+
mapping_df = mapping_df.drop_duplicates(subset=["sequence"], keep="first")
175205
all_peptides = set(standard_format["Peptide"])
176206
missing_peptides = all_peptides - set(mapping_df["sequence"])
177207
missing_fraction = len(missing_peptides) / len(all_peptides) if all_peptides else 0.0
@@ -195,12 +225,47 @@ def _apply_mapping(
195225
if missing_peptides:
196226
df = df[~df["Peptide"].isin(missing_peptides)].reset_index(drop=True)
197227

228+
sequences_with_unsupported_mods = {
229+
sequence: mods
230+
for sequence in df["Sequence"].unique()
231+
for mods in (unsupported_modifications(sequence),)
232+
if mods
233+
}
234+
if sequences_with_unsupported_mods:
235+
n_bad = len(sequences_with_unsupported_mods)
236+
examples = ", ".join(
237+
f"{seq} ({'/'.join(mods)})" for seq, mods in list(sequences_with_unsupported_mods.items())[:5]
238+
)
239+
raise EntrapmentError(
240+
f"{n_bad} identified sequence(s) carry a modification other than methionine oxidation "
241+
f"(Oxidation) and/or cysteine carbamidomethylation (Carbamidomethyl). The entrapment mapping "
242+
f"file only supports those two modifications.\n\n"
243+
f"First {min(5, n_bad)} examples: {examples}"
244+
)
245+
246+
mapping_cols = mapping_df[["sequence", "peptide_pair_index", "peptide_type"]]
247+
198248
df = df.merge(
199-
mapping_df[["sequence", "peptide_pair_index", "peptide_type"]],
249+
mapping_cols.rename(columns={"sequence": "_mapped_sequence"}),
200250
how="left",
201-
left_on="Peptide",
202-
right_on="sequence",
203-
).drop(columns=["sequence"])
251+
left_on="Sequence",
252+
right_on="_mapped_sequence",
253+
).drop(columns=["_mapped_sequence"])
254+
255+
unmatched = df["peptide_pair_index"].isna()
256+
if unmatched.any():
257+
n_unmatched = int(unmatched.sum())
258+
examples = ", ".join(sorted(df.loc[unmatched, "Sequence"].unique())[:5])
259+
raise EntrapmentError(
260+
f"{n_unmatched} identified sequence(s) use only methionine oxidation and/or cysteine "
261+
f"carbamidomethylation, but their exact modified form is absent from the entrapment "
262+
f"mapping file. This should not happen and likely indicates a gap in the mapping file "
263+
f"generation; please report this as a bug.\n\n"
264+
f"First {min(5, n_unmatched)} examples: {examples}"
265+
)
266+
267+
df["peptide_pair_index"] = df["peptide_pair_index"].astype("Int64")
268+
204269
df["Target or Entrapment"] = df["peptide_type"].replace("p_target", "entrapment")
205270
df = df.drop(columns=["peptide_type"])
206271

webinterface/pages/markdown_files/Entrapment/DIA/ion/Astral/introduction_DIA_entrapment_ions_astral.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ The benchmark uses three technical replicates of a human plasma digest acquired
44

55
ProteoBench classifies each identified precursor as target or entrapment, maps entrapment peptides to their paired target peptides, and reports lower-bound, combined, and paired false discovery proportion (FDP) estimates. These estimates are compared with the FDR threshold parsed from the submitted tool output.
66

7-
The module accepts DIA-NN, FragPipe, FragPipe (DIA-NN quant), and AlphaDIA outputs. For a valid comparison, keep the raw file names unchanged, use the ProteoBench entrapment FASTA, disable in-silico digestion, and do not add variable modifications.
7+
The module accepts DIA-NN and AlphaDIA outputs. For a valid comparison, keep the raw file names unchanged, use the ProteoBench entrapment FASTA, disable in-silico digestion, and do not add variable modifications other than methionine oxidation. Any other modification present in the identified sequences causes metric calculation to fail with an error.

webinterface/pages/markdown_files/Entrapment/DIA/ion/Astral/result_description.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ It contains the following columns:
66
- **Charge**: precursor charge
77
- **Q-Value**: precursor-level FDR value parsed from the submitted output
88
- **Target or Entrapment**: target/entrapment classification from the ProteoBench mapping file
9-
- **peptide_pair_index**: identifier linking each entrapment peptide to its paired target peptide
9+
- **peptide_pair_index**: identifier linking each entrapment peptide to its paired target peptide. A methionine-oxidized or carbamidomethylated identification has its own index, distinct from the unmodified form of the same peptide.
1010
- **Lower bound FDP**, **Combined FDP**, and **Paired FDP**: empirical FDP estimates used to classify the submitted workflow

0 commit comments

Comments
 (0)