msfiddle 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- msfiddle/__init__.py +44 -0
- msfiddle/config/fiddle_tcn_casmi.yml +113 -0
- msfiddle/config/fiddle_tcn_demo.yml +121 -0
- msfiddle/config/fiddle_tcn_demo_wo_da.yml +121 -0
- msfiddle/config/fiddle_tcn_embl.yml +100 -0
- msfiddle/config/fiddle_tcn_orbitrap.yml +163 -0
- msfiddle/config/fiddle_tcn_qtof.yml +196 -0
- msfiddle/dataset.py +152 -0
- msfiddle/demo/input_msms.mgf +491 -0
- msfiddle/demo/output_buddy.csv +22 -0
- msfiddle/demo/output_fiddle.csv +22 -0
- msfiddle/demo/output_fiddle_all.csv +22 -0
- msfiddle/demo/output_sirius.csv +22 -0
- msfiddle/download.py +194 -0
- msfiddle/main.py +356 -0
- msfiddle/model_tcn.py +222 -0
- msfiddle/utils/__init__.py +40 -0
- msfiddle/utils/mol_utils.py +138 -0
- msfiddle/utils/msms_utils.py +312 -0
- msfiddle/utils/pkl_utils.py +372 -0
- msfiddle/utils/refine_utils.py +246 -0
- msfiddle-0.1.0.dist-info/METADATA +151 -0
- msfiddle-0.1.0.dist-info/RECORD +27 -0
- msfiddle-0.1.0.dist-info/WHEEL +5 -0
- msfiddle-0.1.0.dist-info/entry_points.txt +4 -0
- msfiddle-0.1.0.dist-info/licenses/LICENSE +661 -0
- msfiddle-0.1.0.dist-info/top_level.txt +1 -0
msfiddle/download.py
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module for downloading and locating pre-trained models for msfiddle.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
import shutil
|
|
8
|
+
import tempfile
|
|
9
|
+
import urllib.request
|
|
10
|
+
import zipfile
|
|
11
|
+
import argparse
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from tqdm import tqdm
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
MODEL_URLS = {
|
|
17
|
+
"fiddle_tcn_qtof": "https://github.com/JosieHong/FIDDLE/releases/download/v1.0.0/fiddle_tcn_qtof.zip",
|
|
18
|
+
"fiddle_fdr_qtof": "https://github.com/JosieHong/FIDDLE/releases/download/v1.0.0/fiddle_fdr_qtof.zip",
|
|
19
|
+
"fiddle_tcn_orbitrap": "https://github.com/JosieHong/FIDDLE/releases/download/v1.0.0/fiddle_tcn_orbitrap.zip",
|
|
20
|
+
"fiddle_fdr_orbitrap": "https://github.com/JosieHong/FIDDLE/releases/download/v1.0.0/fiddle_fdr_orbitrap.zip"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class DownloadProgressBar(tqdm):
|
|
25
|
+
"""Progress bar for downloads using tqdm."""
|
|
26
|
+
|
|
27
|
+
def update_to(self, b=1, bsize=1, tsize=None):
|
|
28
|
+
if tsize is not None:
|
|
29
|
+
self.total = tsize
|
|
30
|
+
self.update(b * bsize - self.n)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def download_url(url, output_path):
|
|
34
|
+
"""Download a file with a progress bar."""
|
|
35
|
+
with DownloadProgressBar(unit='B', unit_scale=True, miniters=1, desc=url.split('/')[-1]) as t:
|
|
36
|
+
urllib.request.urlretrieve(url, filename=output_path, reporthook=t.update_to)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_package_dir():
|
|
40
|
+
"""Get the directory where the package is installed."""
|
|
41
|
+
import msfiddle
|
|
42
|
+
return os.path.dirname(os.path.abspath(msfiddle.__file__))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_checkpoint_dir():
|
|
46
|
+
"""Get the directory where model checkpoints are stored."""
|
|
47
|
+
package_dir = get_package_dir()
|
|
48
|
+
return os.path.join(package_dir, "check_point")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def download_models(destination=None, models=None):
|
|
52
|
+
"""
|
|
53
|
+
Download pre-trained models for msfiddle.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
destination (str, optional): Path to save the models. If None, uses the package directory.
|
|
57
|
+
models (list, optional): List of model names to download. If None, downloads all models.
|
|
58
|
+
"""
|
|
59
|
+
if destination is None:
|
|
60
|
+
destination = get_checkpoint_dir()
|
|
61
|
+
else:
|
|
62
|
+
destination = os.path.abspath(destination)
|
|
63
|
+
|
|
64
|
+
# Create the destination directory if it doesn't exist
|
|
65
|
+
os.makedirs(destination, exist_ok=True)
|
|
66
|
+
|
|
67
|
+
print(f"Models will be downloaded to: {destination}")
|
|
68
|
+
|
|
69
|
+
# Determine which models to download
|
|
70
|
+
if models is None:
|
|
71
|
+
models = list(MODEL_URLS.keys())
|
|
72
|
+
|
|
73
|
+
# Create a temporary directory for downloads
|
|
74
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
75
|
+
for model_name in models:
|
|
76
|
+
if model_name not in MODEL_URLS:
|
|
77
|
+
print(f"Warning: Unknown model '{model_name}', skipping.")
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
url = MODEL_URLS[model_name]
|
|
81
|
+
zip_path = os.path.join(temp_dir, f"{model_name}.zip")
|
|
82
|
+
|
|
83
|
+
# Download the model
|
|
84
|
+
print(f"Downloading {model_name} model...")
|
|
85
|
+
try:
|
|
86
|
+
download_url(url, zip_path)
|
|
87
|
+
except Exception as e:
|
|
88
|
+
print(f"Error downloading {model_name}: {e}")
|
|
89
|
+
print(f"You can manually download it from {url}")
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
# Extract the model
|
|
93
|
+
try:
|
|
94
|
+
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
|
95
|
+
zip_ref.extractall(temp_dir)
|
|
96
|
+
|
|
97
|
+
# Move the model file to the destination
|
|
98
|
+
model_file = f"{model_name}.pt"
|
|
99
|
+
src_path = os.path.join(temp_dir, model_file)
|
|
100
|
+
dest_path = os.path.join(destination, model_file)
|
|
101
|
+
|
|
102
|
+
if os.path.exists(src_path):
|
|
103
|
+
shutil.move(src_path, dest_path)
|
|
104
|
+
print(f"Installed {model_file} to {dest_path}")
|
|
105
|
+
else:
|
|
106
|
+
print(f"Warning: Model file {model_file} not found after extraction")
|
|
107
|
+
except Exception as e:
|
|
108
|
+
print(f"Error extracting {model_name}: {e}")
|
|
109
|
+
print(f"You can manually download and extract it from {url}")
|
|
110
|
+
|
|
111
|
+
print("\nDownload complete!")
|
|
112
|
+
print(f"Models are installed in: {destination}")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def check_models_exist():
|
|
116
|
+
"""Check if models exist in the package directory."""
|
|
117
|
+
model_dir = get_checkpoint_dir()
|
|
118
|
+
|
|
119
|
+
if not os.path.exists(model_dir):
|
|
120
|
+
return False
|
|
121
|
+
|
|
122
|
+
# Check if all model files exist
|
|
123
|
+
all_exist = True
|
|
124
|
+
for model_name in MODEL_URLS.keys():
|
|
125
|
+
model_path = os.path.join(model_dir, f"{model_name}.pt")
|
|
126
|
+
if not os.path.exists(model_path):
|
|
127
|
+
all_exist = False
|
|
128
|
+
break
|
|
129
|
+
|
|
130
|
+
return all_exist
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def get_model_path(model_name):
|
|
134
|
+
"""Get the path to a specific model file."""
|
|
135
|
+
model_dir = get_checkpoint_dir()
|
|
136
|
+
return os.path.join(model_dir, f"{model_name}.pt")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def print_checkpoint_paths():
|
|
140
|
+
"""Print the checkpoint directory path and available models."""
|
|
141
|
+
checkpoint_dir = get_checkpoint_dir()
|
|
142
|
+
print(f"Checkpoint directory: {checkpoint_dir}")
|
|
143
|
+
|
|
144
|
+
if os.path.exists(checkpoint_dir):
|
|
145
|
+
print("\nAvailable models:")
|
|
146
|
+
for model_name in MODEL_URLS.keys():
|
|
147
|
+
model_path = os.path.join(checkpoint_dir, f"{model_name}.pt")
|
|
148
|
+
if os.path.exists(model_path):
|
|
149
|
+
print(f" [+] {model_name}.pt")
|
|
150
|
+
else:
|
|
151
|
+
print(f" [-] {model_name}.pt (not downloaded)")
|
|
152
|
+
else:
|
|
153
|
+
print("\nCheckpoint directory does not exist. No models are downloaded.")
|
|
154
|
+
print("Run 'msfiddle-download-models' to download the models.")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# Command-line interface for the download_models function
|
|
158
|
+
def download_models_cli():
|
|
159
|
+
parser = argparse.ArgumentParser(description="Download pre-trained models for msfiddle")
|
|
160
|
+
parser.add_argument("-d", "--destination", help="Path to save the models (optional, defaults to package directory)")
|
|
161
|
+
parser.add_argument("-m", "--models", nargs="+", help="Specific model names to download (optional, defaults to all models)")
|
|
162
|
+
args = parser.parse_args()
|
|
163
|
+
|
|
164
|
+
# If destination is not provided, it will be None and download_models will use get_checkpoint_dir()
|
|
165
|
+
download_models(destination=args.destination, models=args.models)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# Command-line interface for the print_checkpoint_paths function
|
|
169
|
+
def print_checkpoint_paths_cli():
|
|
170
|
+
parser = argparse.ArgumentParser(description="Print checkpoint paths and available models")
|
|
171
|
+
parser.parse_args() # This just consumes any arguments passed
|
|
172
|
+
print_checkpoint_paths()
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
if __name__ == "__main__":
|
|
176
|
+
if len(sys.argv) < 2:
|
|
177
|
+
print("Usage: python download.py [command]")
|
|
178
|
+
print("Commands:")
|
|
179
|
+
print(" print_checkpoint_paths - Print the checkpoint directory path and available models")
|
|
180
|
+
print(" download_models [destination] - Download pre-trained models to the specified destination")
|
|
181
|
+
sys.exit(1)
|
|
182
|
+
|
|
183
|
+
if sys.argv[1] == "print_checkpoint_paths":
|
|
184
|
+
print_checkpoint_paths()
|
|
185
|
+
|
|
186
|
+
elif sys.argv[1] == "download_models":
|
|
187
|
+
# If the first argument is 'download', treat the second as destination
|
|
188
|
+
if len(sys.argv) > 2:
|
|
189
|
+
download_models(destination=sys.argv[2])
|
|
190
|
+
else:
|
|
191
|
+
download_models()
|
|
192
|
+
|
|
193
|
+
else:
|
|
194
|
+
raise ValueError(f"Unknown command: {sys.argv[1]}")
|
msfiddle/main.py
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import argparse
|
|
3
|
+
from tqdm import tqdm
|
|
4
|
+
import yaml
|
|
5
|
+
import time
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import pandas as pd
|
|
10
|
+
from collections import OrderedDict
|
|
11
|
+
|
|
12
|
+
import torch
|
|
13
|
+
import torch.nn as nn
|
|
14
|
+
from torch.utils.data import DataLoader
|
|
15
|
+
|
|
16
|
+
from .dataset import MGFDataset
|
|
17
|
+
from .model_tcn import MS2FNet_tcn, FDRNet
|
|
18
|
+
from .utils.mol_utils import vector_to_formula, formula_to_vector
|
|
19
|
+
from .utils.msms_utils import mass_calculator
|
|
20
|
+
from .utils.refine_utils import formula_refinement
|
|
21
|
+
from .download import get_checkpoint_dir, check_models_exist, download_models
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_step(model, loader, device):
|
|
25
|
+
model.eval()
|
|
26
|
+
spec_ids = []
|
|
27
|
+
y_pred = []
|
|
28
|
+
exp_precursor_mz = []
|
|
29
|
+
exp_precursor_type = []
|
|
30
|
+
mass_pred = []
|
|
31
|
+
atomnum_pred = []
|
|
32
|
+
hcnum_pred = []
|
|
33
|
+
with tqdm(total=len(loader)) as bar:
|
|
34
|
+
for _, batch in enumerate(loader):
|
|
35
|
+
spec_id, exp_pre_type, x, env, neutral_add = batch
|
|
36
|
+
x = x.to(device, dtype=torch.float32)
|
|
37
|
+
env = env.to(device, dtype=torch.float32)
|
|
38
|
+
neutral_add = neutral_add.to(device, dtype=torch.float32)
|
|
39
|
+
exp_pre_mz = env[:, 0]
|
|
40
|
+
|
|
41
|
+
with torch.no_grad():
|
|
42
|
+
_, pred_f, pred_mass, pred_atomnum, pred_hcnum = model(x, env)
|
|
43
|
+
pred_f = pred_f - neutral_add # add the neutral adduct
|
|
44
|
+
|
|
45
|
+
bar.set_description('Eval')
|
|
46
|
+
bar.update(1)
|
|
47
|
+
|
|
48
|
+
spec_ids = spec_ids + list(spec_id)
|
|
49
|
+
y_pred.append(pred_f.detach().cpu())
|
|
50
|
+
exp_precursor_mz.append(exp_pre_mz.detach().cpu())
|
|
51
|
+
exp_precursor_type = exp_precursor_type + list(exp_pre_type)
|
|
52
|
+
mass_pred.append(pred_mass.detach().cpu())
|
|
53
|
+
atomnum_pred.append(pred_atomnum.detach().cpu())
|
|
54
|
+
hcnum_pred.append(pred_hcnum.detach().cpu())
|
|
55
|
+
|
|
56
|
+
y_pred = torch.cat(y_pred, dim = 0)
|
|
57
|
+
exp_precursor_mz = torch.cat(exp_precursor_mz, dim = 0)
|
|
58
|
+
mass_pred = torch.cat(mass_pred, dim = 0)
|
|
59
|
+
atomnum_pred = torch.cat(atomnum_pred, dim = 0)
|
|
60
|
+
hcnum_pred = torch.cat(hcnum_pred, dim = 0)
|
|
61
|
+
return spec_ids, y_pred, exp_precursor_mz, exp_precursor_type, mass_pred, atomnum_pred, hcnum_pred
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def rerank_by_fdr(fdr_model, spec, env, refined_results, device, K):
|
|
65
|
+
fdr_model.eval()
|
|
66
|
+
|
|
67
|
+
refine_f = [f for f in refined_results['formula'] if f != None]
|
|
68
|
+
refine_m = [m for m in refined_results['mass'] if m != None]
|
|
69
|
+
if len(refine_f) == 0:
|
|
70
|
+
refined_results['fdr'] = [0.] * K
|
|
71
|
+
return refined_results
|
|
72
|
+
|
|
73
|
+
# convert refine_f (formula strings) to f (formula vectors)
|
|
74
|
+
f = [formula_to_vector(f_str) for f_str in refine_f]
|
|
75
|
+
f = torch.from_numpy(np.array(f))
|
|
76
|
+
|
|
77
|
+
spec = spec.to(device, dtype=torch.float32).repeat(f.size(0), 1)
|
|
78
|
+
env = env.to(device, dtype=torch.float32).repeat(f.size(0), 1)
|
|
79
|
+
f = f.to(device, dtype=torch.float32)
|
|
80
|
+
|
|
81
|
+
with torch.no_grad():
|
|
82
|
+
fdr = fdr_model(spec, env, f)
|
|
83
|
+
fdr = torch.sigmoid(fdr).detach().cpu().numpy()
|
|
84
|
+
|
|
85
|
+
# Create a list of tuples with (fdr_value, refine_f_value, mass_value) pairs
|
|
86
|
+
combined_list = list(zip(fdr, refine_f, refine_m))
|
|
87
|
+
# Sort the combined list based on the fdr_value
|
|
88
|
+
sorted_combined_list = sorted(combined_list, key=lambda x: x[0], reverse=True)
|
|
89
|
+
# Unpack the sorted lists
|
|
90
|
+
sorted_fdr, sorted_refine_f, sorted_mass = zip(*sorted_combined_list)
|
|
91
|
+
sorted_fdr, sorted_refine_f, sorted_mass = list(sorted_fdr), list(sorted_refine_f), list(sorted_mass)
|
|
92
|
+
|
|
93
|
+
# Pad sorted_refine_f and sorted_fdr with None if necessary
|
|
94
|
+
if len(sorted_refine_f) < K:
|
|
95
|
+
sorted_refine_f += [None] * (K - len(sorted_refine_f))
|
|
96
|
+
sorted_fdr += [0.] * (K - len(sorted_fdr))
|
|
97
|
+
sorted_mass += [None] * (K - len(sorted_mass))
|
|
98
|
+
|
|
99
|
+
return {'formula': sorted_refine_f, 'mass': sorted_mass, 'fdr': sorted_fdr}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def init_random_seed(seed):
|
|
103
|
+
np.random.seed(seed)
|
|
104
|
+
torch.manual_seed(seed)
|
|
105
|
+
torch.cuda.manual_seed(seed)
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def main():
|
|
110
|
+
parser = argparse.ArgumentParser(description='msfiddle')
|
|
111
|
+
|
|
112
|
+
# Define two exclusive argument groups
|
|
113
|
+
mode_group = parser.add_mutually_exclusive_group(required=True)
|
|
114
|
+
mode_group.add_argument('--demo', action='store_true',
|
|
115
|
+
help='Run prediction on demo data')
|
|
116
|
+
mode_group.add_argument('--test_data', type=str,
|
|
117
|
+
help='Path to data (.mgf)')
|
|
118
|
+
|
|
119
|
+
# Add instrument type option
|
|
120
|
+
parser.add_argument('--instrument_type', type=str, choices=['qtof', 'orbitrap'], default='orbitrap',
|
|
121
|
+
help='Instrument type: qtof or orbitrap (default: orbitrap)')
|
|
122
|
+
|
|
123
|
+
# Add other required arguments
|
|
124
|
+
parser.add_argument('--result_path', type=str, required=True,
|
|
125
|
+
help='Path to save predicted results')
|
|
126
|
+
|
|
127
|
+
# Add optional arguments
|
|
128
|
+
parser.add_argument('--buddy_path', type=str, default='',
|
|
129
|
+
help='Path to saved BUDDY\'s results')
|
|
130
|
+
parser.add_argument('--sirius_path', type=str, default='',
|
|
131
|
+
help='Path to saved SIRIUS\'s results')
|
|
132
|
+
parser.add_argument('--seed', type=int, default=42,
|
|
133
|
+
help='Seed for random functions')
|
|
134
|
+
parser.add_argument('--device', type=int, nargs='+', default=[0],
|
|
135
|
+
help='Which GPUs to use if any (default: [0]). Accepts multiple values separated by space.')
|
|
136
|
+
parser.add_argument('--no_cuda', action='store_true',
|
|
137
|
+
help='Disables CUDA')
|
|
138
|
+
|
|
139
|
+
# Add advanced arguments for custom configurations
|
|
140
|
+
advanced_group = parser.add_argument_group('Advanced options')
|
|
141
|
+
advanced_group.add_argument('--config_path', type=str,
|
|
142
|
+
help='Custom path to configuration (.yaml)')
|
|
143
|
+
advanced_group.add_argument('--resume_path', type=str,
|
|
144
|
+
help='Custom path to pretrained TCN model')
|
|
145
|
+
advanced_group.add_argument('--fdr_resume_path', type=str,
|
|
146
|
+
help='Custom path to pretrained FDR model')
|
|
147
|
+
|
|
148
|
+
args = parser.parse_args()
|
|
149
|
+
|
|
150
|
+
# Initialize random seed
|
|
151
|
+
init_random_seed(args.seed)
|
|
152
|
+
start_time = time.time()
|
|
153
|
+
|
|
154
|
+
# Get package directory
|
|
155
|
+
package_dir = os.path.dirname(os.path.abspath(__file__))
|
|
156
|
+
|
|
157
|
+
# Make sure models are downloaded
|
|
158
|
+
if not check_models_exist():
|
|
159
|
+
print("Pre-trained models not found. Downloading now...")
|
|
160
|
+
download_models()
|
|
161
|
+
print("Models downloaded successfully.")
|
|
162
|
+
|
|
163
|
+
# Set default paths based on mode and instrument type
|
|
164
|
+
if args.demo:
|
|
165
|
+
test_data_path = os.path.join(package_dir, 'demo', 'input_msms.mgf')
|
|
166
|
+
print(f"Using demo data: {test_data_path}")
|
|
167
|
+
else:
|
|
168
|
+
test_data_path = args.test_data
|
|
169
|
+
print(f"Using custom data: {test_data_path}")
|
|
170
|
+
|
|
171
|
+
# Set paths based on instrument type
|
|
172
|
+
instrument_suffix = args.instrument_type
|
|
173
|
+
|
|
174
|
+
# Set config path
|
|
175
|
+
if args.config_path:
|
|
176
|
+
config_path = args.config_path
|
|
177
|
+
else:
|
|
178
|
+
config_path = os.path.join(package_dir, 'config', f'fiddle_tcn_{instrument_suffix}.yml')
|
|
179
|
+
print(f"Using config: {config_path}")
|
|
180
|
+
|
|
181
|
+
# Set model paths
|
|
182
|
+
checkpoint_dir = get_checkpoint_dir()
|
|
183
|
+
|
|
184
|
+
if args.resume_path:
|
|
185
|
+
resume_path = args.resume_path
|
|
186
|
+
else:
|
|
187
|
+
resume_path = os.path.join(checkpoint_dir, f'fiddle_tcn_{instrument_suffix}.pt')
|
|
188
|
+
print(f"Using TCN model: {resume_path}")
|
|
189
|
+
|
|
190
|
+
if args.fdr_resume_path:
|
|
191
|
+
fdr_resume_path = args.fdr_resume_path
|
|
192
|
+
else:
|
|
193
|
+
fdr_resume_path = os.path.join(checkpoint_dir, f'fiddle_fdr_{instrument_suffix}.pt')
|
|
194
|
+
print(f"Using FDR model: {fdr_resume_path}")
|
|
195
|
+
|
|
196
|
+
# Load config
|
|
197
|
+
with open(config_path, 'r') as f:
|
|
198
|
+
config = yaml.load(f, Loader=yaml.FullLoader)
|
|
199
|
+
print('Loaded model & training configuration from {}'.format(config_path))
|
|
200
|
+
|
|
201
|
+
# Set device
|
|
202
|
+
device_1st = torch.device("cuda:" + str(args.device[0])) if torch.cuda.is_available() and not args.no_cuda else torch.device("cpu")
|
|
203
|
+
print(f'Device(s): {args.device}')
|
|
204
|
+
|
|
205
|
+
# 1. Data
|
|
206
|
+
valid_set = MGFDataset(test_data_path, config['encoding'])
|
|
207
|
+
valid_loader = DataLoader(
|
|
208
|
+
valid_set,
|
|
209
|
+
batch_size=1,
|
|
210
|
+
shuffle=False,
|
|
211
|
+
num_workers=0,
|
|
212
|
+
drop_last=True)
|
|
213
|
+
|
|
214
|
+
# 2. Model
|
|
215
|
+
# 2.1 MS2F
|
|
216
|
+
model = MS2FNet_tcn(config['model']).to(device_1st)
|
|
217
|
+
num_params = sum(p.numel() for p in model.parameters())
|
|
218
|
+
print(f'# MS2FNet_tcn Params: {num_params}')
|
|
219
|
+
if len(args.device) > 1: # Wrap the model with nn.DataParallel
|
|
220
|
+
model = nn.DataParallel(model, device_ids=args.device)
|
|
221
|
+
|
|
222
|
+
print('Loading the formula prediction model...')
|
|
223
|
+
state_dict = torch.load(resume_path, map_location=device_1st, weights_only=False)['model_state_dict']
|
|
224
|
+
is_multi_gpu = any(key.startswith('module.') for key in state_dict.keys())
|
|
225
|
+
if is_multi_gpu and len(args.device) == 1: # Convert the model to single GPU
|
|
226
|
+
new_state_dict = OrderedDict()
|
|
227
|
+
for key, value in state_dict.items():
|
|
228
|
+
if key.startswith('module.'):
|
|
229
|
+
new_key = key[7:] # Remove the 'module.' prefix
|
|
230
|
+
new_state_dict[new_key] = value
|
|
231
|
+
else:
|
|
232
|
+
new_state_dict[key] = value
|
|
233
|
+
model.load_state_dict(new_state_dict)
|
|
234
|
+
else:
|
|
235
|
+
model.load_state_dict(state_dict)
|
|
236
|
+
|
|
237
|
+
# 2.2 FDR
|
|
238
|
+
fdr_model = FDRNet(config['model']).to(device_1st)
|
|
239
|
+
num_params = sum(p.numel() for p in fdr_model.parameters())
|
|
240
|
+
print(f'# FDRNet Params: {num_params}')
|
|
241
|
+
if len(args.device) > 1: # Wrap the model with nn.DataParallel
|
|
242
|
+
fdr_model = nn.DataParallel(fdr_model, device_ids=args.device)
|
|
243
|
+
|
|
244
|
+
print('Loading the FDR prediction model...')
|
|
245
|
+
state_dict = torch.load(fdr_resume_path, map_location=device_1st, weights_only=False)['model_state_dict']
|
|
246
|
+
is_multi_gpu = any(key.startswith('module.') for key in state_dict.keys())
|
|
247
|
+
if is_multi_gpu and len(args.device) == 1: # Convert the fdr_model to single GPU
|
|
248
|
+
new_state_dict = OrderedDict()
|
|
249
|
+
for key, value in state_dict.items():
|
|
250
|
+
if key.startswith('module.'):
|
|
251
|
+
new_key = key[7:] # Remove the 'module.' prefix
|
|
252
|
+
new_state_dict[new_key] = value
|
|
253
|
+
else:
|
|
254
|
+
new_state_dict[key] = value
|
|
255
|
+
fdr_model.load_state_dict(new_state_dict)
|
|
256
|
+
else:
|
|
257
|
+
fdr_model.load_state_dict(state_dict)
|
|
258
|
+
|
|
259
|
+
# 3. Formula Prediction
|
|
260
|
+
spec_ids, y_pred, exp_precursor_mz, exp_precursor_type, mass_pred, atomnum_pred, hcnum_pred = test_step(model, valid_loader, device_1st)
|
|
261
|
+
|
|
262
|
+
prediction_time = time.time() - start_time
|
|
263
|
+
prediction_time /= len(valid_set)
|
|
264
|
+
|
|
265
|
+
formula_pred = [vector_to_formula(y) for y in y_pred] # calculate the formula strings
|
|
266
|
+
y_pred = [';'.join(y) for y in y_pred.numpy().astype('str')]
|
|
267
|
+
|
|
268
|
+
spectra = []
|
|
269
|
+
environments = []
|
|
270
|
+
for batch in valid_loader:
|
|
271
|
+
_, _, spec, env, _ = batch
|
|
272
|
+
spectra.append(spec)
|
|
273
|
+
environments.append(env)
|
|
274
|
+
|
|
275
|
+
# 4. Post-processing
|
|
276
|
+
if args.buddy_path != '':
|
|
277
|
+
buddy_df = pd.read_csv(args.buddy_path)
|
|
278
|
+
if args.sirius_path != '':
|
|
279
|
+
sirius_df = pd.read_csv(args.sirius_path)
|
|
280
|
+
|
|
281
|
+
formula_redined = {'Refined Formula ({})'.format(str(k)): [] for k in range(config['post_processing']['top_k'])}
|
|
282
|
+
mass_redined = {'Refined Mass ({})'.format(str(k)): [] for k in range(config['post_processing']['top_k'])}
|
|
283
|
+
fdr_refined = {'FDR ({})'.format(str(k)): [] for k in range(config['post_processing']['top_k'])}
|
|
284
|
+
running_time = []
|
|
285
|
+
exp_mass = []
|
|
286
|
+
# Please note that here we use the experimental precursor m/z, rather than the theoretic precursor m/z.
|
|
287
|
+
for idx, pred_f, exp_pre_mz, exp_pre_type, spec, env in tqdm(zip(spec_ids,
|
|
288
|
+
formula_pred,
|
|
289
|
+
exp_precursor_mz,
|
|
290
|
+
exp_precursor_type, spectra, environments), total=len(exp_precursor_mz), desc='Post'):
|
|
291
|
+
m = mass_calculator(exp_pre_type, exp_pre_mz) # Use experimental precursor m/z and precursor type to calculate molmass
|
|
292
|
+
exp_mass.append(m.item())
|
|
293
|
+
|
|
294
|
+
f0_list = [pred_f]
|
|
295
|
+
if args.buddy_path != '' and len(buddy_df.loc[buddy_df['ID'] == idx]) > 0:
|
|
296
|
+
buddy_f = buddy_df.loc[buddy_df['ID'] == idx].iloc[0][['Pred Formula (1)',
|
|
297
|
+
'Pred Formula (2)',
|
|
298
|
+
'Pred Formula (3)',
|
|
299
|
+
'Pred Formula (4)',
|
|
300
|
+
'Pred Formula (5)',]].tolist()
|
|
301
|
+
buddy_fdr = buddy_df.loc[buddy_df['ID'] == idx].iloc[0][['BUDDY Score (1)',
|
|
302
|
+
'BUDDY Score (2)',
|
|
303
|
+
'BUDDY Score (3)',
|
|
304
|
+
'BUDDY Score (4)',
|
|
305
|
+
'BUDDY Score (5)',]].tolist()
|
|
306
|
+
buddy_f = [x for x, fdr in zip(buddy_f, buddy_fdr) if str(x) != 'nan' and fdr < config['post_processing']['buddy_fdr_thr']]
|
|
307
|
+
f0_list.extend(buddy_f)
|
|
308
|
+
if args.sirius_path != '':
|
|
309
|
+
sirius_f = sirius_df.loc[sirius_df['ID'] == idx].iloc[0][['Pred Formula (1)',
|
|
310
|
+
'Pred Formula (2)',
|
|
311
|
+
'Pred Formula (3)',
|
|
312
|
+
'Pred Formula (4)',
|
|
313
|
+
'Pred Formula (5)',]].tolist()
|
|
314
|
+
sirius_score = sirius_df.loc[sirius_df['ID'] == idx].iloc[0][['SIRIUS Score (1)',
|
|
315
|
+
'SIRIUS Score (2)',
|
|
316
|
+
'SIRIUS Score (3)',
|
|
317
|
+
'SIRIUS Score (4)',
|
|
318
|
+
'SIRIUS Score (5)',]].tolist()
|
|
319
|
+
sirius_f = [x for x, score in zip(sirius_f, sirius_score) if str(x) != 'nan' and score > config['post_processing']['sirius_score_thr']]
|
|
320
|
+
f0_list.extend(sirius_f)
|
|
321
|
+
|
|
322
|
+
f0_list = list(set(f0_list)) # deduplicates
|
|
323
|
+
start_time = time.time()
|
|
324
|
+
refined_results = formula_refinement(f0_list, m.item(),
|
|
325
|
+
config['post_processing']['mass_tolerance'],
|
|
326
|
+
config['post_processing']['ppm_mode'],
|
|
327
|
+
config['post_processing']['top_k'],
|
|
328
|
+
config['post_processing']['maxium_miss_atom_num'],
|
|
329
|
+
config['post_processing']['time_out'],
|
|
330
|
+
config['post_processing']['refine_atom_type'],
|
|
331
|
+
config['post_processing']['refine_atom_num'],
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
# Rerank the results by predicted FDR
|
|
335
|
+
refined_results = rerank_by_fdr(fdr_model, spec, env, refined_results, device_1st, config['post_processing']['top_k'])
|
|
336
|
+
|
|
337
|
+
for i, (refined_f, refined_m, refined_fdr) in enumerate(zip(refined_results['formula'], refined_results['mass'], refined_results['fdr'])):
|
|
338
|
+
formula_redined[f'Refined Formula ({i})'].append(refined_f)
|
|
339
|
+
mass_redined[f'Refined Mass ({i})'].append(refined_m)
|
|
340
|
+
fdr_refined[f'FDR ({i})'].append(refined_fdr)
|
|
341
|
+
refinement_time = time.time() - start_time
|
|
342
|
+
running_time.append(prediction_time + refinement_time)
|
|
343
|
+
|
|
344
|
+
# 5. Save the final results
|
|
345
|
+
print('\nSaving predicted results...')
|
|
346
|
+
out_dict = {'ID': spec_ids, 'Y Pred': y_pred, 'Mass': exp_mass,
|
|
347
|
+
'Pred Formula': formula_pred, 'Pred Mass': mass_pred.tolist(),
|
|
348
|
+
'Pred Atom Num': atomnum_pred.tolist(), 'Pred H/C Num': hcnum_pred.tolist(),
|
|
349
|
+
'Running Time': running_time}
|
|
350
|
+
res_df = pd.DataFrame({**out_dict, **formula_redined, **mass_redined, **fdr_refined})
|
|
351
|
+
res_df.to_csv(args.result_path, index=False)
|
|
352
|
+
print(f'Done! Results saved to {args.result_path}')
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
if __name__ == "__main__":
|
|
356
|
+
main()
|