Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
49d0bd3
Update nf-core modules openms and openmsthirdparty/cometadapter
jonasscheid Oct 13, 2026
49d6c6a
Bump local OpenMS thirdparty modules to 3.6.0
jonasscheid Oct 13, 2026
6d730d4
Read the Comet xcorr from MS:1002252, which replaces COMET:xcorr in O…
jonasscheid Oct 13, 2026
1cedb80
Update pipeline test snapshots for OpenMS 3.6.0
jonasscheid Oct 13, 2026
cf2a3cd
Add CHANGELOG entry for the OpenMS 3.6.0 bump
jonasscheid Oct 13, 2026
08d9416
Name PSI-MS accession columns in the result TSV by their CV term (COM…
jonasscheid Oct 13, 2026
4958d73
Mention the Comet:xcorr column rename in the CHANGELOG
jonasscheid Oct 13, 2026
ef81b3a
Make DeepLC numerics independent of the CI runner in nf-test
jonasscheid Oct 13, 2026
136edcc
Pin the OpenBLAS kernel in nf-test so MS²Rescore features match betwe…
jonasscheid Oct 13, 2026
1591b9d
Drop the OpenBLAS pin, which has no effect on CI, and take the reprod…
jonasscheid Oct 13, 2026
a87a36c
Bump local pyOpenMS modules to 3.6.0 and use its simpler idXML, trafo…
jonasscheid Oct 14, 2026
d29d9f5
Parse --remove_precursor_peak as a flag so that false no longer enabl…
jonasscheid Oct 14, 2026
1889770
Remove the TensorFlow test settings, which CI does not need, and snap…
jonasscheid Oct 14, 2026
16dd4b7
Add a USI column to the result TSV from the OpenMS 3.6.0 TextExporter…
jonasscheid Oct 15, 2026
80323fb
Detect samples without PSMs after FDR filtering by their PSMs instead…
jonasscheid Oct 16, 2026
576ca2c
Keep the OpenMS USI for timsTOF spectra instead of leaving it empty
jonasscheid Oct 20, 2026
4b8c5ba
Sort the per-TSV peptidoform snapshot entries by file name so the fil…
jonasscheid Oct 20, 2026
12ba603
Split the CHANGELOG entry into Added, Fixed and Changed and list Perc…
jonasscheid Oct 22, 2026
2d34faa
Set the USI dataset ID in the TextExporter ext.args instead of a pipe…
jonasscheid Oct 22, 2026
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
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## 3.4.0dev

### `Changed`

- Bumped OpenMS and pyOpenMS modules to 3.6.0, renamed `COMET:xcorr` to its PSI-MS term name `Comet:xcorr` in the result TSV added a `USI` column (`--usi_dataset_id`) to the result TSV and fixed the ion annotator ignoring `--remove_precursor_peak false` and samples without PSMs after FDR filtering crashing `--quantify` [#494](https://github.com/nf-core/mhcquant/pull/494)

### `Dependencies`

| Dependency | Old version | New version |
| ------------------- | ----------- | ----------- |
| `OpenMS` | 3.5.0 | 3.6.0 |
| `OpenMS-thirdparty` | 3.5.0 | 3.6.0 |
| `Percolator` | 3.7.1 | 3.9 |
Comment thread
jonasscheid marked this conversation as resolved.
Resolved
| `pyOpenMS` | 3.4.1 | 3.6.0 |

Comment thread
jonasscheid marked this conversation as resolved.
Resolved
## 3.3.0 - Favorite - 29/09/26

### `Added`
Expand Down
18 changes: 7 additions & 11 deletions bin/get_ion_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,7 @@ def parse_arguments() -> Tuple[argparse.ArgumentParser, argparse.Namespace]:
parser.add_argument(
"-rpp",
"--remove_precursor_peak",
type=bool,
default=False,
action="store_true",
help="Do not consider precursor masses in the theoretical spectrum generation",
)

Expand Down Expand Up @@ -143,12 +142,10 @@ def flatten(ls: list) -> list:

def __main__():
parser, args = parse_arguments()
protein_ids = []
peptide_ids = []
# Each peptide ID should only contain one hit in the FDR_filtered idXML
IdXMLFile().load(args.filtered_idXML, protein_ids, peptide_ids)
protein_ids, peptide_ids = IdXMLFile().load(args.filtered_idXML)
# Get the list of mzML files that have been merged together by IDmerger previously
filenames = [filename.decode("utf-8") for filename in protein_ids[0].getMetaValue("spectra_data")]
filenames = protein_ids[0].getMetaValue("spectra_data")
# Define empty lists that collect all the necessary information, which is comprised in DataFrames
ions = []
spectra_mz = []
Expand All @@ -172,7 +169,7 @@ def __main__():
MzMLFile().load(file, exp)
# Create lookup object for easy spectrum reference.
spectrum_lookup = SpectrumLookup()
spectrum_lookup.readSpectra(exp, "scan=(?<SCAN>\\d+)")
spectrum_lookup.readSpectra(exp)
# Iterate over the FDR filtered peptideIDs
for peptide_id in peptide_ids:
# Check if the PeptideHit originates from the current mzML file
Expand All @@ -184,19 +181,18 @@ def __main__():
spectrum_mz, spectrum_intensities = spectrum.get_peaks()
spectra_mz.append(spectrum_mz)
spectra_intensities.append(spectrum_intensities)
sequence = peptide_id.getHits()[0].getSequence()
sequence = peptide_id.getHits()[0].getSequence().toString()
spectra_peptides.append(np.repeat(sequence, len(spectrum_mz)))
is_matching_ion_peptide = np.repeat(False, len(spectrum_mz))
spectra_nativeIDs.append(np.repeat(peptide_id.getMetaValue("spectrum_reference"), len(spectrum_mz)))
# Generate theoretical spectrum of peptide hit
theo_spectrum = generate_theoretical_spectrum(peptide_id, args)
# Align both spectra
alignment = []
spa.getSpectrumAlignment(alignment, theo_spectrum, spectrum)
alignment = spa.getSpectrumAlignment(theo_spectrum, spectrum)
# Obtain ion annotations from theoretical spectrum if there are at least two matching ions
if len(alignment) > 1:
for theo_idx, obs_idx in alignment:
ion_name = theo_spectrum.getStringDataArrays()[0][theo_idx].decode()
ion_name = theo_spectrum.getStringDataArrays()[0][theo_idx]
ion_charge = theo_spectrum.getIntegerDataArrays()[0][theo_idx]
obs_mz = spectrum[obs_idx].getMZ()
obs_int = spectrum[obs_idx].getIntensity()
Expand Down
34 changes: 20 additions & 14 deletions bin/summarize_results.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import json
import pandas as pd
import numpy as np
from pyopenms import AASequence, TransformationXMLFile, TransformationDescription
from pyopenms import AASequence, ControlledVocabulary, File, TransformationXMLFile
from argparse import ArgumentParser
import re
from collections import Counter
Expand Down Expand Up @@ -94,15 +94,6 @@ def parse_multiTSV(file_path):
elif line.startswith("#UNASSIGNEDPEPTIDE"):
unassigned_peptide_cols = line.strip().split('\t')[1:]

# Workaround for OpenMS 3.5.0 TextExporter bug (https://github.com/OpenMS/OpenMS/issues/9120):
# consensusXML export writes a phantom column in data rows between 'end' and 'FFId_category'
# that is missing from the header. Remove it to realign columns.
for rows, cols in [(peptide_rows, peptide_cols), (unassigned_peptide_rows, unassigned_peptide_cols)]:
if rows and len(rows[0]) > len(cols) and 'end' in cols:
extra_idx = cols.index('end') + 1
for i, row in enumerate(rows):
rows[i] = row[:extra_idx] + row[extra_idx + 1:]

peptide_df = pd.DataFrame(peptide_rows, columns=peptide_cols)
consensus_df = pd.DataFrame(consensus_rows, columns=consensus_cols)
# Concatenate CONSENSUS and PEPTIDE columns
Expand Down Expand Up @@ -159,6 +150,18 @@ def write_box_stats(values, sample, plot_id, path):
json.dump({"id": plot_id, "data": {sample: stats}}, f, indent=4)


def decode_cv_columns(data):
"""Rename PSI-MS accession columns (e.g. MS:1002252) to their term names (e.g. Comet:xcorr)"""
cv = ControlledVocabulary()
cv.loadFromOBO("PSI-MS", File.find("CV/psi-ms.obo", []))
data.rename(columns={col: cv.getTerm(col).name for col in data.columns if cv.exists(col)}, inplace=True)


def format_usi(data):
"""Use the original run name (without .mzML) in USIs"""
data["USI"] = data["USI"].fillna("").astype(str).str.replace(r"\.mzML(?=:(?:scan|index|nativeId):)", "", regex=True, flags=re.IGNORECASE)


def process_file(file, prefix, quantify, keep_cols):
"""Extract all the relevant information and write it to a TSV file for the MultiQC report

Expand All @@ -177,8 +180,12 @@ def process_file(file, prefix, quantify, keep_cols):
data.rename(columns={data.columns[0]: data.columns[0].replace('#', '')}, inplace=True)
n_psms = 0

decode_cv_columns(data)
if "USI" in data.columns:
format_usi(data)

# Check if all required columns are present in the DataFrame
required_columns = ['sequence', 'accessions', 'mz', 'rt', 'score', 'COMET:xcorr']
required_columns = ['sequence', 'accessions', 'mz', 'rt', 'score', 'Comet:xcorr']
missing_columns = set(required_columns) - set(data.columns)
if data.shape[0] > 0: # If the DataFrame is not empty
if missing_columns:
Expand Down Expand Up @@ -247,7 +254,7 @@ def process_file(file, prefix, quantify, keep_cols):
# ---------------------------------
# Box plots
# ---------------------------------
write_box_stats(data["COMET:xcorr"], prefix, "scores_plot_xcorr", f"{prefix}_xcorr_scores.json")
write_box_stats(data["Comet:xcorr"], prefix, "scores_plot_xcorr", f"{prefix}_xcorr_scores.json")
if 'intensity_cf' in data.columns:
with np.errstate(divide='ignore'):
log_intensity = np.log2(data["intensity_cf"].astype(float))
Expand Down Expand Up @@ -299,8 +306,7 @@ def write_aligned_residuals(trafoxml_paths):
(max reference RT) so boxes are comparable across runs and LC setups.
"""
for path in trafoxml_paths:
td = TransformationDescription()
TransformationXMLFile().load(path, td, True)
td = TransformationXMLFile().load(path, True)
points = td.getDataPoints()
gradient = max((pt.second for pt in points), default=0)
if gradient <= 0:
Expand Down
11 changes: 7 additions & 4 deletions conf/modules.config
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ process {
process {
withName: 'NFCORE_MHCQUANT:MHCQUANT:QUANT:OPENMS_IDSCORESWITCHER' {
ext.args = [
"-new_score COMET:xcorr",
"-new_score MS:1002252",
"-new_score_orientation higher_better",
"-old_score q-value"
].join(' ').trim()
Expand Down Expand Up @@ -469,7 +469,7 @@ process {

withName: 'OPENMS_TEXTEXPORTER' {
ext.prefix = { "${meta.id}_exported" }
ext.args = '-id:peptides_only -id:add_hit_metavalues 0 -id:add_metavalues 0'
ext.args = { "-id:peptides_only -id:add_hit_metavalues 0 -id:add_metavalues 0 -id:add_usi -id:usi_dataset_id '${params.usi_dataset_id}'" }
publishDir = [
enabled: false
]
Expand All @@ -481,6 +481,7 @@ process {
[
"peptidoform",
"sequence",
"USI",
"score", "score_type",
"psm",
"rt",
Expand All @@ -489,7 +490,7 @@ process {
"accessions",
"aa_before", "aa_after",
"start", "end",
"COMET:deltaCn", "COMET:deltaLCn", "COMET:lnExpect", "COMET:xcorr",
"COMET:deltaCn", "COMET:deltaLCn", "COMET:lnExpect", "Comet:xcorr",
"rt_diff_best",
"observed_retention_time_best", "predicted_retention_time_best",
"spec_pearson",
Expand Down Expand Up @@ -527,6 +528,8 @@ process {

withName: 'OPENMS_TEXTEXPORTER_GLOBAL' {
label = 'process_high_memory'
// The global TSV is not post-processed by SUMMARIZE_RESULTS, which corrects the OpenMS USIs
ext.args = '-id:peptides_only -id:add_hit_metavalues 0 -id:add_metavalues 0'
Comment thread
jonasscheid marked this conversation as resolved.
Resolved
publishDir = [
path: {"${params.outdir}/global_fdr"},
mode: params.publish_dir_mode,
Expand All @@ -550,7 +553,7 @@ process {
ext.args = { [
"--precursor_charge ${meta.prec_charge}",
"--fragment_mass_tolerance ${meta.fragment_mass_tolerance}",
"--remove_precursor_peak ${params.remove_precursor_peak}"
params.remove_precursor_peak ? "--remove_precursor_peak" : ""
].join(' ').trim() }
publishDir = [
path: {"${params.outdir}/intermediate_results/ion_annotations"},
Expand Down
Loading
Loading