Skip to content

Commit b690ada

Browse files
Merge pull request #104 from hgb-bin-proteomics/feat/top-down-convert
Feat/top down convert
2 parents 1320570 + f7c5049 commit b690ada

1 file changed

Lines changed: 122 additions & 0 deletions

File tree

convert/topdown_msf_to_msamanda.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
#!/usr/bin/env python3
2+
#
3+
# /// script
4+
# requires-python = ">=3.12"
5+
# dependencies = [
6+
# "pandas",
7+
# "psm-utils"
8+
# ]
9+
# ///
10+
11+
import sqlite3
12+
import argparse
13+
import pandas as pd
14+
from psm_utils.io import read_file
15+
16+
__version = "1.0.0"
17+
__data = "2026-03-22"
18+
19+
20+
def read_msf(msf_file: str) -> dict[str, pd.DataFrame]:
21+
conn = sqlite3.connect(msf_file)
22+
proteoforms = pd.read_sql_query(
23+
"SELECT * FROM TargetProteoformSpectrumMatchs", conn
24+
)
25+
modifications = pd.read_sql_query("SELECT * FROM FoundModifications", conn)
26+
conn.close()
27+
return {"proteoforms": proteoforms, "modifications": modifications}
28+
29+
30+
def to_msamanda(msf_file: str, verify: bool = True) -> int:
31+
msf = read_msf(msf_file)
32+
##
33+
mods = dict()
34+
for i, row in msf["modifications"].iterrows():
35+
name = str(row["Name"]).strip()
36+
abbreviation = str(row["Abbreviation"]).strip()
37+
mass = float(row["DeltaMonoisotopicMass"])
38+
if name not in mods:
39+
mods[name] = mass
40+
if abbreviation not in mods:
41+
mods[abbreviation] = mass
42+
##
43+
title = list()
44+
sequence = list()
45+
modifications = list()
46+
protein_accessions = list()
47+
score = list()
48+
mz = list()
49+
charge = list()
50+
rt = list()
51+
filename = list()
52+
id = list()
53+
for i, row in msf["proteoforms"].iterrows():
54+
title.append(
55+
f"controllerType=0 controllerNumber=1 scan={row['FragmentationScans']}"
56+
)
57+
sequence.append(row["ModifiedSequence"])
58+
modifications_str = ""
59+
if not pd.isna(row["Modifications"]):
60+
for mod in str(row["Modifications"]).split(";"):
61+
loc = mod.split("(")[0].strip()
62+
name = ")".join("(".join(mod.split("(")[1:]).split(")")[:-1]).strip()
63+
mass = mods[name]
64+
modifications_str += f"{loc}({name}|{mass}|variable);"
65+
modifications.append(modifications_str.rstrip(";"))
66+
if pd.isna(row["ParentProteinAccessions"]):
67+
protein_accessions.append("sp|UNKNOWN")
68+
else:
69+
protein_accessions.append(row["ParentProteinAccessions"])
70+
score.append(row["CScore"])
71+
mz.append(row["MassOverCharge"])
72+
charge.append(row["Charge"])
73+
rt.append(row["RetentionTime"])
74+
filename.append(row["SpectrumFileName"])
75+
id.append(i)
76+
amanda = pd.DataFrame(
77+
{
78+
"Title": title,
79+
"Sequence": sequence,
80+
"Modifications": modifications,
81+
"Protein Accessions": protein_accessions,
82+
"Amanda Score": score,
83+
"m/z": mz,
84+
"Charge": charge,
85+
"RT": rt,
86+
"Filename": filename,
87+
"Id": id,
88+
}
89+
)
90+
amanda.to_csv(f"{msf_file}.csv", sep="\t", index=False)
91+
if verify:
92+
psms = read_file(f"{msf_file}.csv", filetype="msamanda")
93+
print(f"Successfully read {len(psms)} PSMs from file!")
94+
return 0
95+
96+
97+
def main(argv=None) -> int:
98+
parser = argparse.ArgumentParser(
99+
prog="topdown_msf_to_msamanda.py",
100+
description="Converts a MSF file with top-down proteomics results to MS Amanda format.",
101+
epilog="(c) Bioinformatics Research Group, FH OÖ Campus Hagenberg, 2026",
102+
)
103+
parser.add_argument(
104+
dest="msf",
105+
help="MSF file to convert to MS Amanda format.",
106+
type=str,
107+
)
108+
parser.add_argument(
109+
"-c",
110+
"--check",
111+
dest="verify",
112+
action="store_true",
113+
help="Check PSMs with psm_utils.",
114+
)
115+
parser.add_argument("--version", action="version", version=__version)
116+
args = parser.parse_args(argv)
117+
118+
return to_msamanda(args.msf, args.verify)
119+
120+
121+
if __name__ == "__main__":
122+
exit(main())

0 commit comments

Comments
 (0)