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
4 changes: 2 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ jobs:
enable-cache: true

- name: Install package and dependencies
run: uv sync --group installer
run: uv sync --group installer --locked

- name: Install Inno Setup
uses: crazy-max/ghaction-chocolatey@v3
uses: crazy-max/ghaction-chocolatey@v4
with:
args: install innosetup -y --allow-unofficial

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,10 @@ jobs:
enable-cache: true

- name: Install package and dependencies
run: uv sync --group installer
run: uv sync --group installer --locked

- name: Install Inno Setup
uses: crazy-max/ghaction-chocolatey@v1
uses: crazy-max/ghaction-chocolatey@v4
with:
args: install innosetup -y --allow-unofficial

Expand Down
517 changes: 380 additions & 137 deletions CHANGELOG.md

Large diffs are not rendered by default.

10 changes: 9 additions & 1 deletion CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,19 @@ How to contribute
- Fork `ms2rescore <https://github.com/compomics/ms2rescore>`_ on GitHub to
make your changes.
- Commit and push your changes to your
`fork <https://help.github.com/articles/pushing-to-a-remote/>`_.
`fork <https://help.github.com/articles/pushing-to-a-remote/>`_. Commit messages
should follow the `Conventional Commits <https://www.conventionalcommits.org/>`_
format (e.g. ``fix: ...``, ``feat: ...``, ``docs: ...``).
- Ensure that the tests and documentation (both Python docstrings and files in
``/docs/source/``) have been updated according to your changes. Python
docstrings are formatted in the
`numpydoc style <https://numpydoc.readthedocs.io/en/latest/format.html>`_.
- Update ``CHANGELOG.md`` as part of your pull request: add an entry describing your
change under the ``[Unreleased]`` section, following
`Keep a Changelog <https://keepachangelog.com/en/1.0.0/>`_.
- Code should be formatted with `ruff <https://docs.astral.sh/ruff/>`_, using the
configuration in ``pyproject.toml``. Run ``ruff format .`` and ``ruff check .``
before submitting your pull request.
- Open a
`pull request <https://help.github.com/articles/creating-a-pull-request/>`_
with these changes. You pull request message ideally should include:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ files:
- [MS Amanda](http://ms.imp.ac.at/?goto=msamanda) `.csv`
- [Sage](https://github.com/lazear/sage) `.sage.tsv`
- [PeptideShaker](https://compomics.github.io/projects/peptide-shaker.html) `.mzid`
- [ProteomeDiscoverer](#)`.msf`
- ProteomeDiscoverer `.msf`
- [MSGFPlus](https://omics.pnl.gov/software/ms-gf) `.mzid`
- [Mascot](https://www.matrixscience.com/) `.mzid`
- [MaxQuant](https://www.maxquant.org/) `msms.txt`
Expand Down
10 changes: 8 additions & 2 deletions docs/source/config_schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,21 @@
- <a id="definitions/basic"></a>**`basic`** *(object)*: Basic feature generator configuration. Can contain additional properties. Refer to *[#/definitions/feature_generator](#definitions/feature_generator)*.
- <a id="definitions/ms2pip"></a>**`ms2pip`** *(object)*: MS²PIP feature generator configuration. Can contain additional properties. Refer to *[#/definitions/feature_generator](#definitions/feature_generator)*.
- **`model`** *(string)*: MS²PIP model to use (see MS²PIP documentation). Default: `"HCD"`.
- **`ms2_tolerance`** *(number)*: MS2 error tolerance in Da. Minimum: `0`. Default: `0.02`.
- **`model_dir`**: Directory containing MS²PIP models. Default: `null`.
- **One of**
- *string*
- *null*
- <a id="definitions/deeplc"></a>**`deeplc`** *(object)*: DeepLC feature generator configuration. Can contain additional properties. Refer to *[#/definitions/feature_generator](#definitions/feature_generator)*.
- **`calibration_set_size`**: Calibration set size. Default: `0.15`.
- **One of**
- *integer*
- *number*
- <a id="definitions/ms2"></a>**`ms2`** *(object)*: MS2 spectrum-based feature generator configuration. Can contain additional properties. Refer to *[#/definitions/feature_generator](#definitions/feature_generator)*.
- <a id="definitions/im2deep"></a>**`im2deep`** *(object)*: Ion mobility feature generator configuration using IM2Deep. Can contain additional properties. Refer to *[#/definitions/feature_generator](#definitions/feature_generator)*.
- **`reference_dataset`** *(string)*: Path to IM2Deep reference dataset file. Default: `"Meier_unimod.parquet"`.
- **`reference_dataset`**: Path to IM2Deep reference dataset file. Defaults to IM2Deep's own bundled reference dataset if not set. Default: `null`.
- **One of**
- *string*
- *null*
- <a id="definitions/mumble"></a>**`mumble`** *(object)*: Mumble PSM generator configuration. Mumble proposes candidate Unimod modifications that explain each PSM's precursor mass shift (open-modification-search style). Requires the optional `mumble` dependency (`pip install ms2rescore[mumble]`). Mumble is still under active development (beta): review results and expect occasional errors, especially on unusual input. Can contain additional properties. Refer to *[#/definitions/psm_generator](#definitions/psm_generator)*.
- **`aa_combinations`** *(integer)*: Number of amino acid combinations to consider as an additional mass-shift explanation, on top of Unimod modifications. Requires `fasta_file`. Increases runtime combinatorially; keep low. Default: `0`.
- **`combination_length`** *(integer)*: Maximum number of modifications to combine per mass shift. Lower combination lengths are always included as well. Default: `1`.
Expand Down
178 changes: 178 additions & 0 deletions docs/source/userguide/migrating-to-4.0.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
.. _migrating-to-4.0:

Migrating to 4.0
=================

MS²Rescore 4.0 is a major release: the rescoring engine, feature generation, and reporting
were all substantially reworked. This guide covers what changed from a user's perspective and
what to update in existing configuration files, scripts, and pipelines when upgrading from
3.2.x.

.. admonition:: Summary
:class: note

Rescoring now runs on **ristretto** instead of mokapot or Percolator -- both of those
engines are gone, along with their configuration options. Feature generation is faster
(Rust-based MS2/MS2PIP calculation, centralized spectrum parsing) and DeepLC/IM2Deep were
upgraded to their latest major versions. The main PSM output file and rescoring result
tables were renamed. See below for the concrete changes to make.

Computational performance
--------------------------

Internal benchmarking across several representative datasets shows that 4.0 uses substantially
less CPU time than 3.2.x, mainly thanks to the reworked feature generation step and the faster
rescoring engine. Peak memory usage is higher in 4.0, and scales with the size of the raw spectrum
files. Spectra are now read once into memory instead of separately per worker process, so this is
worth taking into account on memory-constrained machines or with large acquisitions. Identification
rates are unchanged between versions.

Rescoring engine: mokapot/Percolator → ristretto
-------------------------------------------------

MS²Rescore no longer supports mokapot or a standalone Percolator installation as rescoring
engines. Both are removed. Rescoring now always runs through **ristretto**, a lean,
dependency-light reimplementation of the same underlying semi-supervised algorithm.

If your configuration has a ``rescoring_engine`` section, replace it with ``rescoring``:

.. tab:: Before (3.2.x)

.. code-block:: json

{
"ms2rescore": {
"rescoring_engine": {
"mokapot": {
"train_fdr": 0.01,
"fasta_file": "proteins.fasta",
"write_weights": true,
"write_txt": true
}
}
}
}

.. tab:: After (4.0.0)

.. code-block:: json

{
"ms2rescore": {
"rescoring": {
"train_fdr": 0.01,
"model": "svm"
}
}
}

Notes on that change:

- ``model`` chooses between ``"svm"`` (default, iterative Percolator-style SVM) and ``"lda"``
(a faster but less powerful single-pass Fisher LDA).
- The top-level ``fasta_file`` option and FASTA-based protein inference are removed entirely --
there is no direct replacement. Protein-level rollups still work; they use picked-protein
competition when ``id_decoy_pattern`` is set. (This does not affect Mumble's own, unrelated
``psm_generator.mumble.fasta_file`` option, which is still used to validate amino-acid-
combination candidates.)
- ``write_weights``/``write_txt`` are gone: rescoring result tables (PSM, peptidoform, peptide,
protein, and feature-weight tables) are now **always** written as plain TSV. There is no
config option to disable this.
- Rescoring can no longer be skipped. If you previously set ``rescoring_engine`` to an empty
object (or ``rescoring: null``) to only write engineered features, that path is gone --
rescoring always runs.
- If you had a locally installed ``percolator`` binary configured via the old Percolator CLI
integration, that integration no longer exists. Switch to the default ``rescoring`` config
above.

Score direction is now automatic
---------------------------------

The ``lower_score_is_better`` option is removed. MS²Rescore now automatically infers whether a
higher or lower search-engine score is better, by running a small target-decoy competition
under both hypotheses and picking whichever identifies more PSMs at the configured
``rescoring.train_fdr``. This requires no configuration and handles multi-file input correctly
(spectrum IDs are grouped by run so colliding scan numbers across files don't affect the
result).

If your search engine's score direction is genuinely ambiguous (very few decoys, heavily
quantized scores), double-check the inferred direction in the log output at ``debug`` level.

New: ``report_fdr``
--------------------

The FDR threshold used for console-logged identification counts, the HTML report's charts, and
FlashLFQ output filtering was previously hardcoded to 1%. It's now a configurable top-level
option:

.. code-block:: json

{
"ms2rescore": {
"report_fdr": 0.01
}
}

This is independent of ``rescoring.train_fdr``, which only controls ristretto's training.

Output file names changed
--------------------------

+-------------------------------------+-----------------------------------------------------------------------------------------------------------------------+
| 3.2.x | 4.0.0 |
+=====================================+=======================================================================================================================+
| ``<prefix>.psms.tsv`` (main output) | ``<prefix>.tsv`` |
+-------------------------------------+-----------------------------------------------------------------------------------------------------------------------+
| ``<prefix>.intermediate.psms.tsv`` | ``<prefix>.intermediate.tsv`` |
+-------------------------------------+-----------------------------------------------------------------------------------------------------------------------+
| *(mokapot weights/txt output)* | ``<prefix>.psms.tsv``, ``.peptidoforms.tsv``, ``.peptides.tsv``, ``.proteins.tsv``, ``.weights.tsv`` (always written) |
+-------------------------------------+-----------------------------------------------------------------------------------------------------------------------+
| ``<prefix>.psms.pin`` | removed (no PIN output) |
+-------------------------------------+-----------------------------------------------------------------------------------------------------------------------+

If you have downstream scripts or pipelines that read ``<prefix>.psms.tsv`` expecting the main
PSM list, update them to read ``<prefix>.tsv`` instead -- ``<prefix>.psms.tsv`` is now the
PSM-level *rescoring result table* (a leaner view: identifiers and score/q-value/PEP only, no
rescoring features or provenance data).

Feature generators: MaxQuant and ionmob removed
------------------------------------------------

- The MaxQuant feature generator is removed; its functionality is folded into the MS2 feature
generator, which now works directly from parsed spectra regardless of search engine.
- The ionmob feature generator is removed, replaced by the upgraded IM2Deep v2 integration.
- ``MS2PIPFeatureGenerator`` no longer accepts ``ms2_tolerance``, ``spectrum_path``, or
``spectrum_id_pattern`` -- fragment mass tolerance and spectrum access are now centralized at
the top level via ``tolerance_value``, ``tolerance_mode``, and ``spectrum_path``/
``spectrum_id_pattern`` on the main configuration.
- ``MS2FeatureGenerator`` no longer accepts ``spectrum_path``, ``spectrum_id_pattern``,
``mass_mode``, or ``processes`` for the same reason.

If you configured either of these generators directly, remove those per-generator parameters
and set ``tolerance_value``/``tolerance_mode``/``fragmentation_model`` at the top level instead.

Python API
----------

``ms2rescore.utils`` (previously public) is renamed and split into two internal modules,
``ms2rescore._utils`` and ``ms2rescore._ristretto_utils``, neither of which is part of the
public API -- if any code imports from ``ms2rescore.utils`` directly, that will break. The
documented, public entry points remain ``ms2rescore.rescore()`` for the full pipeline and
``ms2rescore.rescoring.rescore()`` for calling ristretto directly on a feature DataFrame; see the
:doc:`Python API tutorial </tutorials/in-depth-python-api>` for the updated, step-by-step
version.

MS²Rescore 4.0.0 requires **Python 3.11 or newer**.

New, optional
-------------

Nothing below requires any migration, but may be worth adopting:

- **Mumble** (beta): an optional PSM generator that proposes alternative peptide identifications
for a PSM's precursor mass shift. Install with ``pip install ms2rescore[mumble]`` and see the
:doc:`Mumble guide </userguide/mumble>`.
- **GUI**: runs now also produce an HTML log file (``<prefix>.log.html``), matching the CLI.
- **Standalone report regeneration**: ``ms2rescore-report`` can rebuild the HTML report from just
the main PSM TSV file, with an optional ``--fdr`` override, without needing the original config
or log files.
4 changes: 2 additions & 2 deletions examples/sage-ms2rescore.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ spectrum_path = "examples/data/spectra/qExactive01819.mzML"

[ms2rescore.feature_generators]
"basic" = {}
"ms2pip" = { "model" = "HCD", "ms2_tolerance" = 0.02 }
"ms2pip" = { "model" = "HCD" }
"ms2" = {}
"deeplc" = { "deeplc_retrain" = false }
"deeplc" = {}
83 changes: 48 additions & 35 deletions ms2rescore/feature_generators/deeplc.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import logging
import warnings
from typing import List, Union
from typing import List, Optional, Union

import numpy as np
from deeplc.calibration import SplineTransformerCalibration
Expand All @@ -42,18 +42,34 @@ class DeepLCFeatureGenerator(FeatureGeneratorBase):

required_ms_data = {MSDataType.retention_time}

# Flat kwargs forwarded to DeepLC's `predict()` and `finetune()`, keyed by which call(s) each
# applies to. `device` and `batch_size` apply to both. getfullargspec(predict).args does not
# work on this outer predict function, so the keys are hardcoded here instead of introspected.
_PREDICT_KWARGS = ("device", "batch_size")
_FINETUNE_KWARGS = (
"device",
"batch_size",
"epochs",
"learning_rate",
"patience",
"trainable_layers",
"validation_split",
)

def __init__(
self,
*args,
calibration_set_size: Union[int, float, None] = None,
processes: int = 1,
finetune: Optional[bool] = None,
**kwargs,
) -> None:
"""
Generate DeepLC-based features for rescoring.

DeepLC retraining is on by default. Add ``deeplc_retrain: False`` as a keyword argument to
disable retraining.
DeepLC finetuning is off by default. Set ``finetune=True`` to enable it. The deprecated
``deeplc_retrain`` keyword is still accepted and mapped onto ``finetune``, with a
deprecation warning; an explicitly passed ``finetune`` takes precedence over it.

Parameters
----------
Expand All @@ -63,6 +79,9 @@ def __init__(
than the number of available PSMs, all PSMs will be used. (default: 0.15)
processes: {int, None}
Number of processes to use in DeepLC. Defaults to 1.
finetune: bool
Whether to finetune DeepLC on high-confidence target PSMs before prediction.
Defaults to False.
kwargs: dict
Additional keyword arguments are passed to DeepLC.

Expand All @@ -77,41 +96,35 @@ def __init__(
self.calibration_set_size = calibration_set_size
self.deeplc_kwargs = kwargs or {}

self._verbose = logger.getEffectiveLevel() <= logging.DEBUG

self.model = self.deeplc_kwargs.get("model", None)

self.calibration = None

# Prepare DeepLC predict kwargs
self.predict_kwargs = {
k: v
for k, v in self.deeplc_kwargs.items()
if k in ["device", "batch_size", "num_threads"]
} # getfullargspec(predict).args does not work on this outer predict function
self.predict_kwargs["num_threads"] = processes if processes > 0 else None

# Prepare DeepLC finetune kwargs
if "deeplc_retrain" not in self.deeplc_kwargs:
self.deeplc_kwargs["deeplc_retrain"] = False
return # skip the rest of the init if no retraining

if self.deeplc_kwargs["deeplc_retrain"]:
self.finetune_kwargs = {
k: v
for k, v in self.deeplc_kwargs.items()
if k
in [
"epochs",
"device",
"batch_size",
"learning_rate",
"patience",
"trainable_layers",
"validation_split",
]
}
self.finetune_kwargs["num_threads"] = processes if processes > 0 else None
# `deeplc_retrain` was renamed to `finetune`; keep accepting the old kwarg for now.
if "deeplc_retrain" in self.deeplc_kwargs:
warnings.warn(
"The `deeplc_retrain` DeepLC option has been renamed to `finetune` and will be "
"removed in a future release.",
DeprecationWarning,
stacklevel=2,
)
deprecated_finetune = self.deeplc_kwargs.pop("deeplc_retrain")
if finetune is None:
finetune = deprecated_finetune
self.finetune = bool(finetune)

self.predict_kwargs = self._select_deeplc_kwargs(self._PREDICT_KWARGS, processes)

if not self.finetune:
return # skip the rest of the init if no finetuning

self.finetune_kwargs = self._select_deeplc_kwargs(self._FINETUNE_KWARGS, processes)

def _select_deeplc_kwargs(self, keys: tuple, processes: int) -> dict:
"""Pick `deeplc_kwargs` entries by name and inject the resolved `num_threads`."""
selected = {k: v for k, v in self.deeplc_kwargs.items() if k in keys}
selected["num_threads"] = processes if processes > 0 else None
return selected

@property
def feature_names(self) -> List[str]:
Expand Down Expand Up @@ -146,7 +159,7 @@ def add_features(self, psm_list: PSMList) -> None:
]
psm_list_df["sequence"] = psm_list_df["peptidoform"].apply(lambda x: x.modified_sequence)

if self.deeplc_kwargs["deeplc_retrain"]:
if self.finetune:
# Filter high-confidence target PSMs once for transfer learning
target_mask = (
(psm_list["qvalue"] <= 0.01) & (~psm_list["is_decoy"]) & original_hit_mask
Expand Down
Loading
Loading