Skip to content

Commit db6abd8

Browse files
Merge pull request #1065 from Proteobench/DIA_entrapment_peptide_level
Dia entrapment peptide level
2 parents 747efd6 + 62844c7 commit db6abd8

37 files changed

Lines changed: 5967 additions & 10 deletions

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,13 @@ jupyter_notebooks/analysis/
153153
testfolder
154154
.claude
155155
uv.lock
156+
test/mock_entrapment_datapoints
156157

157158
# Large files
158159
*.gz
160+
jupyter_notebooks/
161+
testfolder/
162+
proteobench/score/static_files
159163
# Atomic-write / editor temp files
160164
*.tmp
161165
*.tmp.*
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# DIA Ion Entrapment - Astral
2+
3+
This module uses entrapment peptides to assess whether the false discovery rate (FDR) reported by a DIA search engine is reliable. It is based on the entrapment approach described in [Wen et al., 2025](https://www.nature.com/articles/s41592-025-02719-x).
4+
5+
> **This module is in alpha stage. Results and interfaces may change.**
6+
7+
## Purpose
8+
9+
FDR control is a central assumption in proteomics data analysis. When a search engine reports 1% FDR, the actual proportion of false discoveries should not exceed 1%. This module tests that assumption empirically by spiking a known set of "entrapment" peptides into the search space. These peptides cannot be present in the sample (they are derived from a shuffled or decoupled database), so any identification of an entrapment peptide represents a false discovery.
10+
11+
It is important to note that different search engines calculate the FDR on different levels - within a run, globally, on the precursor level, peptidoform level, protein group level, ... .
12+
This module tests the **global precursor ion level FDR**. Search engines that do not calculate the FDR on this level can not be benchmarked with this module.
13+
14+
Three metrics are computed:
15+
16+
- **Lower bound FDP**: minimum estimate of the false discovery proportion, based on the raw count of entrapment identifications.
17+
- **Combined FDP** (upper bound): corrected upper bound accounting for the 1:1 target-to-entrapment ratio in the search database.
18+
- **Paired FDP** (upper bound): refined upper bound using a paired peptide mapping, which accounts for entrapment peptides that are harder to identify than their target counterparts.
19+
20+
Each workflow submission is classified as:
21+
22+
- **Valid**: the upper bound FDP is lower than the reported FDR threshold.
23+
- **Inconclusive**: the lower bound is lower than the declared FDR but the upper bound is higher. This means that the actual FDP could be lower or higher than the reported FDR, therefore we can not say if the FDR is valid or not.
24+
- **Invalid**: even the lower bound is higher than the declared FDR threshold.
25+
26+
## Critical requirements before running
27+
28+
> **Do not rename the raw files after download.** File names are used for run mapping and must match exactly.
29+
30+
> **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.
31+
32+
> **Do not add any variable modifications.**
33+
34+
## Data set
35+
36+
The benchmark dataset consists of three technical replicates of a human plasma digest acquired on an Orbitrap Astral (Thermo Fisher Scientific) in DIA mode with a 15-minute gradient.
37+
38+
Download the raw files from the ProteoBench server:
39+
40+
- `LFQ_Astral_DIA_15min_50ng_Human_01`
41+
- `LFQ_Astral_DIA_15min_50ng_Human_02`
42+
- `LFQ_Astral_DIA_15min_50ng_Human_03`
43+
44+
**It is imperative not to rename the files once downloaded.**
45+
46+
The entrapment FASTA (`ProteoBenchFASTA_Entrapment_Human_with_contaminants_entrapment_pep.txt`) is available from the ProteoBench server. It contains human peptide sequences alongside a matched set of entrapment peptide sequences (suffixed `_p_target` to allow classification). **This file is pre-digested: do not apply enzymatic digestion in your search engine settings.**
47+
48+
## Metric calculation
49+
50+
ProteoBench reads the search engine output, maps runs to samples, and classifies each precursor identification as either a **target** or an **entrapment** hit based on the tag in the fasta.
51+
52+
The three FDP estimates are computed from the resulting set and compared to the reported FDR threshold (inferred from the output file).
53+
54+
## How to use
55+
56+
### Input data for private visualisation
57+
58+
The module currently accepts DIA-NN output (`report.tsv` or `report.parquet`). Use the suggested parameters in Table 1 for a fair comparison between tools.
59+
60+
**Table 1. Suggested parameters**
61+
62+
| Parameter | Value |
63+
|---|---|
64+
| PSM / precursor FDR | 0.01 |
65+
| Spectral library | Predicted from entrapment FASTA |
66+
| Digestion | **None** (FASTA is pre-digested) |
67+
| Fixed modifications | Carbamidomethylation (C) |
68+
| Variable modifications | **None** |
69+
| Precursor charge range | 1–5 |
70+
| Precursor m/z range | 400–1000 |
71+
| Fragment m/z range | 100–1800 |
72+
73+
### Submit your run for public usage
74+
75+
After uploading and privately inspecting your results, you can submit the run for public display. Upload the parameter file associated with your search (see tool-specific sections below), fill in the `Comments for submission` field if needed, confirm the metadata is correct, and press `I really want to upload it`.
76+
77+
You will receive a link to a GitHub pull request. Save it — it contains your run's unique identifier and allows you to communicate with the ProteoBench maintainers. Accepted submissions become publicly visible within a few working days.
78+
79+
**Table 2. Input files required for metric calculation and public submission**
80+
81+
| Tool | Input file | Parsed FDR Column | Parameter file |
82+
|---|---|---|---|
83+
| DIA-NN | `report.tsv` or `report.parquet` | Lib.Q.Value | `report.log.txt` |
84+
85+
## Tool-specific settings
86+
87+
### [DIA-NN](https://github.com/vdemichev/DiaNN)
88+
89+
1. Import the raw `.raw` files.
90+
2. Add the entrapment FASTA. Do not enable "Contaminants" — contaminants are already included in the FASTA.
91+
3. **Disable in-silico digestion.** The FASTA is pre-digested; use '--cut ' in the additional parameter fields to disable enzymatic cleavage.
92+
4. Enable library-free search / FASTA-based library generation (activates deep-learning prediction of spectra, RTs, and IMs).
93+
5. Do not set verbosity / log level higher than 1, otherwise parameter parsing will fail.
94+
6. Upload `report.tsv` or `report.parquet` for metric calculation, and `report.log.txt` for public submission.
95+
96+
## Result description
97+
98+
After uploading, you will see the FDP bounds plotted against the FDR estimates by the search engine. A valid (conservative) FDR calculation shows as both the upper and lower bound being higher than the FDR estimation.
99+
100+
You can also compare the results with other submissions in the 'View Public + New Results' Tab. The following plots are shown:
101+
102+
- **Forest plot**: one horizontal interval per workflow, showing the lower bound FDP to the paired FDP upper bound. The declared FDR threshold is marked with a diamond.
103+
- **FDP / FDR ratio vs # IDs**: scatter plot of paired FDP divided by the declared FDR (x-axis) against the number of identifications at 1% FDR (y-axis). Points to the left of x = 1 have an empirical FDP below the declared threshold.
104+
105+
## Define parameters
106+
107+
For public submission, the following parameters are extracted from the parameter file. If a parameter is not in the file, add it in `Comments for submission`.
108+
109+
- Software tool name and version
110+
- FDR threshold for PSM / precursor, peptide, and protein level
111+
- Precursor and fragment m/z range
112+
- Precursor mass tolerance
113+
- Fragment mass tolerance
114+
- Fixed and variable modifications
115+
- Minimum and maximum precursor charge
116+
- Enzyme (should be "None" for this module)
117+
118+
**DISCLAIMER**: Parameter files may contain file paths that reveal personal usernames, system architecture, or directory structures (FASTA location, raw data location, tool installation paths). Review and sanitize file paths before submission to avoid disclosing institutional or personal identifiers.
119+
120+
Once submitted, a pull request link is shown. Save it to track your submission. Contact us via a [GitHub issue](https://github.com/Proteobench/ProteoBench/issues/new) or [email](mailto:proteobench@eubic-ms.org?subject=ProteoBench_query) if you encounter any problems.

docs/module_grid_generated.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,16 @@ Available ProteoBench modules
8181
+++
8282
`Web app <https://proteobench.cubimed.rub.de/denovo_DDA_HCD>`__ · `Documentation <https://proteobench.readthedocs.io/en/latest/available-modules/active-modules/11-denovo-dda-hcd/>`__
8383

84+
.. grid-item-card:: Entrapment DIA ion Astral
85+
:columns: 6 6 4 4
86+
:padding: 1
87+
88+
:bdg-info:`in development`
89+
^^^
90+
91+
+++
92+
`Web app <https://proteobench.cubimed.rub.de/Entrapment_DIA_ion_Astral>`__ · `Documentation <https://proteobench.readthedocs.io/en/latest/available-modules/active-modules/13-entrapment-ion-dia-astral/>`__
93+
8494
.. grid-item-card:: Quant LFQ DIA ion Plasma
8595
:columns: 6 6 4 4
8696
:padding: 1
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
"""
2+
This module provides functionality for handling and processing quantitative datapoints in the ProteoBench framework.
3+
"""
4+
5+
from __future__ import annotations
6+
7+
import dataclasses
8+
import hashlib
9+
import logging
10+
from collections import ChainMap, defaultdict
11+
from dataclasses import dataclass
12+
from datetime import datetime
13+
from typing import Any, Dict
14+
15+
import numpy as np
16+
import pandas as pd
17+
18+
import proteobench
19+
from proteobench.datapoint.datapoint_base import DatapointBase
20+
from proteobench.score.entrapmentscores import EntrapmentScores
21+
22+
23+
@dataclass
24+
class EntrapmentDatapoint(DatapointBase):
25+
"""
26+
A data structure used to store the results of a entrapment benchmark run.
27+
28+
This class extends DatapointBase to implement entrapment-specific metrics and metadata
29+
storage for LFQ benchmarking runs.
30+
31+
Attributes:
32+
id (str): Unique identifier for the benchmark run.
33+
software_name (str): Name of the software used in the benchmark.
34+
software_version (str): Version of the software.
35+
search_engine (str): Name of the search engine used.
36+
search_engine_version (str): Version of the search engine.
37+
ident_fdr_psm (float): False discovery rate for PSMs.
38+
ident_fdr_peptide (float): False discovery rate for peptides.
39+
ident_fdr_protein (float): False discovery rate for proteins.
40+
enable_match_between_runs (bool): Whether matching between runs is enabled.
41+
precursor_mass_tolerance (str): Mass tolerance for precursor ions.
42+
fragment_mass_tolerance (str): Mass tolerance for fragment ions.
43+
enzyme (str): Enzyme used for digestion.
44+
allowed_miscleavages (int): Number of allowed miscleavages.
45+
min_peptide_length (int): Minimum peptide length.
46+
max_peptide_length (int): Maximum peptide length.
47+
is_temporary (bool): Whether the data is temporary.
48+
intermediate_hash (str): Hash of the intermediate result.
49+
results (dict): A dictionary of metrics for the benchmark run.
50+
nr_id_features (int): Number of identified features.
51+
lower_bound_FDP (float): estimated false discovery proportion based on entrapment IDs.
52+
combined_FDP (float): estimated False discovery proportion based on entrapment IDs.
53+
paired_FDP (float): estimated False discovery proportion based on entrapment IDs.
54+
reported_fdr_parsed_from_input (float): FDR threshold inferred from the input data (max Q-value).
55+
comments (str): Any additional comments.
56+
proteobench_version (str): Version of the Proteobench tool used.
57+
"""
58+
59+
id: str = None
60+
software_name: str = None
61+
software_version: int = 0
62+
search_engine: str = None
63+
search_engine_version: int = 0
64+
ident_fdr_psm: int = 0
65+
ident_fdr_peptide: int = 0
66+
ident_fdr_protein: int = 0
67+
enable_match_between_runs: bool = False
68+
precursor_mass_tolerance: str = None
69+
fragment_mass_tolerance: str = None
70+
enzyme: str = None
71+
allowed_miscleavages: int = 0
72+
min_peptide_length: int = 0
73+
max_peptide_length: int = 0
74+
is_temporary: bool = True
75+
intermediate_hash: str = ""
76+
results: dict = None
77+
nr_id_features: int = 0
78+
lower_bound_FDP: float = np.nan
79+
combined_FDP: float = np.nan
80+
category_combined: str = ""
81+
category_paired: str = ""
82+
paired_FDP: float = np.nan
83+
reported_fdr_parsed_from_input: float = np.nan
84+
fdp_curve: dict = None
85+
comments: str = ""
86+
proteobench_version: str = ""
87+
88+
def generate_id(self) -> None:
89+
"""
90+
Generate a unique ID for the benchmark run by combining the software name and a timestamp.
91+
92+
This ID is used to uniquely identify each run of the benchmark.
93+
"""
94+
time_stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
95+
self.id = "_".join([self.software_name, str(time_stamp)])
96+
logging.info(f"Assigned the following ID to this run: {self.id}")
97+
98+
@staticmethod
99+
def generate_datapoint(
100+
intermediate: pd.DataFrame,
101+
input_format: str,
102+
user_input: dict,
103+
) -> pd.Series:
104+
"""
105+
Generate a Datapoint object containing metadata and results from the benchmark run.
106+
107+
Parameters
108+
----------
109+
intermediate : pd.DataFrame
110+
The intermediate DataFrame containing benchmark results.
111+
input_format : str
112+
The format of the input data (e.g., file format).
113+
user_input : dict
114+
User-defined input values for the benchmark.
115+
default_cutoff_min_prec : int, optional
116+
The default minimum precursor cutoff value. Defaults to 3.
117+
max_nr_observed : int, optional
118+
Maximum nr_observed value to calculate metrics for. If None, defaults to 6.
119+
120+
Returns
121+
-------
122+
pd.Series
123+
A Pandas Series containing the Datapoint's attributes as key-value pairs.
124+
"""
125+
current_datetime = datetime.now()
126+
formatted_datetime = current_datetime.strftime("%Y%m%d_%H%M%S_%f")
127+
128+
if "comments_for_plotting" not in user_input.keys():
129+
user_input["comments_for_plotting"] = ""
130+
131+
try:
132+
user_input = defaultdict(
133+
user_input.default_factory, # Preserve the default factory
134+
{key: ("" if value is None else value) for key, value in user_input.items()},
135+
)
136+
except AttributeError:
137+
user_input = {key: ("" if value is None else value) for key, value in user_input.items()}
138+
139+
result_datapoint = EntrapmentDatapoint(
140+
id=input_format + "_" + user_input["software_version"] + "_" + formatted_datetime,
141+
software_name=input_format,
142+
software_version=user_input["software_version"],
143+
search_engine=user_input["search_engine"],
144+
search_engine_version=user_input["search_engine_version"],
145+
ident_fdr_psm=user_input["ident_fdr_psm"],
146+
ident_fdr_peptide=user_input["ident_fdr_peptide"],
147+
ident_fdr_protein=user_input["ident_fdr_protein"],
148+
enable_match_between_runs=user_input["enable_match_between_runs"],
149+
precursor_mass_tolerance=user_input["precursor_mass_tolerance"],
150+
fragment_mass_tolerance=user_input["fragment_mass_tolerance"],
151+
enzyme=user_input["enzyme"],
152+
allowed_miscleavages=user_input["allowed_miscleavages"],
153+
min_peptide_length=user_input["min_peptide_length"],
154+
max_peptide_length=user_input["max_peptide_length"],
155+
intermediate_hash=str(hashlib.sha1(intermediate.to_string().encode("utf-8")).hexdigest()),
156+
comments=user_input["comments_for_plotting"],
157+
proteobench_version=proteobench.__version__,
158+
)
159+
160+
result_datapoint.generate_id()
161+
metrics = EntrapmentDatapoint.get_metrics(intermediate)
162+
163+
result_datapoint.reported_fdr_parsed_from_input = metrics["reported_fdr_parsed_from_input"]
164+
result_datapoint.nr_id_features = metrics["nr_id_features"]
165+
result_datapoint.lower_bound_FDP = metrics["lower_bound_FDP"]
166+
result_datapoint.combined_FDP = metrics["combined_FDP"]
167+
result_datapoint.paired_FDP = metrics["paired_FDP"]
168+
result_datapoint.category_combined = metrics["category_combined"]
169+
result_datapoint.category_paired = metrics["category_paired"]
170+
result_datapoint.fdp_curve = metrics["fdp_curve"]
171+
172+
result_datapoint.results = metrics
173+
results_series = pd.Series(dataclasses.asdict(result_datapoint))
174+
175+
return results_series
176+
177+
@staticmethod
178+
def get_metrics(intermediate: pd.DataFrame) -> Dict[str, Any]:
179+
metrics = EntrapmentScores.calculate_metrics(intermediate)
180+
return metrics

proteobench/exceptions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ def __init__(self, message):
6262
super().__init__(self.message)
6363

6464

65+
class EntrapmentError(ProteobenchError):
66+
def __init__(self, message):
67+
self.message = message
68+
super().__init__(self.message)
69+
70+
6571
class PlotError(ProteobenchError):
6672
def __init__(self, message):
6773
self.message = message

proteobench/io/params/diann.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,8 +485,10 @@ def extract_params(
485485
parameters[proteobench_setting] = parse_setting(proteobench_setting, cmdline_dict[cmd_setting])
486486

487487
# Parse cut parameter to standard enzyme name
488-
if "enzyme" not in parameters.keys(): # This happens when running fragpipe-diann or if kept as default in the GUI
488+
if "enzyme" not in parameters.keys(): # This happens when running fragpipe-diann
489489
parameters["enzyme"] = "Trypsin/P"
490+
elif parameters["enzyme"] is True: # bare --cut flag with no value = no-digestion mode
491+
parameters["enzyme"] = "No digestion"
490492
elif parameters["enzyme"] == "K*,R*":
491493
parameters["enzyme"] = "Trypsin/P"
492494
elif parameters["enzyme"] == "K*,R*,!P":

0 commit comments

Comments
 (0)