Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
9f4d3e7
[Fix] Fix typo
lucas-diedrich Aug 14, 2025
521fa13
[Doc] Update docstring, add more explicit parameter explanations
lucas-diedrich Aug 14, 2025
3fd9d98
[Doc] Update docstrings and remove incorrect copy documentation
lucas-diedrich Aug 14, 2025
7717cd2
[Doc] Clarify docstring
lucas-diedrich Aug 14, 2025
31e46e3
[Feature] Spectronaut reader
lucas-diedrich Aug 11, 2025
5aaf207
[Feature] Spectronaut reader config
lucas-diedrich Aug 11, 2025
3cfc4ca
[API] Expose spectronaut reader to users
lucas-diedrich Aug 11, 2025
f28d73e
[Test] Spectronaut integration tests
lucas-diedrich Aug 11, 2025
4573149
[Doc] Add examples
lucas-diedrich Aug 11, 2025
c287cd2
[Fix] Rename pre-configured regex to default
lucas-diedrich Aug 11, 2025
4940a73
[Fix] Fix typos
lucas-diedrich Aug 11, 2025
b5c7a9b
[Feature] Add parquet support. Specifically desirable for spectronaut…
lucas-diedrich Aug 12, 2025
41eef70
[Test] Add integration test for spectronaut.parquet report
lucas-diedrich Aug 12, 2025
207e386
[Doc] Add regular expression documentation
lucas-diedrich Aug 14, 2025
ef71f1a
[Doc] Improve docstring of class initialization
lucas-diedrich Aug 23, 2025
a2427d7
[Feature] Add additional column mapping for parquet report protein gr…
lucas-diedrich Aug 23, 2025
0e13e68
[Test] Refactor tests so that they work with local data
lucas-diedrich Aug 23, 2025
5e9c1c0
[Test-Data] Add test data reference for spectronaut reports
lucas-diedrich Aug 23, 2025
85e05cb
[Feature] Implement fragpipe reader
lucas-diedrich Aug 11, 2025
4a46205
[API] Expose FragPipeReader to users
lucas-diedrich Aug 11, 2025
808547b
[Tests] Add integration test for FragPipeReader
lucas-diedrich Aug 11, 2025
38b995d
[Fix] Fix typo in URL
lucas-diedrich Aug 11, 2025
deccde6
[Test] Update tests to work with local data (FragPipe PG reader)
lucas-diedrich Aug 23, 2025
6cbe7d9
[Test-data] Add test reference data (PG fragpipe)
lucas-diedrich Aug 23, 2025
cc9b861
[Feature] Add MZTab protein group reader
lucas-diedrich Aug 11, 2025
6699fac
[API] Expose MZTab to users
lucas-diedrich Aug 11, 2025
8e0e8c5
[Tests] Add integration tests
lucas-diedrich Aug 11, 2025
2a2848e
[Refactor] Set correct default values
lucas-diedrich Aug 11, 2025
00c72f0
[Tests] Add integration tests
lucas-diedrich Aug 11, 2025
86eeb7a
[Test] Refactor mztab integration test so that it runs with local data
lucas-diedrich Aug 23, 2025
15545aa
[Test-data] Add local test data (mzTAB PG Reader)
lucas-diedrich Aug 23, 2025
0343aeb
[Doc] Add PG-reader API docs
lucas-diedrich Aug 11, 2025
75b5ab1
Merge pull request #334 from MannLabs/pg-reader-9-docs-I
lucas-diedrich Aug 26, 2025
639c81e
Merge pull request #333 from MannLabs/pg-reader-8-mztab
lucas-diedrich Aug 26, 2025
778dac6
Merge pull request #332 from MannLabs/pg-reader-7-msfragger
lucas-diedrich Aug 26, 2025
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
46 changes: 46 additions & 0 deletions alphabase/constants/const_files/pg_reader.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,49 @@ maxquant:
"raw": "^Intensity(?!\\s[LHM]\\s).+$" # Raw intensity-based quantification
"lfq": "^LFQ intensity(?!\\s[LHM]\\s).+$" # LFQ intensity-based quantification
"ibaq": "^iBAQ(?!\\s[LHM]\\s).+$" # Intensity-Based Absolute Quantification


# Based on Spectronaut 20.0 Run Pivot Report (Page 80/81)
# https://biognosys.com/content/uploads/2025/06/Spectronaut-20-Manual.pdf
Comment thread
lucas-diedrich marked this conversation as resolved.
spectronaut:
reader_type: "spectronaut"
column_mapping:
"proteins": ["PG.ProteinNames", "PG_ProteinGroups"]
"genes": "PG.Genes"
"uniprot_ids": "PG.UniProtIds"
measurement_regex:
# Spectronaut marks sample columns with "[number]..."
# At the beginning (^) of the string, match open square bracket "\\[", any number of digits "[0-9]+", closed square bracket "\\]"
# Square brackets are special characters -> need to be escaped with "\[". The backslash itself needs to be escaped -> "\\["
"default": "^\\[[0-9]+\\]"


# https://fragpipe.nesvilab.org/docs/tutorial_fragpipe_outputs.html#proteintsv
fragpipe:
reader_type: "fragpipe"
column_mapping:
"proteins": "Entry Name"
"uniprot_ids": "Protein ID"
"genes": "Gene Names"
"description": "Description"
measurement_regex:
"raw": "Intensity$"
"razor": "Razor Intensity$"
"unique": "Unique Intensity$"
"total": "Total Intensity$"
"lfq": "MaxLFQ Intensity$"
"lfq_unique": "MaxLFQ Unique Intensity$"
"lfq_total": "MaxLFQ Total Intensity$"


# mzTab
# version 2.0.0 (2019-03)
mztab:
reader_type: "mztab"
column_mapping:
"uniprot_ids": "accession"
"description": "description"
"source_db": "database"
measurement_regex:
"assay": "^protein_abundance_assay\\[[0-9]+\\]" # The protein's abundance as measured in the given assay through whatever technique was employed
"study_variable": "^protein_abundance_study_variable\\[[0-9]+\\]" # The protein's abundance as measured in the given study variable (condition) through whatever technique was employed
6 changes: 6 additions & 0 deletions alphabase/pg_reader/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
from .alphadia_pg_reader import AlphaDiaPGReader
from .alphapept_pg_reader import AlphaPeptPGReader
from .diann_pg_reader import DiannPGReader
from .fragpipe_pg_reader import FragPipePGReader
from .maxquant_pg_reader import MaxQuantPGReader
from .mztab_pg_reader import MZTabPGReader
from .pg_reader import pg_reader_provider
from .spectronaut_reader import SpectronautPGReader

__all__ = [
"pg_reader_provider",
"AlphaDiaPGReader",
"DiannPGReader",
"AlphaPeptPGReader",
"MaxQuantPGReader",
"SpectronautPGReader",
"FragPipePGReader",
"MZTabPGReader",
]
2 changes: 1 addition & 1 deletion alphabase/pg_reader/alphapept_pg_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def __init__(
Parameters
----------
column_mapping
Dictionary mapping alphabase column names (keys) to AlphaPep column names (values).
Dictionary mapping alphabase column names (keys) to AlphaPept column names (values).
If `None`, uses default mapping from configuration file.
measurement_regex
Pattern to select quantity columns
Expand Down
47 changes: 47 additions & 0 deletions alphabase/pg_reader/fragpipe_pg_reader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""FragPipe protein group reader."""

from typing import Literal, Optional, Union

from .pg_reader import PGReaderBase, pg_reader_provider


class FragPipePGReader(PGReaderBase):
"""Reader for `protein.tsv` reports from FragPipe.

Example:
-------
Per default, the reader will return the raw intensities from the `razor` method. Additional protein features are stored
in the dataframe index, samples are stored as columns.

.. code-block:: python

# Get raw intensities
reader = FragPipePGReader()
results = reader.import_file(download_path)


References:
----------
- FragPipe Documentation https://fragpipe.nesvilab.org/docs/tutorial_fragpipe_outputs.html#proteintsv

"""

_reader_type: str = "fragpipe"

def __init__( # noqa: D107 inherited from base class
self,
*,
column_mapping: Optional[dict[str, str]] = None,
measurement_regex: Union[
Literal[
"raw", "razor", "unique", "total", "lfq", "lfq_unique", "lfq_total"
],
None,
] = "razor",
):
super().__init__(
column_mapping=column_mapping, measurement_regex=measurement_regex
)


pg_reader_provider.register_reader("fragpipe", reader_class=FragPipePGReader)
41 changes: 21 additions & 20 deletions alphabase/pg_reader/maxquant_pg_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,7 @@ class MaxQuantPGReader(PGReaderBase):
results.columns
> Index([...], dtype='object', length=312)


To read the LFQ values, pass the pre-configured key `lfq` to the reader, which represents a regular expression
that automatically extracts the `LFQ` columns from the protein group table.

.. code-block:: python

# Get raw intensities
reader = MaxQuantPGReader(measurement_regex="lfq")
results = reader.import_file(download_path)

You can get other intensity types by passing a specific pattern to the `measurment_regex` parameter during class initialization.
To checkout all preconfigured regular expressions that enable you to retrieve different intensity modalities,
use the `get_preconfigured_regex` method:

Expand All @@ -76,14 +67,6 @@ class MaxQuantPGReader(PGReaderBase):
reader = MaxQuantPGReader(measurement_regex="^Intensity H .+")


If desired, remove the test data

.. code-block:: python

# Clean up
os.rmdir(download_dir)


References
----------
- MaxQuant Documentation (Cox Lab, 2024-06-27): https://cox-labs.github.io/coxdocs/output_tables.html#protein-groups,
Expand All @@ -93,12 +76,30 @@ class MaxQuantPGReader(PGReaderBase):

_reader_type = "maxquant"

def __init__( # noqa: D107 inherited from base class
def __init__(
self,
*,
column_mapping: Optional[dict[str, str]] = None,
measurement_regex: Union[str, Literal["raw", "lfq", "ibaq"], None] = "raw", # noqa: PYI051 raw and lfq are special casees and not equivalent to string
measurement_regex: Union[str, Literal["raw", "lfq", "ibaq"], None] = "raw", # noqa: PYI051 raw and lfq are special cases and not equivalent to string
):
"""Initialize MaxQuant protein group matrix reader.

Parameters
----------
column_mapping
Dictionary mapping alphabase column names (keys) to MaxQuant column names (values).
If `None`, uses default mapping from configuration file.
measurement_regex
Pattern to select quantity columns

- "raw" (default): Raw intensities
- "lfq": LFQ-corrected intensities
- "ibaq": Intensity-Based Absolute Quantification-corrected intensities
- custom: Any valid regular expression

See class documentation for usage examples and `get_preconfigured_regex()` for available patterns.

"""
super().__init__(
column_mapping=column_mapping, measurement_regex=measurement_regex
)
Expand Down
116 changes: 116 additions & 0 deletions alphabase/pg_reader/mztab_pg_reader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
"""FragPipe protein group reader."""

from pathlib import Path
from typing import Literal, Optional, Union

import pandas as pd

from .pg_reader import PGReaderBase, pg_reader_provider


class MZTabPGReader(PGReaderBase):
"""Reader for MZTab search engine output.

MZTab is a standardized tab-delimited format for reporting proteomics and metabolomics results.
The format organizes data into distinct sections: metadata (MTD), protein groups (PRH/PRT),
peptides (PEH/PEP), PSMs (PSH/PSM), and small molecules (SMH/SML), with each section identified
by specific three-letter prefixes. This reader extracts protein-level quantification data from
the PRT lines, which contain protein abundances across samples or study variables.

Example:
-------
Per default, the reader will return the raw intensities from the `razor` method. Additional protein features are stored
in the dataframe index, samples are stored as columns.

.. code-block:: python

from alphabase.pg_reader import MZTabPGReader

# Get raw intensities
reader = MZTabPGReader()
results = reader.import_file(path)


References:
----------
- Griss, J. et al. The mzTab Data Exchange Format: Communicating Mass-spectrometry-based Proteomics and Metabolomics Experimental Results to a Wider Audience*. Molecular & Cellular Proteomics 13, 2765-2775 (2014).
- Official MZTab Repository: https://github.com/HUPO-PSI/mzTab.git
- Official documentation: https://hupo-psi.github.io/mzTab/

"""

_reader_type: str = "mztab"

_PROTEIN_ROW_INDICATOR: str = "PRT"
_PROTEIN_HEADER_INDICATOR: str = "PRH"
_SEPARATOR: str = "\t"

def __init__( # noqa: D107 inherited from base class
self,
*,
column_mapping: Optional[dict[str, str]] = None,
measurement_regex: Union[
str, Literal["assay", "study_variable"], None # noqa: PYI051 raw and lfq are special cases and not equivalent to string
] = "assay",
):
super().__init__(
column_mapping=column_mapping, measurement_regex=measurement_regex
)

def _load_file(self, file_path: str) -> pd.DataFrame:
"""Load MZTab file and extract protein data section.

Parameters
----------
file_path : str
Path to MZTab file

Returns
-------
pd.DataFrame
DataFrame containing protein data from MZTab file

Notes
-----
Protein lines are indicated with a leading `PRT`. The protein metadata header is
indicated with a leading `PRH`. The file is tab separated.

Raises
------
ValueError
If no protein data or metadata is found in the file

"""
file_path = Path(file_path)
protein_header = None
protein_rows = []

with file_path.open() as f:
for line in f:
line_stripped = line.strip()

if line_stripped.startswith(self._PROTEIN_HEADER_INDICATOR):
# Protein header line - remove 'PRH' prefix and parse columns
header_content = line_stripped[3:].strip()
protein_header = header_content.split(self._SEPARATOR)

elif line_stripped.startswith(self._PROTEIN_ROW_INDICATOR):
# Protein data line - remove 'PRT' prefix and parse data
row_content = line_stripped[3:].strip()
protein_rows.append(row_content.split(self._SEPARATOR))

# Validate that we found protein data
if protein_header is None:
raise ValueError(
f"No protein header ({self._PROTEIN_HEADER_INDICATOR}) found in MZTab file"
)

if not protein_rows:
raise ValueError(
f"No protein data rows ({self._PROTEIN_ROW_INDICATOR}) found in MZTab file"
)

return pd.DataFrame(protein_rows, columns=protein_header)


pg_reader_provider.register_reader("mztab", reader_class=MZTabPGReader)
12 changes: 7 additions & 5 deletions alphabase/pg_reader/pg_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,18 +168,20 @@ def _load_file(self, file_path: str) -> pd.DataFrame:
"""
if Path(file_path).suffix == ".hdf":
return pd.read_hdf(file_path)
if Path(file_path).suffix == ".parquet":
return pd.read_parquet(file_path)

sep = _get_delimiter(file_path)
return pd.read_csv(file_path, sep=sep, keep_default_na=False)

def _pre_process(self, df: pd.DataFrame) -> pd.DataFrame:
"""Preprocess dataframe before standardizing columns and return an updated copy."""
"""Preprocess dataframe before standardizing columns."""
return df

def _translate_columns(
self, df: pd.DataFrame, column_mapping: dict[str, str]
) -> pd.DataFrame:
"""Translate standardized columns in dataframe from other search engines to AlphaBase format and return an updated copy."""
"""Translate standardized columns in dataframe from other search engines to AlphaBase format."""
return df.rename(columns=column_mapping)

def _filter_measurement(
Expand All @@ -188,7 +190,7 @@ def _filter_measurement(
regex: str,
extra_columns: Optional[Iterable[str]] = None,
) -> pd.DataFrame:
"""Subset :class:`pd.DataFrame` to columns matching a regex plus optionally extra columns and return an updated copy.
"""Subset :class:`pd.DataFrame` to columns matching a regex plus optionally extra columns.

Parameters
----------
Expand Down Expand Up @@ -217,13 +219,13 @@ def _filter_measurement(
return df[regex_columns + extra_columns]

def _post_process(self, df: pd.DataFrame) -> pd.DataFrame:
"""Process dataframe after standardizing columns and return an updated copy."""
"""Process dataframe after standardizing columns."""
return df

def _get_measurement_regex(self, regex: Optional[str]) -> Union[str, None]:
"""Get the correct named measurement regex from the reader configuration.

The function tries to match the provided `regex` to the keys in `measurement_regex`. This
The function tries to match the provided `regex` to the keys in `measurement_regex` in the reader configuration. This
enables users to provide tangible names for the columns they want instead of abstract regular expressions.
If a match is found, it returns the associated value (the actual regex).
If this not possible, the function assumes that a regular expression was passed and
Expand Down
Loading
Loading